webrek/saga

Orchestration-based sagas for PHP: run multi-step processes and roll them back with per-step compensations when one fails. Framework-agnostic core with an optional Laravel bridge.

Maintainers

Package info

github.com/webrek/saga

pkg:composer/webrek/saga

Transparency log

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-06-29 22:35 UTC

This package is auto-updated.

Last update: 2026-07-04 23:15:51 UTC


README

Latest Version Tests PHP License

Run multi-step processes and, if one fails midway, undo the previous ones through their compensation — in reverse order. The saga pattern, for when an operation touches several services and there's no database transaction that spans them all.

A framework-independent core (plain PHP) with an optional Laravel bridge (Eloquent journal, events, facade).

use Webrek\Saga\Laravel\Facades\Saga;
use Webrek\Saga\SagaContext;

$resultado = Saga::for('checkout', ['order_id' => 42])
    ->step('cobrar',
        fn (SagaContext $c) => $c->set('cargo', $pago->charge($c['order_id'])),
        compensate: fn (SagaContext $c) => $pago->refund($c['cargo']))
    ->step('apartar',
        fn (SagaContext $c) => $inventario->reserve($c['order_id']),
        compensate: fn (SagaContext $c) => $inventario->release($c['order_id']))
    ->step('enviar',
        fn (SagaContext $c) => $envios->create($c['order_id']))   // if this fails…
    ->run();

// …the inventory is released and the charge is refunded, automatically.
$resultado->isCompleted();   // false
$resultado->status;          // SagaStatus::Compensated

Completes webrek's resilience quartet: input (idempotency) · output (outbox) · dependencies (circuit-breaker) · and coordination (this one).

Installation

composer require webrek/saga

On Laravel, the service provider is auto-discovered. Publish the journal migration:

php artisan vendor:publish --tag=saga-migrations
php artisan migrate
php artisan vendor:publish --tag=saga-config   # optional

How it runs

Steps execute in order. A SagaContext (a bag of data) is passed to each step: what one step writes, the next — and the compensations — read.

If a step throws an exception, the already completed steps are compensated in reverse order and the saga ends with one of three statuses:

Status Meaning
Completed All steps ran successfully.
Compensated A step failed and everything before it was undone cleanly.
CompensationFailed A step failed and a compensation did too — left half-done, requires manual intervention.

The result never swallows the original exception ($resultado->failure), and the CompensationFailed statuses are exactly the ones worth watching ($resultado->needsAttention()).

The journal (Laravel)

Every run is recorded in the sagas table: name, status, completed steps, context, the step that failed and compensation errors. It's your audit trail and, above all, the way to find the sagas whose compensation failed. Each run also dispatches an event (SagaCompleted, SagaCompensated, SagaCompensationFailed).

Disable it with 'journal' => false in the configuration.

Without Laravel

use Webrek\Saga\{Saga, SagaRunner, SagaContext};

$saga = new Saga(new SagaRunner);   // no events, no journal
$resultado = $saga->for('checkout', ['order_id' => 42])
    ->step('cobrar', $accion, compensate: $compensacion)
    ->run();

For your own events or persistence, implement Webrek\Saga\Contracts\EventDispatcher and Webrek\Saga\Contracts\SagaStore and pass them to the SagaRunner.

Scope

This is a synchronous, in-process orchestration saga: it runs inside your request or your job. It's not a two-phase commit, and it doesn't retry steps on its own — if you need retries, combine it with Laravel's queue or with webrek/laravel-circuit-breaker inside a step. Compensations must be idempotent.

Testing

composer test

The core suite (tests/Unit) runs without a framework; the Laravel suite (tests/Feature) exercises the Eloquent journal.

Contributing

See CONTRIBUTING.md. Run make check before opening a pull request.

Security

Report vulnerabilities through the security advisory form, not as public issues. See SECURITY.md.

License

The MIT License (MIT). See LICENSE.