phil / ddd-bundle
DDD/CQRS core for Symfony: command and query bus ports, Messenger adapters, UUIDv7 identifiers and their optional Doctrine DBAL type, domain event and domain errors
Package info
github.com/parler-haut-interagir-librement/ddd-bundle
Type:symfony-bundle
pkg:composer/phil/ddd-bundle
Requires
- php: >=8.5
- symfony/config: ^8.2
- symfony/dependency-injection: ^8.2
- symfony/messenger: ^8.2
- symfony/uid: ^8.2
Requires (Dev)
- doctrine/dbal: ^4.2
- phpunit/phpunit: ^13.4
- psr/log: ^3.0
- symfony/console: ^8.2
- symfony/event-dispatcher: ^8.2
- symfony/framework-bundle: ^8.2
- symfony/http-kernel: ^8.2
Suggests
- doctrine/dbal: To store identifiers in a native uuid column, or BINARY(16) where the platform has none, through Infrastructure\Doctrine\IdentifierType
Provides
None
Conflicts
- doctrine/dbal: <4.2
Replaces
None
This package is auto-updated.
Last update: 2026-11-11 06:15:51 UTC
README
The few types every bounded context shares: identifiers, domain events, errors, command and query buses.
phil/ddd-bundle is the DDD/CQRS core of a Symfony application split into bounded contexts. It gives UUIDv7 aggregate
identifiers that the caller generates, a domain event interface, domain errors, and command and query bus ports with
their Messenger adapters. Handlers are autoconfigured onto the right bus. An optional Doctrine DBAL column type stores
identifiers in a native uuid column, or BINARY(16) where the platform has none. The bundle does not declare your
Messenger buses, and it provides no aggregate base class, clock interface, event sourcing, business value objects or
public presentation of identifiers.
It is for developers who write a Symfony application as several contexts and want them to share a small, stable
vocabulary. The core stays deliberately tiny, because every addition is a dependency all contexts acquire at once,
and a breaking change costs every one of them a major version. Other phil/* bundles that need identifiers or domain
errors build on it, such as phil/audit-bundle and phil/tenancy-bundle. An application with a single context and
plain services has little use for it.
It is a Symfony bundle on Messenger and the Uid component, with Doctrine DBAL as an option. The design is ports and
adapters: the domain and application layers see only CommandBus and QueryBus, and the Messenger classes sit
behind them in the infrastructure layer.
Status
| Part | State |
|---|---|
Identifier, DomainEvent, DomainError, InvalidValue |
Implemented and tested |
Command, Query, CommandBus, QueryBus, CommandHandler, QueryHandler |
Implemented and tested |
MessengerCommandBus, MessengerQueryBus, PhilDddBundle (configuration phil_ddd) |
Implemented and tested |
IdentifierType (DBAL, optional) |
Implemented; unit-tested on five platforms, integration-tested on MySQL 8 and PostgreSQL |
| Dispatching domain events after the Doctrine flush, inside the transaction | Not started |
| Documented Messenger inbox configuration | Not started |
Documentation
- Installation: requirements, installing, registering the bundle, and the two Messenger buses the application declares.
- Configuration: the
phil_dddkeys with their defaults, the services and autoconfiguration rules the bundle registers, and the errors raised at build time. - Identifiers:
Identifierand its UUIDv7 rules, why the caller generates it, and equality by class and value. - Domain events: the
DomainEventinterface, its rules, and what is not implemented yet for dispatching events. - Domain errors:
DomainErrorandInvalidValue, and how value objects validate themselves. - Command and query buses: commands, queries, handlers, why the markers decide the bus, how failures come out, and messages on a transport.
- Doctrine identifier type:
IdentifierType, the column per platform, the conversions and their errors. - Out of scope: what the bundle deliberately leaves out, and what is not started yet.
- Extension points: every class and interface the application extends or implements, with its contract and an example.
- Testing: the bundle's unit and integration suites, their environment variables, and how an application tests its handlers and wiring.
- Reference: the public API by layer, one line per type.
Quick start
composer require phil/ddd-bundle
// config/bundles.php Phil\DddBundle\PhilDddBundle::class => ['all' => true],
# config/packages/messenger.yaml framework: messenger: buses: command.bus: middleware: [validation, doctrine_transaction] query.bus: ~
final readonly class OrderId extends Identifier {} final readonly class PlaceOrder implements Command { public function __construct(public string $orderId) {} } final readonly class PlaceOrderHandler implements CommandHandler { public function __invoke(PlaceOrder $command): void { /* … */ } } $orderId = OrderId::generate(); $commandBus->dispatch(new PlaceOrder($orderId->toString()));
Requirements
PHP 8.5 or later; symfony/config, symfony/dependency-injection, symfony/messenger and symfony/uid 8.2 or
later; doctrine/dbal 4.2 or later only for the identifier column type.
Contributing
Contributions are welcome on the PHIL monorepo: read the contribution guide and the code of conduct. Report a vulnerability privately, as the security policy explains, never in a public issue.
License
MIT, © Philippe Gamache, doing business as Le Gnome Archiviste; a Parler Haut Interagir Librement project. See LICENSE.