joggapp/eloquent-state-machines

This Laravel package simplifies the creation of state machines for your Eloquent models

Maintainers

Package info

github.com/JoggApp/eloquent-state-machines

Homepage

pkg:composer/joggapp/eloquent-state-machines

Statistics

Installs: 20

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v3.0.0 2026-03-20 10:10 UTC

This package is auto-updated.

Last update: 2026-05-20 08:39:04 UTC


README

This package simplifies state transitions for Eloquent models by centralizing the transition logic in a single StateMachine class. It allows you to define available transitions between states and handle actions during each transition. Additionally, it automatically records the history of all state transitions for a model.

Installation

Install the package via Composer:

composer require joggapp/eloquent-state-machines

Publish the package's configuration:

php artisan vendor:publish --provider="JoggApp\EloquentStateMachines\EloquentStateMachinesServiceProvider"

Run the migrations:

php artisan migrate

Usage

Defining a StateMachine

Consider an Invoice model with a status field that can have one of the following values: 'draft', 'open', 'paid', 'void', or 'un-collectable'.

We can manage these states and their transitions within a single StateMachine class. To create one, use the following command:

php artisan make:state-machine InvoiceStatusStateMachine

This command will create a new StateMachine class in the App\StateMachines directory.

Within this class, you can define the default state:

public static function defaultState(): ?string
{
    return 'draft';
}

Specify the allowed transitions:

public static function transitions(): array
{
    return [
        'draft' => ['open'],
        'open' => ['paid', 'void', 'un-collectable'],
        'un-collectable' => ['paid'],
        'paid' => null,
        'void' => null,
    ];
}

Enable or disable history logging:

public static function saveTransitions(): bool
{
    return true;
}

Registering the StateMachine

After defining the StateMachine, register it in the Invoice model using the $stateMachines property.

use App\StateMachines\InvoiceStatusStateMachine;
use JoggApp\EloquentStateMachines\Traits\HasStateMachines;

class Invoice extends Model
{
    use HasStateMachines;

    protected $stateMachines = [
        'status' => InvoiceStatusStateMachine::class,
    ];
}

State Machine Methods

By registering $stateMachines in your model, the HasStateMachines trait provides a method to interact with each state field. For example, $invoice->status() can be used to check the current state, apply transitions, and view state history.

Checking State

$invoice->status; // 'draft'

$invoice->status()->is('draft'); // true

$invoice->status()->isNot('draft'); // false

Verify transition

$invoice->status; // 'draft'

$invoice->status()->canTransitionTo('open'); // true

$invoice->status()->canTransitionTo('void'); // false

Transitioning States

To transition from one state to another, use the transitionTo method:

$invoice->status()->transitionTo('open');

You can also pass additional data if needed:

$invoice->status()->transitionTo('open', [
    'terms' => 'net30',
]);

The state machine will verify if the transition is allowed based on the defined transitions(). If the transition is not allowed, an exception will be thrown.

Querying History

If saveTransitions() is set to true in the StateMachine, each state transition will be recorded in the state_transitions table created during installation.

$invoice->status()->history();

Advanced Usage

Override canTransitionTo() Method

By default the canTransitionTo() method uses the transitions defined in our StateMachine's transitions() method. It is possible to override any of the transitions by adding a method with the naming convention canTransitionFrom{Studly Case From State}To{Studly Case To State}().

public function canTransitionFromDraftToOpen(array $data): bool
{
    return $this->model->tasks()->notCompleted()->isEmpty();
}

Override transitionTo() Method

Similar to the canTransitionTo() method, the transitionTo() method can also be modified for any transition. The naming convention for this is:

  • beforeTransitionFrom{Studly Case From State}To{Studly Case To State}()
  • beforeTransitionFrom{Studly Case From State}()
  • beforeTransitionTo{Studly Case To State}()
  • afterTransitionFrom{Studly Case From State}To{Studly Case To State}()
  • afterTransitionFrom{Studly Case From State}
  • afterTransitionTo{Studly Case To State}()
public function beforeTransitionFromDraftToOpen(array $data): void
{
    $this->model->sendPaymentNotification();
}

public function beforeTransitionFromOpen(array $data): void
{
    // Do something before transitioning away from "open" to any state
}

public function afterTransitionFromOpenToVoid(array $data): void
{
    $this->model->delete();
}

public function afterTranstionToPaid(array $data): void
{
    // Do something after transitioning to "paid" from any state
}