Skip to content

Sylius 2 - #1

Open
LucaGallinari wants to merge 44 commits into
mainfrom
sylius-2
Open

Sylius 2#1
LucaGallinari wants to merge 44 commits into
mainfrom
sylius-2

Conversation

@LucaGallinari

Copy link
Copy Markdown
Member

No description provided.

LucaGallinari and others added 30 commits July 27, 2026 16:26
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant