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

Workflow Laravel Package

draw/workflow

draw/workflow is a Laravel/PHP package for modeling and running workflows. Define steps and transitions, track state changes, and execute processes consistently across your application. Useful for approvals, onboarding flows, and other multi-step business processes.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps to First Use

  1. Install the Package

    composer require draw/workflow
    

    Requires Symfony Workflow ^6.4.0 and PHP 8.1+.

  2. Define a Workflow Extension Create a class implementing ExtensionInterface:

    use Draw\Workflow\Extension\ExtensionInterface;
    use Symfony\Component\Workflow\WorkflowInterface;
    
    class CustomGuardExtension implements ExtensionInterface
    {
        public function getName(): string
        {
            return 'custom_guard';
        }
    
        public function canApply(WorkflowInterface $workflow, $entity, string $transition, string $from, string $to): bool
        {
            // Custom guard logic (e.g., role-based, attribute checks)
            return true;
        }
    }
    
  3. Register the Extension Extend your Symfony Workflow configuration (e.g., config/packages/workflow.yaml):

    workflows:
        my_workflow:
            type: 'state_machine'
            supports: [App\Entity\MyEntity]
            initial_marking: 'pending'
            places: ['pending', 'approved', 'rejected']
            transitions:
                - from: pending
                  to: approved
                  guard: 'custom_guard'  # Reference your extension
    
  4. Apply the Workflow Use the extended workflow in a controller or service:

    use Symfony\Component\Workflow\WorkflowInterface;
    
    class OrderController
    {
        public function __construct(private WorkflowInterface $workflow) {}
    
        public function approveOrder(MyEntity $order)
        {
            $this->workflow->apply($order, 'approve');
            // Custom guard logic is now enforced
        }
    }
    
  5. First Use Case: Dynamic Guards Implement a guard that checks user permissions:

    class RoleGuardExtension implements ExtensionInterface
    {
        public function canApply(WorkflowInterface $workflow, $entity, string $transition, string $from, string $to): bool
        {
            $user = auth()->user();
            return $user->hasRole('admin');
        }
    }
    

Implementation Patterns

Core Usage Patterns

  1. Extension-Based Workflows

    • Pattern: Use ExtensionInterface to add reusable logic (guards, transitions, events).
    • Example:
      class EmailNotificationExtension implements ExtensionInterface
      {
          public function onTransition(WorkflowInterface $workflow, $entity, string $transition, string $from, string $to)
          {
              Mail::to($entity->user)->send(new WorkflowNotification($transition));
          }
      }
      
  2. Event-Driven Extensions

    • Pattern: Hook into Symfony’s event system to modify workflow behavior dynamically.
    • Example:
      use Symfony\Component\EventDispatcher\EventDispatcherInterface;
      
      class DynamicTransitionExtension implements ExtensionInterface
      {
          public function __construct(private EventDispatcherInterface $dispatcher) {}
      
          public function getTransitions(WorkflowInterface $workflow, $entity): array
          {
              $this->dispatcher->dispatch(new WorkflowTransitionsEvent($entity));
              return [...]; // Modified transitions
          }
      }
      
  3. Conditional Transitions

    • Pattern: Use extensions to enable/disable transitions based on runtime data.
    • Example:
      transitions:
          - from: pending
            to: approved
            guard: 'custom_guard'
            # Only enabled if $entity->isValid()
      
  4. Integration with Laravel

    • Pattern: Bridge Symfony’s EventDispatcher to Laravel’s Events system.
    • Example:
      use Symfony\Component\EventDispatcher\EventDispatcher;
      use Illuminate\Support\Facades\Event;
      
      $dispatcher = new EventDispatcher();
      $dispatcher->addListener('workflow.transition', function ($event) {
          Event::dispatch(new LaravelWorkflowEvent($event));
      });
      

Workflow Integration Tips

  1. Leverage Symfony’s DI

    • Register extensions as services in services.yaml:
      services:
          App\Workflow\CustomExtension:
              tags: ['draw.workflow.extension']
      
  2. Compose Complex Workflows

    • Combine multiple extensions for layered logic:
      extensions:
          - 'guard_extension'
          - 'notification_extension'
          - 'audit_extension'
      
  3. Test Workflows

    • Use Symfony’s WorkflowTestCase or Laravel’s WorkflowTestTrait:
      use Symfony\Component\Workflow\Tests\WorkflowTestCase;
      
      class MyWorkflowTest extends WorkflowTestCase
      {
          public function testCustomGuard()
          {
              $this->assertTrue($this->workflow->can($entity, 'approve'));
          }
      }
      
  4. Debugging Workflows

    • Enable debug mode for workflow events:
      $workflow->registerTransition('debug', function ($transition) {
          logger()->debug('Transition', ['transition' => $transition->getName()]);
      });
      

Gotchas and Tips

Common Pitfalls

  1. Extension Registration Order

    • Issue: Extensions may override each other if not ordered correctly.
    • Fix: Use ExtensionInterface::getPriority() to control execution order.
  2. Circular Dependencies

    • Issue: Extensions depending on other extensions can cause bootstrapping errors.
    • Fix: Use lazy-loading or dependency injection carefully.
  3. Symfony Version Mismatches

    • Issue: Conflicts with existing symfony/workflow or symfony/event-dispatcher.
    • Fix: Pin versions in composer.json:
      "require": {
          "symfony/workflow": "^6.4.0",
          "symfony/event-dispatcher": "^6.4.0"
      }
      
  4. Laravel-Specific Quirks

    • Issue: Symfony’s EventDispatcher may not play well with Laravel’s service container.
    • Fix: Use a bridge like symfony/event-dispatcher-bundle or manually bind services.
  5. State Persistence

    • Issue: Custom extensions may not persist state across requests.
    • Fix: Store workflow state in the entity or a separate service.

Debugging Tips

  1. Log Workflow Events

    $workflow->registerTransition('log', function ($transition) {
        \Log::debug('Workflow transition', [
            'transition' => $transition->getName(),
            'from' => $transition->getFrom(),
            'to' => $transition->getTo(),
        ]);
    });
    
  2. Inspect Workflow State

    $state = $workflow->getEnabledTransitions($entity);
    \Log::info('Enabled transitions', $state);
    
  3. Use Symfony’s Debug Toolbar

    • Install symfony/web-profiler-bundle to visualize workflow states.

Extension Points

  1. Custom Guards

    • Implement canApply() to add logic before transitions:
      public function canApply(WorkflowInterface $workflow, $entity, string $transition, string $from, string $to): bool
      {
          return $entity->isValid() && auth()->check();
      }
      
  2. Transition Handlers

    • Implement onTransition() for post-transition logic:
      public function onTransition(WorkflowInterface $workflow, $entity, string $transition, string $from, string $to)
      {
          $entity->markAsProcessed();
          $entity->save();
      }
      
  3. Event Listeners

    • Extend Symfony’s event system:
      use Symfony\Component\Workflow\Event\TransitionEvent;
      
      $dispatcher->addListener(TransitionEvent::NAME, function (TransitionEvent $event) {
          // Custom logic
      });
      

Performance Considerations

  1. Avoid Heavy Logic in Guards

    • Guards should be lightweight (e.g., database checks, role validation).
    • Anti-pattern: Calling external APIs in guards.
  2. Cache Workflow Definitions

    • If workflows are static, cache the WorkflowInterface instance:
      $workflow = $container->get('workflow.my_workflow');
      
  3. Batch Processing

    • For bulk workflow operations, use applyAll() or queue transitions:
      $workflow->applyAll($entities, 'transition_name');
      

Laravel-Specific Tips

  1. Service Provider Integration

    • Register extensions in a Laravel service provider:
      use Draw\Workflow\Extension\ExtensionInterface;
      
      class WorkflowServiceProvider extends ServiceProvider
      {
          public function register()
          {
              $this->app->tag(
                  [CustomExtension::class],
                  ['draw.workflow.extension']
              );
          }
      }
      
  2. Artisan Commands

    • Create commands to manage workflows:
      use Symfony\Component\Workflow\WorkflowInterface;
      
      class WorkflowCommand extends Command
      {
          protected $workflow;
      
          public function __construct(WorkflowInterface $workflow)
          {
              $this
      
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.
cadot.eu/make
besmartand-pro/php-quality-config
sentix/ai-chatbot
codifyo/ts-generator-bundle
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky