PHPackages                             norotaro/enumata - 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. [Database &amp; ORM](/categories/database)
4. /
5. norotaro/enumata

ActiveLibrary[Database &amp; ORM](/categories/database)

norotaro/enumata
================

State machines for Eloquent models with Enums

v2.1.0(1y ago)32.6k↓50%1[1 PRs](https://github.com/norotaro/enumata/pulls)1MITPHPPHP ^8.2

Since Jul 23Pushed 1y ago1 watchersCompare

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

READMEChangelog (9)Dependencies (6)Versions (10)Used By (1)

Enumata
=======

[](#enumata)

[![Latest Version](https://camo.githubusercontent.com/9272df4dbf0c93e40333401afc1305a76efaefa4a7bd99b6b77874cbc2ab0e36/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6e6f726f7461726f2f656e756d6174612e7376673f6c6162656c3d72656c65617365)](https://packagist.org/packages/norotaro/enumata)[![Tests](https://github.com/norotaro/enumata/actions/workflows/test.yaml/badge.svg)](https://github.com/norotaro/enumata/actions/workflows/test.yaml)[![Packagist PHP Version](https://camo.githubusercontent.com/2cdd91451973ae4b1eda06a9aacf6c578b2adc05bbd4241f5123a7097ff4a34f/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f646570656e64656e63792d762f6e6f726f7461726f2f656e756d6174612f7068703f6c6f676f3d70687026636f6c6f723d253233414542324435)](https://php.net)[![Packagist Laravel Version](https://camo.githubusercontent.com/dca585dac0b4b2d929576a63925037ffcf7024c53a4b99bb1dfca93fcee9c967/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f646570656e64656e63792d762f6e6f726f7461726f2f656e756d6174612f696c6c756d696e617465253246737570706f72743f6c6f676f3d6c61726176656c266c6162656c3d4c61726176656c26636f6c6f723d253233463035333430)](https://laravel.com)

State Machines for Eloquent models using Enums.

Table of Contents
-----------------

[](#table-of-contents)

- [Enumata](#enumata)
    - [Table of Contents](#table-of-contents)
    - [Description](#description)
    - [Live demo](#live-demo)
    - [Installation](#installation)
    - [Basic usage](#basic-usage)
        - [The State Definition file - enum file](#the-state-definition-file---enum-file)
        - [Configuring the model](#configuring-the-model)
    - [Access the current state](#access-the-current-state)
    - [Transitioning](#transitioning)
        - [Disable default transition methods](#disable-default-transition-methods)
        - [Transition not allowed exception](#transition-not-allowed-exception)
        - [Force transitions](#force-transitions)
    - [Nullable States](#nullable-states)
        - [Create a State Definition file](#create-a-state-definition-file)
        - [Register the State Definition file](#register-the-state-definition-file)
    - [The State Machine](#the-state-machine)
        - [Using the State Machine](#using-the-state-machine)
            - [Transitioning](#transitioning-1)
            - [Checking available transitions](#checking-available-transitions)
    - [Events](#events)
        - [Listening to events using `$dispatchesEvents`](#listening-to-events-using-dispatchesevents)
        - [Listening to events using Closures](#listening-to-events-using-closures)
    - [Testing](#testing)
    - [Inspiration](#inspiration)
    - [LICENSE](#license)

Description
-----------

[](#description)

This package helps to implement State Machines to Eloquent models in an easy way using Enum files to represent all possible states and also to configure transitions.

Live demo
---------

[](#live-demo)

You can check the [norotaro/enumata-demo](https://github.com/norotaro/enumata-demo) repository or go to the live version of the demo in this [PHP Sandbox](https://phpsandbox.io/e/x/ultlf?layout=EditorPreview&defaultPath=%2F&theme=dark&showExplorer=no&openedFiles=#app/Models/OrderStatus.php).

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

[](#installation)

```
composer require norotaro/enumata
```

Basic usage
-----------

[](#basic-usage)

Having a model with a `status` field and 4 possible states:

```
$order->status; // 'pending', 'approved', 'declined' or 'processed'
```

We need to create an `enum` file with the State Definitions which we will call `OrderStatus`. We can do this with the `make:model-state` command:

```
php artisan make:model-state OrderStatus
```

### The State Definition file - enum file

[](#the-state-definition-file---enum-file)

The above command will create a default file that we can adapt to meet our needs:

```
namespace App\Models;

use Norotaro\Enumata\Contracts\Nullable;
use Norotaro\Enumata\Contracts\DefineStates;

enum OrderStatus implements DefineStates
{
    case Pending;
    case Approved;
    case Declined;
    case Processed;

    public function transitions(): array
    {
        return match ($this) {
            // when the order is Pending we can approve() or decline() it
            self::Pending => [
                'approve' => self::Approved,
                'decline' => self::Delined,
            ],
            // when the order is Approved we can apply the processOrder() transition
            self::Approved => [
                'processOrder' => self::Processed,
            ],
        };
    }

    public static function default(): self
    {
        return self::Pending;
    }
}
```

The `transitions()` method must return an array with `key=>value` where the key is the name of the transition and the value is the state to apply in that transition.

> Note that, by default, methods will be created in the model for each transition. In the case of the example, the `approve()`, `decline()`, and `processOrder()` methods will be created.

### Configuring the model

[](#configuring-the-model)

In the model we have to implement the contract `HasStateMachine` and register the `EloquentHasStateMachines` trait and then the `enum` file in the `$casts` property:

```
use Norotaro\Enumata\Contracts\HasStateMachine;
use Norotaro\Enumata\Traits\EloquentHasStateMachines;

class Order extends Model implements HasStateMachine
{
    use EloquentHasStateMachines;

    protected $casts = [
        'status' => OrderStatus::class,
    ];
}
```

That's it! Now we can transition between the states.

Access the current state
------------------------

[](#access-the-current-state)

If you access the attributes, Eloquent will return the `enum` object with the current state:

```
$model = new Order;
$model->save();

$model->status; // App\Model\OrderStatus{name: "Pending"}
$model->fulfillment; // null
```

Transitioning
-------------

[](#transitioning)

By default this package will create methods in the model for each transition returned by `transitions()` so, for this example, we will have these methods available:

```
$model->approve(); // Change status to OrderStatus::Approved
$model->decline(); // Change status to OrderStatus::Declined
$model->processOrder(); // Change status to OrderStatus::Processed
```

### Disable default transition methods

[](#disable-default-transition-methods)

You can disable the creation of transition methods by making the `$defaultTransitionMethods` attribute of the model `false`.

Internally these methods use the `transitionTo($state)` method available in the `StateMachine` class, so you can implement your custom transition methods with it.

```
use Norotaro\Enumata\Contracts\HasStateMachine;
use Norotaro\Enumata\Traits\EloquentHasStateMachines;

class Order extends Model implements HasStateMachine
{
    use EloquentHasStateMachines;

    // disable the creation of transition methods
    public bool $defaultTransitionMethods = false;

    protected $casts = [
        'status' => OrderStatus::class,
    ];

    // custom transition method
    public function myApproveTransition(): void {
        $this->status()->transitionTo(OrderStatus::Approved);
        //...
    }
}
```

### Transition not allowed exception

[](#transition-not-allowed-exception)

If a transition is applied and the current state does not allow it, the `TransitionNotAllowedException` will be thrown.

```
$model->status; // App\Model\OrderStatus{name: "Pending"}
$model->processOrder(); // throws Norotaro\Enumata\Exceptions\TransitionNotAllowedException
```

### Force transitions

[](#force-transitions)

All the methods of transitions created by the trait and also the `transitionTo()` method have the `force` parameter which, when true, the transition is applied without checking the defined rules.

```
$model->status; // App\Model\OrderStatus{name: "Pending"}

$model->processOrder(force: true); // this will apply the transition and will not throw the exception

$model->status; // App\Model\OrderStatus{name: "Processed"}

$model->status()->transitionTo(OrderStatus::Pending, force:true); // will apply the transition without errors
```

Nullable States
---------------

[](#nullable-states)

If the model has nullable states we only have to implement the `Norotaro\Enumata\Contracts\Nullable` contract in the State Definition file.

As an example, we will add the `fulfillment` attribute to the Order model:

```
$order->fulfillment; // null, 'pending', 'completed'
```

### Create a State Definition file

[](#create-a-state-definition-file)

We can create the enum file with the `make:model-state` command and the `--nullable` option:

```
php artisan make:model-state OrderFulfillment --nullable
```

After editing the generated file we can have something like this:

```
namespace App\Models;

use Norotaro\Enumata\Contracts\Nullable;
use Norotaro\Enumata\Contracts\DefineStates;

enum OrderFulfillment implements DefineStates, Nullable
{
    case Pending;
    case Completed;

    public function transitions(): array
    {
        return match ($this) {
            self::Pending => [
                'completeFulfillment' => self::Completed,
            ],
        };
    }

    public static function default(): ?self
    {
        return null;
    }

    public static function initialTransitions(): array
    {
        return [
            'initFulfillment' => self::Pending,
        ];
    }
}
```

The `initialTransitions()` method must return the list of available transitions when the field is null.

> As with `transitions()`, by default methods will be created with the name of the keys returned by `initialTransitions()`.

### Register the State Definition file

[](#register-the-state-definition-file)

As we previously did with the `status` definition, we need to register the file in the `$casts` property:

```
use Norotaro\Enumata\Contracts\HasStateMachine;
use Norotaro\Enumata\Traits\EloquentHasStateMachines;

class Order extends Model implements HasStateMachine
{
    use EloquentHasStateMachines;

    protected $casts = [
        'status'      => OrderStatus::class,
        'fulfillment' => OrderFulfillment::class,
    ];
}
```

The State Machine
-----------------

[](#the-state-machine)

To access the State Machine we only need to add parentheses to the attribute name:

```
$model->status(); // Norotaro\Enumata\StateMachine
```

> If the attribute uses underscore such as `my_attribute`, you can access the state machine using the camel case name of the attribute, `myAttribute()` in this case.

### Using the State Machine

[](#using-the-state-machine)

#### Transitioning

[](#transitioning-1)

We can transition between states with the `transitionTo($state)` method:

```
$model->status()->transitionTo(OrderStatus::Approved);
```

#### Checking available transitions

[](#checking-available-transitions)

```
$model->status; // App\Model\OrderStatus{name: "Pending"}

$model->status()->canBe(OrderStatus::Approved); // true
$model->status()->canBe(OrderStatus::Processed); // false
```

Events
------

[](#events)

This package adds two new events to those dispatched by Eloquent by default and can be used in the same way.

> More information about Eloquent Events can be found in the [official documentation](https://laravel.com/docs/10.x/eloquent#events).

- `transitioning:{attribute}`: This event is dispatched before saving the transition to a new state.
- `transitioned:{attribute}`: This event is dispatched after saving the transition to a new state.

In the `transitioning` event you can access the original and the new state in this way:

```
$from = $order->getOriginal('fulfillment'); // App\Model\OrderFulfillment{name: "Pending"}
$to   = $order->fulfillment; // App\Model\OrderFulfillment{name: "Complete"}
```

### Listening to events using `$dispatchesEvents`

[](#listening-to-events-using-dispatchesevents)

```
use App\Events\TransitionedOrderFulfillment;
use App\Events\TransitioningOrderStatus;
use Norotaro\Enumata\Traits\HasStateMachines;

class Order extends Model
{
    use HasStateMachines;

    protected $casts = [
        'status'      => OrderStatus::class,
        'fulfillment' => OrderFulfillment::class,
    ];

    protected $dispatchesEvents = [
        'transitioning:status'     => TransitioningOrderStatus::class,
        'transitioned:fulfillment' => TransitionedOrderFulfillment::class,
    ];
}
```

### Listening to events using Closures

[](#listening-to-events-using-closures)

The `transitioning($field, $callback)` and `transitioned($field, $callback)` methods help to register closures.

> Note that the first parameter must be the name of the field we want to listen to.

```
use Norotaro\Enumata\Contracts\HasStateMachine;
use Norotaro\Enumata\Traits\EloquentHasStateMachines;

class Order extends Model implements HasStateMachine
{
    use EloquentHasStateMachines;

    protected $casts = [
        'status'      => OrderStatus::class,
        'fulfillment' => OrderFulfillment::class,
    ];

    protected static function booted(): void
    {
        static::transitioning('fulfillment', function (Order $order) {
            $from = $order->getOriginal('fulfillment');
            $to   = $order->fulfillment;

            \Log::debug('Transitioning fulfillment field', [
                'from' => $from->name,
                'to' => $to->name,
            ]);
        });

        static::transitioned('status', function (Order $order) {
            \Log::debug('Order status transitioned to ' . $order->status->name);
        });
    }
}
```

Testing
-------

[](#testing)

To run the test suite:

```
composer run test
```

Inspiration
-----------

[](#inspiration)

This package was inspired by [asantibanez/laravel-eloquent-state-machines](https://github.com/asantibanez/laravel-eloquent-state-machines).

LICENSE
-------

[](#license)

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

###  Health Score

40

—

FairBetter than 88% of packages

Maintenance48

Moderate activity, may be stable

Popularity25

Limited adoption so far

Community12

Small or concentrated contributor base

Maturity62

Established project with proven stability

 Bus Factor1

Top contributor holds 89.2% 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 ~80 days

Recently: every ~154 days

Total

9

Last Release

388d ago

Major Versions

v1.2.0 → v2.0.02024-10-07

PHP version history (2 changes)v1.0.0PHP ^8.1

v1.2.0PHP ^8.2

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/1774870?v=4)[Nelson Otazo](/maintainers/norotaro)[@norotaro](https://github.com/norotaro)

---

Top Contributors

[![norotaro](https://avatars.githubusercontent.com/u/1774870?v=4)](https://github.com/norotaro "norotaro (33 commits)")[![l3aro](https://avatars.githubusercontent.com/u/25253808?v=4)](https://github.com/l3aro "l3aro (4 commits)")

---

Tags

eloquentfinite-automatafinite-automatonfinite-state-machinelaravellaravel-packagestate-machinelaraveleloquentstate-machinesfinite-state machinesfinite automatonfinite automata

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/norotaro-enumata/health.svg)

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

###  Alternatives

[cviebrock/eloquent-sluggable

Easy creation of slugs for your Eloquent models in Laravel

4.0k13.6M253](/packages/cviebrock-eloquent-sluggable)[tucker-eric/eloquentfilter

An Eloquent way to filter Eloquent Models

1.8k4.8M26](/packages/tucker-eric-eloquentfilter)[watson/validating

Eloquent model validating trait.

9723.3M47](/packages/watson-validating)[cybercog/laravel-love

Make Laravel Eloquent models reactable with any type of emotions in a minutes!

1.2k302.7k1](/packages/cybercog-laravel-love)[cviebrock/eloquent-taggable

Easy ability to tag your Eloquent models in Laravel.

567694.8k3](/packages/cviebrock-eloquent-taggable)[reedware/laravel-relation-joins

Adds the ability to join on a relationship by name.

2121.2M13](/packages/reedware-laravel-relation-joins)

PHPackages © 2026

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