grazulex / laravel-statecraft
A declarative and testable way to model entity state transitions (orders, documents, processesβ¦) with guard conditions, actions, and events.
Requires
- php: ^8.3
- illuminate/contracts: ^12.0
- illuminate/support: ^12.19
- nesbot/carbon: ^3.10
- symfony/yaml: ^7.3
Requires (Dev)
- doctrine/dbal: ^4.2
- larastan/larastan: ^3.4
- laravel/pint: ^1.22
- orchestra/testbench: ^10.0
- pestphp/pest: ^3.8
- pestphp/pest-plugin-laravel: ^3.2
- rector/rector: ^2.0
README

Elegant and testable state machines for Laravel applications β Define entity workflows declaratively (YAML), and control transitions with guards, actions, and events.
π Features
- π Declarative state machines for Eloquent models
- π‘οΈ Guard conditions with AND/OR/NOT logic expressions
- βοΈ Lifecycle actions on transitions
- π¦ Auto-generated methods like
canPublish()
andpublish()
- π§ͺ Built-in test support for transitions
- π Laravel event support (
Transitioning
,Transitioned
) - π§Ύ Optional transition history tracking
- βοΈ Comprehensive Artisan commands for YAML definitions and PHP classes
- π§ Configurable paths, events, and history tracking
- π― Dynamic resolution of guards and actions via Laravel container
- π§© Complex guard expressions with nested conditional logic
- π Export capabilities (JSON, Mermaid, Markdown)
- β Validation system for YAML definitions
- π Comprehensive documentation and examples
π¦ Installation
composer require grazulex/laravel-statecraft
Configuration (Optional)
Publish the configuration file and migrations:
# Publish configuration php artisan vendor:publish --tag=statecraft-config # Publish migrations (if using history tracking) php artisan vendor:publish --tag=statecraft-migrations php artisan migrate
The configuration file will be published to config/statecraft.php
where you can customize:
- State machine definitions path
- Default state field name
- Event system settings
- History tracking options
β¨ Example: Order Workflow
YAML Definition
state_machine: name: OrderWorkflow model: App\Models\Order states: [draft, pending, approved, rejected] initial: draft transitions: - from: draft to: pending guard: canSubmit action: notifyReviewer - from: pending to: approved guard: isManager - from: pending to: rejected action: refundCustomer
π§© Guard Expressions
Laravel Statecraft supports powerful guard expressions with AND/OR/NOT logic for complex business rules:
AND Logic - All conditions must be true
- from: pending to: approved guard: and: - IsManager - HasMinimumAmount
OR Logic - At least one condition must be true
- from: pending to: approved guard: or: - IsManager - IsVIP
NOT Logic - Condition must be false
- from: pending to: approved guard: not: IsBlacklisted
Nested Expressions - Complex combinations
- from: pending to: approved guard: and: - IsManager - or: - IsVIP - IsUrgent
Key Features:
- π Backward Compatible - Simple string guards still work
- π― Dynamic Evaluation - Guards resolved at runtime
- π§© Nested Logic - Complex business rules supported
- π Event Integration - Expressions serialized in events and history
- β‘ Boolean Logic - AND/OR/NOT operations with short-circuit evaluation
π§βπ» Usage
Basic Model Setup
Add the trait to your model:
use Grazulex\LaravelStatecraft\Traits\HasStateMachine; class Order extends Model { use HasStateMachine; protected function getStateMachineDefinitionName(): string { return 'order-workflow'; // YAML file name } }
Using the State Machine
$order = Order::find(1); // Check if transitions are allowed if ($order->canApprove()) { $order->approve(); // Executes guard + action + state change } // Get current state and available transitions $currentState = $order->getCurrentState(); $availableTransitions = $order->getAvailableTransitions();
With History Tracking
use Grazulex\LaravelStatecraft\Traits\HasStateHistory; class Order extends Model { use HasStateMachine, HasStateHistory; // ... rest of your model } // Access transition history $history = $order->stateHistory(); $lastTransition = $order->latestStateTransition();
βοΈ Custom Guard
class IsManager implements \Grazulex\LaravelStatecraft\Contracts\Guard { public function check(Model $model, string $from, string $to): bool { return auth()->user()?->is_manager; } }
π Custom Action
class NotifyReviewer implements \Grazulex\LaravelStatecraft\Contracts\Action { public function execute(Model $model, string $from, string $to): void { Notification::route('mail', 'review@team.com') ->notify(new OrderPendingNotification($model)); } }
π Transition History (optional)
$order->stateHistory(); // β returns a collection of past transitions
β Artisan Commands
Generate YAML Definition
php artisan statecraft:make order-workflow php artisan statecraft:make article-status --states=draft,review,published --initial=draft
Generate PHP Classes from YAML
php artisan statecraft:generate database/state_machines/order-workflow.yaml
This generates:
- Guard classes in
app/StateMachines/Guards/
- Action classes in
app/StateMachines/Actions/
- Model examples in
app/StateMachines/
List and Inspect Definitions
# List all YAML definitions php artisan statecraft:list # Show definition details php artisan statecraft:show order-workflow # Validate definitions php artisan statecraft:validate --all
Export to Different Formats
# Export to JSON, Mermaid, or Markdown
php artisan statecraft:export order-workflow json
php artisan statecraft:export order-workflow mermaid
php artisan statecraft:export order-workflow md --output=docs/workflow.md
Command Options
statecraft:make supports additional options:
php artisan statecraft:make order-workflow --model=App\\Models\\Order --states=draft,pending,approved --initial=draft
statecraft:generate uses configurable output paths:
- Configure output directory via
statecraft.generated_code_path
- Defaults to
app/StateMachines/
if not configured
π§ͺ Testing
Use the built-in test utilities:
use Grazulex\LaravelStatecraft\Testing\StateMachineTester; // Test transitions StateMachineTester::assertTransitionAllowed($order, 'approved'); StateMachineTester::assertTransitionBlocked($order, 'rejected'); // Test states StateMachineTester::assertInState($order, 'pending'); StateMachineTester::assertHasAvailableTransitions($order, ['approved', 'rejected']); // Test methods StateMachineTester::assertCanExecuteMethod($order, 'approve'); StateMachineTester::assertCannotExecuteMethod($order, 'reject');
Testing Guard Expressions
Test complex guard expressions by setting up your models and authentication:
// Test AND logic with actual conditions $manager = User::factory()->create(['is_manager' => true]); $order = Order::factory()->create(['amount' => 1000]); $this->actingAs($manager); // Both conditions true: IsManager AND HasMinimumAmount StateMachineTester::assertTransitionAllowed($order, 'approved'); // Make one condition false $nonManager = User::factory()->create(['is_manager' => false]); $this->actingAs($nonManager); StateMachineTester::assertTransitionBlocked($order, 'approved'); // Test OR logic with different conditions $vipOrder = Order::factory()->create(['is_vip' => true]); StateMachineTester::assertTransitionAllowed($vipOrder, 'approved'); // Test NOT logic $blacklistedOrder = Order::factory()->create(['customer_blacklisted' => true]); StateMachineTester::assertTransitionBlocked($blacklistedOrder, 'approved');
## π Events
Laravel Statecraft dispatches events during transitions:
```php
use Grazulex\LaravelStatecraft\Events\StateTransitioning;
use Grazulex\LaravelStatecraft\Events\StateTransitioned;
// Listen to state changes
Event::listen(StateTransitioning::class, function ($event) {
// Before transition
$event->model; // The model
$event->from; // From state
$event->to; // To state
$event->guard; // Guard class (if any)
$event->action; // Action class (if any)
});
Event::listen(StateTransitioned::class, function ($event) {
// After transition
Log::info("Order {$event->model->id} transitioned from {$event->from} to {$event->to}");
});
π Documentation
For comprehensive documentation, examples, and advanced usage:
- Console Commands - Console commands reference
- Guards and Actions - Dynamic guards and actions
- Guard Expressions - AND/OR/NOT logic for guards
- Configuration - Configuration options
- Events - Event system usage
- Testing - Testing utilities
- History - State transition history
- Examples - Practical examples and use cases
π― Next Steps
- Quick Start: Check out the OrderWorkflow example
- Console Commands: Explore the console commands
- Guard Expressions: See guard-expressions-workflow.yaml for comprehensive examples
- Advanced Usage: Read the Guards and Actions documentation
- Configuration: Review the Configuration guide
- Testing: Learn about Testing utilities
β€οΈ About
Laravel-Statecraft is part of the Grazulex Tools ecosystem:
Laravel-Arc
(DTOs) β’ Laravel-Flowpipe
(Business Steps) β’ Laravel-Statecraft
(State Machines)
Designed for clean, testable, and modular Laravel applications.
π§ Author
JeanβMarc Strauven / @Grazulex
Blog: Open Source My Friend