joggapp / eloquent-state-machines
This Laravel package simplifies the creation of state machines for your Eloquent models
Package info
github.com/JoggApp/eloquent-state-machines
pkg:composer/joggapp/eloquent-state-machines
Requires
- php: ^8.4
- laravel/framework: ^13.0
Requires (Dev)
- orchestra/testbench: ^11.0
- phpunit/phpunit: ^12.0
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 }