PHPackages                             benrowe/stateflow - 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. [Utility &amp; Helpers](/categories/utility)
4. /
5. benrowe/stateflow

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

benrowe/stateflow
=================

A flexible state machine library for PHP

v0.0.0-beta.2(4mo ago)04[4 issues](https://github.com/benrowe/stateflow/issues)MITPHPPHP ^8.2CI passing

Since Nov 26Pushed 4mo agoCompare

[ Source](https://github.com/benrowe/stateflow)[ Packagist](https://packagist.org/packages/benrowe/stateflow)[ RSS](/packages/benrowe-stateflow/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (3)Dependencies (7)Versions (4)Used By (0)

StateFlow
=========

[](#stateflow)

**A powerful state workflow engine for PHP that handles complex state transitions with built-in observability and race condition prevention.**

[![PHP Version](https://camo.githubusercontent.com/d840cef9807c8f76051ad687841d67f4d830c84e0d83236968e53124ef6742d5/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d253345253344382e322d3838393242462e737667)](https://php.net/)[![Build Status](https://github.com/benrowe/stateflow/actions/workflows/ci.yml/badge.svg)](https://github.com/benrowe/stateflow/actions)[![Total Downloads](https://camo.githubusercontent.com/13a158dc321e273bceb945be95bc98deabfc0366c28be5e05ed70e51ef5023b4/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f62656e726f77652f7374617465666c6f77)](https://packagist.org/packages/benrowe/stateflow)[![Latest Stable Version](https://camo.githubusercontent.com/959dd58109619e9e529dcc1c4715e51475a1b556682cc86231f1a309ba659620/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f62656e726f77652f7374617465666c6f77)](https://packagist.org/packages/benrowe/stateflow)[![License](https://camo.githubusercontent.com/7013272bd27ece47364536a221edb554cd69683b68a46fc0ee96881174c4214c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e737667)](LICENSE)

---

Why StateFlow?
--------------

[](#why-stateflow)

Most state machines force you into rigid patterns. StateFlow is different:

- 🎯 **Delta-Based Transitions** - Specify only what changes, not the entire state
- ⚙️ **Granular Execution Control** - Manage workflow execution at the per-action level
- 🔒 **Race-Safe by Design** - Built-in mutex locking prevents concurrent modification
- 👀 **Fully Observable** - Events fired at every step for monitoring and debugging
- 🎨 **Flexible Validation** - Two-tier gates (transition-level + action-level)
- 📦 **Serializable Context** - Pause, store, and resume workflows hours or days later
- 🔧 **User-Controlled** - You define state structure, merge strategy, and lock behavior

Perfect For
-----------

[](#perfect-for)

- E-commerce order processing with payment/inventory/shipping workflows
- Content publishing pipelines with approval stages and notifications
- Long-running batch jobs that need checkpointing
- Multi-step user onboarding flows
- Any scenario where state transitions need audit trails and concurrency control

Quick Example
-------------

[](#quick-example)

```
use BenRowe\StateFlow\StateFlow;
use BenRowe\StateFlow\Configuration;

// Define your state
class Order implements State {
    public function __construct(
        private string $status,
        private ?string $paymentId = null,
    ) {}

    public function with(array $changes): State {
        return new self(
            status: $changes['status'] ?? $this->status,
            paymentId: $changes['paymentId'] ?? $this->paymentId,
        );
    }

    public function toArray(): array {
        return ['status' => $this->status, 'paymentId' => $this->paymentId];
    }
}

// Configure the workflow
$stateFlow = new StateFlow(
    configProvider: fn($state, $delta) => new Configuration(
        transitionGates: [new CanProcessGate()],  // Must pass to proceed
        actions: [
            new ChargePaymentAction(),   // Execute in order
            new ReserveInventoryAction(), // Skip if guard fails
            new SendConfirmationAction(),
        ],
    ),
    eventDispatcher: new Logger(),      // See everything that happens
    lockProvider: new RedisLock($redis), // Prevent race conditions
);

// Execute transition with automatic locking
$order = new Order('pending');
$worker = $stateFlow->transition($order, new ArrayDelta(['status' => 'processing']));
$context = $worker->execute();

if ($context->isCompleted()) {
    echo "Order processed!";
} elseif ($context->isPaused()) {
    // Action paused (e.g., waiting for external API)
    // Lock is HELD across pause
    saveToDatabase($context->serialize());

    // Resume hours later...
    $resumedWorker = $stateFlow->fromContext($context);
    $resumedWorker->execute();
}
```

Key Features
------------

[](#key-features)

### 🎯 Delta-Based Transitions

[](#-delta-based-transitions)

Specify only what changes:

```
// Just this
$worker = $stateFlow->transition($state, new ArrayDelta(['status' => 'published']));
$context = $worker->execute();

// Not this
$worker = $stateFlow->transition($state, new ArrayDelta(['status' => 'published', 'author' => 'same', 'created' => 'same', ...]));
$context = $worker->execute();
```

### ⚙️ Granular Execution Control

[](#️-granular-execution-control)

The `StateWorker` gives you full control over the workflow execution:

```
$worker = $stateFlow->transition($state, new ArrayDelta(['status' => 'published']));

// 1. Run gates first
$gateResult = $worker->runGates();

// 2. Then run actions if gates pass
if (!$gateResult->shouldStopTransition()) {
    $context = $worker->runActions();
}

// Or let actions pause themselves for async operations
class ProcessVideoAction implements Action {
    public function execute(ActionContext $context): ActionResult {
        $job = dispatch(new VideoProcessingJob());

        // Pause execution, lock is held
        return ActionResult::pause(metadata: ['jobId' => $job->id]);
    }
}

// Resume later when ready
$resumedWorker = $stateFlow->fromContext($pausedContext);
$resumedWorker->execute();
```

### 🔒 Race Condition Prevention

[](#-race-condition-prevention)

Built-in mutex locking, configured on the `StateFlow`:

```
$lockProvider = new RedisLockProvider($redis, $config);
$stateFlow = new StateFlow(
    configProvider: $configProvider,
    lockProvider: $lockProvider,
);

// This transition will be automatically locked
$worker = $stateFlow->transition($state, new ArrayDelta(['status' => 'published']));
$context = $worker->execute();
```

If another process tries to transition the same entity, it will wait, fail, or skip based on your lock provider's behavior.

### 👀 Fully Observable

[](#-fully-observable)

Every step emits events:

```
class MyEventDispatcher implements EventDispatcher {
    public function dispatch(Event $event): void {
        match (true) {
            $event instanceof TransitionStarting => $this->log('Starting...'),
            $event instanceof GateEvaluated => $this->log('Gate: ' . $event->result),
            $event instanceof ActionExecuted => $this->log('Action done'),
            $event instanceof TransitionCompleted => $this->log('Complete!'),
        };
    }
}
```

### 🎨 Two-Tier Validation

[](#-two-tier-validation)

**Transition Gates** - Must pass for transition to begin:

```
class CanPublishGate implements Gate {
    public function evaluate(GateContext $context): GateResult {
        return $context->currentState->hasContent()
            ? GateResult::ALLOW
            : GateResult::DENY;
    }
}
```

**Action Gates** - Skip individual actions if guard fails:

```
class NotifyAction implements Action, Guardable {
    public function gate(): Gate {
        return new HasSubscribersGate();
    }

    public function execute(ActionContext $context): ActionResult {
        // Only runs if HasSubscribersGate passes
    }
}
```

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

[](#installation)

```
composer require benrowe/stateflow
```

**Requirements:** PHP 8.2+

Documentation
-------------

[](#documentation)

📚 Comprehensive documentation available in the [`docs/`](./docs) directory:

DocumentDescription[Architecture Overview](./docs/architecture.md)Design goals and principles[Flow Diagrams](./docs/diagrams.md)Visual flowcharts (Mermaid)[Core Concepts](./docs/core-concepts.md)State, Gates, Actions, Configuration[Observability](./docs/observability.md)Event system and monitoring[Locking System](./docs/locking.md)Race condition handling[Interface Reference](./docs/interfaces.md)Complete API documentation[Usage Examples](./docs/examples.md)Real-world patternsReal-World Example
------------------

[](#real-world-example)

### E-Commerce Order Processing

[](#e-commerce-order-processing)

```
// 1. Define state with your domain model
class OrderState implements State {
    public function __construct(
        private string $id,
        private string $status,
        private float $total,
        private ?string $paymentId = null,
    ) {}

    public function with(array $changes): State {
        return new self(
            id: $this->id,
            status: $changes['status'] ?? $this->status,
            total: $changes['total'] ?? $this->total,
            paymentId: $changes['paymentId'] ?? $this->paymentId,
        );
    }

    public function toArray(): array { /* ... */ }
}

// 2. Configure workflow based on transition type
$configProvider = function(State $state, Delta $delta): Configuration {
    return match ($delta->get('status')) {
        'processing' => new Configuration(
            transitionGates: [new HasInventoryGate($inventory)],
            actions: [
                new ChargePaymentAction($paymentGateway),
                new ReserveInventoryAction($inventory),
                new SendEmailAction($mailer),
            ],
        ),
        'shipped' => new Configuration(
            transitionGates: [new HasPaymentGate()],
            actions: [new CreateShipmentAction($shipping)],
        ),
        default => new Configuration(),
    };
};

// 3. Create state flow with observability and locking
$stateFlow = new StateFlow(
    configProvider: $configProvider,
    eventDispatcher: new MetricsDispatcher(),
    lockProvider: new RedisLockProvider($redis),
    lockKeyProvider: new class implements LockKeyProvider {
        public function getLockKey(State $state, Delta $delta): string {
            return "order:" . $state->toArray()['id'];
        }
    },
);

// 4. Execute with race protection
try {
    $order = new OrderState('ORD-123', 'pending', 99.99);
    $worker = $stateFlow->transition($order, new ArrayDelta(['status' => 'processing']));
    $context = $worker->execute();

    if ($context->isCompleted()) {
        return response()->json(['status' => 'success']);
    }

} catch (LockAcquisitionException $e) {
    // Another request is processing this order
    return response()->json(['error' => 'Order is being processed'], 409);
}
```

What Makes StateFlow Different?
-------------------------------

[](#what-makes-stateflow-different)

FeatureStateFlowTraditional State Machines**Granular Control**✅ Per-action execution &amp; pause/resume❌ Must complete in one execution**Race-Safe**✅ Built-in mutex locking❌ Manual coordination required**Observable**✅ Events at every step❌ Limited visibility**Flexible State**✅ User-defined merge strategy❌ Rigid state structure**Lazy Config**✅ Load gates/actions on-demand❌ All configured upfront**Lock Persistence**✅ Lock held across pauses❌ N/A**Execution Trace**✅ Complete audit trail❌ Limited historyStatus
------

[](#status)

🚧 **Alpha Stage**

The architecture is designed and documented. The project is under active development.

Contributing
------------

[](#contributing)

Contributions welcome! See [Contributing Guide](./docs/contributing.md) for development setup and guidelines.

License
-------

[](#license)

The MIT License (MIT). See [LICENSE](LICENSE) for details.

Credits
-------

[](#credits)

- [Ben Rowe](https://github.com/benrowe)
- [All Contributors](../../contributors)

---

Built with ❤️ for developers who need powerful, observable, race-safe workflows.

###  Health Score

32

—

LowBetter than 72% of packages

Maintenance76

Regular maintenance activity

Popularity3

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity35

Early-stage or recently created project

 Bus Factor1

Top contributor holds 99.6% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~20 days

Total

3

Last Release

133d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/cf5434720df0ea6e192a1e8ea8e7c0e243d54995f650ff6c78ec5afcd5df763b?d=identicon)[benrowe](/maintainers/benrowe)

---

Top Contributors

[![benrowe](https://avatars.githubusercontent.com/u/1509442?v=4)](https://github.com/benrowe "benrowe (278 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (1 commits)")

---

Tags

stateworkflowtransitionstate-machine

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/benrowe-stateflow/health.svg)

```
[![Health](https://phpackages.com/badges/benrowe-stateflow/health.svg)](https://phpackages.com/packages/benrowe-stateflow)
```

###  Alternatives

[symfony/workflow

Provides tools for managing a workflow or finite state machine

62842.3M170](/packages/symfony-workflow)[yohang/finite

A simple PHP Finite State Machine

1.3k3.5M10](/packages/yohang-finite)[gomachan46/state-machine

simple state machine with annotations for PHP, inspired by AASM known as a Ruby state machine.

1893.9k](/packages/gomachan46-state-machine)[ringierimu/state-workflow

Laravel State Workflow provide tools for defining and managing workflows and activities with ease.

3251.1k](/packages/ringierimu-state-workflow)[shrink0r/workflux

Finite state machine for php.

375.6k1](/packages/shrink0r-workflux)[tarfin-labs/event-machine

Event-driven state machines for Laravel with event sourcing, type-safe context, and full audit trail.

188.5k](/packages/tarfin-labs-event-machine)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
