|
@@ -5,7 +5,7 @@
|
|
|
* (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 $
|
|
|
+ * $Id: u_fsm.php,v 1.4 2007-05-08 20:52:06 marand Exp $
|
|
|
*/
|
|
|
require_once('misc.php'); // for func_name()
|
|
|
error_reporting(E_ALL|E_STRICT);
|
|
@@ -16,6 +16,12 @@ error_reporting(E_ALL|E_STRICT);
|
|
|
*/
|
|
|
class fsm_result
|
|
|
{
|
|
|
+ /**
|
|
|
+ * The return value of the event handler
|
|
|
+ * @var mixed
|
|
|
+ */
|
|
|
+ public $fsm_return;
|
|
|
+
|
|
|
/**
|
|
|
* The name of the state to which the FSM must change. If NULL, do not change
|
|
|
* the current state.
|
|
@@ -36,8 +42,9 @@ class fsm_result
|
|
|
* @param string $action
|
|
|
* @return void
|
|
|
*/
|
|
|
- public function __construct($state = NULL, $action = NULL)
|
|
|
+ public function __construct($return = NULL, $state = NULL, $action = NULL)
|
|
|
{
|
|
|
+ $this->fsm_return = $return;
|
|
|
$this->fsm_state = $state;
|
|
|
$this->fsm_action = $action;
|
|
|
}
|
|
@@ -49,6 +56,7 @@ class fsm_result
|
|
|
* Applications must create a fsm descendant in which they will:
|
|
|
*
|
|
|
* - define the f_transitions table, usually in their constructor
|
|
|
+ * - invoke the parent constructor to set up the FSM from the transitions table
|
|
|
* - create "f_foo" functions for each "foo" event, except "idle", which is builtin.
|
|
|
*
|
|
|
* Applications may:
|
|
@@ -64,6 +72,9 @@ abstract class fsm
|
|
|
{
|
|
|
const IDLE_EVENT = 'idle'; // must not change name: method f_idle depends on it
|
|
|
|
|
|
+ const INIT_STATE = 'init';
|
|
|
+ const FINAL_STATE = 'final';
|
|
|
+
|
|
|
const EVENT_NORMAL = 1; // processes events
|
|
|
const EVENT_QUEUE = 2; // queue events for later use
|
|
|
const EVENT_SINK = 3; // throw away events
|
|
@@ -116,7 +127,7 @@ abstract class fsm
|
|
|
private function _check_transitions()
|
|
|
{
|
|
|
if (!isset($this->f_transitions))
|
|
|
- throw new Exception('No FSM processing is allowed without a transitions table');
|
|
|
+ throw new Exception('No FSM processing is allowed without a transitions table\n');
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -135,8 +146,18 @@ abstract class fsm
|
|
|
{
|
|
|
$this->_check_transitions();
|
|
|
|
|
|
- $events = array_keys($this->f_transitions[$this->f_state]);
|
|
|
- // echo func_name() . ": " . print_r($events, true). "\n";
|
|
|
+ try
|
|
|
+ {
|
|
|
+ $events = array_keys($this->f_transitions[$this->f_state]);
|
|
|
+ // echo func_name() . ": state $this->f_state, accepted events are:\n" . print_r($events, true). "\n";
|
|
|
+ }
|
|
|
+ catch (Exception $e)
|
|
|
+ {
|
|
|
+ echo "Exception in get_accepted_events" . print_r($e);
|
|
|
+ print_r(debug_backtrace());
|
|
|
+ $events = array();
|
|
|
+ }
|
|
|
+
|
|
|
return $events;
|
|
|
}
|
|
|
|
|
@@ -151,11 +172,16 @@ abstract class fsm
|
|
|
// echo func_name() . "\n";
|
|
|
$this->_check_transitions();
|
|
|
|
|
|
+ /**
|
|
|
+ * Spare some paranioa
|
|
|
+ *
|
|
|
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";
|
|
|
+ // print_r($this->f_transitions[$this->f_state][$event_name]);
|
|
|
+ // echo "outcomes for event $event_name: " . var_dump($outcomes) . "\n";
|
|
|
return $outcomes;
|
|
|
}
|
|
|
|
|
@@ -172,7 +198,7 @@ abstract class fsm
|
|
|
$this->_check_transitions();
|
|
|
|
|
|
$ret = in_array($event_name, $this->get_accepted_events());
|
|
|
- // echo ", result = <$ret>\n";
|
|
|
+ // echo " in state $this->f_state, result = <$ret>\n";
|
|
|
return $ret;
|
|
|
}
|
|
|
|
|
@@ -230,7 +256,8 @@ abstract class fsm
|
|
|
*/
|
|
|
private function apply_simple_event($event_name)
|
|
|
{
|
|
|
- //echo func_name() . "\n";
|
|
|
+ // echo func_name() . ", event = $event_name";
|
|
|
+ $current_state = $this->f_state;
|
|
|
if (($event_name == fsm::IDLE_EVENT) && !$this->idle_work)
|
|
|
{
|
|
|
return new fsm_result();
|
|
@@ -238,24 +265,45 @@ abstract class fsm
|
|
|
|
|
|
if (! $this->is_event_allowed($event_name))
|
|
|
throw new Exception(func_name()
|
|
|
- . ": Event \"$event_name\" not accepted in current state \"$this->f_state\"");
|
|
|
+ . ": Event \"$event_name\" not accepted in current state \"$current_state\"");
|
|
|
|
|
|
- $method_name = "f_$event_name";
|
|
|
$outcomes = $this->get_accepted_outcomes($event_name);
|
|
|
|
|
|
- $result = $this->$method_name();
|
|
|
- if (!is_object($result))
|
|
|
+ $method_name = "f_$event_name";
|
|
|
+ if (!method_exists($this, $method_name))
|
|
|
{
|
|
|
- $result = new fsm_result($result, NULL);
|
|
|
+ die (func_name() . ": missing method "
|
|
|
+ . get_class($this) . "::$method_name. Aborting.\n");
|
|
|
}
|
|
|
- if (!in_array($result->fsm_state, $outcomes))
|
|
|
+ $outcome = $this->$method_name();
|
|
|
+
|
|
|
+ if (!in_array($outcome, $outcomes))
|
|
|
+ {
|
|
|
throw new Exception(func_name()
|
|
|
- . ": event guard. Transition on \"$event_name\" return invalid result: "
|
|
|
- . var_dump($result)
|
|
|
- . "\n");
|
|
|
+ . ": event guard. Transition on \"$event_name\" return invalid outcome: "
|
|
|
+ . print_r($outcome, true)
|
|
|
+ . " for state $this->f_state\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";
|
|
|
+ $transition = &$this->f_transitions[$current_state][$event_name][$outcome];
|
|
|
+ $result = new fsm_result
|
|
|
+ (
|
|
|
+ $outcome,
|
|
|
+ $transition[0],
|
|
|
+ $transition[1]
|
|
|
+ );
|
|
|
+ if (isset($result->fsm_state))
|
|
|
+ {
|
|
|
+ $this->f_state = $result->fsm_state;
|
|
|
+ }
|
|
|
+ /* print_r($this->f_transitions[$current_state][$event_name]);
|
|
|
+ var_dump($result); */
|
|
|
+ if (!isset($outcome))
|
|
|
+ $outcome = 'NULL';
|
|
|
+ /* echo func_name()
|
|
|
+ . ": $current_state: " . $event_name . '[' . $outcome . ']'
|
|
|
+ . " -> $this->f_state / " . $result->fsm_action . PHP_EOL;
|
|
|
+ */
|
|
|
return $result;
|
|
|
}
|
|
|
|
|
@@ -319,4 +367,90 @@ abstract class fsm
|
|
|
$this->f_event_mode = $mode;
|
|
|
return $this->f_event_mode;
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Load the f_transitions table from an external resource.
|
|
|
+ *
|
|
|
+ * @param string $url Optional: defaults to the name of the class, . ".xml"
|
|
|
+ */
|
|
|
+ public function load_fsm($url = NULL)
|
|
|
+ {
|
|
|
+ $osd = FALSE; // on screen display (debug)
|
|
|
+ if ($osd)
|
|
|
+ echo "Loading FSM from $url\n";
|
|
|
+
|
|
|
+ if (!isset($url))
|
|
|
+ {
|
|
|
+ $url = get_class($this) . ".xml";
|
|
|
+ }
|
|
|
+
|
|
|
+ $fsm = simplexml_load_file($url);
|
|
|
+ $fsm_version = (string) $fsm['fsm_version'];
|
|
|
+ if ($fsm_version !== '1.3')
|
|
|
+ {
|
|
|
+ die("Revision $fsm_version of schema is not supported.\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->idle_work = ($fsm['idle_work'] == 1);
|
|
|
+ $this->allow_actions = ($fsm['allow_actions'] == 1);
|
|
|
+
|
|
|
+ $this->f_transitions = array();
|
|
|
+ $t = &$this->f_transitions;
|
|
|
+
|
|
|
+ // (string) casts in this loop are required: RHS is a SimpleXMLElement
|
|
|
+ foreach ($fsm->state as $state)
|
|
|
+ {
|
|
|
+ $id = (string) $state['id'];
|
|
|
+ if ($osd)
|
|
|
+ echo "State $id :\n";
|
|
|
+ $t[$id] = array();
|
|
|
+ switch ($id)
|
|
|
+ {
|
|
|
+ case fsm::INIT_STATE :
|
|
|
+ if ($osd)
|
|
|
+ echo " Initial state\n";
|
|
|
+ break;
|
|
|
+ case fsm::FINAL_STATE :
|
|
|
+ if ($osd)
|
|
|
+ echo " Final state\n";
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ foreach ($state->event as $event)
|
|
|
+ {
|
|
|
+ $ename = (string) $event['name'];
|
|
|
+ if ($osd)
|
|
|
+ echo " Event $ename";
|
|
|
+
|
|
|
+ if (!isset($event['type']))
|
|
|
+ $event['type'] = 'void';
|
|
|
+ $etype = (string) $event['type'];
|
|
|
+ if ($osd)
|
|
|
+ echo ", type $etype\n";
|
|
|
+
|
|
|
+ foreach ($event as $next)
|
|
|
+ {
|
|
|
+ if ($event['type'] == 'void')
|
|
|
+ {
|
|
|
+ $next['result'] = 'always';
|
|
|
+ $eresult = null;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ $eresult = (string) $next['result'];
|
|
|
+
|
|
|
+ if (!isset($next['state']))
|
|
|
+ $next['state'] = (string) $state['id'];
|
|
|
+ if ($osd)
|
|
|
+ echo " Next(" . $next['result'] . ') = ' . $next['state'];
|
|
|
+ if ($osd)
|
|
|
+ if (isset($next['action']))
|
|
|
+ echo " + event " . $next['action'];
|
|
|
+ if ($osd)
|
|
|
+ echo PHP_EOL;
|
|
|
+ $t[$id][$ename][$eresult] = array(
|
|
|
+ (string) $next['state'],
|
|
|
+ (string) $next['action']);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|