The PHP Workflow Engine

Durable, identifier-mapped, broker-agnostic workflows declared in plain PHP — on the database and broker you already operate. Sagas, orchestrators, chained handlers, timeouts, outbox, and distributed routing in one attribute-driven model.

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

What you get

Workflows that survive crashes, on infrastructure you already operate

Durable execution on your own database

Sagas persist state per identifier in your DB. The outbox commits business state and message dispatch in one DBAL transaction. Broker redelivery resumes work after crashes, restarts, and rolling deploys — no separate workflow runtime to deploy or operate.

Three workflow shapes, one model

#[Saga] for stateful processes that react to events arriving over time. #[Orchestrator] for declarative routing-slip workflows whose step list is visible at one glance. Chained #[InternalHandler] for stateless durable flows where the message carries the state.

Every workflow primitive in the same codebase

#[Delayed] timeouts, #[Priority], #[TimeToLive], #[Deduplicated], per-handler failure isolation, retry/DLQ, CombinedMessageChannel outbox, #[Distributed] cross-service routing, multi-tenant dynamic channels — one declarative attribute model across every primitive.

Durable execution model

Durable execution on your own database

Workflows survive crashes, restarts, and rolling deploys because state and dispatch live in infrastructure you already operate — no external runtime to deploy, no engine-shaped event history to maintain.

Step 1

State persisted per identifier

A #[Saga] records its state in your own database, keyed by the correlation identifier the events carry.

Step 2

State + dispatch commit together

CombinedMessageChannel writes the business state change and the outgoing message into one DBAL transaction — no dual-write window.

Step 3

Crash recovery is broker redelivery

Worker dies mid-step? The broker redelivers; the saga reloads from the DB by identifier; built-in deduplication tolerates the duplicate; the work resumes.

What this gives you
  • Workers can be killed mid-flow; no work is lost — the broker holds the message until the next consumer picks it up.
  • Deploys are no different from any rolling deploy of your app — no special workflow-version migration, no engine state to coordinate.
  • The data stays in your own database — your backups, your retention, your access controls, your existing operational tooling.
  • New subscribers and projections can be added against the same event stream because it lives in your DB and on your broker — directly queryable, directly extensible.
What robust workflow requires

Five properties Ecotone delivers as primitives

Crash survival

Worker killed mid-step? The broker redelivers; the saga reloads from its identifier; the handler runs again. Built-in deduplication tolerates the duplicate. State + dispatch commit together via CombinedMessageChannel, so there is no dual-write window to leak inconsistent state.

Time-shifted steps

#[Delayed] resumes a handler after a TimeSpan, an exact DateTime, or an expression — no cron, no separate timer service. The broker's underlying delayed-message primitive does the waiting; the attribute model hides the broker specifics from your code.

Per-step retry isolation

Each subscriber receives its own copy of the message. A failing handler retries on its own envelope; sibling handlers are unaffected. Retry strategy is per-handler, not per-transport — the failure of one step never replays a side effect on another.

Identifier-mapped correlation

#[Saga] binds events to instances by payload field, header, or expression via identifierMapping. State is loaded by identifier on every event arrival; the saga remembers where it is across hours, days, or months of events.

Cross-service routing

#[Distributed] handlers and the Distributed Bus extend the same workflow primitives across bounded contexts. Service Map carries the topology; multi-broker single-topology is first-class. The retry, DLQ, idempotency, and outbox semantics that protect in-process steps apply uniformly across service boundaries.

Workflow scenarios

The shape of every PHP workflow scenario

Each shape below maps to one or more Ecotone primitives applied through PHP attributes on plain classes.

Multi-step order, payment, or fulfillment process

#[Orchestrator] declares the step list as channel names; each step is an independently testable #[InternalHandler]. The outbox commits business state and next-step dispatch atomically. Compensation steps are channels you append on failure — declarative, not a manual try/catch chain.

Long-running onboarding with timeouts and human waits

#[Saga] holds state across days or weeks of events. #[Delayed] handlers fire after a TimeSpan or DateTime — no cron job, no timer service. The same saga handles the human approval event, the timeout event, and the resume event with one identifier.

Cross-service business processes spanning bounded contexts

#[Distributed] handlers exchange commands and events between PHP services over the broker you already operate. Service Map declares which service consumes which routing keys on which broker. Adding a service is a config change, not code in every caller.

High-throughput event-driven workflows with strict isolation

Per-handler failure isolation: each subscriber consumes its own copy. A slow or failing subscriber never blocks siblings. CombinedMessageChannel keeps the outbox in the database and pushes execution to the broker — one poller drains the outbox, many consumers scale the work.

Multi-tenant workflows with isolated per-tenant channels

Dynamic channels route per-tenant at runtime via headers — no redeploy. The same workflow code serves every tenant; isolation is operational, not duplicated. Tenant-scoped channels carry their own retry, priority, and DLQ policies.

Workflow primitives

The attributes you declare workflows with

#[Saga]

Stateful process manager. #[Identifier] maps incoming events to the right instance by payload, header, or expression. State persisted in your DB; reloaded per event.

#[Orchestrator]

Routing-slip workflow. Returns the channel list for the next steps — including dynamic step lists computed from input data. Each step is reusable.

Chained #[InternalHandler]

Stateless durable flow. The message carries the state; outputChannelName advances it. Combined with the outbox, each step commits atomically with its business write.

#[Delayed]

Saga timeouts as attribute — TimeSpan, exact DateTime, or expression. The broker's delayed-message primitive handles the wait. No cron, no timer service.

CombinedMessageChannel (outbox)

One DBAL transaction commits business state and the outgoing message together. One poller drains the database; the broker carries execution. No dual-write window.

#[Distributed]

Cross-service commands and events on the brokers you already operate. Service Map carries the topology; multi-broker single-topology is first-class.

Dynamic channels

Per-tenant message routing at runtime via headers. Multi-tenant workflows in one deployment, with per-channel retry and priority.

#[Deduplicated]

Gateway-level deduplication absorbs redelivered messages, double-clicks, and webhook retries. Every handler behind the bus is covered without per-handler code.

In production

Workflows running where failure is regulated, expensive, or public

  • Payment gateways where a retried handler must never double-charge a customer.
  • Credit card systems where transaction loss is catastrophic and the outbox is non-negotiable.
  • Certification authorities whose event log is the audit log of record.
  • E-commerce platforms orchestrating order, payment, and fulfillment sagas.
  • Public transportation subscription systems coordinating nationwide transit subscriptions with Kafka integration to Java services.
  • Two-sided marketplaces coordinating customer orders, provider subscriptions, and B2B partnerships.

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