potfur/statemachine

State machine

2.0.0 2015-08-02 09:25 UTC

This package is not auto-updated.

Last update: 2024-10-26 17:55:57 UTC


README

Scrutinizer Code Quality Code Coverage Build Status License Latest Stable Version

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.

StateMachine can be used to describe order & payment processing, newsletter opt-in process, customer registration - any not trivial process.

Processes

StateMachine follows process defined by states taken by context and events that activate transitions between them.

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.

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.

Each state can have special event StateMachine::ON_STATE_WAS_SET which will be triggered as soon as context enters state.

Schema

Processes are constructed from schemas. StateMachine comes with ArrayFactory which creates Process instances from plain arrays.

$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.

$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.