Sylius 2 - #1
Open
LucaGallinari wants to merge 44 commits into
Open
Conversation
…HP 8.2 Bumps sylius/sylius to ~2.2.0, php to ^8.2, drops the obsolete doctrine/dbal ^3.0 conflict and the abandoned friendsofsymfony/jsrouting-bundle (incompatible with Symfony 7), and aligns dev tooling (phpstan, phpspec, phpunit, psalm, sylius-labs/coding-standard, sylius/test-application) with current Sylius 2.x versions.
…ions/ Aligns with the Sylius 2.x plugin layout: config/app/*.yml -> config/*.yaml, views/* -> templates/* (snake_case), translations/*.yml -> translations/*.yaml. Overrides Bundle::getPath() to point at the plugin root so Symfony resolves the new top-level directories instead of the legacy Resources/ convention, and updates every internal template/config path reference accordingly (controllers, routing, DI extension, README install/copy instructions).
Winzou's SM\Factory\FactoryInterface was replaced in Sylius 2.x by Sylius\Abstraction\StateMachine\StateMachineInterface (graph-aware can()/apply()). Also fixes GatewayConfigInterface being imported from Payum\Core\Model instead of Sylius\Component\Payment\Model, a pre-existing type mismatch masked by the old phpstan version and only surfaced once PaymentMethodInterface::getGatewayConfig() got strict typed.
Drops the legacy tests/Application Symfony 4/5-style app (custom Kernel, gulp-based frontend, JMSSerializer/FOSRestBundle/Sonata-era bundle list) in favor of the sylius/test-application vendor package, keeping only the two customer/product-variant repository overrides this plugin's tests actually need in tests/TestApplication/src. Updates phpunit.xml.dist and behat.yml.dist to boot Sylius\TestApplication\Kernel instead of the removed app kernel.
- OrderIndexPage no longer redeclares the parent's now-readonly $tableAccessor - ManagingOrdersContext: EmailCheckerInterface moved from Sylius\Component\Core\Test\Services to Sylius\Behat\Service\Checker - services.xml: sylius.behat.notification_checker -> sylius.behat.notification_checker.admin, sylius.address_comparator -> Sylius\Component\Addressing\Comparator\AddressComparatorInterface
phpstan.neon: drop pre-1.0 config keys (excludes_analyse, checkMissingIterableValueType) no longer understood by phpstan ^1.12, add tests/Behat to the analysed paths, and baseline 78 pre-existing errors that were never actually checked under the old phpstan 0.12.96 pin. Add psalm.xml + baseline (previously not configured at all) and consolidate the duplicate easy-coding-standard.neon/.yml into a single ecs.php matching sylius-labs/coding-standard ^4.4's expected format. Remaining diffs across src/ and spec/ are ECS's automatic style fixes (trailing commas, closure spacing) with no behavioral change.
Matrix: PHP 8.2/8.3, Symfony ^7.3, Sylius ~2.2.0, Node 22.x, MySQL 8.0/8.4. Bumps actions/checkout and actions/cache/upload-artifact to v4, replaces the deprecated ::set-output syntax with $GITHUB_OUTPUT, points every step at vendor/bin/console and vendor/sylius/test-application instead of the removed tests/Application, adds container linting, psalm, and a roave_bc_check job, and runs static analysis before spinning up MySQL/Chrome.
Adds compose.yml (php/mysql/nginx + chrome/chrome-proxy for Behat JS tests + mailhog) and compose.override.dist.yml, following the same pattern used in SyliusMailchimpPlugin: prebuilt ghcr.io/sylius/sylius-* images, nginx pointed at vendor/sylius/test-application via WORKING_DIR, and a nodejs service to build test-application's frontend assets. compose.override.yml/compose.test.yml and docker/xdebug.ini are gitignored as personal local overrides, same as upstream. Documents the workflow in README.md.
…impPlugin Matrix collapsed to php 8.4 / mysql 8.4 (was 8.2/8.3 and 8.0/8.4), matching the other Sylius 2.x plugin. Also aligns steps: explicit phpstan flags (-c phpstan.neon -l max src/), psalm without --no-cache, phpunit split into unit/non-unit steps (matching phpunit.xml.dist's unit/functional/integration/ non-unit testsuites, with empty placeholder tests/Unit, tests/Functional, tests/Integration dirs since none exist yet), behat without -f progress, and roave_bc_check bumped to PHP 8.4.
…ring with Mailchimp Switches from the BUNDLES_TO_ENABLE/CONFIGS_TO_IMPORT/ROUTES_TO_IMPORT env vars (Wishlist-style) to a physical bundles.php + routes.yaml driven via SYLIUS_TEST_APP_BUNDLES_PATH/SYLIUS_TEST_APP_CONFIGS_TO_IMPORT/ SYLIUS_TEST_APP_ROUTES_TO_IMPORT, matching SyliusMailchimpPlugin's tests/TestApplication convention. Also adds BEHAT_CHROME_URL and gitignores tests/TestApplication/.env*.local for parity.
Adds .editorconfig/.dockerignore, points the Behat Chrome driver and kernel environment at the Docker setup via env vars, adds the suite-tags-extension and browser-kit dependencies it needs, bumps Psalm's target PHP version to 8.4, and adds the empty Encore controllers.json entrypoints Sylius 2's asset pipeline expects.
sylius-labs/coding-standard's ordered-imports rule was drifting out of sync with the actual import order in these files; ecs --fix output.
Documents the plugin's purpose, commands, architecture, conventions, and the Sylius 1.x-to-2.x migration status for agents (and humans) working in this repo.
Replaces the removed ResourceAutocompleteChoiceType-based customer field with a Symfony UX Autocomplete type, rewrites the controller to handle GET submissions server-side (no more jQuery), and rebuilds the template against Tabler markup instead of Semantic UI. Adds NotBlank constraints (with required:false, so the browser's native HTML5 validation doesn't block the server-side check) to surface "no customer selected" / "customer email required" errors that previously never rendered.
Rebuilds the item/shipment/payment/adjustment collections as a Symfony UX Live Component (OrderFormComponent) using LiveCollectionType, so rows can be added/removed without a full page reload. Product variant selection moves to a channel-scoped UX Autocomplete type; shipping method choices are now filtered to eligible methods via ShippingMethodChoiceType's native `subject` option, computed from a throwaway order built from the live form state. This retires the old jQuery/Semantic-UI collection widgets and the AJAX endpoint (ProvideAvailableShippingMethodsAction / ajax.yaml / AvailableShippingMethodsListProvider) and preparator (NewOrderPreparator) they existed to serve, all superseded by the Live Component doing this work in-process.
Moves the preview form's content into a dedicated Live Component (OrderPreviewFormComponent, a thin subclass reusing OrderFormComponent's logic under a different name/template) so order- and item-level discounts can be added/edited in place before confirming. Money formatting in this template drops the locale argument to sylius_format_money(): app.user is null during a Live Component's AJAX sub-request even though it's populated on the full page load, so relying on app.user.localeCode here would break mid-edit.
Replaces the removed @SyliusAdmin template-override mechanism (templates/bundles/..., templates/order/show/_item.html.twig, _summary.html.twig) with hook templates: a "Pay" action on the payment row, an order-discount summary line, and item-row overrides for unit discount / discounted unit price / subtotal. The item-row overrides matter beyond display: core's own templates only sum adjustments of Sylius's built-in promotion types, so a manually-entered per-item discount (our own 'order_item_discount' adjustment type) rendered as if no discount had been applied at all. Aligning our type with core's promotion types instead was rejected - core's order processor clears and recalculates promotion-typed adjustments on every OrderPreviewAction::process() call, which would wipe a manual discount that isn't backed by an actual promotion rule. Also gives OrderShowPage's two plugin-specific assertions (hasPaymentLink, hasNoPaymentBlock) [data-test-*] selectors matching the new hook markup; core's own ShowPage already covers the rest for Tabler.
Replaces every selector tied to the old Semantic UI/jQuery collection widgets with Bootstrap/TomSelect/data-test-attribute equivalents, and swaps the plugin's custom AutoCompleteSelector for core's own Sylius\Behat\Service\Helper\AutocompleteHelperInterface. Core's getSelectedItems() turned out to always resolve to the first <select> on the page when several share the "type" it disambiguates on, which silently broke "remove/edit item N of several" - fixed by reading the selected <option> directly from each item's own select instead. Other fixes surfaced by actually running the suite against Sylius 2, not just selector churn: - sylius.behat.context.hook.mailer was missing from both suites, so the mailer spool was never purged between scenarios and "no email should have been sent" checks saw leftovers from earlier scenarios. - ManagingOrdersContext's "no payment link sent" check relied on the old EmailChecker throwing when a recipient has zero messages; the current one just returns 0, so the check always failed - rewritten to assert directly instead of catching an exception that never comes. - Live Component model updates debounce for 150ms before the "busy" attribute appears, so a readiness check placed right after a field change could race ahead of a request that hadn't started yet. - Sylius's stricter money-string parsing needs exactly 2 decimals and comma-separated thousands; several feature files still used the old "$N" / "$1000.00" formats. - One scenario's fixture order relied on which channel happens to be "current" in shared storage, which depends on creation order - reordered the Background so the product step doesn't pick up pricing for the channel it's meant to exclude. - core's ShowPage now shows "No promotion applied" instead of "$0.00" for a zero promotion total.
phpstan-baseline.neon was still keyed to the pre-rename Sylius\AdminOrderCreationPlugin\... namespace throughout, silently masking real errors project-wide; regenerated from scratch. psalm-baseline.xml picks up OrderPreviewFormComponent, which Psalm can't see is used since it's only referenced by name in a Twig Live Component attribute.
…nstraint
The exact pins on api-platform/metadata, api-platform/symfony,
symfony/config and symfony/property-info were never actually needed -
none of them are used directly by this plugin, and letting Composer
resolve them normally (as SyliusGiftCardPlugin does) works fine.
The real incompatibility is symfony/type-info 7.4.x: it added stricter
construction validation for union/collection/generic types that
breaks Sylius's API Bundle routing loader ("Cannot create union with
both 'object' and class type", thrown while parsing
Sylius/Bundle/ApiBundle/Resources/config/routing.yml) - not an
api-platform/symfony version issue as previously assumed, that
package resolves to 4.3.17 without any problem on its own.
Verified against SyliusGiftCardPlugin, which runs the same
api-platform/symfony 4.3.17 successfully by keeping symfony/type-info
below 7.4 - applied the same constraint here instead of pinning
everything to an exact snapshot.
The previous note blamed api-platform/symfony version drift; the actual cause (verified against SyliusGiftCardPlugin) is symfony/type-info 7.4's stricter type-construction validation breaking Sylius's API Bundle routing loader.
vendor/bin/ecs check --fix. These 9 files were already out of order before this migration and CI (never run on this branch until now) never caught them.
The 3 scenarios needing a payment method with an online gateway (to exercise the payment-link email/button, gated on the gateway not being "offline") had no step to bind to: Sylius 2's own PaymentContext only offers a fixed-to-Offline theStoreHasAPaymentMethodWithACode, and this test app has no non-offline Payum gateway factory registered. Checked how Sylius/PayPalPlugin and Sylius/StripePlugin - both already migrated to Sylius 2 - solve this in their own Behat suites: neither requires a real, resolvable Payum gateway for the fixture step itself. Both build the PaymentMethod directly via sylius.fixture.example_factory.payment_method with an arbitrary gatewayName/gatewayFactory string; StripeContext goes as far as setUsePayum(false). Confirmed in PaymentMethodExampleFactory/PaymentMethodFactory::createWithGateway() that the factory name is never looked up against Payum's registry at that point - only at actual gateway-build time, which our own PaymentTokenProvider never reaches (it only calls TokenFactory::createCaptureToken(), itself registry-free). So no new Payum package is needed: a small dedicated Tests\...\Behat\Context\Setup\PaymentContext, mirroring core's own PaymentContext::createPaymentMethod(), handles the exact step text the 3 feature files already use.
…scenario removeProduct() pressed the item row's Delete button without waiting for the Live Component's async removal to settle, unlike every other mutating action in this class. Harmless when the next step happened to give it enough time, but "reorder previously placed order with different products" - never actually run before since it depends on the online-payment fixture just added - submitted the form before the removal had taken effect, resubmitting the "removed" item along with everything else.
Previously, confirming an order with a non-offline payment method
always emailed the customer a payment link, with no way for the
admin to opt out. Adds a checkbox on the preview page ("Send a
payment link to the customer via email"), unchecked by default, so
no email goes out unless the admin explicitly asks for it.
The checkbox only gates OrderPaymentLinkSender::sendPaymentLink() -
the payment-link itself (used by the "Pay" button on the order show
page) is still generated and stored on the payment regardless, since
that's a separate concern from whether we proactively email it.
PaymentLinkCreationListener::setPaymentLink() runs on
sylius.order.post_admin_create, dispatched by Sylius's own generic
resource controller - it only gets the created Order as the event
subject, no access to the submitted form. Rather than persisting a
one-shot UI flag onto the Order model (a migration for something
that only matters for the single request that creates the order),
the listener reads the checkbox straight off the current Request via
RequestStack, keyed by NewOrderType::BLOCK_PREFIX (now a public
constant so the read stays in sync with the form's actual name).
The field itself is declared unmapped on NewOrderType (CheckboxType,
mapped: false) rather than a hand-rolled <input> outside the form,
since Symfony's form component rejects unrecognized POST keys by
default ("This form should not contain extra fields").
…tion Live Component actions (add discount, add item, ...) only re-submit raw form values onto a freshly instantiated order, so nothing recalculated derived data (unit prices, totals, shipping cost), and the shipment choice-list eligibility check could invalidate an already-selected method, silently dropping the whole shipment from the order. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
PaymentLinkCreationListener hardcoded the single gateway name 'offline' to decide when to skip payment-link generation, forcing host applications to fully decorate the listener just to recognize their own offline gateways (e.g. bank transfer). Expose it as plugin configuration (sylius_admin_order_creation_plugin.offline_gateway_names, defaulting to ['offline']) instead.
Host applications that don't want the plugin's payment-link/email behavior had no way to opt out short of writing a compiler pass that removes the PaymentLinkCreationListener service entirely. Add a payment_link_generation_enabled configuration flag (default true) so this is a one-line config change instead.
The bundle never overrode Bundle::getContainerExtension(), and Symfony's default naming convention (which derives the expected extension class name from the bundle class name) didn't match the actual SyliusAdminOrderCreationExtension class name. As a result the extension's load()/prepend() were never invoked by the container compiler, silently making Configuration.php dead code and the two parameters introduced in the previous commits undefined at runtime (discovered via the new integration test, which failed to boot the container until this was fixed).
The order creation/preview/reorder routes resolved their _sylius.factory via expr:service() against the concrete OrderFactory class name, bypassing the OrderFactoryInterface seam that decorating host applications are expected to use. Alias the interface to the service and point routing.yaml at it instead, so a host app's decorator is picked up wherever the plugin resolves the factory.
Host applications wanting to hook into "an order was just created from the admin panel" (e.g. to notify the placing agent, or to build an anti-double-submission guard) had nothing to listen to but Sylius core's generic, untyped sylius.order.post_admin_create GenericEvent - forcing them to duplicate/replace whole listeners to get a stable extension point. OrderCreationListener now also dispatches a typed OrderCreatedByAdminEvent carrying the created order, so this becomes a plain #[AsEventListener] on a plugin-owned event class.
The section still described the pre-migration (Sylius 1.x) template override mechanism (templates/bundles/SyliusAdminBundle) instead of the Twig Hooks now used for the Order Show sections, didn't mention the plugin's own configuration/events, and implied the AdjustmentType constants were open for extension when the class is final. Document what's actually configurable/hookable today, and where the plugin's Twig/UI migration to Sylius 2 still has ground to cover (order creation/preview/select-customer pages).
OrderCreatedByAdminEvent alone only covers the very end of the creation flow. Add two more plugin-owned events at other points a host application is likely to need a hook, generalized from real needs observed in a downstream project rather than tied to it: - OrderCreationInitializedEvent, dispatched by OrderFactory whenever an order is initialized for admin creation or reorder (both createForCustomerAndChannel and createFromExistingOrder). Lets a host guard/veto or enrich the order before it's shown/processed, without having to replace the controllers or factory outright. - PaymentLinkGeneratedEvent, dispatched by PaymentLinkCreationListener right after a payment link is generated, decoupled from the "send email" step, for side effects (notifications, audit, CRM sync, ...) that shouldn't be tied to the email toggle.
Keep the Events subsection in sync with the OrderCreationInitializedEvent and PaymentLinkGeneratedEvent additions.
The create.html.twig page rendered the order-form Live Component inline, forcing a host application to copy the whole template to change anything in the page body. Replace that with a sylius_admin_order_creation.order.create.content hook (single "form" hookable, unchanged content) so a host can add a sibling hookable or disable/replace it without owning the page shell.
Same rationale as the create page: the preview-form Live Component was embedded inline in preview.html.twig. Replace it with a sylius_admin_order_creation.order.preview.content hook (single "form" hookable, unchanged content).
select_customer.html.twig hardcoded both the "existing customer" and "create new customer" cards inline, so a host application wanting to drop the create-new-customer path (as mtdistribuzione did, by copying the whole template) had no lighter-weight option. Split each card into its own hookable (existing_customer, new_customer) registered under sylius_admin_order_creation.order.select_customer.content, so it can now be done with `enabled: false` in the host's own twig_hooks config.
Document the new order create/preview/select-customer hooks alongside the existing Order Show ones, and correct AGENTS.md's outdated note that the Twig/UI layer was still mid-migration and templates targeted removed Sylius 1.x paths - verified during this work that none of the plugin's @SyliusAdmin/... references are actually broken, and the migration is now complete.
Previously OrderPreviewAction skipped form validation entirely, so an incomplete order (e.g. missing shipping address) always moved on to the preview step with no errors shown.
The preview form only visibly renders items, adjustments and the send- payment-link checkbox; every other field (addresses, payments, shipments, locale, currency) is dumped via form_rest inside a display:none block, so their validation errors existed in the DOM but were never visible when confirming an order with invalid data.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.