grazulex/laravel-statecraft

A declarative and testable way to model entity state transitions (orders, documents, processes…) with guard conditions, actions, and events.

v0.1.0 2025-07-17 11:23 UTC

This package is auto-updated.

Last update: 2025-07-17 20:07:55 UTC


README

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

πŸš€ 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

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:

🎯 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