From #[CommandHandler] on day one to sagas, orchestrators, event sourcing, outbox, and distributed messaging at scale — one package, same codebase, no forced migrations between growth stages.
Works on the Laravel, Symfony, or Tempest you already run · Install with Composer · Configure with PHP attributes
// 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 on the broker or queue you already run — Laravel Queue, Symfony Messenger, RabbitMQ, Kafka, SQS, Redis, DBAL
// ✓ Automatic retries on failure
// ✓ Dead letter queue if all retries fail
// ✓ Message tracing and correlationEcotone 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, Tempest, or PSR-11 container underneath.
A Composer package, not a rewrite. Your Eloquent models and Doctrine entities become aggregates as-is, and #[Asynchronous] rides the queue or transport you already run — no new broker required.
Two attributes turn an ordinary service into a typed command/query bus. No aggregates, no event sourcing, no broker. The advanced patterns are there when you need them — not before.
// Start with one attribute — no aggregates, no event sourcing, no broker.
class OrderService
{
#[CommandHandler]
public function placeOrder(PlaceOrder $command): void
{
$this->orders[$command->orderId] = $command->productName;
}
#[QueryHandler]
public function getOrder(GetOrder $query): string
{
return $this->orders[$query->orderId];
}
}Show integration — pick your framework, then an example, to see the real code.
Install is one line — a Symfony bundle entry, Laravel auto-discovery, or a composer require ecotone/tempest on Tempest. The tables Ecotone needs (outbox, dead-letter, deduplication, event store, projection cursors) live in your own database and are created by ecotone:migration:database:setup — or dumped as SQL into your existing migrations.
Each new stage is a new attribute, not a new library. The more advanced your needs get — interceptors, sagas, orchestration, event sourcing, multi-tenancy, PII encryption — the more Ecotone already has built in, on the same code.
Familiar handlers.
CQRS buses and aggregates, auto-wired from attributes. Five-minute start.
Async & persistence.
Move work off the request cycle, and reach data through repositories and business-method interfaces — all declarative.
Intercept & tune delivery.
Cross-cutting concerns and delivery control — interceptors, priority, delays, time-to-live — declaratively.
Long-running processes.
Sagas and orchestrators coordinate multi-step business flows, outbox-consistent.
Event sourcing & distribution.
Replayable aggregates, projections, cross-service messaging, and per-tenant connections — on the same code.
Scale & protect.
Streaming and partitioned projections plus end-to-end PII encryption — Enterprise capabilities, same model.
✦ Enterprise capability — included with an Enterprise licence
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.
Ecotone delivers the building blocks itself — aggregates, sagas, projections, the outbox, CQRS, async, and distribution — composed through attributes on one messaging foundation, not assembled from separate libraries. Pick the category that matches your need: each comparison shows how Ecotone stacks up against the single-purpose tool you'd otherwise reach for.
vs Temporal PHP SDK · Durable Workflow (formerly Laravel Workflow) · Symfony Messenger alone · Laravel Queues / Horizon
See comparison →
vs Spatie laravel-event-sourcing · EventSauce · Patchlevel event-sourcing
See comparison →
vs Symfony Messenger alone · Laravel Queues / Horizon
See comparison →
vs Temporal PHP SDK · Durable Workflow (formerly Laravel Workflow) · Symfony Messenger alone
See comparison →
vs Symfony Messenger alone · Laravel Queues / Horizon
See comparison →
vs Spatie laravel-event-sourcing · EventSauce · Patchlevel event-sourcing
See comparison →
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 covers the whole path — aggregate to broker to projection — in one model. There's no glue layer to assemble between slices. It covers every layer itself when you want it to, and composes with the alternatives above when you prefer a specific tool for one piece.
CQRS, event sourcing, sagas, outbox, EIP routing, multi-tenancy, distributed bus — all attribute-driven, all on the same messaging foundation.
Crash-resistant multi-step processes on your existing database and broker. Sagas, orchestrators, chained workflows, outbox, and #[Delayed] timeouts — plain PHP attributes, no separate workflow service, no replay-deterministic DSL.
RabbitMQ, Kafka, SQS, Redis, DBAL outbox. Move handlers to async with one attribute. CombinedMessageChannel writes the message in the same DBAL transaction as the business state, then forwards it to the broker for consumers to handle — an atomic outbox, so a broker outage can't lose it. Delivery is at-least-once, and built-in deduplication makes redelivery safe. Transports swap without code changes.
Transactional outbox, DBAL dead-letter queue with replay, retries with exponential backoff, deduplication, OpenTelemetry on every handler — default behaviour, not assembly required.
A copy of the message is dispatched to every handler. Each retries independently, fails independently — no shared envelope, no sibling re-runs. One failing subscriber doesn't abort the others.
Stateful long-running processes with compensation. Handler-chaining workflows for stateless pipelines. Saga timeouts in one #[Delayed] attribute. All declarative, all attribute-driven.
Command, query, and event bus — all auto-wired from your attributes. No registration, no factory classes, no container bindings.
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.
Pipe handlers together. Route by payload or headers. Split one message into many. Filter, enrich, transform — all as attributes, no glue code.
Extend CommandBus into use-case-specific interfaces. Attach retry, DLQ, and dedup as attributes directly on the interface declaration.
Tenant-isolated event streams, tenant-routed message channels, priority routing by customer status. Multi-tenancy as a topology property, not a WHERE clause.
Rename classes, move handlers, refactor namespaces — messages still route correctly. The endpoint ID is the contract; FQCN is not on the wire.
Aggregate publishes event. Saga subscribes by attribute. Projection subscribes by attribute. Async handler subscribes by attribute. The attribute is the wiring.
Same aggregates, handlers, sagas, and projections run on Laravel, Symfony, Tempest, or any PSR-11 container. Hedge against framework changes over a decade.
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.
A projection can publish its own event the moment it applies a change — so dependent logic reacts in the same flow instead of a separate eventually-consistent process; 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.
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 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.
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.
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.
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 model: aggregates, projections, outbox, and more — all attribute-driven, all testable in-process, all running on the Laravel queues you already have.
Per-handler isolation, the transactional outbox, end-to-end PII encryption, deduplication — the defaults that matter when a dropped or double-processed message is a real-world incident, not a log line.
Where retried handlers cannot double-charge, and the outbox must guarantee that every committed transaction produces exactly one downstream message.
Where transaction loss is catastrophic, and every state change must be auditable and replayable.
Whose entire business depends on reconstructible, tamper-evident audit trails; the event log is the audit log.
Orchestrating order → payment → fulfillment → notification as declarative sagas with compensation.
Managing nationwide transit subscriptions (create, renew, terminate) with distributed bus integration to Java and PHP services over Kafka.
Coordinating customer orders, provider subscriptions, lead distribution, and B2B enterprise partnerships on one event-driven backbone.
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:
// 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:
// 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.
Every mature platform has this. Now PHP does too.
| Ecosystem | Framework | Enterprise Patterns | CQRS | Event Sourcing | Workflows | Messaging |
|---|---|---|---|---|---|---|
| Java | Spring | Axon Framework | ||||
| .NET | ASP.NET | NServiceBus / MassTransit | ||||
| PHP | Laravel / Symfony / Tempest | Ecotone |
Ecotone follows a stability commitment — your business code keeps working across releases, so upgrades are safe to apply.
Behind Ecotone stands SimplyCodedSoftware and its open-source contributors. Every Enterprise licence directly supports continued development.
Available for teams running Ecotone in production. Consulting, onboarding workshops, and Support Agreements: a guaranteed next-business-day response and prioritised fixes for framework defects.
Facades, magic methods, and wiring that lives far from the code an agent is editing are exactly what make AI generate plausible-but-wrong PHP. Ecotone's contract sits on the method — typed, explicit, local — so agents read it instead of guessing.
AI invents Eloquent and facade methods that autocomplete cleanly and only fail at runtime — facades resolve through __callStatic, so there's no real signature to read. A #[CommandHandler] on a typed method is a contract the agent reads right where it's editing.
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.
Direct documentation access for AI assistants via Model Context Protocol. Works with Claude Code, Cursor, Windsurf, GitHub Copilot, and others.
Frameworks relocate central wiring between versions — Laravel 11 moved middleware registration out of Kernel.php into bootstrap/app.php — and agents trained on the old layout write to a file that's gone. Attribute wiring lives on the method, not in a convention file that moves.
Add Ecotone to your existing Laravel, Symfony, or Tempest project in minutes.
composer require ecotone/laravel