PHP Class izzum\statemachine\StateMachine

PACKAGE PHILOSOPHY This whole package strives to follow the open/closed principle, making it open for extension (adding your own logic through subclassing) but closed for modification. For that purpose, we provide Loader interfaces, Builders and Persistence Adapters for the backend implementation. States and Transitions can also be subclassed to store data and provide functionality. Following the same philosophy: if you want more functionality in the statemachine, you can use the provided methods and override them in your subclass. multiple hooks are provided. Most functionality should be provided by using the diverse ways of interacting with the statemachine: callables can be injected, commands can be injected, event handlers can be defined and hooks can be overriden. We have provided multiple persistance backends to function as a data store to store all relevant information for a statemachine including configuration, transition history and current states. - relational databases: postgresql, mysql, sqlite - nosql key/value: redis - nosql document based: mongodb Memory and session backend adapters can be used to temporarily store the state information. yaml, json and xml loaders can be used to load configuration data from files containing those data formats. Examples are provided in the 'examples' folder and serve to highlight some of the features and the way to work with the package. The unittests can serve as examples too, including interaction with databases. ENVIRONMENTS OF USAGE: - 1: a one time process: -- on webpages where there are page refreshes in between (use session/pdo adapter) see 'examples/session' -- an api where succesive calls are made (use pdo adapter) -- cron jobs (pdo adapter) - 2: a longer running process: -- a php daemon that runs as a background process (use memory adapter) -- an interactive shell environment (use memory adapter) see 'examples/interactive' DESCRIPTION OF THE 4 MAIN MODELS OF USAGE: - 1: DELEGATION: Use an existing domain model. You can use a subclass of the AbstractFactory to get a StateMachine, since that will put the creation of all the relevant Contextual classes and Transitions in a reusable model. usage: This is the most formal, least invasive and powerful model of usage, but the most complex. Use Rules and Commands to interact with your domain model without altering a domain model to work with the statemachine. see 'examples/trafficlight' - 2: INHERITANCE: Subclass a statemachine. Build the full Context and all transitions in the constructor of your subclass (which could be a domain model itself) and call the parent constructor. use the hooks to provide functionality (optionally a ModelBuilder and callbacks). usage: This is the most flexible model of usage, but mixes your domain logic with the statemachine. see 'examples/inheritance' - 3: COMPOSITION: Use object composition. Instantiate and build a statemachine in a domain model and build the full Context, Transitions and statemachine there. Use a ModelBuilder and callbacks to drive functionality. usage: This is a good mixture between encapsulating statemachine logic and flexibility/formal usage. see 'examples/composition' - 4: STANDALONE: Use the statemachine as is, without any domain models. Your application will use it and inspect the statemachine to act on it. Use callables to provide functionality usage: This is the easiest model of usage, but the least powerful. see 'examples/interactive' MECHANISMS FOR GUARD AND TRANSITION LOGIC 1. rules and commands: they are fully qualified class names of Rule and Command (sub)classes that can be injected in Transition and State instances. they accept the domain model provided via an EntityBuilder in their constructor. 2. hooks: the methods in this class that start with an underscore and can be overriden in a subclass. these can be used to provide a subclass of this machine specifically tailored to your application domain.
Author: Rolf Vreijdenberger
Show file Open project: rolfvreijdenberger/izzum-statemachine Class Usage Examples

Public Methods

Method Description
__call ( string $name, array $arguments ) : boolean This method is used to trigger an event on the statemachine and delegates the actuall call to the 'handle' method
__construct ( Context $context ) Constructor
__toString ( )
add ( string $message = null ) : boolean add state information to the persistence layer if it is not there.
addState ( State $state ) : boolean Add a state. without a transition.
addTransition ( Transition $transition, boolean $allow_self_transition_by_regex = false ) : integer Add a fully configured transition to the machine.
canHandle ( string $event ) : boolean checks if one or more transitions are possible and/or allowed for the current state when triggered by an event
canTransition ( string $transition_name ) : boolean Check if an existing transition on the curent state is allowed by the guard logic.
getContext ( ) : Context Get the current context
getCurrentState ( ) : State gets the current state (or retrieve it from the backend if not set).
getInitialState ( boolean $allow_null = false ) : State Get the initial state, the only state with type State::TYPE_INITIAL
getState ( string $name ) : State get a state by name.
getStates ( ) : State[] All known/loaded states for this statemachine
getTransition ( string $name ) : Transition get a transition by name.
getTransitions ( ) : Transition[] All known/loaded transitions for this statemachine
handle ( string $event, string $message = null ) : boolean Try to apply a transition from the current state by handling an event string as a trigger.
hasEvent ( string $event ) : boolean check if the current state has one or more transitions that can be triggered by an event
run ( string $message = null ) : boolean Have the statemachine do the first possible transition.
runToCompletion ( string $message = null ) : integer run a statemachine until it cannot run any transition in the current state or until it is in a final state.
setContext ( Context $context ) set the context on the statemachine and provide bidirectional association.
setState ( State $state, string $message = null ) sets the state as the current state and on the backend.
toString ( $elaborate = false )
transition ( string $transition_name, string $message = null ) : boolean Apply a transition by name.

Protected Methods

Method Description
_onCheckCanTransition ( Transition $transition ) : boolean hook method.
_onEnterState ( Transition $transition ) hook method.
_onExitState ( Transition $transition ) hook method.
_onTransition ( Transition $transition ) hook method.
addTransitionWithoutRegex ( Transition $transition ) : boolean Add the transition, after it has previously been checked that is did not contain states with a regex.
callEventHandler ( mixed $object, string $method ) : boolean | mixed Helper method to generically call methods on the $object.
handlePossibleNonStatemachineException ( Exception $e, integer $code, Transition $transition = null ) Always throws an izzum exception (converts a non-izzum exception to an izzum exception)
handleTransitionException ( Transition $transition, Exception $e ) called whenever an exception occurs from inside 'performTransition()' can be used for logging etc. in some sort of history structure in the persistence layer

Private Methods

Method Description
doCheckCanTransition ( Transition $transition ) : boolean Template method to call a hook and to call a possible method defined on the domain object/contextual entity
doEnterState ( Transition $transition ) the enter state action method
doExitState ( Transition $transition ) the exit state action method
doTransition ( Transition $transition, string $message = null ) the transition action method
getTransitionWithNullCheck ( string $name ) : Transition Helper method that gets the Transition object from a transition name or throws an exception.
performTransition ( Transition $transition, string $message = null ) : boolean Perform a transition by specifiying the transitions' name from a state that the transition is allowed to run.

Method Details

__call() public method

$statemachine->triggerAnEvent() actually calls $this->handle('triggerAnEvent') This is also very useful if you use object composition to include a statemachine in your domain model. The domain model itself can then use it's own __call implementation to directly delegate to the statemachines' __call method. $model->walk() will actually call $model->statemachine->walk() which will then call $model->statemachine->handle('walk'); since transition event names default to the transition name, it is possible to execute this kind of code (if the state names contain allowed characters): $statemachine->_to_(); $statemachine->eventName(); $statemachine->eventName('this is an informational message about why we do this transition');
public __call ( string $name, array $arguments ) : boolean
$name string the name of the unknown method called
$arguments array an array of arguments (if any). an argument could be $message (informational message for the transition)
return boolean true in case a transition was triggered by the event, false otherwise

__construct() public method

Constructor
public __construct ( Context $context )
$context Context a fully configured context providing all the relevant parameters/dependencies to be able to run this statemachine for an entity.

__toString() public method

public __toString ( )

_onCheckCanTransition() protected method

override in subclass if necessary. Before a transition is checked to be possible, you can add domain specific logic here by overriding this method in a subclass. In an overriden implementation of this method you can stop the transition by returning false from this method.
protected _onCheckCanTransition ( Transition $transition ) : boolean
$transition Transition
return boolean if false, the transition and it's associated logic will not take place

_onEnterState() protected method

override in subclass if necessary. Called after each transition has run and has executed the associated transition logic. a hook to implement in subclasses if necessary, to do stuff such as dispatching events, unlocking an entity, logging, cleanup, commit transaction via the persistence layer etc.
protected _onEnterState ( Transition $transition )
$transition Transition

_onExitState() protected method

override in subclass if necessary. Called before each transition will run and execute the associated transition logic. A hook to implement in subclasses if necessary, to do stuff such as dispatching events, locking an entity, logging, begin transaction via persistence layer etc.
protected _onExitState ( Transition $transition )
$transition Transition

_onTransition() protected method

override in subclass if necessary.
protected _onTransition ( Transition $transition )
$transition Transition

add() public method

Used to mark the initial construction of a statemachine at a certain point in time. This method only makes sense the first time a statemachine is initialized and used since it will do nothing once a transition has been made. It will set the initial state on the backend which sets the construction time. Make sure that the transitions and states are loaded before you call this method. in other words: the machine should be ready to go.
public add ( string $message = null ) : boolean
$message string optional message. this can be used by the persistence adapter to provide extra information in the history of the machine transitions, in this case, about the first adding of this machine to the persistence layer.
return boolean true if not persisted before, false otherwise

addState() public method

Normally, you would not use this method directly but instead use addTransition to add the transitions with the states in one go. This method makes sense if you would want to load the statemachine with states and not transitions, and then add the transitions with regex states. This saves you the hassle of adding transitions before you add the regex transitions (just so the states are already known on the machine). in case a State is not used in a Transition, it will be orphaned and not reachable via other states.
public addState ( State $state ) : boolean
$state State
return boolean true if the state was not know to the machine or wasn't added, false otherwise.

addTransition() public method

It is possible to add transition that have 'regex' states: states that have a name in the format of 'regex://' or 'not-regex://'. When adding a transition with a regex state, it will be matched against all currently known states if there is a match. If you just want to use regex transitions, it might be preferable to store some states via 'addState' first, so the Self transitions for regex states are disallowed by default since you would probably only want to do that explicitly. Regex states can be both the 'to' and the 'from' state of a transition. Transitions from a 'final' type of state are not allowed. the order in which transitions are added matters insofar that when a StateMachine::run() is called, the first Transition for the current State will be tried first. Since a transition has complete knowledge about it's states, the addition of a transition will also trigger the adding of the to and from state on this class. this method can also be used to add a Transition directly (instead of via a loader). Make sure that transitions that share a common State use the same instance of that State object and vice versa.
public addTransition ( Transition $transition, boolean $allow_self_transition_by_regex = false ) : integer
$transition Transition
$allow_self_transition_by_regex boolean optional: to allow regexes to set a self transition.
return integer a count of how many transitions were added. In case of a regex transition this might be multiple and in case a transition already exists it might be 0.

addTransitionWithoutRegex() protected method

Add the transition, after it has previously been checked that is did not contain states with a regex.
protected addTransitionWithoutRegex ( Transition $transition ) : boolean
$transition Transition
return boolean true in case it was added. false otherwise

callEventHandler() protected method

Try to call a method on the contextual entity / domain model ONLY IF the method exists.Any arguments passed to this method will be passed on to the method called on the entity.
protected callEventHandler ( mixed $object, string $method ) : boolean | mixed
$object mixed the object on which we want to call the method
$method string the method to call on the object
return boolean | mixed

canHandle() public method

checks if one or more transitions are possible and/or allowed for the current state when triggered by an event
public canHandle ( string $event ) : boolean
$event string
return boolean false if no transitions possible or existing

canTransition() public method

Check if an existing transition on the curent state is allowed by the guard logic.
public canTransition ( string $transition_name ) : boolean
$transition_name string convention: _to_
return boolean

getContext() public method

Get the current context
public getContext ( ) : Context
return Context

getCurrentState() public method

the state will be: - the state that was explicitely set via setState - the state we have moved to after the last transition - the initial state. if we haven't had a transition yet and no current state has been set, the initial state will be retrieved (the state with State::TYPE_INITIAL)
public getCurrentState ( ) : State
return State

getInitialState() public method

This method can be used to 'add' the state information to the backend via the context/persistence adapter when a machine has been initialized for the first time.
public getInitialState ( boolean $allow_null = false ) : State
$allow_null boolean optional
return State (or null or Exception, only when statemachine is improperly loaded)

getState() public method

get a state by name.
public getState ( string $name ) : State
$name string
return State or null if not found

getStates() public method

All known/loaded states for this statemachine
public getStates ( ) : State[]
return State[]

getTransition() public method

get a transition by name.
public getTransition ( string $name ) : Transition
$name string convention: _to_
return Transition or null if not found

getTransitions() public method

All known/loaded transitions for this statemachine
public getTransitions ( ) : Transition[]
return Transition[]

handle() public method

If the event is applicable for a transition then that transition from the current state will be applied. If there are multiple transitions possible for the event, the transitions will be tried until one of them is possible. This type of (event/trigger) handling is found in mealy machines.
public handle ( string $event, string $message = null ) : boolean
$event string in case the transition will be triggered by an event code (mealy machine) this will also match on the transition name (_to_)
$message string optional message. this can be used by the persistence adapter to be part of the transition history to provide extra information about the transition.
return boolean true in case a transition was triggered by the event, false otherwise

handlePossibleNonStatemachineException() protected method

Always throws an izzum exception (converts a non-izzum exception to an izzum exception)
protected handlePossibleNonStatemachineException ( Exception $e, integer $code, Transition $transition = null )
$e Exception
$code integer if the exception is not of type Exception, wrap it and use this code.
$transition Transition optional. if set, we handle it as a transition exception too so it can be logged or handled

handleTransitionException() protected method

called whenever an exception occurs from inside 'performTransition()' can be used for logging etc. in some sort of history structure in the persistence layer
protected handleTransitionException ( Transition $transition, Exception $e )
$transition Transition
$e Exception

hasEvent() public method

check if the current state has one or more transitions that can be triggered by an event
public hasEvent ( string $event ) : boolean
$event string
return boolean

run() public method

The first possible transition is based on the configuration of the guard logic and the current state of the statemachine. This method is the main way to have the statemachine do useful work. TRICKY: Be careful when using this function, since all guard logic must be mutually exclusive! If not, you might end up performing the state transition with priority n when you really want to perform transition n+1. An alternative is to use the 'transition' method to target 1 transition specifically: $statemachine->transition('a_to_b'); So you are always sure that you are actually doing the intented transition instead of relying on the configuration and guard logic (which *might* not be correctly implemented, leading to transitions that would normally not be executed).
public run ( string $message = null ) : boolean
$message string optional message. this can be used by the persistence adapter to be part of the transition history to provide extra information about the transition.
return boolean true if a transition was applied.

runToCompletion() public method

when using cyclic graphs, you could get into an infinite loop between states. design your machine correctly.
public runToCompletion ( string $message = null ) : integer
$message string optional message. this can be used by the persistence adapter to be part of the transition history to provide extra information about the transition.
return integer the number of sucessful transitions made.

setContext() public method

change the context for a statemachine that already has a context. When the context is changed, but it is for the same statemachine (with the same transitions), the statemachine can be used directly with the new context. The current state is reset to whatever state the machine should be in (either the initial state or the stored state) whenever a context change is made. we can change context to: - switch builders/persistence adapters at runtime - reuse the statemachine for a different entity so we do not have to load the statemachine with the same transition definitions
public setContext ( Context $context )
$context Context

setState() public method

This should only be done: - initially, right after a machine has been created, to set it in a certain state if the state has not been persisted before. - when changing context (since this resets the current state) via $machine->setState($machine->getCurrentState()) This method allows you to bypass the transition guards and the transition logic. no exit/entry/transition logic will be performed
public setState ( State $state, string $message = null )
$state State
$message string optional message. this can be used by the persistence adapter to be part of the transition history to provide extra information about the transition.

toString() public method

public toString ( $elaborate = false )

transition() public method

this type of handling is found in moore machines.
public transition ( string $transition_name, string $message = null ) : boolean
$transition_name string convention: _to_
$message string optional message. this can be used by the persistence adapter to be part of the transition history to provide extra information about the transition.
return boolean true if the transition was made