123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- <?php
- /**
- * Finite state machine skeleton
- * embodying a Moore FSM
- * (c) 2006 Ouest Systèmes Informatiques (OSI)
- * Licensed under the CeCILL 2.0 license
- *
- * $Id: u_fsm.php,v 1.2 2007-04-28 20:02:50 marand Exp $
- */
- require_once('misc.php'); // for func_name()
- /**
- * This class defines a possible outcome for a given FSM transition
- *
- */
- class fsm_result
- {
- /**
- * The name of the state to which the FSM must change. If NULL, do not change
- * the current state.
- *
- * @var string
- */
- public $fsm_state;
- /**
- * The name of an event to be fired after the state change has been applied
- *
- * @var string
- */
- public $fsm_action;
- /**
- * @param string $state
- * @param string $action
- * @return void
- */
- public function __construct($state = NULL, $action = NULL)
- {
- $this->fsm_state = $state;
- $this->fsm_action = $action;
- }
- }
- abstract class fsm
- {
- /**
- * the current state of the object
- * @var string
- */
- protected $f_state;
- /**
- * Transitions holds the transitions table
- * state1
- * event1
- * result1
- * state_name|fsm_result
- * event2
- * ...
- * ..
- * ..
- * @var array
- */
- protected $f_transitions = null;
- /**
- * constructor initializes the FSM to the first
- * state in the transitions table
- * @return void
- */
- public function __construct()
- {
- $this->_check_transitions();
- reset($this->f_transitions);
- $x = each($this->f_transitions);
- $x = $x[0];
- $this->f_state = $x;
- }
- /**
- * make sure a transitions graph has been defined
- * @return void
- */
- private function _check_transitions()
- {
- if (!isset($this->f_transitions))
- throw new Exception('No FSM processing is allowed without a transitions table');
- }
- /**
- * getter for f_state
- */
- public function get_state()
- {
- return $this->f_state;
- }
- /**
- * return the list of events accepted in the current state
- * @return array
- */
- public function get_accepted_events()
- {
- $this->_check_transitions();
- $events = array_keys($this->f_transitions[$this->f_state]);
- // echo func_name() . ": " . print_r($events, true). "\n";
- return $events;
- }
- /**
- * return the list of outcome accepted in the current state for the give event
- * @param string $event_name
- * @param mixed $outcome
- * @return array
- */
- public function get_accepted_outcomes($event_name)
- {
- // echo func_name() . "\n";
- $this->_check_transitions();
- if (!$this->is_event_allowed($event_name))
- throw new Exception(func_name() . ": event \"$event_name\" not allowed in state \"$this->f_state\"\n");
- $outcomes = array_keys($this->f_transitions[$this->f_state][$event_name]);
- //echo "outcomes for event $event_name: " . print_r($outcomes, true) . "\n";
- return $outcomes;
- }
- /**
- * is this event accepted in the current state
- * the FSM is in ?
- *
- * @param string $event_name
- * @return boolean
- */
- public function is_event_allowed($event_name)
- {
- // echo func_name() . "($event_name)";
- $this->_check_transitions();
- $ret = in_array($event_name, $this->get_accepted_events());
- // echo ", result = <$ret>\n";
- return $ret;
- }
- /**
- * is a given outcome available for a given event,
- * considering the current context ?
- *
- * @param string $event_name
- * @param mixed $outcome
- * @return boolean
- */
- public function is_outcome_allowed($event_name, $outcome)
- {
- $this->_check_transitions();
- if (!$this->is_event_allowed($event_name))
- return false;
- $ret = array_key_exists($outcome, $this->get_accepted_outcomes($event_name));
- return $ret;
- }
- /**
- * apply an event, and the resulting event chain if needed
- *
- * @param string $event_name
- * @param array $params the
- * @return string resulting state
- */
- public function apply_event($event_name)
- {
- //echo func_name() . "\n";
- do {
- $result = $this->apply_simple_event($event_name);
- $event_name = $result->fsm_action; // can be NULL
- } while($event_name);
- return $result;
- }
- /**
- * Helper for apply_event that does not implement the post-transition action
- *
- * @param string $event_name
- * @return fsm_result
- * @see apply_event()
- */
- private function apply_simple_event($event_name)
- {
- //echo func_name() . "\n";
- if (! $this->is_event_allowed($event_name))
- throw new Exception(func_name()
- . ": Event \"$event_name\" not accepted in current state \"$this->f_state\"");
- $method_name = "f_$event_name";
- $outcomes = $this->get_accepted_outcomes($event_name);
- $result = $this->$method_name();
- if (!is_object($result))
- {
- $result = new fsm_result($result, NULL);
- }
- if (!in_array($result->fsm_state, $outcomes))
- throw new Exception(func_name()
- . ": event guard. Transition on \"$event_name\" return invalid result: "
- . var_dump($result)
- . "\n");
- $this->f_state = $this->f_transitions[$this->f_state][$event_name][$result->fsm_state];
- // echo func_name() . ", new state: $this->f_state, action: $result->fsm_action\n";
- return $result;
- }
- }
|