PHPackages                             moffhub/flow - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. moffhub/flow

ActiveLibrary[Database &amp; ORM](/categories/database)

moffhub/flow
============

Database-driven state machine and workflow engine for Laravel. Multi-step approval gates, role-based guards, auditable transitions, and configurable actions for government revenue systems.

v0.0.1(3mo ago)0120↓66.7%MITPHPPHP ^8.4|^8.5CI passing

Since Mar 30Pushed 3mo agoCompare

[ Source](https://github.com/Moffhub-Solutions/flow)[ Packagist](https://packagist.org/packages/moffhub/flow)[ Docs](https://github.com/moffhub/flow)[ RSS](/packages/moffhub-flow/feed)WikiDiscussions develop Synced 3w ago

READMEChangelog (1)Dependencies (9)Versions (2)Used By (0)

moffhub/flow
============

[](#moffhubflow)

Database-driven state machine and workflow engine for Laravel. Multi-step approval gates, role-based guards, auditable transitions, configurable actions, parallel states, scheduled transitions, a visual builder API, and workflow visualization — built for government revenue systems but applicable to any multi-step business process.

---

Table of Contents
-----------------

[](#table-of-contents)

- [Features](#features)
- [Requirements](#requirements)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Core Concepts](#core-concepts)
- [Transition Lifecycle](#transition-lifecycle)
- [Model Integration](#model-integration)
- [Fluent API (Flow Facade)](#fluent-api-flow-facade)
- [Guards](#guards)
    - [Role Guards](#role-guards)
    - [Permission Guards](#permission-guards)
    - [Condition Guards](#condition-guards)
    - [Custom Guard Classes](#custom-guard-classes)
- [Approval Gates](#approval-gates)
    - [Multi-Role Approvals](#multi-role-approvals)
    - [Rejection Policies](#rejection-policies)
    - [Approval Expiry &amp; Escalation](#approval-expiry--escalation)
- [Actions](#actions)
    - [Built-in Actions](#built-in-actions)
    - [Custom Action Classes](#custom-action-classes)
    - [Integrating with Other Moffhub Packages](#integrating-with-other-moffhub-packages)
- [Immutable Audit Trail](#immutable-audit-trail)
- [Attribute Change Tracking](#attribute-change-tracking)
- [Parallel States (Split/Join)](#parallel-states-splitjoin)
- [Scheduled Transitions](#scheduled-transitions)
- [Query Scopes](#query-scopes)
- [Workflow Subscribers](#workflow-subscribers)
- [Events](#events)
- [Force Transition (Admin Override)](#force-transition-admin-override)
- [Workflow Visualization](#workflow-visualization)
- [Visual Builder API (Block Editor)](#visual-builder-api-block-editor)
    - [Canvas Save](#canvas-save)
    - [Bulk Layout Update](#bulk-layout-update)
    - [Export &amp; Import](#export--import)
- [REST API Reference](#rest-api-reference)
- [Artisan Commands](#artisan-commands)
- [Database Schema](#database-schema)
- [Configuration Reference](#configuration-reference)
- [JSON Seed File Format](#json-seed-file-format)
- [Real-World Example: Business Permit](#real-world-example-business-permit)
- [Testing](#testing)
- [License](#license)

---

Features
--------

[](#features)

- **Database-driven workflow definitions** — non-developers configure workflows via API, visual builder, or JSON seed files. No code changes required.
- **Multi-step approval gates** — N-of-M role-based approvals with configurable rejection policies (`any` or `majority`), expiry timers, and role escalation.
- **Guard system** — role, permission, JSON condition, and custom guard classes run before every transition. If any guard fails, the transition is blocked.
- **Action system** — built-in actions (billing, SMS, email, documents, notifications) and custom action classes run after successful transitions.
- **Immutable audit trail** — every transition logged with performer, comment, approval records, and model attribute diffs.
- **Attribute change tracking** — automatically captures which model fields changed during each transition with old/new values.
- **Parallel states (split/join)** — models can be in multiple states simultaneously for concurrent review branches.
- **Scheduled transitions** — auto-transition at a future time with a queue job that processes due transitions.
- **Query scopes** — Eloquent scopes like `whereWorkflowState('approved')`, `whereWorkflowOverdue()`, `whereWorkflowAssignedTo($userId)`.
- **Workflow subscriber pattern** — convention-based `onEnterApproved()`, `onLeaveDraft()`, `onTransitionSubmit()` event handling.
- **Workflow visualization** — generate Mermaid or Graphviz DOT diagrams from definitions via Artisan command.
- **Visual builder API** — full block editor backend with canvas save, bulk layout updates, export/import for drag-and-drop workflow builders.
- **Full REST API** — CRUD endpoints for definitions, states, transitions, instances, history, approvals.
- **Fluent facade** — `Flow::for($model)->currentState()`, `->transitionTo()`, `->history()`.
- **Artisan commands** — `flow:seed` (import JSON), `flow:status` (inspect workflows), `flow:visualize` (generate diagrams).
- **Soft deletes** — definitions, states, and transitions are soft-deleted to preserve audit trail integrity.

---

Requirements
------------

[](#requirements)

- PHP 8.4+
- Laravel 12.x or 13.x

---

Installation
------------

[](#installation)

```
composer require moffhub/flow
```

Publish and run migrations:

```
php artisan vendor:publish --tag=flow-migrations
php artisan migrate
```

Optionally publish the config:

```
php artisan vendor:publish --tag=flow-config
```

---

Quick Start
-----------

[](#quick-start)

### 1. Apply the trait to your model

[](#1-apply-the-trait-to-your-model)

```
use Moffhub\Flow\Contracts\StatefulModel;
use Moffhub\Flow\Traits\HasWorkflow;

class BusinessPermit extends Model implements StatefulModel
{
    use HasWorkflow;

    // Optional: implement methods for built-in actions
    public function createBill(): void { /* ... */ }
    public function generateDocument(string $state): void { /* ... */ }
    public function sendWorkflowSms(WorkflowTransition $transition): void { /* ... */ }
    public function sendWorkflowEmail(WorkflowTransition $transition): void { /* ... */ }
}
```

When a `BusinessPermit` is created, the `HasWorkflow` trait automatically looks up the active workflow definition for its model type and initializes a workflow instance in the `initial_state`.

### 2. Create a workflow definition

[](#2-create-a-workflow-definition)

**Option A: Via API**

```
POST /api/workflows/definitions
{
    "name": "Business Permit Workflow",
    "code": "business_permit",
    "model_type": "App\\Models\\BusinessPermit",
    "initial_state": "draft"
}
```

**Option B: Via JSON seed file**

```
php artisan flow:seed workflow.json
```

**Option C: Via visual builder** (see [Visual Builder API](#visual-builder-api-block-editor))

### 3. Add states and transitions

[](#3-add-states-and-transitions)

```
POST /api/workflows/definitions/{id}/states
{"name": "draft", "label": "Draft", "type": "initial", "color": "#94a3b8"}

POST /api/workflows/definitions/{id}/states
{"name": "submitted", "label": "Submitted", "type": "intermediate", "color": "#3b82f6"}

POST /api/workflows/definitions/{id}/states
{"name": "approved", "label": "Approved", "type": "final", "color": "#22c55e"}

POST /api/workflows/definitions/{id}/transitions
{
    "name": "submit",
    "label": "Submit Application",
    "from_state": "draft",
    "to_state": "submitted"
}
```

### 4. Use it in code

[](#4-use-it-in-code)

```
$permit = BusinessPermit::create(['name' => 'Cafe Permit', ...]);

$permit->workflowState();           // 'draft'
$permit->availableTransitions();    // Collection of WorkflowTransition models
$permit->canTransition('submit');   // true
$permit->canTransition('approve');  // false (wrong state)
$permit->transition('submit');      // executes → state becomes 'submitted'
$permit->workflowHistory();         // Collection of WorkflowHistory records
$permit->isWorkflowComplete();      // false (not in terminal state)
$permit->isWorkflowOverdue();       // false
```

---

Core Concepts
-------------

[](#core-concepts)

```
WorkflowDefinition  ← blueprint (states + transitions), stored in DB
    ├── WorkflowState       ← named node: initial | intermediate | final | failed
    └── WorkflowTransition  ← directed edge with guards, actions, approval config

WorkflowInstance    ← runtime: tracks one model's progression through a definition
WorkflowHistory     ← immutable audit log of every transition that occurred
WorkflowApproval    ← multi-role approval gate tracking (pending/approved/rejected)
ScheduledTransition ← future auto-transitions processed by a queue job

```

**State types:**

TypeMeaning`initial`Starting state. The `[*]` node in diagrams.`intermediate`Normal processing state.`final`Successfully completed. Terminal — no further transitions allowed.`failed`Ended in failure. Terminal — no further transitions allowed.**Golden rules:**

1. Every state change is a transition — no direct state mutation.
2. Every transition is logged — immutable audit trail.
3. Guards run before — role, permission, condition checks block invalid transitions.
4. Actions run after — side-effects only happen on successful state changes.
5. Approvals are gates — N-of-M roles must approve before the transition executes.

---

Transition Lifecycle
--------------------

[](#transition-lifecycle)

Every call to `$model->transition('approve', comment: 'Looks good')` follows this lifecycle:

```
┌─ BEFORE (Guards) ────────────────────────────┐
│  1. Validate from_state matches current      │
│  2. Check requires_comment                   │
│  3. Run role guards                          │
│  4. Run permission guards                    │
│  5. Run condition guards (JSON operators)    │
│  6. Run custom TransitionGuard classes       │
│  └── Any failure → TransitionDeniedException │
└──────────────────────────────────────────────┘
                    │
                    ▼
┌─ DURING (Approval Gate, if enabled) ─────────┐
│  1. Record user's approval vote              │
│  2. Count approved vs required               │
│  3. If not enough → ApprovalRequiredException│
│     (event fired, return early)              │
│  4. If enough → proceed to execute           │
└──────────────────────────────────────────────┘
                    │
                    ▼
┌─ EXECUTE (DB Transaction) ───────────────────┐
│  1. Capture model attribute changes (diff)   │
│  2. Update instance: current_state,          │
│     previous_state, state_entered_at         │
│  3. Create WorkflowHistory record            │
│  4. Clear approval records for transition    │
└──────────────────────────────────────────────┘
                    │
                    ▼
┌─ AFTER (Actions + Events) ───────────────────┐
│  1. Run built-in actions (bill, sms, etc.)   │
│  2. Run custom TransitionAction classes      │
│  3. Fire WorkflowTransitioned event          │
│  4. Notify WorkflowSubscribers               │
│  5. If terminal → fire WorkflowCompleted     │
└──────────────────────────────────────────────┘

```

---

Model Integration
-----------------

[](#model-integration)

### The HasWorkflow Trait

[](#the-hasworkflow-trait)

Apply `HasWorkflow` to any Eloquent model that participates in a workflow:

```
use Moffhub\Flow\Contracts\StatefulModel;
use Moffhub\Flow\Traits\HasWorkflow;

class BusinessPermit extends Model implements StatefulModel
{
    use HasWorkflow;
}
```

**What the trait provides:**

MethodReturnsDescription`workflowInstance()``MorphOne`The workflow instance relation`workflowState()``?string`Current state name (`'draft'`, `'approved'`, etc.)`availableTransitions()``Collection`Transitions available from current state`canTransition('name')``bool`Whether a transition is structurally valid (ignores guards)`transition('name', 'comment', $ctx)``WorkflowInstance`Execute a transition (runs full lifecycle)`workflowHistory()``Collection`Immutable audit trail`isWorkflowComplete()``bool`True if current state is `final` or `failed``isWorkflowOverdue()``bool`True if deadline has passed`assignWorkflowTo($userId)``void`Assign a handler`setWorkflowDeadline($date)``void`Set a deadline`scheduleTransition(...)``ScheduledTransition`Schedule a future auto-transition**Query scopes provided by the trait** (see [Query Scopes](#query-scopes)):

ScopeDescription`whereWorkflowState('approved')`Models in a specific state`whereWorkflowNotState('draft')`Models NOT in a state`whereWorkflowStateIn(['a', 'b'])`Models in any of the given states`whereWorkflowComplete()`Models in terminal states`whereWorkflowOverdue()`Models past their deadline`whereWorkflowAssignedTo($id)`Models assigned to a user### Auto-Initialization

[](#auto-initialization)

When a model using `HasWorkflow` is created, the trait's `bootHasWorkflow()` method automatically:

1. Looks up the active `WorkflowDefinition` matching the model's morph class
2. Creates a `WorkflowInstance` in the definition's `initial_state`

If no matching definition exists, the model is created without a workflow (no error).

---

Fluent API (Flow Facade)
------------------------

[](#fluent-api-flow-facade)

For a more expressive API, use the `Flow` facade:

```
use Moffhub\Flow\Facades\Flow;

// Get a fluent accessor for a model
$flow = Flow::for($permit);

$flow->currentState();                              // 'under_review'
$flow->availableTransitions();                      // Collection
$flow->canTransition('approve');                    // true
$flow->transitionTo('approve', comment: 'LGTM');   // execute transition
$flow->history();                                   // Collection
$flow->isComplete();                                // false
$flow->isOverdue();                                 // false
$flow->instance();                                  // WorkflowInstance model

// Direct engine methods via facade
Flow::initialize($model);
Flow::transition($model, 'submit', 'Submitting');
Flow::forceTransition($model, 'rejected', $adminId, 'Override');
```

### Helper Function

[](#helper-function)

```
// Global helper — returns the WorkflowEngine instance
$engine = flow();
$engine->transition($model, 'approve', 'Looks good');
```

---

Guards
------

[](#guards)

Guards run **before** a transition is allowed. If any guard fails, the transition is blocked with a `TransitionDeniedException` (HTTP 403).

### Role Guards

[](#role-guards)

Configured per transition. The authenticated user must have at least one of the listed roles:

```
{
    "allowed_roles": ["revenue_officer", "subcounty_officer", "admin"]
}
```

The engine checks roles by calling `$user->hasAnyRole($roles)` (Spatie-compatible) or falling back to `$user->role` attribute matching.

If `allowed_roles` is empty or null, no role check is performed (any user can trigger the transition).

### Permission Guards

[](#permission-guards)

The authenticated user must have **all** listed permissions:

```
{
    "required_permissions": ["permits.approve", "permits.view"]
}
```

Checks via `$user->hasAllPermissions($permissions)` (Spatie-compatible) or `$user->can($permission)` fallback.

### Condition Guards

[](#condition-guards)

JSON-based conditions evaluated against the model's attributes. All conditions must pass (AND logic):

```
{
    "conditions": [
        {"field": "amount_paid", "operator": ">=", "value": 1000},
        {"field": "documents_verified", "operator": "==", "value": true},
        {"field": "inspector_id", "operator": "not_null"},
        {"field": "type", "operator": "in", "value": ["A", "B", "C"]}
    ]
}
```

**Supported operators:**

OperatorExampleDescription`==``amount == 0`Loose equality`===``status === 'pending'`Strict equality`!=``status != 'rejected'`Not equal`>` `>=` `
