xm/symfony-bundle
XM Symfony Bundle provides a Symfony integration layer for XM, including bundle registration, service definitions, and autowiring support, making it easy to add XM features to Symfony applications with minimal configuration.
Architecture fit. Despite the "Laravel/PHP package" framing, this is a Symfony bundle only — there is no Laravel service provider, no framework-agnostic core, and hard dependencies on FrameworkBundle, SecurityBundle, Twig, Doctrine ORM, Symfony Messenger, and OverblogGraphQLBundle. Architecturally it is not a library but an opinionated platform starter: it ships base classes (AggregateRoot, DomainMessage, ValueObject, UuidId, ValueObjectCollection), a full CQRS/ES runtime on top of Prooph Event Store, projections into Doctrine read models, GraphQL type/query/mutation scaffolding, plus two production concerns you would normally build yourself (session idle-expiry enforcement and maintenance mode with a pre-kernel gate). For a team already committed to Symfony + Messenger + Overblog GraphQL + event sourcing, the fit is high because it removes months of glue code. For anyone else, including any Laravel shop, fit is effectively zero — the reusable surface shrinks to a handful of leaf utilities (ContentDisposition, PasswordStrength, StringUtil, some value objects).
Integration feasibility. Low-to-moderate, and gated on inspection rather than documentation. The bundle is not self-contained by its own admission: the post-make:model checklist instructs you to edit App\Projection\Table and App\Messenger\RunProjectionMiddleware, which are host-application classes, not bundle classes. That means the scaffolded output assumes a specific app skeleton the bundle does not ship — you will be reverse-engineering one team's internal project template from a Maker command. Maintenance mode additionally requires editing the front controller to call MaintenanceGate::handleGlobals() before the kernel boots, which is an invasive, per-project change. Session expiry is on by default and requires sessions plus SecurityBundle. Treat adoption as "fork a platform," not "install a dependency."
Technical risk.
=> alignment — modern and disciplined, but it means your CI must match theirs to consume generated code cleanly.Key questions to resolve before any commitment.
make:model outside an XM-shaped app (does it emit the App\* couplings or read them from config)?Stack fit. Target stack is Symfony 6/7, Doctrine ORM, Symfony Messenger, Twig, and OverblogGraphQLBundle, with PHP 8.1+. If the evaluating team is on Laravel, stop at a spike: port ContentDisposition and the value-object primitives by hand and discard the rest. If on Symfony but GraphQL-free, the ES/CQRS layer may still be viable — confirm the coupling in question 1 first.
Migration path. Do not adopt wholesale on a first attempt. Three viable options, in ascending order of commitment:
ContentDisposition (non-ASCII filename handling Symfony's HeaderUtils refuses), PasswordStrength, StringUtil, and the UuidId/ValueObject primitives are low-risk, high-value, and lift cleanly.^ constraint.Compatibility work to budget for.
config/packages/xm_symfony.yaml for session_expiry and maintenance toggles.bin/console event-store:event-stream:create per bounded context.app:graphql:dump-schema) wired into CI, or the schema silently drifts.Sequencing.
make:model once, read 100% of the generated output, and run the test suite.Maintenance. You inherit nearly all of it. With no external dependents there is no compatibility contract, no deprecation policy, and no guarantee of timely releases; make:model output and the documented manual steps can change without notice. The heaviest long-term liability is the event store: any decision to leave Prooph is a data migration across every stream, and projection rebuilds are the only safety net. Budget for one engineer to own the fork and track Symfony major upgrades.
Support. Single-vendor, internal-origin code with an AGENTS.md tuned for AI agents rather than a public support process. Expect no SLA, no issue triage, and no security advisories feed. Escalation means contacting the originating team directly or self-supporting.
Scaling. The read side scales conventionally — projections are Doctrine tables, so you add read replicas and indexes as usual. The write side scales by partitioning event streams per aggregate and horizontally scaling Messenger workers; watch for projection lag under load and plan snapshotting before streams get long. Maintenance mode's worker behaviour is actually well designed for operations — workers pause in-place between messages rather than exiting, avoiding supervisor restart loops. Session expiry adds a cheap per-request check but is security-relevant: any long-poll or status endpoint must set _extend_session: false or it will keep sessions alive indefinitely; conversely, remember-me sessions never expire, which may or may not match your policy.
Failure modes worth instrumenting.
.html artefacts linger.Ramp-up. A mid-level Symfony developer needs roughly two to four weeks to be productive on the ES/CQRS conventions (protected constructors, static factories, command/event pairing, projection wiring) and longer to be safe with event versioning and replay. make:model compresses the boilerplate significantly, but the seven-step post-generation checklist — repository config, projection config, stream creation, App\Projection\Table, RunProjectionMiddleware mapping, GraphQL permissions, schema dump — is entirely manual and app-coupled; that is where onboarding time and defects will concentrate. The dense AGENTS.md is a genuine asset for AI-assisted work, provided the agent has repository access and your team reviews generated code rather than trusting it. Net: attractive as an internal platform accelerant for a Symfony/GraphQL/ES shop that is prepared to fork; not viable as a general-purpose dependency.
How can I help you explore Laravel packages today?