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 Workflow Laravel Package

zerodahero/laravel-workflow

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the Package

    composer require zerodahero/laravel-workflow
    

    Publish the config file (if needed):

    php artisan vendor:publish --provider="Zerodahero\Workflow\WorkflowServiceProvider"
    
  2. Define a Workflow Create a workflow definition in config/workflow.php (or publish the default config):

    'workflows' => [
        'order' => [
            'supports' => [Order::class],
            'places' => ['draft', 'submitted', 'paid', 'shipped', 'cancelled'],
            'transitions' => [
                'submit' => ['from' => 'draft', 'to' => 'submitted'],
                'pay' => ['from' => 'submitted', 'to' => 'paid'],
                // ...
            ],
            'initial_places' => ['draft'],
            'property' => 'status', // Model property to track workflow state
        ],
    ],
    
  3. Apply to a Model Use the HasWorkflow trait in your model (e.g., Order):

    use Zerodahero\Workflow\Traits\HasWorkflow;
    
    class Order extends Model
    {
        use HasWorkflow;
    
        protected $workflow = 'order'; // Matches config key
    }
    
  4. First Use Case: Transition an Order

    $order = Order::find(1);
    $order->apply('submit'); // Transitions from 'draft' to 'submitted'
    $order->save(); // Persist the state change
    

Implementation Patterns

Workflow Integration Workflows

  1. Model-Based Workflows

    • Use HasWorkflow trait for models with stateful processes (e.g., orders, tickets, approvals).
    • Example: Track a Ticket through newassignedresolvedclosed.
    • Tip: Override getWorkflowProperty() to customize the property name dynamically.
  2. Service Layer Abstraction

    • Inject WorkflowManager into services to decouple workflow logic from controllers:
      public function __construct(private WorkflowManager $workflows) {}
      
      public function processOrder(Order $order) {
          $this->workflows->apply($order, 'pay');
      }
      
  3. Guard Clauses for Transitions

    • Validate transitions before applying them:
      if ($order->can('pay')) {
          $order->apply('pay');
      } else {
          throw new \RuntimeException('Order cannot be paid in current state.');
      }
      
  4. Event-Driven Extensions

    • Listen for workflow events to trigger side effects:
      Workflow::onTransitioning(function (TransitionEvent $event) {
          if ($event->getTransition()->getName() === 'pay') {
              // Send payment confirmation email
          }
      });
      
  5. Custom Transition Logic

    • Override canApply() in your model to enforce business rules:
      public function canApply($transition, $to)
      {
          if ($transition === 'cancel' && $this->status === 'shipped') {
              return false;
          }
          return parent::canApply($transition, $to);
      }
      
  6. Bulk Workflow Updates

    • Use WorkflowManager::applyTo() for batch processing:
      $orders = Order::where('status', 'submitted')->get();
      foreach ($orders as $order) {
          $this->workflows->apply($order, 'pay');
      }
      

Gotchas and Tips

Common Pitfalls

  1. Property Mismatch

    • Ensure the property in config matches the model’s attribute name (e.g., status vs. workflow_status).
    • Debug: Check dd($model->getWorkflowProperty()) to verify.
  2. Initial State Confusion

    • initial_places is an array (e.g., ['draft']), not a string.
    • Fix: Update config if migrating from initial_place.
  3. Transition Guard Bypass

    • Overriding canApply() can silently fail if not called explicitly. Use:
      if (!$this->canApply($transition, $to)) {
          throw new \RuntimeException("Transition '$transition' blocked.");
      }
      
  4. Database Sync Issues

    • Always call $model->save() after apply() to persist the state.
    • Tip: Use model observers to auto-save:
      class OrderObserver {
          public function saved(Order $order) {
              if ($order->isDirty($order->getWorkflowProperty())) {
                  $order->workflow->persist();
              }
          }
      }
      
  5. Circular Dependencies

    • Avoid defining workflows that allow loops (e.g., A → B → A). Use validate() in transitions:
      'transitions' => [
          'approve' => [
              'from' => 'draft',
              'to' => 'approved',
              'validate' => function ($order) {
                  return $order->meetsApprovalCriteria();
              },
          ],
      ],
      

Debugging Tips

  1. Inspect Workflow State

    dd($model->workflow->getPlaces());
    dd($model->workflow->getCurrentPlaces());
    
  2. Enable Workflow Logging Add to config/workflow.php:

    'debug' => env('WORKFLOW_DEBUG', false),
    
  3. Test Transitions Isolated Use PHPUnit to verify transitions:

    public function testOrderCannotPayWhenNotSubmitted()
    {
        $order = new Order(['status' => 'draft']);
        $this->assertFalse($order->can('pay'));
    }
    

Extension Points

  1. Custom Transition Validators Extend Workflow to add reusable validators:

    class OrderWorkflow extends Workflow
    {
        protected function validatePayTransition(Order $order)
        {
            return $order->amount > 0;
        }
    }
    
  2. Workflow Events Dispatch custom events for transitions:

    Workflow::onTransitioned(function (TransitionEvent $event) {
        event(new OrderPaid($event->getSubject()));
    });
    
  3. Dynamic Workflows Load workflows from the database:

    $workflowConfig = WorkflowConfig::find($model->workflow_config_id);
    $this->workflows->load($workflowConfig->toArray());
    
  4. API Integration Expose workflow transitions via API:

    Route::post('/orders/{order}/transition', function (Order $order, Request $request) {
        $order->apply($request->transition);
        return response()->json(['status' => 'transited']);
    });
    
  5. Fallback for Missing Transitions Handle undefined transitions gracefully:

    try {
        $order->apply('unknown');
    } catch (TransitionException $e) {
        report($e);
        return back()->withError('Invalid transition.');
    }
    
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.
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament
leek/filament-subtenant-scope