StateMachine
, (*1)
StateMachine is an implementation of state pattern, also can be treated as non-deterministic finite automata.
In different words - machine will react to events and move conext from one state to another depending on result of executed command., (*2)
StateMachine can be used to describe order & payment processing, newsletter opt-in process, customer registration - any not trivial process., (*3)
Processes
StateMachine follows process defined by states taken by context and events that activate transitions between them., (*4)
Each of events can define two transitions successful and erroneous. Also, each event can have a callable command that will be executed before transition. Value returned by it, will decide which transition will be chosen., (*5)
When command returns truthy
value - StateMachine will follow successful transitions, any falsy
value will be erroneous transition.
If event had no command - StateMachine will follow successfull transition., (*6)
Each state can have special event StateMachine::ON_STATE_WAS_SET
which will be triggered as soon as context enters state., (*7)
Schema
Processes are constructed from schemas.
StateMachine comes with ArrayFactory
which creates Process
instances from plain arrays., (*8)
$commandResults = [
true, // from new to pending
false, // from pending to error (onStateWasSet)
true, // from error to pending (onStateWasSet)
true, // from pending to done (onStateWasSet)
];
$command = function () use (&$commandResults) {
if (!count($commandResults)) {
throw new \InvalidArgumentException('Out of results');
}
return array_shift($commandResults);
};
$schema = [
'name' => 'testSchema',
'initialState' => 'new',
'states' => [
[
'name' => 'new',
'events' => [
[
'name' => 'goPending',
'targetState' => 'pending',
'errorState' => 'error',
'command' => $command,
]
],
],
[
'name' => 'pending',
'events' => [
[
'name' => StateMachine::ON_STATE_WAS_SET,
'targetState' => 'done',
'errorState' => 'error',
'command' => $command
]
],
],
[
'name' => 'error',
'events' => [
[
'name' => StateMachine::ON_STATE_WAS_SET,
'targetState' => 'pending',
'errorState' => 'error',
'command' => $command
]
],
],
[
'name' => 'done',
'events' => [],
]
]
];
$process = (new ArrayFactory($schema))->getProcess();
Payload and triggering events
To transit from one state to another an event needs to be trigered.
When it happens StateMachine will pass provided context wrapped as a payload to command, execute it and follow resulting transitions., (*9)
$payload = PayloadEnvelope::wrap('context');
$machine = new StateMachine($process);
$history = $machine->triggerEvent('goPending', $payload);
PayloadEnvelope
is an implementation of Payload
- simple wrapper that holds context. It can be anything, simple value, array, object etc.
::triggerEvent
method returns history of transitions, ie. list of all states that context passed., (*10)