The PHP architecture layer that grows with your system — without rewritesDecorative curve

From #[CommandHandler] on day one, to event sourcing, sagas, outbox, and distributed messaging at scale — one package, same codebase, no forced migrations between growth stages.

Works on the Laravel or Symfony you already run · Install with Composer · Configure with PHP attributes

The growth ladder

Same classes. Same codebase.
No forced migration between stages.

Start with a command handler. Add async when you need it. Add sagas, event sourcing, and distributed messaging later — on the same code, by writing new classes next to the old ones, not by swapping libraries.

1Day 1
  • #[CommandHandler]
  • #[EventHandler]
  • #[QueryHandler]

Familiar handlers.

Five-minute start. Command, query, and event buses auto-wired from attributes.

2Week 1
  • + #[Asynchronous]
  • + #[Interceptor]
  • + Retries / DLQ

Async with resilience.

Move handlers to RabbitMQ, SQS, Kafka, or DBAL with one attribute.

3Month 3
  • + #[Saga]
  • + Workflows
  • + transactional outbox

Stateful workflows.

Long-running processes with compensation, outbox-consistent by default.

4Year 1+
  • + #[EventSourcingAggregate]
  • + #[Projection]
  • + #[DistributedBus]
  • + Multi Tenant Channels

Event sourcing.

Replayable aggregates and cross-service messaging — on the same code.

Every other PHP alternative forces you to re-decide architecture at each column break — swap libraries, add glue, or stitch together a multi-package stack. No other single PHP package spans the full set of growth stages.

See It In Action

Enterprise patterns with the simplicity of PHP attributes

OrderService.php
// Asynchronous command handler — that's the entire setup

#[Asynchronous('orders')]
#[CommandHandler]
public function placeOrder(PlaceOrder $command): void
{
    $order = Order::create($command->orderId, $command->items);
    $this->orderRepository->save($order);
}

// Ecotone handles the rest:
// ✓ Async execution via RabbitMQ / Kafka / SQS / DBAL
// ✓ Automatic retries on failure
// ✓ Dead letter queue if all retries fail
// ✓ Message tracing and correlation
No forced migrations

Every other PHP choice
commits to a ceiling on day one.

Beyond the ceiling lies integration work: stitch another library, write custom glue, or rewrite. Only one PHP package spans every growth stage on the same code.

AlternativeWhat it isIntegration you'll needCeilings as you scale
Spatie laravel-event-sourcingLaravel-only event sourcing. Aggregates, projectors, reactors — ergonomic on Eloquent and Laravel queues.Asynchronous messaging · sagas · outbox · dedup middleware · PII encryption · parallel-rebuild strategy.Single-process projection rebuild.No per-projector cursor; no gap detection — events committed in concurrent transactions can be silently skipped; async retry re-runs every handler; projectors cannot emit downstream events — integration cannot fix the underlying dispatch model.
EventSauceFramework-agnostic event-sourcing library. Clean aggregate + event-store primitives with minimal opinion.Asynchronous messaging · command/query bus · sagas · workflows · outbox · dedup · PII encryption.Per-handler failure isolation.The first throwing consumer aborts dispatch for siblings; retry re-runs every consumer on the same envelope. No subscription engine, no per-consumer cursor, no gap detection (concurrent-commit sequences can be silently skipped). Projections cannot emit downstream events.
Patchlevel event-sourcingDoctrine-based event sourcing with command/query bus, subscription engine, per-subscription isolation, PostgreSQL push streaming, crypto-shredding inside the event store.Asynchronous messaging · sagas · outbox · distributed bus · PII encryption for messages on brokers · automatic correlation/causation propagation.Intra-projection partitioning.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. Encryption stops at the event-store boundary. Subscriptions cannot emit downstream events.
Temporal PHP SDKDurable workflows, polyglot across Go / Java / TypeScript / PHP. Workflow execution with signals, queries, activities.Event-sourcing library · CQRS bus · broker adapters inside activities (Temporal can't use your existing RabbitMQ / Kafka / SQS) · glue to push events into Temporal as workflow signals.Domain event stream ownership.Workflow state lives in Temporal's internal event history — not a stream you own. You cannot add new subscribers later or build custom projections from replay history as your domain evolves. Also brings its own cluster (Frontend / History / Matching / Worker).
Symfony Messenger aloneMessage dispatch with pluggable transports, retry, failure transport, middleware. First-class Symfony component.Event-sourcing library (plus glue to push stream events through the bus and handle its custom serialization) · sagas · outbox · idempotency/dedup · PII encryption · correlation/causation propagation · multi-tenant routing middleware.Per-handler failure isolation + multi-tenant topology.A single envelope is dispatched through all handlers. No built-in outbox, dedup, idempotency, or multi-tenant queue support.
Laravel Queues / HorizonJob runner with supervisors, retries, rate limiting, batches, chains. Excellent operational UI via Horizon.Command/query/event bus · aggregates · event sourcing · sagas · outbox · dedup · PII encryption · correlation/causation propagation.Every architectural pattern.It's a job runner, not a message bus — every pattern becomes a separate library decision.
EcotonePHP architecture layer. Command/query/event buses, aggregates, event sourcing, sagas, projections, outbox, distributed bus, EIP routing, per-handler isolation, and end-to-end PII encryption — all attribute-driven, on the brokers you already operate (RabbitMQ, Kafka, SQS, Redis, DBAL).Integrates seamlessly into your existing architecture via the Symfony Bundle, the Laravel Provider, or Ecotone Lite for any other framework.Built on a messaging foundation.Scalability, resiliency, and per-handler failure isolation are provided uniformly for every higher-level building block — asynchronous event handlers, projections, sagas, and workflows all inherit them. New capabilities are new attributes on the same code.

Composable with alternatives. Complete standalone.

Ecotone doesn't have to be the entire stack. It composes with the other libraries above when a team already has a preferred tool for one layer:

  • Ecotone as the orchestration, saga, and outbox layer — delegating long-running cross-language durable workflows to Temporal.
  • Ecotone as the CQRS, workflows, and asynchronous communication layer — delegating event sourcing to EventSauce, Patchlevel, Spatie laravel-event-sourcing, or your own ES library.
  • Ecotone as the Event Sourcing layer — you bring message handlers on Symfony Messenger (or your bus of choice); Ecotone persists the events and provides scalable (partitioned + streaming) projections.

Ecotone is the only toolkit on this list that runs fully standalone. Every other option requires you to integrate something for the layers it does not cover. Ecotone covers every layer itself when you want it to, and composes with alternatives when you prefer a specific tool for one piece.

Capabilities

Every pattern your domain will need — on one codebase

CQRS, event sourcing, sagas, outbox, EIP routing, multi-tenancy, distributed bus — all attribute-driven, all on the same messaging foundation.

CQRS & Handlers

Command, query, and event bus — all auto-wired from your attributes. No registration, no factory classes, no container bindings.

Event Sourcing & Projections

Store events, not state. Aggregates, projections, replay, snapshotting. Partitioned + streaming projections so rebuilds parallelize across workers and catch up in real time — no single-process bottleneck.

Sagas & Workflows

Stateful long-running processes with compensation. Handler-chaining workflows for stateless pipelines. All declarative, all attribute-driven.

Async Messaging

RabbitMQ, Kafka, SQS, Redis, DBAL. Move handlers to async with one attribute. Transports swap without code changes.

Composable EIP Messaging

Pipe handlers together. Route by payload or headers. Split one message into many. Filter, enrich, transform — all as attributes, no glue code.

Per-handler Failure Isolation

A copy of the message is dispatched to every handler. Each retries independently, fails independently — no shared envelope, no sibling re-runs.

Production Resilience by Default

Transactional outbox, dead letter queue, deduplication, retries, OpenTelemetry — all default behavior, not assembly.

Custom Buses per Use Case

Extend CommandBus into use-case-specific interfaces. Attach retry, DLQ, and dedup as attributes directly on the interface declaration.

Multi-tenant Messaging

Tenant-isolated event streams, tenant-routed message channels, priority routing by customer status. Multi-tenancy as a topology property, not a WHERE clause.

Endpoint-ID Routing

Rename classes, move handlers, refactor namespaces — messages still route correctly. The endpoint ID is the contract; FQCN is not on the wire.

Compose Patterns Without Glue

Aggregate publishes event. Saga subscribes by attribute. Projection subscribes by attribute. Async handler subscribes by attribute. The attribute is the wiring.

Framework-portable Business Code

Same aggregates, handlers, sagas, and projections run on Laravel, Symfony, or any PSR-11 container. Hedge against framework changes over a decade.

End-to-end PII Protection

One #[Sensitive] attribute encrypts a field 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.

Projections That Emit Events

A projection can emit a downstream event the moment it applies a change — sagas, event handlers, and other projections subscribe via the normal #[EventHandler]. Rebuild a projection? Emission is automatically suppressed, so downstream consumers aren't flooded with duplicate historical events while the read model catches up.

Runs on the Brokers You Already Have

First-party packages for RabbitMQ, Kafka, SQS, Redis, and DBAL. Ecotone fits your existing stack — no new cluster to operate, no migration of your message infrastructure.

Correlation & Causation, Automatic

Correlation IDs and parent-message IDs propagate from command to every emitted event without middleware. Your OpenTelemetry spans stitch themselves end-to-end — no bundle to install, no stamps to remember.

Blue-Green Projection Rebuild

Rebuild a projection on a new version in parallel — concurrent async backfill partitioned by aggregate ID scales rebuilds to millions of events across N workers. The live projection keeps serving queries until the atomic flip.

Self-Healing Projections

Projections are trigger-based: on every run they read from the Event Store at their last committed position. Crash at event #42? Fix the bug, deploy, and the projection catches up automatically — no manual reset, no backfill script, no 3am SSH session.

Built For Your Stack

What Ecotone brings to your stack

Laravel handles HTTP beautifully. But the moment you need event sourcing, sagas, or multi-step workflows — you're stitching packages together. Ecotone gives you one coherent toolkit: aggregates, projections, outbox, and more — all attribute-driven, all testable in-process, all running on the Laravel queues you already have.

Works with Eloquent & Doctrine
Laravel Queue integration
Zero config to start
In production

Trusted in regulated
and high-stakes production

The features on the capability matrix are not hypothetical. They run in systems where failure is either regulated, expensive, or public.

Payment gateways

Where retried handlers cannot double-charge, and the outbox must guarantee that every committed transaction produces exactly one downstream message.

Credit card systems

Where transaction loss is catastrophic, and every state change must be auditable and replayable.

Certification authorities

Whose entire business depends on reconstructible, tamper-evident audit trails; the event log is the audit log.

E-commerce platforms

Orchestrating order → payment → fulfillment → notification as declarative sagas with compensation.

Public transportation subscription systems

Managing nationwide transit subscriptions (create, renew, terminate) with distributed bus integration to Java and PHP services over Kafka.

Two-sided marketplaces

Coordinating customer orders, provider subscriptions, lead distribution, and B2B enterprise partnerships on one event-driven backbone.

Testing

One testing model for every flow

Your tests run through the same messaging pipeline as production — bus routing, async channels, event propagation — all in one process. Send a message, run the flow, assert the result. The test shape never changes.

Extract exactly the flow you care about and test it in isolation:

OrderServiceTest.php
// Test a specific flow in isolation — only the services you need

$ecotone = EcotoneLite::bootstrapFlowTesting([OrderService::class]);

$ecotone->sendCommand(new PlaceOrder('order-1'));

$this->assertEquals('placed', $ecotone->sendQueryWithRouting('order.getStatus', 'order-1'));

Bring in async handlers, enable an in-memory channel, and verify the full flow:

OrderWithNotificationsTest.php
// Test the full async flow — in-memory channel, same process

$notifier = new InMemoryNotificationSender();

$ecotone = EcotoneLite::bootstrapFlowTesting(
    [OrderService::class, NotificationService::class],
    [NotificationSender::class => $notifier],
    enableAsynchronousProcessing: [
        SimpleMessageChannelBuilder::createQueueChannel('notifications')
    ]
);

$ecotone
    ->sendCommand(new PlaceOrder('order-1'))
    ->run('notifications');

$this->assertEquals(['order-1'], $notifier->getSentOrderConfirmations());

Swap the in-memory channel for DBAL, RabbitMQ, or Kafka in production — the test stays the same. Ecotone runs the consumer in-process, so switching transports never changes how you test.

Drops into your stack

Your framework stays.
Your ORM, queues, and deploy stay too.

Ecotone is a Composer package that adds architecture on top of what you already run. Business logic as POPOs, messaging topology as attributes, your existing Laravel, Symfony, or PSR-11 container underneath.

Ecosystem

Enterprise Patterns Across Ecosystems

Every mature platform has this. Now PHP does too.

EcosystemFrameworkEnterprise PatternsCQRSEvent SourcingWorkflowsMessaging
JavaSpringAxon Framework
.NETASP.NETNServiceBus / MassTransit
PHPLaravel / SymfonyEcotone
⭐ 550+
GitHub Stars
Star us on GitHub →
550,000+
Packagist Downloads
View on Packagist →
9+
Years of Active Development
No breaking changes across major versions

Ecotone follows a stability commitment — your business code keeps working across releases, so upgrades are safe to apply.

See the changelog →
Commercial support, SLA, and workshops

Available for teams running Ecotone in production. Consulting, onboarding workshops, and SLA-backed support agreements.

Contact us →
AI-Ready

Built for AI-assisted development

Declarative configuration that any coding agent can follow and reproduce. Testing support that lets it verify even the most advanced flows. Less guessing, no hallucinating — just confident iteration.

Less context, less tokens

Declarative attributes mean less infrastructure code to feed into the AI's context window and less boilerplate to generate. Smaller input and output means lower cost, faster iteration and more accurate results.

Agentic Skills

Ready-to-use skills that teach any coding agent how to correctly write handlers, aggregates, sagas, projections, and tests. Your AI generates idiomatic Ecotone code from the start.

MCP & llms.txt

Direct documentation access for AI assistants via Model Context Protocol. Works with Claude Code, Cursor, Windsurf, GitHub Copilot, and others.

AI-testable async

The same straightforward test model applies to async flows. Your coding agent writes tests that run the real pipeline — no special setup to guess at.

Ready to Get Started?Curve

Add Ecotone to your existing Laravel or Symfony project in minutes.

composer require ecotone/laravel
Gradient
DiscordTwitterSupport and ContactTelegram