Browse Source

Added the ability to load from an external schema file
Heavy restructuring: breaks the previous FTP client.

Frederic G. Marand 17 years ago
parent
commit
f6f20a41f3
1 changed files with 153 additions and 19 deletions
  1. 153 19
      u_fsm.php

+ 153 - 19
u_fsm.php

@@ -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']);
+          }
+        }
+      }
+    }
   }