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 Engine Core Laravel Package

solution-forest/workflow-engine-core

Framework-agnostic workflow engine core for PHP 8.3+. Define and run workflows with type-safe steps, state tracking/persistence, plugins for actions/storage, retries, timeouts, and rich error handling. Actively developed; not production-ready.

View on GitHub
Deep Wiki
Context7
## Getting Started

### Minimal Setup for Laravel Integration
1. **Install the package**:
   ```bash
   composer require solution-forest/workflow-engine-core
  1. Create a storage adapter (e.g., database or file-based):

    use SolutionForest\WorkflowEngine\Contracts\StorageAdapter;
    use SolutionForest\WorkflowEngine\Core\WorkflowInstance;
    
    class DatabaseStorageAdapter implements StorageAdapter
    {
        public function save(WorkflowInstance $instance): void
        {
            // Implement DB persistence
        }
    
        public function load(string $id): WorkflowInstance
        {
            // Implement DB retrieval
        }
    
        // ... other required methods
    }
    
  2. Define a simple workflow (e.g., app/Workflows/OrderWorkflow.php):

    use SolutionForest\WorkflowEngine\Core\WorkflowBuilder;
    use App\Actions\ValidateOrderAction;
    use App\Actions\ProcessPaymentAction;
    
    return WorkflowBuilder::create('order-processing')
        ->addStep('validate', ValidateOrderAction::class)
        ->addStep('process-payment', ProcessPaymentAction::class)
        ->build();
    
  3. Initialize the engine (e.g., in a service provider):

    $this->app->singleton(WorkflowEngine::class, function ($app) {
        $storage = new DatabaseStorageAdapter();
        $eventDispatcher = new NullEventDispatcher(); // Replace with Laravel's event system
        return new WorkflowEngine($storage, $eventDispatcher);
    });
    
  4. Trigger a workflow (e.g., in a controller):

    use SolutionForest\WorkflowEngine\Core\WorkflowEngine;
    
    public function processOrder(Request $request, WorkflowEngine $engine)
    {
        $definition = include app_path('Workflows/OrderWorkflow.php');
        $instanceId = $engine->start(
            'order-processing',
            $definition->toArray(),
            ['order_id' => $request->order_id]
        );
    
        return response()->json(['instance_id' => $instanceId]);
    }
    

First Use Case: Order Processing

For a Laravel developer, the most immediate use case is order processing workflows with steps like:

  • Validation
  • Payment processing
  • Inventory deduction
  • Notification sending

Example:

$workflow = WorkflowBuilder::create('order-fulfillment')
    ->addStep('validate-order', ValidateOrderAction::class)
    ->when('order.amount > 1000', function ($builder) {
        $builder->addStep('run-fraud-check', FraudCheckAction::class);
    })
    ->addStep('process-payment', ProcessPaymentAction::class, timeout: 60)
    ->addStep('deduct-inventory', DeductInventoryAction::class)
    ->email('notify-customer', 'customer@example.com', 'Order Confirmed')
    ->build();

Implementation Patterns

1. Action Development Pattern

Laravel-specific actions should extend BaseAction and leverage Laravel's services:

use SolutionForest\WorkflowEngine\Actions\BaseAction;
use SolutionForest\WorkflowEngine\Core\WorkflowContext;
use SolutionForest\WorkflowEngine\Core\ActionResult;
use Illuminate\Support\Facades\Log;

class ProcessPaymentAction extends BaseAction
{
    public function execute(WorkflowContext $context): ActionResult
    {
        $paymentGateway = app(PaymentGateway::class);
        $result = $paymentGateway->charge(
            $context->getData('order_id'),
            $context->getData('amount')
        );

        if ($result->successful()) {
            return ActionResult::success(['transaction_id' => $result->id]);
        }

        Log::error('Payment failed', ['error' => $result->message]);
        return ActionResult::failure('Payment processing failed');
    }
}

Key patterns:

  • Use Laravel's service container (app()) for dependencies
  • Inject context data via $context->getData()
  • Return ActionResult with success/failure status
  • Log errors for observability

2. Workflow Integration Pattern

Laravel service integration should use the engine as a facade:

// app/Providers/WorkflowServiceProvider.php
public function register()
{
    $this->app->singleton(WorkflowEngine::class, function ($app) {
        return new WorkflowEngine(
            new DatabaseStorageAdapter(),
            new LaravelEventDispatcher($app['events'])
        );
    });
}

Event handling should bridge Laravel events to workflow events:

// app/Listeners/WorkflowEventListener.php
public function handle(WorkflowCompletedEvent $event)
{
    // Dispatch Laravel event
    event(new OrderProcessed($event->workflowId, $event->data));

    // Or trigger a job
    ProcessOrderConfirmation::dispatch($event->workflowId);
}

3. State Management Pattern

Tracking workflow state in Laravel:

// Get workflow status
$instance = $engine->getInstance($instanceId);
$state = $instance->getState();

// Check in a controller
public function checkOrderStatus(Request $request, WorkflowEngine $engine)
{
    $instance = $engine->getInstance($request->instance_id);
    return response()->json([
        'status' => $instance->getState()->value,
        'progress' => $instance->getProgress(),
        'current_step' => $instance->getCurrentStep()?->id,
    ]);
}

UI integration (Blade example):

@php
    $state = $instance->getState();
@endphp

<div class="status-badge {{ $state->color() }}">
    {{ $state->label() }} {{ $state->icon() }}
</div>
<p>{{ $state->description() }}</p>

4. Error Handling Pattern

Global exception handling for workflow failures:

// app/Exceptions/Handler.php
public function register()
{
    $this->renderable(function (StepExecutionException $e, $request) {
        return response()->json([
            'error' => 'Workflow step failed',
            'step' => $e->stepId,
            'message' => $e->getMessage(),
            'instance_id' => $e->workflowId,
        ], 500);
    });
}

Retry logic for transient failures:

$workflow = WorkflowBuilder::create('reliable-process')
    ->addStep('api-call', ApiCallAction::class, retryAttempts: 3, backoff: 'exponential')
    ->build();

5. Testing Pattern

Pest test example for workflow execution:

test('order workflow completes successfully', function () {
    $definition = WorkflowBuilder::create('test-order')
        ->addStep('validate', MockValidationAction::class)
        ->addStep('process', MockProcessingAction::class)
        ->build();

    $instanceId = $this->engine->start(
        'test-order',
        $definition->toArray(),
        ['order_id' => 123]
    );

    $instance = $this->engine->getInstance($instanceId);
    expect($instance->getState())->toBe(WorkflowState::COMPLETED);
    expect($instance->getData('processed'))->toBe(true);
});

Mock actions for isolated testing:

class MockValidationAction extends BaseAction
{
    public function execute(WorkflowContext $context): ActionResult
    {
        return ActionResult::success(['valid' => true]);
    }
}

Gotchas and Tips

1. Storage Adapter Pitfalls

  • Gotcha: Forgetting to implement all StorageAdapter methods will cause runtime errors.
    // Missing method implementation
    class BadStorageAdapter implements StorageAdapter {
        public function save(WorkflowInstance $instance): void { /* ... */ }
        // Missing: load(), delete(), findInstances(), etc.
    }
    
  • Tip: Use the provided InMemoryStorage as a starting point for custom adapters.

2. State Transition Errors

  • Gotcha: Invalid state transitions (e.g., trying to resume a completed workflow) throw InvalidWorkflowStateException.
    $engine->resume($instanceId); // Throws if state is COMPLETED
    
  • Tip: Always check $instance->getState()->canTransitionTo($targetState) before calling transition methods.

3. Condition Evaluation Quirks

  • Gotcha: Condition syntax errors silently return true. Enable strict mode:
    // In WorkflowBuilder
    ->when('order.amount > 1000', function ($builder) {
        // ...
    }, strict: true); // Throws on invalid conditions
    
  • Tip: Use simple conditions first, then gradually add complexity. Test edge cases like:
    // Test these in isolation
    ->when('user.is_active === true', ...)
    ->when('order.items.count > 0', ...)
    

4. Action Configuration

  • Gotcha: Action configuration is passed as an array but must match the action's expected keys.
    // Wrong: Missing 'to' key for EmailAction
    $builder->email('notify', 'subject', 'body');
    
    // Right:
    $builder->email('notify', 'user@example.com',
    
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.
craftcms/url-validator
directorytree/privacy-filter-classifier
directorytree/privacy-filter
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony
develia/geo-bundle
dreamzy/livewire-charts
touchestate-sdk/php-sdk
22h/doctrine-garbage-collection-bundle
agtp/agtp-php
agtp/mod-php
splash/sonata-admin
splash/metadata
splash/openapi
splash/scopes
splash/toolkit
testo/output-teamcity
testo/bridge-symfony