Add StateMachine support
This commit is contained in:
131
src/StateMachine.php
Normal file
131
src/StateMachine.php
Normal file
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
/** DomFramework
|
||||
* @package domframework
|
||||
* @author Dominique Fournier <dominique@fournier38.fr>
|
||||
* @license BSD
|
||||
*/
|
||||
|
||||
namespace Domframework;
|
||||
|
||||
/**
|
||||
* This class manage a state machine.
|
||||
* Define the states and the associated transitions.
|
||||
* The states and the transitions are methods
|
||||
* The states are the actions.
|
||||
* The transitions are tests : return false if the test do not match, or true
|
||||
* if the test match. The state machine will skip the test if it doesn't match,
|
||||
* and go to the define toStateName if the test return true.
|
||||
* Officially, the transitions should not have priority. In practise, the
|
||||
* transistions are tested in the added order.
|
||||
*/
|
||||
class StateMachine
|
||||
{
|
||||
private $states = array();
|
||||
private $transitions = array();
|
||||
private $debug = false;
|
||||
|
||||
public function addState(string $stateName, callable $methodName): self
|
||||
{
|
||||
$this->states[$stateName] = new StateMachineState($methodName);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addTransition(string $transitionName, string $fromStateName, string $toStateName, callable $methodName): self
|
||||
{
|
||||
if (! key_exists($fromStateName, $this->states))
|
||||
throw new \Exception("StateMachine can not add Transition from '$fromStateName' : state not defined", 404);
|
||||
if (! key_exists($toStateName, $this->states))
|
||||
throw new \Exception("StateMachine can not add Transition to '$toStateName' : state not defined", 404);
|
||||
if (key_exists($transitionName, $this->transitions))
|
||||
throw new \Exception("StateMachine can not add Transition '$transitionName' : transition already defined", 406);
|
||||
$this->transitions[$transitionName] = new StateMachineTransition($fromStateName, $toStateName, $methodName);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the state machine at the provided state
|
||||
* Will run until no transition match, then return
|
||||
*/
|
||||
public function run(string $stateName)
|
||||
{
|
||||
if (! key_exists ($stateName, $this->states))
|
||||
throw new \Exception("StateMachine can not runState '$stateName' : state not defined", 404);
|
||||
while(1)
|
||||
{
|
||||
$this->debug("Start state '$stateName'");
|
||||
$rc = $this->states[$stateName]->run();
|
||||
$this->debug("End state '$stateName' with type '".gettype($rc)."'");
|
||||
foreach($this->transitions as $transitionName => $transition)
|
||||
{
|
||||
if ($transition->getFromStateName() !== $stateName)
|
||||
continue;
|
||||
if ($transition->run() === false)
|
||||
{
|
||||
$this->debug("Look at transition '$transitionName' : Return FALSE (not match)");
|
||||
continue;
|
||||
}
|
||||
$this->debug("Look at transition '$transitionName' : Match");
|
||||
$stateName = $transition->getToStateName();
|
||||
continue 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return $rc;
|
||||
}
|
||||
|
||||
private function debug(string $msg)
|
||||
{
|
||||
if ($this->debug === false)
|
||||
return;
|
||||
echo "$msg\n";
|
||||
}
|
||||
}
|
||||
|
||||
class StateMachineState
|
||||
{
|
||||
private $methodName;
|
||||
|
||||
public function __construct(callable $methodName)
|
||||
{
|
||||
$this->methodName = $methodName;
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
return call_user_func($this->methodName);
|
||||
}
|
||||
}
|
||||
|
||||
class StateMachineTransition
|
||||
{
|
||||
private $fromStateName;
|
||||
private $toStateName;
|
||||
private $methodName;
|
||||
|
||||
public function __construct(string $fromStateName, string $toStateName, callable $methodName)
|
||||
{
|
||||
$this->fromStateName = $fromStateName;
|
||||
$this->toStateName = $toStateName;
|
||||
$this->methodName = $methodName;
|
||||
}
|
||||
|
||||
public function getFromStateName(): string
|
||||
{
|
||||
return $this->fromStateName;
|
||||
}
|
||||
|
||||
public function getToStateName(): string
|
||||
{
|
||||
return $this->toStateName;
|
||||
}
|
||||
|
||||
public function getMethodName(): callable
|
||||
{
|
||||
return $this->methodName;
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
return call_user_func($this->methodName);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user