Event Sourcing in PHP

Event-sourced aggregates with #[EventSourcingAggregate]. Projections via #[ProjectionV2] — global, partitioned, or streaming. Gap detection on by default. Projection emission with rebuild auto-suppression. End-to-end PII encryption across event store, broker, and structured logs.

Composer package · Laravel or Symfony · PostgreSQL or MySQL · RabbitMQ, Kafka, SQS, Redis, or DBAL outbox

How it compares

PHP alternatives for this category

Each row names what you'd need to add to match Ecotone, and the architectural ceiling that integration cannot fix.

Spatie laravel-event-sourcing

Eloquent-bound. No gap detection. No projection emission. Single-process replay.

  • Aggregates must extend `Spatie\EventSourcing\AggregateRoots\AggregateRoot`; events must implement the marker interface `ShouldBeStored`; projectors extend `Projector`; read models extend the Eloquent-backed `Projection` model.
  • No gap detection on the `stored_events` table — concurrent-transaction commits can be silently skipped.
  • No `EventStreamEmitter` equivalent for projections to emit downstream events transactionally.
  • No crypto-shredding / PII encryption.
  • Rebuilds run as a single-process `php artisan event-sourcing:replay` — no parallel-worker, no partitioned rebuild for millions of events.
EventSauce

An event-sourcing library only. Manual glue for everything downstream.

  • An event-sourcing library only — no messaging, no async dispatch, no orchestration ships.
  • Every downstream concern (event dispatch, async communication, multi-step coordination, cross-service distribution, shared retry/DLQ/PII middleware) becomes manual glue you assemble from separate libraries.
  • Aggregates use the `AggregateRootBehaviour` trait; storage via `MessageRepository` (`DoctrineMessageRepository` or `IlluminateMessageRepository`); event consumers implement `MessageConsumer` through a `MessageDispatcher` chain you wire yourself.
  • `MessageRepository::retrieveAll` paginates by aggregate, not by a gap-aware global position — concurrent-commit events can be silently skipped.
  • No subscription engine with per-consumer cursors. No `EventStreamEmitter` for projection-to-store emission. No PII encryption.
Patchlevel event-sourcing

An event-sourcing library only. Subscription failures block the whole pipeline.

  • An event-sourcing library only — no messaging, no async dispatch, no orchestrator, no distributed bus.
  • Every downstream concern (cross-service communication, async messaging, multi-step coordination, shared retry/DLQ/PII middleware) becomes manual glue with separate libraries.
  • **Production-critical failure mode:** the subscription engine advances a single position per subscription but doesn't isolate failure per partition — when one handler fails, the entire processing pipeline blocks until the failure is resolved or skipped.
  • Tightly coupled to Doctrine DBAL — features like `#[Stream]` and Micro-Aggregates work only with `StreamDoctrineDbalStore`.
  • Gap detection is opt-in per setup, not on by default. Crypto-shredding is scoped to the event store only — broker payloads and structured logs travel in cleartext.
  • No `EventStreamEmitter` primitive. Rebuild supports blue-green via subscriber-id version bump, but a single projection still runs on one cursor — no parallel-worker rebuild for millions of events.
Ecotone

Scales with your event volume — from one cursor to N parallel workers.

  • Covers what every PHP event-sourcing library covers — event-sourced aggregates, projections, snapshots, replay — and adds the operational layer they don't.
  • Global tracked projections work for the early-stage system. As volume grows, switch to partitioned projections that rebuild in parallel across N workers: 50 million events that take 14 hours on a single-cursor rebuild finish in minutes when partitioned across 60 workers.
  • Streaming projections consume directly from Kafka or RabbitMQ Streams.
  • Blue-green deployments host the new projection version alongside the old one — atomic switch when ready, zero downtime.
  • Default-on gap detection catches concurrent-commit events before they're silently skipped.
  • End-to-end PII encryption follows the same `#[Sensitive]` field through the event store, the broker, and your structured logs.

Frequently asked questions

Haven’t found what you’re looking for? Contact us

Ecotone provides a built-in event store on PostgreSQL or MySQL, event-sourced aggregates via #[EventSourcingAggregate], projections via #[ProjectionV2] with global / partitioned / streaming variants, default-on gap detection, projection emission via EventStreamEmitter with rebuild auto-suppression, and end-to-end PII encryption across event store + broker payloads + structured logs. Compared to Spatie laravel-event-sourcing (Laravel-only, single-process rebuild, no gap detection), EventSauce (no subscription engine, shared-envelope dispatch, no projection emission), and Patchlevel event-sourcing (single-cursor per projection, encryption stops at the event-store boundary), Ecotone wins on the operational axes for high-volume event-sourced systems.
Events committed in concurrent transactions can be re-ordered relative to their commit timestamps — a transaction that started earlier may commit later. Without gap detection, the projection reads the visible event with the higher position, advances its cursor past the gap, and silently skips the event that committed later. Ecotone's GapAwarePosition tracks the gap and waits for it to close (or fills it) before advancing — so concurrent-commit events are never silently dropped. Spatie and EventSauce don't ship gap detection; Patchlevel makes it opt-in.
Yes. EventStreamEmitter inside a projection handler publishes a new event onto the event bus — sagas, other projections, and event handlers subscribe via the normal #[EventHandler]. Critically, emission is automatically suppressed during a projection rebuild, so downstream consumers aren't flooded with historical duplicate events. Spatie's reactors handle live emission but don't auto-suppress on rebuild; EventSauce and Patchlevel don't ship projection emission.
One #[Sensitive] attribute on an event field encrypts the value in the event store, on the wire over RabbitMQ / SQS / Redis / Kafka / DBAL outbox, and in your structured logs — because all serialization flows through one shared conversion pipeline. Crypto-shred a customer by deleting their key; their events become unreadable everywhere they live. Patchlevel's crypto-shredding is event-store-only; Spatie and EventSauce don't ship encryption.

Be part of the change with EcotoneCurve

Unleash the power of Messaging in PHP
and push productivity to the higher level

Get started
Gradient
Shapes 1
Shapes 2
DiscordTwitterSupport and ContactTelegram