php-architecture-kit/state-machine
最新稳定版本:1.1.1
Composer 安装命令:
composer require php-architecture-kit/state-machine
包简介
Graph-based state machine engine for PHP — supports parallel pointers, conditional transitions, typed state, and domain events.
README 文档
README
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
- 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
Stateobjects attached to eachExecution - Domain events — pointer and state lifecycle emits
DomainEventinstances - 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
composer require php-architecture-kit/state-machine
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
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 finishedExecutionStatus::Running— progress was made but pointers remainExecutionStatus::Suspended— no progress, all pointers are blocked
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
interface NodeHandlerInterface { public function handle(NodeHandlerContext $context): NodeHandlerResult; }
NodeHandlerContext provides:
ExecutionId $executionIdNodeInterface $nodePointer $pointerStates $statesdispatchTask(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
Directed edge between two NodeIds. 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
Holds all Pointers and States for one running instance.
$execution = Execution::create(); $execution->pointers->startAt($entryNodeId);
Pointer
Tracks a single cursor position (nodeId). Pointers can be forked for parallel branches. Retrieve events (fork, remove, etc.) via $execution->pointers->getEvents().
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
Controls graph constraints and pluggable strategies:
new StateMachineConfig( allowCycles: true, allowSelfLoops: true, allowParallelTransitions: true, transitionStrategies: [...], pointersSelectionStrategy: new AllPointersUntilBlockedStrategy(), )
Built-in Components
Components are reusable, pre-wired sub-graphs. Add them with $machine->addDefinition($component) and connect their ports with addTransition().
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
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
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
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
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
| Strategy | Behaviour |
|---|---|
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
| Strategy | Behaviour |
|---|---|
AllPointersUntilBlockedStrategy |
Advances every pointer until each one blocks (default) |
AllPointersStepStrategy |
Advances every pointer exactly one step per execute() call |
License
MIT
统计信息
- 总下载量: 4
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 1
- 点击次数: 3
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-05-07