PHPackages                             smskin/laravel-saga - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. smskin/laravel-saga

ActiveLibrary

smskin/laravel-saga
===================

State-machine (Saga) engine for laravel projects

1.0.0(2y ago)122[1 PRs](https://github.com/smskin/laravel-saga/pulls)MITPHPPHP ^8.1CI passing

Since May 8Pushed 8mo ago1 watchersCompare

[ Source](https://github.com/smskin/laravel-saga)[ Packagist](https://packagist.org/packages/smskin/laravel-saga)[ RSS](/packages/smskin-laravel-saga/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (6)Versions (3)Used By (0)

State Machine Engine for Laravel Projects
=========================================

[](#state-machine-engine-for-laravel-projects)

[![Composer](https://github.com/smskin/laravel-saga/actions/workflows/composer.yml/badge.svg)](https://github.com/smskin/laravel-saga/actions/workflows/composer.yml)[![Static Analysis](https://github.com/smskin/laravel-saga/actions/workflows/static-analysis.yml/badge.svg)](https://github.com/smskin/laravel-saga/actions/workflows/static-analysis.yml)[![Tests](https://github.com/smskin/laravel-saga/actions/workflows/tests.yml/badge.svg)](https://github.com/smskin/laravel-saga/actions/workflows/tests.yml)

While working with .Net Core and [MassTransit](https://masstransit.io), it became frustrating that Laravel lacked a ready-made state machine engine. Inspired by MassTransit, I wrote a library that functions similarly to [MassTransit sagas](https://masstransit.io/documentation/patterns/saga/state-machine).

Installation
------------

[](#installation)

1. `composer require smskin/laravel-saga`
2. `php artisan vendor:publish --provider=SMSkin\LaravelSaga\Providers\ServiceProvider`

Configuration
-------------

[](#configuration)

In the configuration file `config/saga.php`, you will find the engine settings and descriptions of state machines.

- logger - a class responsible for logging the state machine's operation process. Can be changed to another class implementing the `ISagaLogger` interface.
- state-machines - an array of registered state machines.
- repositories
    - default - the repository for storing the state machine's state (database).
    - database
        - class - the repository class. Can be changed to another class implementing the `ISagaRepository` interface.
        - table - the name of the table where the state machine's state will be stored.

Saga Structure
--------------

[](#saga-structure)

Let's examine a saga from an example in this library (`SMSkin\LaravelSaga\Example\SagaExample`).

### Property $context

[](#property-context)

This property describes the type (cast) of the stored object of the state machine. It can be any class inheriting from SagaContext. This object allows storing intermediate values obtained during interaction with other services during operation.

### Method setup()

[](#method-setup)

This method describes the state machine's operation algorithm.

Three key blocks of a saga:

- correlation
- initialization event\\command
- state machine transition logic

#### Correlation

[](#correlation)

This block describes the algorithm for obtaining the identifier of the state machine's context in the repository. It's described using two methods:

- correlatedById - getting the object by ID.
- correlatedBy - getting it by any storage field.

```
$this->builder()
     ->correlatedById(EUserCreated::class, static function (EUserCreated $event) {
            return $event->corrId;
     })
```

This block can be read as: Upon receiving the EUserCreated event, take the state machine's ID from the `corrId` property.

```
$this->builder()
     ->correlatedBy(EUserBlocked::class, 'userId', static function (EUserBlocked $event) {
            return $event->userId;
     });
```

This block can be read as: Upon receiving the EUserBlocked event, find the state machine by the `userId` field, taking the value from the `userId` event. Thus, the engine can search for the state machine's context both by UUID and by any context field.

#### Initialization Event\\Command

[](#initialization-eventcommand)

This block describes the events\\commands that will initialize the state machine.

The `onInitEvent` method takes two arguments:

- The event class to be registered in Laravel.
- A Closure for transforming the event into the state machine's context. With this method, you can save some initialization data in the context object.

```
 $this->builder()
      ->onInitEvent(CreateUserCommand::class, static function (CreateUserCommand $command) {
            return (new SagaExampleContext($command->correlationId))
                ->setEmail($command->email);
      });
```

This block can be read as:

- Upon receiving the CreateUserCommand command, initialize the state machine.
- Take the state machine's ID from the `correlationId` command.
- Save the `email` from the command in the state machine's context.

#### State Machine Transition Logic

[](#state-machine-transition-logic)

This block describes the state machine's algorithm. Key phrases:

- `duringState` - while in the state machine's state.
- `on` - upon receiving an event.
- `then` - do (closure).
- `activity` - perform a subroutine (class implementing the IActivity interface).
- `transitionTo` - switch the state machine's status.
- `publish` - publish an event.
- `initial` - sugar for initialization (first stage).
- `finalize` - sugar for finalization.

```
$this->builder()
     ->initial()
     ->transitionTo(SagaExampleStates::USER_CREATING)
     ->activity(UserCreatingActivity::class)
     ->then(function () {
            (new UserCommandService())->create(
                $this->context->getId(),
                $this->context->getEmail()
            );
    });
```

This block can be read as:

- Upon initialization.
- Switch the state to `USER_CREATING`.
- Perform the `UserCreatingActivity` subprogram.
- Execute the Closure - call `UserCommandService->create`, passing the `context ID` and `email` (which we stored in the context during initialization).

```
 $this->builder()
      ->duringState(SagaExampleStates::USER_CREATING)
      ->on(EUserCreated::class)
      ->then(function () {
            $event = $this->getHandledEvent();
            $this->context->setUserId($event->userId);
      })
      ->transitionTo(SagaExampleStates::USER_BLOCKING)
      ->then(function () {
            (new UserCommandService())->block(
                $this->context->getUserId()
            );
      });
```

This block can be read as:

- While in the state `USER_CREATING`.
- Upon receiving the `EUserCreated` event.
- Execute the Closure, which will write the `userId` (from the event) into the state machine's context.
- Switch the state to `USER_BLOCKING`.
- Execute the Closure - call `UserCommandService->block`, passing the `userId` from the context.

```
$this->builder()
     ->duringState(SagaExampleStates::USER_BLOCKING)
     ->on(EUserBlocked::class)
     ->finalize()
     ->publish(function () {
            return new ESagaExampleFinalized($this->context->getId());
     });
```

This block can be read as:

- While in the state `USER_BLOCKING`.
- Upon receiving the `EUserBlocked` event.
- Finalize the state machine.
- Publish the event `ESagaExampleFinalized`, passing the `saga ID`.

### Basic Operation Principle

[](#basic-operation-principle)

The engine operates based on the [Laravel Events](https://laravel.com/docs/11.x/events). The events described in the `setup()` method are registered in the EventServiceProvider. The saga acts as a Listener.

When an event enters the bus, the Laravel broker executes the `handle` method of the sagas registered for that event.

### Execution Optimization

[](#execution-optimization)

Since the events to which the saga registers are described within the saga itself, Laravel will require time to compute these events from all sagas. To optimize this, an artisan command is written that saves a pre-computed cache of event=saga mapping ready for registration.

Caching

```
php artisan saga:cache
```

Cache Clearing

```
php artisan saga:cache:clear
```

### Configuration Options

[](#configuration-options)

#### Changing the Saga Data Storage Repository

[](#changing-the-saga-data-storage-repository)

1. Create a class implementing the `ISagaRepository` interface.
2. Add it to the `saga.repositories` configuration.
3. Specify it in the `saga.repositories.default` configuration variable.

### Changing the Logger

[](#changing-the-logger)

1. Create a class implementing the `ISagaLogger` interface.
2. Specify it in the `saga.logger` configuration.

### Creating Custom Sagas

[](#creating-custom-sagas)

1. Create a class inheriting from `BaseSaga`.
2. Describe the logic of the state machine in the `setup()` method.
3. Specify the class in the `saga.state-machines` configuration.

###  Health Score

29

—

LowBetter than 60% of packages

Maintenance43

Moderate activity, may be stable

Popularity8

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity50

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

734d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/92de9daad6e0c4ef7b8b4fa112373401ae79ac02d185653724ca4ac425add1b7?d=identicon)[smskin](/maintainers/smskin)

---

Top Contributors

[![smskin](https://avatars.githubusercontent.com/u/3227797?v=4)](https://github.com/smskin "smskin (2 commits)")

---

Tags

laravelstatemachinesaga

###  Code Quality

TestsPHPUnit

Static AnalysisPsalm

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/smskin-laravel-saga/health.svg)

```
[![Health](https://phpackages.com/badges/smskin-laravel-saga/health.svg)](https://phpackages.com/packages/smskin-laravel-saga)
```

###  Alternatives

[anourvalar/eloquent-serialize

Laravel Query Builder (Eloquent) serialization

11320.2M21](/packages/anourvalar-eloquent-serialize)[henzeb/enumhancer

Your framework-agnostic Swiss Army knife for PHP 8.1+ native enums

69287.4k2](/packages/henzeb-enumhancer)[api-platform/laravel

API Platform support for Laravel

59126.4k6](/packages/api-platform-laravel)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
