123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322 |
- <?php
- /**
- * Finite state machine skeleton
- *
- * (c) 2006 Ouest Systèmes Informatiques (OSI)
- * Licensed under the CeCILL 2.0 license
- *
- * $Id: u_fsm.php,v 1.3 2007-04-29 15:40:04 marand Exp $
- */
- require_once('misc.php'); // for func_name()
- error_reporting(E_ALL|E_STRICT);
- /**
- * 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;
- }
- }
- /**
- * This class must be inherited by code implementing actual FSMs.
- *
- * Applications must create a fsm descendant in which they will:
- *
- * - define the f_transitions table, usually in their constructor
- * - create "f_foo" functions for each "foo" event, except "idle", which is builtin.
- *
- * Applications may:
- * - disable event processing by setting $this->event to one of the fsm::EVENT_* constants
- * - disable post-event actions by setting $this->allows actions to false
- * - disable the builtin idle event by setting $this->idle_work to false
- * - query the current state by using $this->get_state()
- * - send an idle event by using $this->idle()
- * - submit any event (including idle) by using $this->apply_event($event_name)
- *
- */
- abstract class fsm
- {
- const IDLE_EVENT = 'idle'; // must not change name: method f_idle depends on it
- const EVENT_NORMAL = 1; // processes events
- const EVENT_QUEUE = 2; // queue events for later use
- const EVENT_SINK = 3; // throw away events
- public $idle_work = TRUE;
- public $allow_actions = TRUE;
- protected $f_event_mode = fsm::EVENT_NORMAL;
- protected $f_queue = array(); // event queue for EVENT_QUEUE mode
- /**
- * 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);
- if ($this->allow_actions)
- {
- $event_name = $result->fsm_action; // can be NULL
- }
- else
- {
- $event_name = 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 (($event_name == fsm::IDLE_EVENT) && !$this->idle_work)
- {
- return new fsm_result();
- }
- 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;
- }
- /**
- * Default event
- * @return boolean
- */
- public function f_idle()
- {
- return TRUE;
- }
- /**
- * Apply an fsm::IDLE_EVENT event. Do not confuse with f_idle !
- *
- * @return fsm_result
- */
- public function idle()
- {
- return $this->apply_event(fsm::IDLE_EVENT);
- }
- /**
- * return the current operating mode of the FSM
- * @return int
- */
- public function get_event_mode()
- {
- return $this->f_event_mode;
- }
- /**
- * set the current operating mode for the FSM
- *
- * @param int $mode fsm::EVENT_* constants
- */
- public function set_event_mode($mode)
- {
- switch ($mode)
- {
- case fsm::EVENT_NORMAL :
- if (count($this->f_queue) > 0)
- {
- while ($event = array_shift($this->f_queue))
- {
- $this->apply_event($event);
- }
- }
- break;
- case fsm::EVENT_QUEUE : // nothing special to do
- break;
- case fsm::EVENT_SINK : // empty queue if needed
- if (count($this->f_queue) > 0)
- {
- $this->f_queue = array();
- }
- break;
- default:
- throw new Exception("Trying to set unknown FSM mode $mode");
- }
- $this->f_event_mode = $mode;
- return $this->f_event_mode;
- }
- }
|