Durable Workflows in PHP

Sagas, orchestrators, chained workflows, outbox, and #[Delayed] saga timeouts — on the database and broker you already operate. No separate workflow service. Plain PHP classes with attributes.

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.

Temporal PHP SDK

A Temporal cluster, RoadRunner, gRPC, and a deterministic-replay DSL.

  • Requires a running Temporal cluster (Go-based server) plus RoadRunner as the PHP worker runtime and `ext-grpc` on the client.
  • Workflow code constrained to deterministic replay — no `Date::now`, no `random_int`, no direct I/O outside Activities; the failure mode is the non-determinism error.
  • Long-running flows accumulate `getVersion` / `patched` branches that survive until the last in-flight execution finishes.
  • Task queues are internal to the cluster — cannot reuse your existing RabbitMQ / Kafka / SQS without writing bridging Activities.
  • Durability primitives are best-in-class, at the cost of operating the cluster.
Durable Workflow (formerly Laravel Workflow)

Laravel-only; workflow-only; no first-class sagas as process managers.

  • Workflow classes extend the `Workflow` base, activities extend `Activity`.
  • Generator DSL (`yield activity(MyActivity::class, $arg)`) drives the durability.
  • Durability is bounded by Laravel queue guarantees, not a dedicated cluster.
  • No first-class identifier-mapped saga that lives across events arriving over time.
  • No distributed bus, no cross-broker — single Laravel app oriented.
Symfony Messenger alone

Retry is per-transport, not per-handler. No saga.

  • Retry strategy is configured per-transport, so two handlers on the same message share the retry envelope.
  • No saga base class or process-manager primitive (Symfony Workflow is a state-machine library, not a durable workflow engine).
  • No `#[Delayed]`-style timeout attribute on event handlers — long-running waits become custom scheduler code.
Laravel Queues / Horizon

A job runner. No saga state, no workflow durability primitives.

  • Jobs implement `ShouldQueue` + `Queueable` trait; one job = one handler.
  • No saga state machine that lives across events arriving over time.
  • Durability of multi-step processes is bounded by Laravel queue guarantees; no built-in identifier-mapped coordinator.
  • Horizon is Redis-only.
Ecotone

Workflows that grow from one saga to a distributed orchestration topology.

  • Start with a `#[Saga]` for the first long-running process — plain PHP, no separate workflow runtime to deploy.
  • Layer `#[Orchestrator]` for declarative multi-step workflows with routing-slip semantics as complexity grows.
  • Use chained `#[InternalHandler]` for stateless flows where the message itself carries the state.
  • Saga timeouts are `#[Delayed]` attributes — no cron, no separate timer service.
  • The same retry / DLQ / idempotency / outbox primitives that protect your in-process messages apply across the saga's lifetime, on the brokers you already operate.

Frequently asked questions

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

No. Ecotone delivers durable execution on the database and broker you already operate. Sagas persist state per identifier, chained workflows survive crashes through the outbox plus async channel, saga timeouts use a #[Delayed] attribute, and Orchestrators give declarative multi-step workflows. No separate cluster, no RoadRunner / ext-grpc, no replay-deterministic DSL. If your team already runs Temporal, Ecotone composes around it for CQRS, projections, distributed messaging, and sagas that aren't full workflows.
Saga timeouts are a single #[Delayed] attribute on an event handler. The handler fires after the specified TimeSpan, exact DateTime, or expression — no cron, no separate timer service. The broker's underlying delayed-message primitive (RabbitMQ delayed exchange, SQS message timer, Redis sorted-set) does the waiting; the consistent attribute model hides the broker specifics from your code.
The broker redelivers the message. The saga reloads from the database on the next event arrival bearing its identifier. The handler runs again — built-in deduplication tolerates duplicate delivery, and the outbox ensures the business write and the message commit together. The same recovery semantics Temporal gives by replaying Event History, Ecotone gives by redelivering the message — and the message is in your own broker, in your own database, in your own schema.
Yes. Chained handlers via outputChannelName give stateless durable workflows where the message itself carries the state. When the async channel runs through an outbox (CombinedMessageChannel), each step is committed atomically with its business write before the message advances. If a worker crashes, the broker redelivers; the work resumes on the next consumer. No saga record needed.

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