Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Fsm Laravel Package

christhompsontldr/laravel-fsm

Robust finite state machine for Laravel with zero-config setup. Define states and transitions with guards, actions, and entry/exit callbacks. Event-driven with comprehensive transition logging, validation, caching, and support for multiple state machines per model column.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation:

    composer require christhompsontldr/laravel-fsm
    php artisan vendor:publish --provider="Fsm\FsmServiceProvider" --tag="fsm-config"
    
  2. Define States: Create an enum implementing FsmStateEnum in app/Fsm/Enums:

    enum OrderStatus implements FsmStateEnum {
        case Pending; case Paid; case Shipped;
        public function label(): string { return ucfirst($this->value); }
    }
    
  3. Register FSM Definition: Create a class in app/Fsm/Definitions implementing FsmDefinition:

    class OrderStatusFsm implements FsmDefinition {
        public function define() {
            FsmBuilder::for(Order::class, 'status')
                ->initialState(OrderStatus::Pending)
                ->state(OrderStatus::Pending)
                ->state(OrderStatus::Paid)
                ->from(OrderStatus::Pending)->to(OrderStatus::Paid)->event('pay')
                ->build();
        }
    }
    
  4. Add Trait to Model:

    class Order extends Model {
        use HasFsm;
        protected $fillable = ['status'];
    }
    
  5. Trigger Transition:

    $order = Order::create(['status' => OrderStatus::Pending->value]);
    $order->fsm()->trigger('pay');
    

First Use Case

Order Workflow: Implement a basic order processing flow with states pending → paid → shipped. Use trigger() to move orders through states and can() to validate transitions before execution.


Implementation Patterns

Core Workflows

  1. State Definition: Use the fluent FsmBuilder API to define states, transitions, and metadata:

    FsmBuilder::for(Order::class, 'status')
        ->initialState(OrderStatus::Pending)
        ->state(OrderStatus::Paid, fn ($state) => $state->onEntry([SendReceipt::class]))
        ->from(OrderStatus::Pending)->to(OrderStatus::Paid)->event('pay')
        ->guard([ValidatePayment::class])
        ->action([LogPayment::class])
        ->build();
    
  2. Multi-Column FSMs: Define independent workflows on the same model:

    FsmBuilder::for(Document::class, 'approval_status')
        ->initialState('draft')
        ->from('draft')->to('review')->event('submit')
        ->build();
    
    FsmBuilder::for(Document::class, 'publication_status')
        ->initialState('unpublished')
        ->from('unpublished')->to('published')->event('publish')
        ->build();
    

    Access via:

    $doc->fsm('approval_status')->trigger('submit');
    
  3. Event-Driven Transitions: Listen to StateTransitioned events for audit trails or side effects:

    Event::listen(StateTransitioned::class, function ($event) {
        Log::info("Transitioned {$event->model->id} from {$event->fromState} to {$event->toState}");
    });
    

Integration Tips

  • Validation: Use guards to enforce business rules:
    ->guard(function ($model, $from, $to) {
        return $model->amount > 0;
    })
    
  • Queued Actions: Offload long-running tasks:
    ->queuedAction(NotifyCustomerJob::class)
    
  • Dry Runs: Test transitions without mutation:
    $preview = $order->fsm()->dryRun('pay');
    
  • Diagrams: Visualize workflows:
    php artisan fsm:diagram
    

Common Patterns

  1. State Metadata: Attach metadata to states for UI/UX (e.g., colors, labels):

    ->state(OrderStatus::Paid, fn ($state) => $state->metadata(['color' => 'green']))
    
  2. Terminal States: Mark states as unreachable from others:

    ->state(OrderStatus::Delivered, fn ($state) => $state->isTerminal(true))
    
  3. Wildcard Transitions: Handle bulk transitions:

    ->from(\Fsm\Constants::STATE_WILDCARD)->to(OrderStatus::Cancelled)->event('cancel_all')
    

Gotchas and Tips

Pitfalls

  1. Caching:

    • Clear the FSM cache after definition changes:
      php artisan fsm:cache:clear
      
    • Cache invalidation is automatic during development (config('app.debug' => true)).
  2. Transactions:

    • Disabling use_transactions in config/fsm.php may lead to partial state updates if actions fail mid-transition.
  3. State Enums:

    • Ensure enums implement FsmStateEnum and include a label() method for human-readable output.
  4. Event Order:

    • Guards run before actions/callbacks. Failed guards prevent subsequent hooks from executing.
  5. Context Data:

    • Pass context via trigger() for dynamic transitions:
      $order->fsm()->trigger('pay', ['amount' => 100]);
      
    • Access context in guards/actions via $context->get('amount').

Debugging

  1. Transition Failures:

    • Enable log_failures in config/fsm.php to log rejected transitions.
    • Listen to TransitionFailed events for detailed error context:
      Event::listen(TransitionFailed::class, function ($event) {
          Log::error("Failed transition: {$event->error}");
      });
      
  2. State Validation:

    • Use dryRun() to validate transitions before execution:
      if (!$order->fsm()->dryRun('pay')->can_transition) {
          throw new \Exception("Invalid transition");
      }
      
  3. Diagram Mismatches:

    • Regenerate diagrams after definition changes:
      php artisan fsm:diagram --force
      

Extension Points

  1. Custom Guards:

    • Implement FsmGuardContract for reusable validation logic:
      class MinAmountGuard implements FsmGuardContract {
          public function __invoke($model, $from, $to, $context) {
              return $model->amount >= $context->get('min_amount');
          }
      }
      
  2. Dynamic States:

    • Load states dynamically via FsmStateProvider:
      FsmBuilder::for(Order::class)
          ->stateProvider([DynamicStateProvider::class])
          ->build();
      
  3. Event Logging:

    • Extend TransitionAttempted/StateTransitioned for custom logging:
      Event::listen(StateTransitioned::class, function ($event) {
          Analytics::track($event->model, 'state_change', [
              'from' => $event->fromState,
              'to' => $event->toState
          ]);
      });
      

Performance Tips

  1. Caching:

    • Enable event_logging.queue to defer logging to a queue for high-throughput systems.
  2. Batch Transitions:

    • Use transitionFsm() for bulk state updates (bypasses event system):
      Order::where('status', OrderStatus::Pending->value)
           ->update(['status' => OrderStatus::Paid->value]);
      
  3. Lazy Loading:

    • Avoid eager-loading FSM definitions unless needed:
      $order->loadFsm(); // Loads FSM definition on demand
      
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
codraw/framework-extra-bundle
codraw/messenger
codraw/security
codraw/mailer
codraw/contracts
codraw/profiling
codraw/dependency-injection
codraw/tester
codraw/core
nexmo/api-specification
capell-app/block-library
axium/identity
cetria/laravel-dummy-models
cetria/reflection-helper
agropredict/sso-auth-bundle
evolvestudio/spam-protection
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony