Skip to content

🧩 Define entity workflows declaratively (YAML or PHP), and control transitions with guards, actions, and events.

License

Notifications You must be signed in to change notification settings

Grazulex/laravel-statecraft

Repository files navigation

Laravel Statecraft

Laravel Statecraft

Elegant and testable state machines for Laravel applications β€” Define entity workflows declaratively (YAML), and control transitions with guards, actions, and events.

Latest Version Total Downloads License PHP Version Laravel Version Tests Code Quality Code Style

Overview

Laravel Statecraft est la solution élégante pour gérer les machines d'état dans vos applications Laravel. Définissez vos workflows de manière déclarative avec YAML, contrôlez les transitions avec des guards, des actions, et des événements.

✨ Parfait pour : workflows de commandes, validation de contenu, gestion d'utilisateurs, processus d'approbation πŸš€ SimplicitΓ© : Configuration YAML intuitive + mΓ©thodes auto-gΓ©nΓ©rΓ©es
πŸ”§ FlexibilitΓ© : Guards complexes, actions personnalisΓ©es, historique des transitions

πŸš€ Features

  • πŸ” Declarative state machines for Eloquent models
  • πŸ›‘οΈ Guard conditions with AND/OR/NOT logic expressions
  • βš™οΈ Lifecycle actions on transitions
  • πŸ“¦ Auto-generated methods like canPublish() and publish()
  • πŸ§ͺ 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

Install via Composer:

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

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

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', '[email protected]')
            ->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

πŸ§ͺ 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:

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:

🎯 Next Steps

  1. Quick Start: Check out the OrderWorkflow example
  2. Console Commands: Explore the console commands
  3. Guard Expressions: See guard-expressions-workflow.yaml for comprehensive examples
  4. Advanced Usage: Read the Guards and Actions documentation
  5. Configuration: Review the Configuration guide
  6. 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

About

🧩 Define entity workflows declaratively (YAML or PHP), and control transitions with guards, actions, and events.

Topics

Resources

License

Code of conduct

Security policy

Stars

Watchers

Forks