PHPackages                             php-architecture-kit/state-machine - 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. php-architecture-kit/state-machine

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

php-architecture-kit/state-machine
==================================

Graph-based state machine engine for PHP — supports parallel pointers, conditional transitions, typed state, and domain events.

1.6.0(3w ago)017MITPHPPHP ^8.4

Since May 7Pushed 6d agoCompare

[ Source](https://github.com/php-architecture-kit/state-machine)[ Packagist](https://packagist.org/packages/php-architecture-kit/state-machine)[ Docs](https://github.com/php-architecture-kit/state-machine)[ RSS](/packages/php-architecture-kit-state-machine/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (9)Versions (18)Used By (0)

php-architecture-kit/state-machine
==================================

[](#php-architecture-kitstate-machine)

Graph-based state machine engine for PHP. Supports parallel pointers, conditional transitions, typed state, pluggable transition strategies, and task dispatch — all built on top of `php-architecture-kit/graph`.

Features
--------

[](#features)

- **Graph-backed** — transitions are directed edges, nodes are vertices
- **Parallel pointers** — multiple execution cursors moving through the graph simultaneously
- **Conditional transitions** — guard each edge with a `TransitionCondition`
- **Typed state** — key-value `State` objects attached to each `Execution`
- **Domain events** — pointer and state lifecycle emits `DomainEvent` instances
- **Pluggable strategies** — swap transition selection and pointer scheduling strategies
- **Built-in components** — reusable higher-level building blocks for common patterns
- **PHP 8.4+** — uses asymmetric visibility and readonly classes

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

[](#installation)

```
composer require php-architecture-kit/state-machine
```

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

[](#quick-start)

```
use PhpArchitecture\StateMachine\Foundation\StateMachine;
use PhpArchitecture\StateMachine\Foundation\Execution\Execution;
use PhpArchitecture\StateMachine\Foundation\Node\Node;
use PhpArchitecture\StateMachine\Foundation\Node\Identity\NodeId;
use PhpArchitecture\StateMachine\Foundation\Node\Handler\NodeHandlerInterface;
use PhpArchitecture\StateMachine\Foundation\Node\Handler\NodeHandlerContext;
use PhpArchitecture\StateMachine\Foundation\Node\Handler\NodeHandlerResult;

// 1. Define a node
final class SendEmailNode extends Node
{
    public function handlerClass(): string
    {
        return SendEmailHandler::class;
    }
}

// 2. Implement the handler
final class SendEmailHandler implements NodeHandlerInterface
{
    public function handle(NodeHandlerContext $context): NodeHandlerResult
    {
        // do work...
        return NodeHandlerResult::Continue;
    }
}

// 3. Define the workflow
final class OrderWorkflow extends StateMachine
{
    public function __construct(ContainerInterface $container)
    {
        parent::__construct($container);

        $send   = new SendEmailNode();
        $notify = new NotifyAdminNode();

        $this->addNode($send)
             ->addNode($notify)
             ->addTransition($send->id, $notify->id);
    }
}

// 4. Run
$machine   = new OrderWorkflow($container);
$execution = Execution::create();
$execution->pointers->startAt($sendNodeId);

$status = $machine->execute($execution);
// ExecutionStatus::Completed | ::Running | ::Suspended
```

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

[](#core-concepts)

### StateMachine

[](#statemachine)

Extend `StateMachine` and register nodes and transitions in the constructor (or any method you choose). Call `execute(Execution $execution)` to advance all active pointers.

```
$status = $machine->execute($execution);
```

Returns:

- `ExecutionStatus::Completed` — all pointers finished
- `ExecutionStatus::Running` — progress was made but pointers remain
- `ExecutionStatus::Suspended` — no progress, all pointers are blocked

### Node

[](#node)

Extend `abstract class Node` for each step in the workflow. Override `handlerClass()` to return the FQCN of the `NodeHandlerInterface` implementation resolved by the PSR-11 container.

Override `transitionStrategy()` to control how outgoing transitions are selected for that node (defaults to `AllValidTransitionsStrategy`).

### NodeHandlerInterface

[](#nodehandlerinterface)

```
interface NodeHandlerInterface
{
    public function handle(NodeHandlerContext $context): NodeHandlerResult;
}
```

`NodeHandlerContext` provides:

- `ExecutionId $executionId`
- `NodeInterface $node`
- `Pointer $pointer`
- `States $states`
- `dispatchTask(Task $task, array $stamps = []): void`

Return `NodeHandlerResult::Continue` to let the pointer move on, or `NodeHandlerResult::Suspended` to pause it in place without advancing.

### Transition

[](#transition)

Directed edge between two `NodeId`s. Optionally attach a guard condition:

```
$this->addTransition($from->id, $to->id);

// with a condition closure
$this->addTransition($from->id, $to->id, function (States $states): TransitionConditionDecision {
    return $states->getState('order') !== null
        ? TransitionConditionDecision::Accepted
        : TransitionConditionDecision::Wait;
});
```

### Execution

[](#execution)

Holds all `Pointers` and `States` for one running instance.

```
$execution = Execution::create();
$execution->pointers->startAt($entryNodeId);
```

### Pointer

[](#pointer)

Tracks a single cursor position (`nodeId`). Pointers can be forked for parallel branches. Retrieve events (fork, remove, etc.) via `$execution->pointers->getEvents()`.

### State

[](#state)

Named key-value bags attached to an execution.

```
$execution->states->defineState('order', [
    new StateDetail('status', 'pending'),
    new StateDetail('amount', 1500),
]);

// read inside a handler or transition condition
$order = $context->states->getState('order');
$status = $order?->details['status']?->value;
```

### StateMachineConfig

[](#statemachineconfig)

Controls graph constraints and pluggable strategies:

```
new StateMachineConfig(
    allowCycles: true,
    allowSelfLoops: true,
    allowParallelTransitions: true,
    transitionStrategies: [...],
    pointersSelectionStrategy: new AllPointersUntilBlockedStrategy(),
)
```

Built-in Components
-------------------

[](#built-in-components)

Components are reusable, pre-wired sub-graphs. Add them with `$machine->addDefinition($component)` and connect their ports with `addTransition()`.

### AwaitStateComponent

[](#awaitstatecomponent)

Suspends the pointer until a named state appears in `States`, with an optional timeout.

```
$await = AwaitStateComponent::create('payment_result', detailName: 'status', timeout: 60);

$machine->addDefinition($await);
$machine->addTransition($prevNode->id,       $await->input->trigger);
$machine->addTransition($await->output->done,    $nextNode->id);
$machine->addTransition($await->output->expired, $timeoutNode->id);
```

### SwitchCaseComponent

[](#switchcasecomponent)

Routes to the first output whose predicate returns `true` (XOR gateway).

```
$switch = SwitchCaseComponent::create([
    'approved' => fn(States $s): bool => $s->getState('decision')?->details['value']?->value === 'approved',
    'rejected' => fn(States $s): bool => $s->getState('decision')?->details['value']?->value === 'rejected',
]);

$machine->addDefinition($switch);
$machine->addTransition($prevNode->id,           $switch->input->trigger);
$machine->addTransition($switch->output->approved, $approvedNode->id);
$machine->addTransition($switch->output->rejected, $rejectedNode->id);
```

### ParallelComponent

[](#parallelcomponent)

Spawns one pointer per branch (AND-split). Branches can be unconditional or conditionally filtered.

```
// all branches always fire
$parallel = ParallelComponent::create(['email', 'sms', 'audit']);

// some branches are conditional
$parallel = ParallelComponent::create(
    branches:   ['email', 'sms', 'audit'],
    conditions: [
        'sms'   => fn(States $s): bool => $s->getState('user')?->details['hasSms']?->value === true,
        'audit' => fn(States $s): bool => $s->getState('order')?->details['highValue']?->value === true,
    ],
);

$machine->addDefinition($parallel);
$machine->addTransition($prevNode->id,           $parallel->input->trigger);
$machine->addTransition($parallel->output->email, $emailNode->id);
$machine->addTransition($parallel->output->sms,   $smsNode->id);
$machine->addTransition($parallel->output->audit, $auditNode->id);
```

### AwaitAllComponent

[](#awaitallcomponent)

AND-join: collects all declared branch pointers before continuing.

```
$join = AwaitAllComponent::create(['email', 'sms', 'audit']);

$machine->addDefinition($join);
$machine->addTransition($emailNode->id, $join->input->email);
$machine->addTransition($smsNode->id,   $join->input->sms);
$machine->addTransition($auditNode->id, $join->input->audit);
$machine->addTransition($join->output->done, $nextNode->id);
```

### AsyncComponent

[](#asynccomponent)

Dispatches a `Task` exactly once, then suspends until a named state confirms completion. An `AwaitStateStamp` is automatically added to the task envelope so the handler knows which state key to write back.

```
$async = AsyncComponent::create(
    stateName:   'payment_result',
    taskFactory: fn(States $s): Task => new ProcessPaymentTask(
        $s->getState('order')?->details['id']?->value,
    ),
    timeout: 120,
);

$machine->addDefinition($async);
$machine->addTransition($prevNode->id,      $async->input->trigger);
$machine->addTransition($async->output->done,    $nextNode->id);
$machine->addTransition($async->output->expired, $timeoutNode->id);
```

The external task handler reads `AwaitStateStamp::$stateName` from the envelope and calls:

```
$execution->states->defineState('payment_result', [
    new StateDetail('status', 'completed'),
]);
$machine->execute($execution); // pointer resumes
```

Built-in Transition Strategies
------------------------------

[](#built-in-transition-strategies)

StrategyBehaviour`AllValidTransitionsStrategy`Evaluates all outgoing transitions; passes accepted ones forward`ForkTransitionStrategy`Forks a new pointer for each accepted transition (used internally)`WaitStrategy`Suspends the pointer when all transitions return `Wait``WaitAndForkStrategy`Waits, then forks when multiple transitions become valid`RejectStrategy`Throws when no strategy matched (safety net)Built-in Pointer Selection Strategies
-------------------------------------

[](#built-in-pointer-selection-strategies)

StrategyBehaviour`AllPointersUntilBlockedStrategy`Advances every pointer until each one blocks (default)`AllPointersStepStrategy`Advances every pointer exactly one step per `execute()` callLicense
-------

[](#license)

MIT

###  Health Score

45

—

FairBetter than 91% of packages

Maintenance97

Actively maintained with recent releases

Popularity8

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity59

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% 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 ~1 days

Total

14

Last Release

24d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/145db325ca53baabce2d955faebe43d56bc4f3122b9ae463d41ee0e5da487cea?d=identicon)[patrykbaszak](/maintainers/patrykbaszak)

---

Top Contributors

[![patrykbaszak](https://avatars.githubusercontent.com/u/66377724?v=4)](https://github.com/patrykbaszak "patrykbaszak (34 commits)")

---

Tags

workflowdddgraphframework agnosticstate-machine

### Embed Badge

![Health badge](/badges/php-architecture-kit-state-machine/health.svg)

```
[![Health](https://phpackages.com/badges/php-architecture-kit-state-machine/health.svg)](https://phpackages.com/packages/php-architecture-kit-state-machine)
```

###  Alternatives

[ecotone/ecotone

Enterprise architecture layer for Laravel and Symfony — CQRS, Event Sourcing, Durable Workflows (Sagas, Orchestrators), Projections, and Outbox messaging via PHP attributes.

562565.8k41](/packages/ecotone-ecotone)[symfony/symfony

The Symfony PHP framework

31.4k86.9M2.2k](/packages/symfony-symfony)[symfony/dependency-injection

Allows you to standardize and centralize the way objects are constructed in your application

4.2k447.1M8.9k](/packages/symfony-dependency-injection)[tempest/framework

The PHP framework that gets out of your way.

2.2k31.1k11](/packages/tempest-framework)[fab2s/nodalflow

A PHP Nodal WorkFlow

16369.0k1](/packages/fab2s-nodalflow)[ringierimu/state-workflow

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

3464.7k](/packages/ringierimu-state-workflow)

PHPackages © 2026

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