PHPackages                             soap/eloquent-workflow - 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. soap/eloquent-workflow

ActiveLibrary

soap/eloquent-workflow
======================

Simple states and transitions workflow for Laravel Eloquent

v0.1.0-beta.2(3y ago)16[4 PRs](https://github.com/soap/eloquent-workflow/pulls)MITPHPPHP ^8.0|^8.1CI passing

Since Jan 29Pushed 4mo ago1 watchersCompare

[ Source](https://github.com/soap/eloquent-workflow)[ Packagist](https://packagist.org/packages/soap/eloquent-workflow)[ Docs](https://github.com/soap/eloquent-workflow)[ RSS](/packages/soap-eloquent-workflow/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (2)Dependencies (12)Versions (7)Used By (0)

Simple states and transitions workflow for Laravel Eloquent
===========================================================

[](#simple-states-and-transitions-workflow-for-laravel-eloquent)

[![Latest Version on Packagist](https://camo.githubusercontent.com/97b3e144099c49e38b623a7e01745e6cb1a69e3a92e8e219a3f9ce48caa552fb/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f736f61702f656c6f7175656e742d776f726b666c6f772e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/soap/eloquent-workflow)[![GitHub Tests Action Status](https://camo.githubusercontent.com/b319cc65dae7087972db02bb4323604780ea7ff724262a5554290cbd031ce9bc/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f736f61702f656c6f7175656e742d776f726b666c6f772f72756e2d74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/soap/eloquent-workflow/actions?query=workflow%3Arun-tests+branch%3Amain)[![GitHub Code Style Action Status](https://camo.githubusercontent.com/0d7799648ecd230e622d5d0623e69ea3d9f77f24bef6a8d038d3513615d9b449/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f736f61702f656c6f7175656e742d776f726b666c6f772f6669782d7068702d636f64652d7374796c652d6973737565732e796d6c3f6272616e63683d6d61696e266c6162656c3d636f64652532307374796c65267374796c653d666c61742d737175617265)](https://github.com/soap/eloquent-workflow/actions?query=workflow%3A%22Fix+PHP+code+style+issues%22+branch%3Amain)[![Total Downloads](https://camo.githubusercontent.com/aac357bd29b92270197b1d42cd3d4c00b1bd19c4ca29b1f1b0dfbf5dea4e7c14/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f736f61702f656c6f7175656e742d776f726b666c6f772e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/soap/eloquent-workflow)

This package add state transition workflow to eloquent model. User can choose to take action from available transitions, makes eloquent model to transit from one state to another state. It is based on lots of work from [codewiser/workflow](https://github.com/codewiser/workflow)

Support us
----------

[](#support-us)

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

[](#installation)

You can install the package via composer:

```
composer require soap/eloquent-workflow
```

You can publish and run the migrations with:

```
php artisan vendor:publish --tag="eloquent-workflow-migrations"
php artisan migrate
```

You can publish the config file with:

```
php artisan vendor:publish --tag="eloquent-workflow-config"
```

This is the contents of the published config file:

```
return [
];
```

Optionally, you can publish the views using

```
php artisan vendor:publish --tag="eloquent-workflow-views"
```

Usage
-----

[](#usage)

- [Setup](#setup)
- [Consistency](#consistency)
- [Authorization](#authorization)
- [Business Logic](#business-logic)
    - [Disabling Transitions](#disabling-transitions)
    - [Removing Transitions](#removing-transitions)
    - [User Provided Data](#additional-context)
- [Translations](#translations)
- [JSON](#json-serialization)
- [Events](#events)
    - [EventListener](#eventlistener)
    - [Callback](#transition-callback)
- [Log Transitions](#transition-history)

Package provides workflow functionality to Eloquent Models.

Workflow is a sequence of states, document evolve through. Transitions between states inflicts the evolution road.

### Setup

[](#setup)

First, describe the workflow blueprint with available states and transitions:

```
class ArticleWorkflow extends \Soap\EloquentWorkflow\WorkflowBlueprint
{
    public function states(): array
    {
        return [
            'new',
            'review',
            'published',
            'correction',
        ];
    }

    public function transitions(): array
    {
        return [
            ['new', 'review'],
            ['review', 'published'],
            ['review', 'correction'],
            ['correction', 'review']
        ];
    }
}
```

> You may use `Enum` instead of scalar values:

Next, include trait and create method to bind a blueprint to model's attribute.

```
use \Soap\EloquentWorkflow\Example\Enum;
use \Soap\EloquentWorkflow\Example\ArticleWorkflow;
use \Soap\EloquentWorkflow\StateMachineEngine;

class Article extends Model
{
    use \Soap\EloquentWorkflow\Traits\HasWorkflow;

    public function state(): StateMachineEngine
    {
        return $this->workflow(ArticleWorkflow::class, 'state');
    }
}
```

That's it.

### Consistency

[](#consistency)

Workflow observes Model and keeps state machine consistency healthy.

```
use \Soap\EloquentWorkflow\Example\Enum;

// creating: will set proper initial state
$article = new \Soap\EloquentWorkflow\Example\Article();
$article->save();
assert($article->state == 'new');

// updating: will examine state machine consistency
$article->state = 'review';
$article->save();
// No exceptions thrown
assert($article->state == 'review');
```

### State and Transition objects

[](#state-and-transition-objects)

In an example above we describe blueprint with scalar values, but actually they will be transformed to the objects. Those objects bring some additional functionality to the states and transitions, such as caption translations, transit authorization, routing rules etc...

```
use \Soap\EloquentWorkflow\State;
use \Soap\EloquentWorkflow\Transition;

class ArticleWorkflow extends \Soap\EloquentWorkflow\WorkflowBlueprint
{
    public function states(): array
    {
        return [
            State::make('new'),
            State::make('review'),
            State::make('published'),
            State::make('correction'),
        ];
    }

    public function transitions(): array
    {
        return [
            Transition::make('new', 'review'),
            Transition::make('review', 'published'),
            Transition::make('review', 'correction'),
            Transition::make('correction', 'review'),
        ];
    }
}
```

### Authorization

[](#authorization)

As model's actions are not allowed to any user, as changing state is not allowed to any user. You may define transition authorization rules either using `Policy` or using `callable`.

#### Using Policy

[](#using-policy)

Provide ability name:

```
use \Soap\EloquentWorkflow\Transition;

Transition::make('new', 'review')
    ->authorizedBy('transit');
```

#### Using Closure

[](#using-closure)

```
use \Soap\EloquentWorkflow\Transition;
use \Illuminate\Support\Facades\Gate;

Transition::make('new', 'review')
    ->authorizedBy(fn(Article $article) => Gate::allows('transit', $article));
```

#### Authorized Transitions

[](#authorized-transitions)

To get only transitions, authorized to the current user, use `authorized` method of `TransitionCollection`:

```
$article = new \Soap\EloquentWorkflow\Example\Article();

$transitions = $article->state()
    // Get transitions from model's current state.
    ->transitions()
    // Filter only authorized transitions.
    ->onlyAuthorized();
```

#### Authorizing Transition

[](#authorizing-transition)

When accepting user request, do not forget to authorize workflow state changing.

```
public function update(Request $request, \Soap\EloquentWorkflow\Example\Article $article)
{
    $this->authorize('update', $article);

    if ($state = $request->input('state')) {
        // Check if user allowed to make this transition
        $article->state()->authorize($state);
    }

    $article->fill($request->validated());

    $article->save();
}
```

### Business Logic

[](#business-logic)

#### Disabling transitions

[](#disabling-transitions)

Transition may have some prerequisites to a model. If model fits this conditions then the transition is possible.

Prerequisite is a `callable` with `Model` argument. It may throw an exception.

To disable transition, prerequisite should throw a `TransitionRecoverableException`. Leave helping instructions in exception message.

Here is an example of issues user may resolve.

```
use \Soap\EloquentWorkflow\Transition;
use \Soap\EloquentWorkflow\Exceptions\TransitionRecoverableException;

Transition::make('new', 'review')
    ->before(function(Article $model) {
        if (strlen($model->body) < 1000) {
            throw new TransitionRecoverableException(
                'Your article should contain at least 1000 symbols. Then you may send it to review.'
            );
        }
    })
    ->before(function(Article $model) {
        if ($model->images->count() == 0) {
            throw new TransitionRecoverableException(
                'Your article should contain at least 1 image. Then you may send it to review.'
            );
        }
    });
```

User will see the problematic transitions in a list of available transitions. User follows instructions to resolve the issue and then may try to perform the transition again.

#### Removing transitions

[](#removing-transitions)

In some cases workflow routes may divide into branches. Way to go forced by business logic, not user. User even shouldn't know about other ways.

To completely remove transition from a list, prerequisite should throw a `TransitionFatalException`.

```
use \Soap\EloquentWorkflow\Transition;
use \Soap\EloquentWorkflow\Exceptions\TransitionFatalException;

Transition::make('new', 'to-local-manager')
    ->before(function(Order $model) {
        if ($model->amount >= 1000000) {
            throw new TransitionFatalException("Order amount is too big for this transition.");
        }
    });

Transition::make('new', 'to-region-manager')
    ->before(function(Order $model) {
        if ($model->amount < 1000000) {
            throw new TransitionFatalException("Order amount is too small for this transition.");
        }
    });
```

User will see only one possible transition depending on order amount value.

#### Additional Context

[](#additional-context)

Sometimes application requires an additional context to perform a transition. For example, it may be a reason the article was rejected by the reviewer.

First, declare validation rules in transition definition:

```
use \Soap\EloquentWorkflow\Transition;

Transition::make('review', 'reject')
    ->rules([
        'reason' => 'required|string|min:100'
    ]);
```

Next, set the transition context in the controller:

```
use Illuminate\Http\Request;

public function update(Request $request, \Soap\EloquentWorkflow\Example\Article $article)
{
    $this->authorize('update', $article);

    if ($state = $request->input('state')) {
        $article->state()
            // Authorize transition
            ->authorize($state)
            // Transit to the new state, passing additional context
            ->transit($state, $request->all())
            // Now save model
            ->save();
    }
}
```

The context will be validated while saving, and you may catch a `ValidationException`.

After all you may handle this context in [events](#events).

Translations
------------

[](#translations)

You may define `State` and `Transition` objects with translatable caption.

```
use \Soap\EloquentWorkflow\State;
use \Soap\EloquentWorkflow\Transition;
use \Soap\EloquentWorkflow\WorkflowBlueprint;

class ArticleWorkflow extends WorkflowBlueprint
{
    protected function states(): array
    {
        return [
            State::make('new')->as(__('Draft')),
            State::make('published')->as(__('Published'))
        ];
    }
    protected function transitions(): array
    {
        return [
            Transition::make('new', 'published')->as(__('Publish'))
        ];
    }
}
```

### Additional Attributes

[](#additional-attributes)

Sometimes we need to add some additional attributes to the workflow states and transitions. For example, we may group states by levels and use this information to color states and transitions in user interface.

```
use \Soap\EloquentWorkflow\State;
use \Soap\EloquentWorkflow\Transition;
use \Soap\EloquentWorkflow\WorkflowBlueprint;

class ArticleWorkflow extends WorkflowBlueprint
{
    protected function states(): array
    {
        return [
            State::make('new'),
            State::make('review')     ->set('level', 'warning'),
            State::make('published')  ->set('level', 'success'),
            State::make('correction') ->set('level', 'danger')
        ];
    }
    protected function transitions(): array
    {
        return [
            Transition::make('new', 'review')         ->set('level', 'warning'),
            Transition::make('review', 'published')   ->set('level', 'success'),
            Transition::make('review', 'correction')  ->set('level', 'danger'),
            Transition::make('correction', 'review')  ->set('level', 'warning')
        ];
    }
}
```

### Json Serialization

[](#json-serialization)

For user to interact with model's workflow we should pass the data to a frontend of the application:

```
use Illuminate\Http\Request;

public function state(\App\Models\Article $article)
{
    return $article->state()->toArray();
}
```

The payload will be like that:

```
{
  "value": "review",
  "name": "Review",
  "transitions": [
    {
      "source": "review",
      "target": "publish",
      "name": "Publish",
      "issues": [
        "Publisher should provide a foreword."
      ],
      "level": "success"
    },
    {
      "source": "review",
      "target": "correction",
      "name": "Send to Correction",
      "rules": {
        "reason": ["required", "string", "min:100"]
      },
      "level": "danger"
    }
  ]
}
```

### Events

[](#events)

#### State Callback

[](#state-callback)

You may define state callback(s), that will be called then state is reached.

Callback is a `callable` with `Model` and `context` arguments.

```
use \Soap\EloquentWorkflow\State;

State::make('correcting')
    ->after(function(Article $article, ?State $previous, array $context) {
        $article->author->notify(
            new ArticleHasProblemNotification(
                $article, $context['reason']
            )
        );
    });
```

#### Transition Callback

[](#transition-callback)

You may define transition callback(s), that will be called after transition were successfully performed.

Callback is a `callable` with `Model` and `context` arguments.

```
use \Soap\EloquentWorkflow\State;
use \Soap\EloquentWorkflow\Transition;

Transition::make('review', 'correcting')
    ->rules([
        'reason' => 'required|string|min:100'
    ])
    ->after(function(Article $article, ?State $previous, array $context) {
        $article->author->notify(
            new ArticleHasProblemNotification(
                $article, $context['reason']
            )
        );
    });
```

You may define few callbacks to a single transition.

#### EventListener

[](#eventlistener)

Transition generates `ModelTransited` event. You may define `EventListener` to listen to it.

```
use \Soap\EloquentWorkflow\Events\ModelTransited;

class ModelTransitedListener
{
    public function handle(ModelTransited $event)
    {
        if ($event->model instanceof Article) {
            $article = $event->model;

            if ($event->transition->target()->is('correction')) {
                // Article was send to correction, the reason described in context
                $article->author->notify(
                    new ArticleHasProblemNotification(
                        $article, $event->transition->context('reason')
                    )
                );
            }
        }
    }
}
```

### Transition Logs

[](#transition-logs)

The Package may log transitions to database table.

Register `\Soap\EloquentWorkflow\EloquentWorkflowServiceProvider` in `providers` section of `config/app.php`.

Run migrations:

```
php artisan migrate

```

Edit `workflow.logging` into `config/eloquent-workflow.php`:

```
    'workflow' => [
        'logging' => true
    ]
```

It's done.

To get logging records, add `\Soap\EloquentWorkflow\Traits\HasTransitionLog` to `Model` with workflow. It brings `transitions` relation.

Historical records presented by `\Soap\EloquentWorkflow\Models\TransitionLog` model, that holds information about transition performer, source and target states and a context, if it were provided.

### Blueprint Validation

[](#blueprint-validation)

The Package may validate Workflow Blueprint that you defined.

Register `\Soap\EloquentWorkflow\WorkflowServiceProvider` in `providers` section of `config/app.php`.

Run console command with blueprint classname:

```
php artisan workflow:blueprint --class=App/Workflow/ArticleWorkflow

```

Testing
-------

[](#testing)

```
composer test
```

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING](CONTRIBUTING.md) for details.

Security Vulnerabilities
------------------------

[](#security-vulnerabilities)

Please review [our security policy](../../security/policy) on how to report security vulnerabilities.

Credits
-------

[](#credits)

- [Prasit Gebsaap](https://github.com/soap)
- [All Contributors](../../contributors)

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

30

—

LowBetter than 64% of packages

Maintenance52

Moderate activity, may be stable

Popularity6

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity44

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 54% 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

Every ~0 days

Total

2

Last Release

1204d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/1073690?v=4)[Prasit Gebsaap](/maintainers/soap)[@soap](https://github.com/soap)

---

Top Contributors

[![soap](https://avatars.githubusercontent.com/u/1073690?v=4)](https://github.com/soap "soap (27 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (13 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (10 commits)")

---

Tags

laravelsoapeloquent-workflow

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/soap-eloquent-workflow/health.svg)

```
[![Health](https://phpackages.com/badges/soap-eloquent-workflow/health.svg)](https://phpackages.com/packages/soap-eloquent-workflow)
```

###  Alternatives

[vormkracht10/laravel-mails

Laravel Mails can collect everything you might want to track about the mails that has been sent by your Laravel app.

24149.7k](/packages/vormkracht10-laravel-mails)[spatie/laravel-prometheus

Export Laravel metrics to Prometheus

2651.3M6](/packages/spatie-laravel-prometheus)[codedredd/laravel-soap

A SoapClient wrapper integration for Laravel

221516.6k3](/packages/codedredd-laravel-soap)[hydrat/filament-table-layout-toggle

Filament plugin adding a toggle button to tables, allowing user to switch between Grid and Table layouts.

6292.3k1](/packages/hydrat-filament-table-layout-toggle)[scalar/laravel

Render your OpenAPI-based API reference

6183.9k2](/packages/scalar-laravel)[ralphjsmit/laravel-helpers

A package containing handy helpers for your Laravel-application.

13704.6k2](/packages/ralphjsmit-laravel-helpers)

PHPackages © 2026

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