PHPackages                             codewiser/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. [Utility &amp; Helpers](/categories/utility)
4. /
5. codewiser/workflow

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

codewiser/workflow
==================

Model Workflow Laravel package

v4.5.14(5mo ago)01.5k11MITPHPPHP ^7.4|^8.0

Since Mar 19Pushed 5mo ago2 watchersCompare

[ Source](https://github.com/C0deWiser/workflow)[ Packagist](https://packagist.org/packages/codewiser/workflow)[ RSS](/packages/codewiser-workflow/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (10)Dependencies (2)Versions (86)Used By (1)

Workflow
========

[](#workflow)

- [Setup](#setup)
- [Consistency](#consistency)
- [Authorization](#authorization)
- [Chargeable Transitions](#chargeable-transitions)
- [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 \Codewiser\Workflow\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.

```
class ArticleWorkflow extends \Codewiser\Workflow\WorkflowBlueprint
{
    public function states(): array
    {
        return Enum::cases();
    }

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

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

```
use \Codewiser\Workflow\Example\Enum;
use \Codewiser\Workflow\Example\ArticleWorkflow;
use \Codewiser\Workflow\StateMachineEngine;

class Article extends Model
{
    use \Codewiser\Workflow\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 \Codewiser\Workflow\Example\Enum;

// creating: will set proper initial state
$article = new \Codewiser\Workflow\Example\Article();
$article->save();
assert($article->state == Enum::new);

// updating: will examine state machine consistency
$article->state = Enum::review;
$article->save();
// No exceptions thrown
assert($article->state == Enum::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, pre- and post-transition callbacks etc...

```
use \Codewiser\Workflow\State;
use \Codewiser\Workflow\Transition;

class ArticleWorkflow extends \Codewiser\Workflow\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. Package will examine given ability against associated model.

```
use \Codewiser\Workflow\Transition;

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

class ArticlePolicy
{
    public function transit(User $user, Article $article, Transition $transition)
    {
        //
    }
}
```

### Using Closure

[](#using-closure)

Authorization Closure may return `true` or `false`, or throw `AuthorizationException`.

```
use \Codewiser\Workflow\Transition;
use \Illuminate\Support\Facades\Gate;

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

### Authorized Transitions

[](#authorized-transitions)

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

```
$article = new \Codewiser\Workflow\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, 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();
}
```

Chargeable Transitions
----------------------

[](#chargeable-transitions)

Chargeable transition will fire only then accumulates some charge. For example, we may want to publish an article only then at least three editors will accept it.

```
use \Codewiser\Workflow\Charge;
use \Codewiser\Workflow\Transition;

Transition::make('review', 'publish')
    ->chargeable(Charge::make(
        progress: function(Article $article) {
            return $article->accepts / 3;
        },
        callback: function (Article $article) {
            $article->accepts++;
            $article->save();
        }
    ));
```

`Charge` class has more options, that allows to provide vote statistics or prevent to vote twice.

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 temporarily disable transition, prerequisite should throw a `TransitionRecoverableException`. Leave helping instructions in exception message.

Here is an example of issues user may resolve.

```
use \Codewiser\Workflow\Transition;
use \Codewiser\Workflow\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 \Codewiser\Workflow\Transition;
use \Codewiser\Workflow\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 or state definition:

```
use \Codewiser\Workflow\Transition;

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

Next, set the context in the controller.

When creating a model:

```
use Illuminate\Http\Request;

public function store(Request $request)
{
    $this->authorize('create', \Codewiser\Workflow\Example\Article::class);

    $article = \Codewiser\Workflow\Example\Article::query()->make(
        $request->all()
    );

    $article->state()
        // Init workflow, passing additional context
        ->init($request->all())
        // Now save model
        ->save();
}
```

When transiting model:

```
use Illuminate\Http\Request;

public function update(Request $request, \Codewiser\Workflow\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. Then using Enums you may implement `\Codewiser\Workflow\Contracts\StateEnum` to `enum`.

`Transition` without caption will inherit caption from its target `State`.

```
use \Codewiser\Workflow\State;
use \Codewiser\Workflow\Transition;
use \Codewiser\Workflow\WorkflowBlueprint;

class ArticleWorkflow extends WorkflowBlueprint
{
    protected function states(): array
    {
        return [
            State::make('new')->as(__('Draft')),
            State::make('published')->as(fn(Article $model) => __('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. Then using Enums you may implement `\Codewiser\Workflow\Contracts\StateEnum` to `enum`.

`Transition` inherits attributes from its target `State`.

```
use \Codewiser\Workflow\State;
use \Codewiser\Workflow\Transition;
use \Codewiser\Workflow\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(\Codewiser\Workflow\Example\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 optional `Transition` arguments.

```
use \Codewiser\Workflow\Context;
use \Codewiser\Workflow\State;
use \Codewiser\Workflow\Transition;

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

### Transition Callback

[](#transition-callback)

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

It is absolutely the same as State Callback.

```
use \Codewiser\Workflow\Context;
use \Codewiser\Workflow\Transition;

Transition::make('review', 'correcting')
    ->rules(['reason' => 'required|string|min:100'])
    ->after(function(Article $article, Context $context) {
        $article->author->notify(
            new ArticleHasProblemNotification(
                $article, $context->data()->get('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 \Codewiser\Workflow\Events\ModelTransited;

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

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

Transition History
------------------

[](#transition-history)

The Package may log transitions to a database table.

Register `\Codewiser\Workflow\WorkflowServiceProvider`.

Publish and run migrations:

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

```

It's done.

To get historical records, add `\Codewiser\Workflow\Traits\HasTransitionHistory`to a `Model` with workflow. It brings `transitions` relation.

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

Sometimes you may need to eager load the latest transition:

```
Article::query()->withLatestTransition();
```

Or:

```
$article->loadLatestTransition();
```

You may add a constraining:

```
Article::query()->withLatestTransition(
    performer:      fn(MorphTo $builder) => $builder->withTrashed(),
    transitionable: fn(MorphTo $builder) => $builder->withTrashed()
);
```

Or you may override a default constraining in a model:

```
use Codewiser\Workflow\Traits\HasTransitionHistory;
use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    use HasTransitionHistory;

    protected function getLatestTransitionConstraining(
        ?\Closure $performer = null,
        ?\Closure $transitionable = null
    ) : array
    {
        $performer = $performer ??
            fn(MorphTo $builder) => $builder->withTrashed();

        $transitionable = $transitionable ??
            fn(MorphTo $builder) => $builder->withTrashed();

        return [
            'latest_transition' => [
                'performer'      => $performer,
                'transitionable' => $transitionable
            ]
        ];
    }
}
```

###  Health Score

48

—

FairBetter than 95% of packages

Maintenance71

Regular maintenance activity

Popularity19

Limited adoption so far

Community12

Small or concentrated contributor base

Maturity77

Established project with proven stability

 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

Every ~25 days

Recently: every ~58 days

Total

85

Last Release

164d ago

Major Versions

1.5.8 → 2.0.52021-10-30

2.0.16 → 3.0.02022-05-19

v3.1.1 → v4.0.02022-11-18

PHP version history (4 changes)2.0.5PHP ^7.4|^8.0

3.0.0PHP ^8.0

v4.0.0PHP ^8.1

v4.1.3PHP ^7.3|^8.0

### Community

Maintainers

![](https://www.gravatar.com/avatar/eee572e3e962b0100046aa05f861eddc2b6796b4622453b40d340cde53d3726c?d=identicon)[trunow](/maintainers/trunow)

![](https://www.gravatar.com/avatar/ce23eaf7ae48d79d2b07efc83ff7cecb2664f428bd531b167b90169423f1453d?d=identicon)[Cellard](/maintainers/Cellard)

---

Top Contributors

[![Cellard](https://avatars.githubusercontent.com/u/1220316?v=4)](https://github.com/Cellard "Cellard (131 commits)")

---

Tags

laravelstate-machineworkflowlaravelworkflowstate-machine

###  Code Quality

TestsPHPUnit

### Embed Badge

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

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

###  Alternatives

[livewire/volt

An elegantly crafted functional API for Laravel Livewire.

4205.3M84](/packages/livewire-volt)[brexis/laravel-workflow

Integerate Symfony Workflow component into Laravel.

283125.6k](/packages/brexis-laravel-workflow)[ringierimu/state-workflow

Laravel State Workflow provide tools for defining and managing workflows and activities with ease.

3251.1k](/packages/ringierimu-state-workflow)[tarfin-labs/event-machine

Event-driven state machines for Laravel with event sourcing, type-safe context, and full audit trail.

188.5k](/packages/tarfin-labs-event-machine)[forxer/laravel-gravatar

A library providing easy gravatar integration in a Laravel project.

4235.6k](/packages/forxer-laravel-gravatar)[iteks/laravel-enum

A comprehensive Laravel package providing enhanced enum functionalities, including attribute handling, select array conversions, and fluent facade interactions for robust enum management in Laravel applications.

2516.7k](/packages/iteks-laravel-enum)

PHPackages © 2026

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