zaber-dev/laravel-workflow

Application-level workflow orchestration for Laravel with state transitions, audit history, rollback support, cache and database drivers, Eloquent integration, and a fluent API.

Maintainers

Package info

github.com/zaber-dev/laravel-workflow

pkg:composer/zaber-dev/laravel-workflow

Transparency log

Statistics

Installs: 8

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.1 2026-07-21 17:16 UTC

This package is auto-updated.

Last update: 2026-07-21 17:19:59 UTC


README

Laravel Workflow Banner

Laravel Workflow

Latest Version on Packagist Run Tests Total Downloads PHP Version Require License

Supports: Laravel 11, 12 & 13+ • PHP 8.2+ • Redis • Memcached • Database

Application-level workflow orchestration for Laravel. Model business processes, manage state transitions, and track workflow history using cache or database storage.

Manage workflows with a clean, expressive API using cache or persistent database storage, attach them directly to Eloquent models, protect routes with declarative middleware, and extend the package with custom storage drivers.

Workflow Definition Defines the workflow graph (for example, OrderWorkflow).

Workflow Instance Tracks a specific entity moving through that graph (for example, Order #1542).

Laravel Workflow manages workflow instances with validated state transitions, audit history, rollback support, and interchangeable storage drivers.

Workflow Lifecycle

Created
   │
   ▼
In Progress
   │
 ┌─┴────────────┐
 ▼             ▼
Approved   Rejected
   │
   ▼
Completed

Quick Example

Workflow::for($order, new OrderWorkflow())
    ->block('payment_processed', function () {
        // Capture payment...
        // Send confirmation email...
    });

Documentation

Features

  • Workflow lifecycle ⭐: Model complete business processes with validated state transitions, ensuring entities only move through permitted workflow steps.
  • Atomic transitions ⭐: Prevent concurrent status mutations and race conditions across multi-threaded webhooks using row-level locking.
  • Audit history ⭐: Every state transition logs exact chronological records including old step, new step, timestamp, transition metadata, and actor.
  • Rollback support ⭐: Safely revert any workflow instance to a previously visited historical step using $workflow->rollbackTo('previous_step').
  • Expressive API: Chain expressive calls like Workflow::for($order, new OrderWorkflow())->using('database')->transition('shipped').
  • Eloquent integration: Attach the HasWorkflows trait to any model for scoped workflow management.
  • Middleware: Protect endpoints automatically using workflow:step_name,WorkflowClass with automatic HTTP 409 enforcement.
  • Storage drivers: Switch seamlessly between high-performance cache stores (Redis, Memcached, Array) and persistent database storage.
  • DTOs: Work safely with strict WorkflowStateInfo and WorkflowHistoryItem Data Transfer Objects.
  • Extensibility: Register custom storage drivers on the fly with closure-based creators.
  • Prunable storage: Built-in Prunable trait integration ensures completed or archived workflow instances never clutter your database.

Why not a status column?

A simple status column works well for straightforward applications.

Laravel Workflow is designed for applications where the process itself is part of the business logic.

  • Order fulfillment
  • Employee onboarding
  • Approval chains
  • Publishing workflows
  • Claims processing
  • KYC verification
Feature Status Column Laravel Workflow
Primary Purpose Simple column enum/status Multi-step process orchestration & audit trails
Fluent Builder API ❌ Manual Expressive & Clean
First-Class Eloquent Integration Native (HasWorkflows)
Storage Architecture ❌ DB Column Only Cache & Database
Transition Validation
Chronological Audit Logs ❌ Manual Table Built-in (history())
Historical Transition Data
Rollback to Previous Steps Built-in (rollbackTo())
Auto-Rollback Route Middleware Built-in (RequireWorkflowStep)
Immutable DTOs Strict (CarbonImmutable)
Automatic Database Pruning N/A Built-in (Prunable)

Installation

Ready to get started? Install the package with Composer:

composer require zaber-dev/laravel-workflow

Publish the configuration and database migrations:

php artisan vendor:publish --provider="ZaberDev\Workflow\WorkflowServiceProvider"

Run migrations if you intend to use the database driver:

php artisan migrate

Configuration

The configuration file lets you define the default storage driver, driver parameters, and event dispatching behaviors:

return [
    /*
    |--------------------------------------------------------------------------
    | Default Workflow Driver
    |--------------------------------------------------------------------------
    |
    | Supported drivers: "cache", "database"
    |
    */
    'default' => env('WORKFLOW_DRIVER', 'cache'),

    'drivers' => [
        'cache' => [
            'driver' => 'cache',
            'store' => env('WORKFLOW_CACHE_STORE', null),
            'prefix' => 'workflows:',
        ],
        'database' => [
            'driver' => 'database',
            'table' => 'workflow_instances',
        ],
    ],

    'events' => [
        'dispatch' => true,
    ],
];

Usage Guide

1. Defining Your Workflow

Implement WorkflowDefinitionContract to define your steps, valid transitions, and starting point:

namespace App\Workflows;

use ZaberDev\Workflow\Contracts\WorkflowDefinitionContract;

class OrderProcessingWorkflow implements WorkflowDefinitionContract
{
    public function name(): string
    {
        return 'order_processing';
    }

    public function initialStep(): string
    {
        return 'created';
    }

    public function steps(): array
    {
        return ['created', 'inventory_reserved', 'payment_processed', 'shipped', 'cancelled'];
    }

    public function transitions(): array
    {
        return [
            'created' => ['inventory_reserved', 'cancelled'],
            'inventory_reserved' => ['payment_processed', 'cancelled'],
            'payment_processed' => ['shipped', 'cancelled'],
            'shipped' => [],
            'cancelled' => [],
        ];
    }
}

2. The Fluent Workflow API

The Workflow facade provides an expressive builder interface for inspecting, transitioning, and rolling back workflow instances.

Checking Status & Transitioning

use ZaberDev\Workflow\Facades\Workflow;

$instance = Workflow::for($order, new OrderProcessingWorkflow());

if ($instance->can('inventory_reserved')) {
    $instance->transition('inventory_reserved', [
        'reserved_at' => now()->toIso8601String(),
        'warehouse' => 'US_WEST_1'
    ]);
}

// Check current status
echo $instance->current(); // "inventory_reserved"

Atomic Block Transition (block)

For operations vulnerable to partial failure across external APIs, use the block() helper. block() transitions to your target step, executes your callback, and automatically rolls back (rollbackTo) if the closure throws an exception:

Workflow::for($order, new OrderProcessingWorkflow())->block('payment_processed', function () use ($order) {
    $this->stripeService->charge($order);
}, payload: ['gateway' => 'stripe']);

Rolling Back & History Inspection

// Inspect chronological step history
foreach ($instance->history() as $item) {
    echo "Step: " . $item->step . " at " . $item->transitionedAt->toDateTimeString();
}

// Rollback to a previous step
$instance->rollbackTo('created', 'Payment failed, returning order to created.');

3. Eloquent Model Integration (HasWorkflows)

Add the HasWorkflows trait to any Eloquent model (such as Order, Document, or User) to manage workflow instances directly off the entity:

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use ZaberDev\Workflow\HasWorkflows;

class Order extends Model
{
    use HasWorkflows;
}

You can now interact directly with your model instance:

$order = Order::find(1);

// Transition this order to "payment_processed"
$order->workflow(new OrderProcessingWorkflow())->transition('payment_processed');

// Check current step
if ($order->workflow(new OrderProcessingWorkflow())->current() === 'shipped') {
    // Order already shipped
}

Polymorphic Database Querying

When using the database driver, HasWorkflows also exposes a workflowInstances() polymorphic relationship, allowing direct querying and bulk management:

// Get all database workflow instances for this order
$instances = $order->workflowInstances()->get();

4. Route Middleware

Protect routes declaratively without writing boilerplate checks in your controllers using the RequireWorkflowStep middleware:

use Illuminate\Support\Facades\Route;

// Enforce that $order is currently in the "payment_processed" step before allowing shipment dispatch
Route::post('/orders/{order}/ship', [ShipmentController::class, 'ship'])
    ->middleware('workflow:payment_processed,App\Workflows\OrderProcessingWorkflow');

How the Middleware Works

  • Verifies the entity is currently in the required workflow step.
  • Allows the request to continue when validation succeeds.
  • Returns HTTP 409 Conflict when the entity is in an invalid state.

5. Working with Storage Drivers (using & driver)

By default, the package uses the storage driver defined in config/workflows.php. You can switch storage drivers on the fly per request or action:

// Store high-speed ephemeral user wizard workflows in cache
Workflow::for($user, new WizardWorkflow())->using('cache')->transition('step_2');

// Store critical audit-trailed order workflows inside SQL database
Workflow::for($order, new OrderProcessingWorkflow())->using('database')->transition('shipped');

Registering Custom Storage Drivers

You can extend the WorkflowManager with your own storage drivers (e.g., DynamoDB, MongoDB) in your AppServiceProvider:

use ZaberDev\Workflow\Contracts\WorkflowDriverContract;
use ZaberDev\Workflow\Facades\Workflow;

public function boot(): void
{
    Workflow::extend('dynamodb', function ($app) {
        return new MyDynamoDbWorkflowDriver($app['config']['workflows.drivers.dynamodb']);
    });
}

6. Database Pruning (Prunable)

When using the database driver, completed or cancelled instances can be marked for pruning via Laravel's Prunable trait on the ZaberDev\Workflow\Models\WorkflowInstanceModel model.

To clean up old records automatically, schedule Laravel's model:prune command in your console.php or Kernel.php:

use Illuminate\Support\Facades\Schedule;
use ZaberDev\Workflow\Models\WorkflowInstanceModel;

Schedule::command('model:prune', ['--model' => WorkflowInstanceModel::class])->daily();

7. Events

Whenever a workflow transitions or rolls back, the package dispatches strongly typed events if enabled (workflows.events.dispatch = true):

  • ZaberDev\Workflow\Events\WorkflowTransitioned: Dispatched when transition() succeeds ($key, $oldStep, $newStep, $payload, $info).
  • ZaberDev\Workflow\Events\WorkflowRolledBack: Dispatched when rollbackTo() succeeds ($key, $targetStep, $reason, $info).

You can listen to these in your EventServiceProvider for notification emails, analytics tracking, or webhook triggers.

Related Packages

This package is part of the ZaberDev Laravel Ecosystem (Laravel Productivity Toolkit) — a cohesive suite of high-level application primitives engineered for concurrency, state management, and resource allocation.

Explore the complete directory of packages, detailed use cases, and documentation in our Ecosystem Index Hub.

Testing & Quality

Run the comprehensive PHPUnit test suite locally:

composer test

Contributing

Thank you for considering contributing! Please ensure any pull requests include thorough PHPUnit tests covering unit, feature, and driver integration scenarios.

License

The MIT License (MIT). Please see LICENSE.md for more information.

Built with ❤️ as part of the ZaberDev Laravel Ecosystem.