proophsoftware/prooph-bundle
Symfony bundle that integrates Prooph's CQRS and event sourcing components, wiring event stores, command and event buses, projections, and service configuration so PHP applications can build domain-driven, event-driven systems with less boilerplate.
Minimum viable setup for proophsoftware/prooph-bundle v0.1.2:
composer require proophsoftware/prooph-bundle:^0.1.2
v0.1.2 added two hard dependencies worth knowing about before you install:
sandrokeil/interop-config — used to register command/query/event handlers from config rather than hand-wiring services.doctrine/doctrine-migrations-bundle — the event store and projection tables are now expected to be created via Doctrine migrations, and the install instructions assume you already have that bundle wired up.Where to look first:
DependencyInjection/Configuration.php (or equivalent config tree) — this is the contract for how you declare command buses, event buses, event stores and projections.Resources/config/ service definitions — this tells you which services you can inject and which IDs to alias.First use case: a single write model. Define one aggregate (RegisterUser), one command, one domain event, register a command handler, and configure a Doctrine-based event store. Don't add projections until you can prove the command → aggregate → event → store loop works end-to-end. That single loop is what the bundle actually buys you; everything else is prooph components you could wire yourself.
Caveat up front: this is a Symfony bundle, not a Laravel package. There is no ProophServiceProvider, no facade, no publishable config. In Laravel you are consuming the configuration patterns and service wiring ideas, plus the underlying prooph components — expect to write a service provider that mirrors what the bundle's extension class does.
Command bus per boundary. Configure at least two buses: a synchronous command bus for immediate validation and an asynchronous one for anything that touches the event store or external systems. Handlers are registered by config key with interop-config, so the mapping lives in config rather than in AppServiceProvider closures.
Aggregate + repository pairing. One EventSourcedRepository per aggregate type, built from an EventStore. Keep repositories out of controllers — inject the command bus into controllers/HTTP layer and let handlers resolve repositories.
Event store via Doctrine. The doctrine/doctrine-migrations-bundle dependency added in v0.1.2 signals that the event store tables are migration-managed. Treat the event store schema as a first-class migration: commit it, version it, and never let doctrine:schema:update touch it.
Projections as separate consumers. Register read-model projectors against the event bus (sync, for small local projections) or against a persistent subscription (async, for anything expensive). In Laravel, async projectors map naturally onto queue workers: one job per subscription poll, or a long-running artisan prooph:projection --name=... command you supervise.
Config-driven handler registration. Because interop-config is now a dependency, the idiomatic pattern is:
// config/prooph.php (translated into a Laravel config array)
'command_bus' => [
'plugins' => [/* ... */],
'handlers' => [
'App\Registration\RegisterUser' => 'App\Registration\RegisterUserHandler',
],
],
Resolve these keys in a service provider with $this->app->bind(...) in a loop. The payoff is that adding a handler is a config change, not a container change.
Integration tips for Laravel specifically:
ProophServiceProvider; expose command_bus, query_bus, event_bus, event_store as singletons.DB::transaction() around the command handler only if you are not using the event store's own transactional wrapper; don't nest them.config/prooph.php so it is cacheable with config:cache.The v0.1.2 install instructions are load-bearing. Both new dependencies are behavioural, not cosmetic. If you upgrade from v0.1.1 and your composer.json has doctrine/doctrine-migrations-bundle pinned or excluded, Composer will fail or you'll get a container that references migration services that don't exist. Re-read the install instructions after any v0.1.x bump.
interop-config failures surface at container compile time, not runtime. A typo in a handler config key produces an unhelpful "service not found" during config:cache or container build. Keep the handler map small and add a php artisan prooph:debug-handlers-style command of your own if the map grows past ~20 entries.
Pre-1.0 versioning. v0.1.x makes no BC promises. Pin exactly (0.1.2) in composer.json and upgrade deliberately; the release notes for this version are dependency-only, which is a hint that the maintainers are still stabilising the wiring rather than the API.
Doctrine migrations conflict. Adding doctrine/doctrine-migrations-bundle to a Laravel app is unusual — Laravel typically uses its own migrations. Pick one owner for the event store schema. Mixing Laravel migrations and Doctrine migrations on the same database is a reliable source of "table already exists" in CI.
Event serialisation is your problem, not the bundle's. Event store rows are only as portable as the serialiser you configure. Version your event class names explicitly (App\Events\V2\UserRegistered) and never rename an event class without a migration or an upcaster — old rows will fail to deserialize on replay.
Projections are eventually consistent. Do not read from a projection immediately after dispatching a command in the same request. Either return the write model's state from the handler or accept a read-after-write gap.
Replay safety. Projectors must be idempotent. If a projection fails mid-stream, reset the projection (truncate the read table, reset the subscription position) and replay — make sure your projector tolerates seeing the same event twice.
Transaction boundaries. The event store append and any synchronous projection update should share a transaction; async projections obviously cannot. Be explicit about which of your projections are sync, because the answer determines whether a "committed" event is guaranteed visible to a read model.
Extension point: the bundle's compiler passes are where you hook custom buses. If you need a third bus (e.g. a separate query bus with a different middleware stack), copy the pattern from the bundle's extension class rather than inventing a parallel one — this keeps upgrades to v0.2.x tractable.
Ecosystem risk. Prooph v7 is effectively end-of-life and maintenance has moved to community forks. For a greenfield Laravel project, evaluate event-engine/php-engine or a Laravel-native event sourcing package before committing; adopt this bundle when you specifically need prooph's existing components or are inheriting a prooph codebase.
How can I help you explore Laravel packages today?