Asynchronous Communication in PHP

Move any handler to async with one attribute. Atomic outbox in one DBAL transaction. Per-handler failure isolation. #[Delayed], #[Priority], #[TimeToLive] consistent across RabbitMQ, Kafka, SQS, Redis, DBAL, Messenger transports, and Laravel Queue channels — handler code is broker-agnostic.

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

How it compares

What Ecotone adds over a single-purpose tool

Each tool below is a capable choice for its slice. Ecotone covers the same ground and adds the operational layer around it — so aggregates, projections, sagas, and messaging share one model and one set of guarantees.

Symfony Messenger alone

A mature dispatch and transport layer.

Where Ecotone goes further than this library

  • Ecotone adds a transactional outbox (CombinedMessageChannel) — the message is committed to the database with the business write, then handled by consuming from the broker.
  • Per-handler failure isolation: each subscriber gets its own copy of the message, so a retry stays scoped to the handler that failed.
  • A uniform #[Delayed], #[Priority], and #[TimeToLive] attribute model works the same across RabbitMQ, SQS, Redis, and DBAL.
  • Header-routed multi-tenant channels run in one deployment.
  • Runs on the Symfony Messenger transports you already configure.
Laravel Queues / Horizon

A robust job runner with an excellent operational UI.

Where Ecotone goes further than this library

  • Ecotone adds first-class command / event / query buses — fan-out to multiple subscribers is part of the model, not hand-wired.
  • A transactional outbox commits the business write and the outgoing message together.
  • A broker-agnostic channel abstraction with a uniform #[Priority] / #[Delayed] / #[TimeToLive] model above the transport.
  • Runs on the same Laravel queues and Horizon you already operate.
Ecotone

From #[Asynchronous] on day one to multi-broker outbox at scale.

  • On day one, #[Asynchronous('channel')] moves any handler to async — the same handler code runs on RabbitMQ, Apache Kafka, Amazon SQS, Redis, DBAL, Symfony Messenger transports, or Laravel Queue channels.
  • As volume grows, CombinedMessageChannel splits outbox storage from execution: the database holds the outbox (one poller drains it), the broker carries execution (consumers scale horizontally).
  • Per-handler failure isolation by default — each subscriber gets its own copy of the message, so one failing handler doesn't trigger sibling re-runs on retry.
  • #[Delayed], #[Priority], #[TimeToLive], and scheduled messages have one consistent attribute model across brokers.
  • Dynamic channels route per-tenant at runtime via headers without redeploying.
  • The same #[Asynchronous] model composes every building block — run a projection asynchronously, route commands and events across applications with the Distributed Bus, or build pipe-and-filter flows from chained handlers — one channel model, not a different mechanism for each.
  • The dispatch you started with composes upward; nothing is replaced as you scale.

Frequently asked questions

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

Symfony Messenger and Laravel Queues are mature dispatchers — they ship transports, retry/failure transport, and middleware. They don't ship the architectural primitives that production async messaging needs above the dispatch layer: transactional outbox, per-handler failure isolation (each subscriber gets its own copy of the message), deduplication, multi-tenant routing, or aggregate / saga / event-store / projection support. Ecotone adds those primitives and uses Messenger transports and Laravel Queue channels as the underlying transport when they're already configured — existing investments stay; new architectural capability is added.
"CombinedMessageChannel::create('outbox_sqs', ['database_channel', 'amazon_sqs_channel'])" writes the message into the database in the same DBAL transaction as the business state change, then dispatches actual handler execution onto SQS (or any broker). One outbox poller drains the database; many consumers handle broker-side execution. If the business transaction rolls back, the atomicy of the changes is ensured. The dual-write problem disappears at the primitive level.
When multiple handlers subscribe to the same event, Ecotone delivers a copy of the message to each handler. Each copy is processed, retried, and fails independently — so if one handler fails, only that handler retries and its siblings are unaffected. That eliminates the class of duplicate side effects that arises when sibling handlers are re-run together on a shared retry.
Yes. Handlers use #[Asynchronous('channel-name')] — broker-agnostic. The channel's underlying transport is bound in one #[ServiceContext] configuration method. Swap RedisBackedMessageChannelBuilder for AmqpBackedMessageChannelBuilder or SqsBackedMessageChannelBuilder — handler code unchanged.

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