coderscantina / laravel-finite
A finite state machine for your Laravel/Eloquent models.
Installs: 1
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/coderscantina/laravel-finite
Requires
- php: ^8.1|^8.2|^8.3|^8.4
- illuminate/support: ^10.0||^11.0||^12.0
Requires (Dev)
- graham-campbell/testbench: ^6.2
- phpunit/phpunit: ^10.0||^11.0
- squizlabs/php_codesniffer: ^3.9
README
A powerful finite state machine (FSM) library for Laravel/Eloquent models. Manage complex state workflows with a simple, fluent API.
Features
- 🎯 Simple & Intuitive API - Define states and transitions with ease
- 🔄 State Management - Apply transitions to Eloquent models automatically
- 📋 Transition Rules - Define allowed transitions between states
- 🎁 Properties - Apply properties during transitions
- 🎧 Event Listeners - Listen to pre/post transition events
- 🛡️ Guards - Control transitions with guard closures
- 📦 Fluent & Trait Accessors - Work with both Eloquent models and Fluent objects
- ✅ Laravel 10, 11, 12 Support - Compatible with modern Laravel versions
Requirements
- PHP 8.1 or higher
- Laravel 10, 11, or 12
Installation
Install the package via Composer:
composer require coderscantina/laravel-finite
For Laravel 5.5+, the service provider is automatically discovered. For older versions, add the service provider to your config/app.php:
CodersCantina\LaravelFinite\ServiceProvider::class,
Quick Start
1. Add StateTrait to Your Model
<?php use Illuminate\Database\Eloquent\Model; use CodersCantina\LaravelFinite\StateMachine; use CodersCantina\LaravelFinite\StateTrait; class Article extends Model { use StateTrait; protected static function initializeState(StateMachine $stateMachine): StateMachine { $stateMachine->initialize([ 'states' => [ 'draft' => ['type' => 'initial'], 'submitted' => ['type' => 'normal'], 'published' => ['type' => 'final'], 'rejected' => ['type' => 'final'], ], 'transitions' => [ 'submit' => ['from' => ['draft'], 'to' => 'submitted'], 'publish' => ['from' => ['submitted'], 'to' => 'published'], 'reject' => ['from' => ['submitted'], 'to' => 'rejected'], 'draft' => ['from' => ['submitted', 'rejected'], 'to' => 'draft'], ] ]); return $stateMachine; } }
2. Store the State
Add a state column to your model's migration:
Schema::create('articles', function (Blueprint $table) { $table->id(); $table->string('title'); $table->text('content'); $table->string('state')->default('draft'); $table->timestamps(); });
3. Use State Transitions
$article = Article::create(['title' => 'My Article']); // Check current state echo $article->getState(); // 'draft' // Check if transition is possible if ($article->canTransition('submit')) { $article->applyTransition('submit'); } // Or apply directly (throws InvalidStateException if not allowed) $article->applyTransition('submit'); echo $article->getState(); // 'submitted'
State Types
Define different state types to control workflow:
- initial - Starting state for new models
- normal - Intermediate states in the workflow
- final - Terminal states (no further transitions allowed)
'states' => [ 'draft' => ['type' => 'initial'], 'submitted' => ['type' => 'normal'], 'published' => ['type' => 'final'], ]
Transitions
Basic Transitions
'transitions' => [ 'publish' => [ 'from' => ['draft', 'submitted'], // Can transition from these states 'to' => 'published', // To this state ] ]
Transitions with Properties
Automatically set properties when transitioning:
'transitions' => [ 'publish' => [ 'from' => ['submitted'], 'to' => 'published', 'properties' => [ 'published_at' => now(), 'is_active' => true, ] ] ]
Transitions with Setters
Use a closure to customize state changes:
'transitions' => [ 'publish' => [ 'from' => ['submitted'], 'to' => 'published', 'setter' => function ($model) { $model->published_at = now(); $model->published_by = auth()->id(); } ] ]
Transitions with Guards
Control transitions with guard closures:
'transitions' => [ 'publish' => [ 'from' => ['submitted'], 'to' => 'published', 'guards' => [ function ($model) { return $model->user->is_admin; // Only admins can publish } ] ] ]
Transitions with Listeners
Listen to pre/post transition events:
'transitions' => [ 'publish' => [ 'from' => ['submitted'], 'to' => 'published', 'listeners' => [ function ($event) { if ($event->isPre()) { Log::info('Publishing article: ' . $event->getObject()->id); } elseif ($event->isPost()) { Mail::send(new ArticlePublished($event->getObject())); } } ] ] ]
Advanced Usage
Manual State Machine Usage
use CodersCantina\LaravelFinite\StateMachine; use CodersCantina\LaravelFinite\Accessor\FluentAccessor; use Illuminate\Support\Fluent; $stateMachine = new StateMachine(new FluentAccessor); $stateMachine->initialize([ 'states' => [ 'draft' => ['type' => 'initial'], 'proposed' => [], 'accepted' => ['type' => 'final'], 'refused' => ['type' => 'final'], ], 'transitions' => [ 'propose' => ['from' => ['draft'], 'to' => 'proposed'], 'accept' => ['from' => ['proposed'], 'to' => 'accepted'], 'refuse' => ['from' => ['proposed'], 'to' => 'refused'], ] ]); $item = new Fluent(); $stateMachine->setObject($item); // Use the state machine $stateMachine->apply('propose'); echo $stateMachine->getCurrentStateName(); // 'proposed'
Adding States Dynamically
$stateMachine->addState('archived', 'final'); $stateMachine->addTransition('archive', ['published'], 'archived');
Checking Transitions
// Check if a transition is possible $stateMachine->can('publish'); // bool // Get all transitions $stateMachine->getTransitions(); // Collection // Get current state $stateMachine->getCurrentState(); // State object // Get all states $stateMachine->getStates(); // Collection
Class-Based Transitions
Create custom transition classes for complex logic:
use CodersCantina\LaravelFinite\Transition; class PublishTransition extends Transition { public function apply($model) { $model->state = $this->getTo(); $model->published_at = now(); $model->published_by = auth()->id(); $model->save(); } public function can($model): bool { return $model->user->is_admin; } } // Use in configuration 'transitions' => [ 'publish' => new PublishTransition('publish', ['submitted'], 'published'), ]
API Reference
Model Methods (StateTrait)
getState()- Get the current state valuesetState($state)- Set the state valuegetStateMachine()- Get the StateMachine instancecanTransition($transition)- Check if a transition is possibleapplyTransition($transition, $payload = null)- Apply a transitionapplyProperties($properties)- Apply properties to the model
StateMachine Methods
initialize($config)- Initialize with configuration arraysetObject($obj)- Attach an object to the state machinecan($transition)- Check if transition is possibleapply($transition, $payload = null)- Apply a transitionaddState($name, $type, $properties)- Add a stateaddTransition($name, $from, $to, $properties, $setter, $guards, $listeners)- Add a transitiongetState($name)- Get a specific stategetCurrentState()- Get the current stategetCurrentStateName()- Get the current state namegetStates()- Get all statesgetTransitions()- Get all transitionsgetTransition($name)- Get a specific transition
Changelog
Please see CHANGELOG for more information on what has changed recently.
Testing
Run the test suite:
composer test
Security
If you discover any security-related issues, please email m.wallner@coderscantina.com instead of using the issue tracker.
License
The MIT License (MIT). Please see License File for more information.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
About
Created and maintained by Coders Cantina.