| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336 | <?php/** * Finite state machine writer. * * Keeping the FSM writer (generation) functions out of the root class minimizes * the code volume for FSM readers, which should represent the majority of FSM * users. * * @copyright  (c) 2007-2012 Ouest Systèmes Informatiques * @author     Frédéric G. MARAND * @license    Licensed under the CeCILL 2.0 * @link       http://wiki.audean.com/fsm/fsm */namespace OSInet\Finite_State_Machine;/** * Needed notably for func_name() */require_once('misc.php');$erFiniteStateMachine = error_reporting(E_ALL|E_STRICT);/** * Read/Write concrete FSM class. * * This (concrete) class extends the default abstract Finite_State_Machine to * add write behaviours. It is to be used by applications wishing to write FSM * files. Other applications should not use it to avoid code bloat. */class Writer extends Machine {  const FORMAT_VERSION = '1.1';  public function __construct() {    $this->fTransitions = array();    parent::__construct();  }  /**   * Return the initial and final states from an abstract FSM graph.   *   * @param array $graph   *   A concrete FSM derived from the abstract FSM.   *   * @return array   */  protected function getSpecialStates($graph) {    $ret = array(NULL, NULL);    foreach ($graph['states'] as $state) {      if (isset($state['type']) && $state['type'] == 'initial') {        $ret[0]  = $state['name'];      }      if (isset($state['type']) && $state['type'] == 'final') {        $ret[1]  = $state['name'];      }      if (isset($ret[0]) && isset($ret[1])) {        break;      }    }    return $ret;  }  /**   * Return the transitions (triggers) available from a given state   *   * @param array $graph   *   A concrete Machine.   * @param string $id   *   State id in the abstract graph (not id in the XML FSM).   *   * @return array   */  protected function getTransitionsForState($graph, $id) {    $ret = array();    foreach ($graph['transitions'] as $txid => $transition) {      // echo "id = $id, txid = $txid from = " . $transition['from'] . PHP_EOL;      if ($transition['from'] == $id) {        $ret[] = $txid;      }    }    return $ret;  }  /**   * Generate the XML FSM file and optionally PHP stubs file.   *   * Source is an abstract FSM representation as an array of states/transitions.   *   * @param array $graph   * @param string $prefix   * @param boolean $php   * @param boolean $overwrite   *   * @todo take $overwrite and $php into account   *   * @deprecated   */  public function oldSaveFsm($graph, $prefix = 'fsm', $php = FALSE, $overwrite = FALSE) {    echo "This is a dump for FSM $prefix"      . ', ' . ($php ? 'with' : 'without') . ' PHP generation'      . ', ' . ($overwrite ? 'with' : 'without') . ' overwrite mode'      . ".\n";    $doc = new DOMDocument('1.0', 'utf-8');    $comment = new DOMComment(' Generated by '      . basename(__FILE__, '.php')      . " version " . Fsm_Writer::VERSION      . " ");    $doc->appendChild($comment);    $fsm = new DOMElement('fsm');    $doc->appendChild($fsm);    $fsm->setAttribute('fsm_version',   "1.3");    $fsm->setAttribute('idle_work',     "1");    $fsm->setAttribute('allow_actions', "1");    list($init, $final) = $this->getSpecialStates($graph);    $fsm->setAttribute('init', $init);    $fsm->setAttribute('final', $final);    foreach ($graph['states'] as $id => $state) {      $state_node = new DOMElement('state');      $fsm->appendChild($state_node);      // setIdAttribute not yet implemented in php 5.1      $state_node->setAttribute('id', $state['name']);      $transitions = $this->getTransitionsForState($graph, $id);      /**       * FGM on ne peut pas simplement boucler ainsi: on a dans le graphe       * abstrait une transition par évènement, alors que dans la FSM les       * transitions partant du même état sont groupées. En se contentant       * de boucler ainsi, on crée un "event" pour chaque transition alors       * qu'il faut créer 1 event et "n" next.       */      foreach ($transitions as $transition_id) {        $event_node = new DOMElement('event');        $state_node->appendChild($event_node);        $transition = $graph['transitions'][$transition_id];        $event_node->setAttribute('name', $transition['trigger']);        /**         * @todo support other event types         */        $event_node->setAttribute('type', 'string');      }    }    $doc->save("$prefix.xml");    print_r($graph['transitions']);  }  /**   * Add a new state to the transitions table.   *   * @param string $name   *   * @return integer 0 on success, < 0 on failure   */  public function addState($name) {    // echo func_name() . "($name)";    if (array_key_exists($name, $this->fTransitions)) {      $ret = -1;    }    else {      $this->fTransitions[$name] = array();      $ret = 0;    }    // echo ", ret = $ret\n";    return $ret;  }  /**   * Add an event definition (name + optional type of handler) to an existing state   *   * @param string $state   * @param string $event   * @param string $type   *   * @return integer   *   - 0 on success   *   - < 0 on failure   */  public function addEvent($state, $event, $type = 'string') {    if (!array_key_exists($state, $this->fTransitions)) {      $ret = -1;    }    elseif (array_key_exists($event, $this->fTransitions[$state])) {      $ret = -2;    }    // state exists, event doesn't: OK    else {      $this->fTransitions[$state][$event] = array();      $this->fTransitions[$state][$event]["#type"] = $type;      $ret = 0;    }    return $ret;  }  /**   * Add an outcome definition to an existing event:   * - event handler result   * - next state   * - event to fire after transition to next state   * All outcome fields are optional   *   * @param name $state   * @param name $event   *   * @return integer   *   - 0 on success   *   - < 0 on failure   */  public function addOutcome($state, $event,    $result     = NULL,  // if null, single possible outcome; only possible on void events    $next_state = NULL,  // if null, stay on same state    $action     = NULL)  // if null, do not send an event after transitioning    {    $t = &$this->fTransitions;    if (!array_key_exists($state, $t)) {      $ret = -1;    }    elseif (!array_key_exists($event, $t[$state])) {      $ret = -2;    }    elseif (($result == NULL) && ($t[$state][$event]['#type'] <> 'void')) {      $ret = -3; // Undefined, single, results are only available for void events    }    elseif (($result != NULL) && array_key_exists($result, $t[$state][$event])) {      $ret = -4; // results must be unique per event    }    else {      $t[$state][$event][$result] = array($next_state, $action);      $ret = 0;    }    return $ret;  }  /**   * Defines the special states in the FSM   *   * @param string $kind Finite_State_Machine::INIT_STATE or ..FINAL_STATE   * @param string $name   */  public function setSpecialState($kind, $name) {    // echo func_name() . "($kind, $name)\n";    if (($kind != Machine::INIT_STATE) && ($kind != Machine::FINAL_STATE)) {      $ret = -1; // unknown special state    }    elseif (!array_key_exists($name, $this->fTransitions)) {      $ret = -2; // non existent state declared as special    }    else {      $this->fTransitions["#$kind"] = $name;      $ret = 0;    }    return $ret;  }  /*   * Generate the XML FSM file and optionally PHP stubs file.   *   * Source is a valid instance of FSM.   *   * @param string $prefix   * @param boolean $php   * @param boolean $overwrite   *   * @todo take $overwrite and $php into account   *   * @throws Exception from within checkTransitions   */  public function saveFsm($prefix = 'fsm', $php = FALSE, $overwrite = FALSE) {    $this->checkTransitions();    $t = &$this->fTransitions;    echo "This is a dump for FSM $prefix"      . ', ' . ($php ? 'with' : 'without') . ' PHP generation'      . ', ' . ($overwrite ? 'with' : 'without') . ' overwrite mode'      . ".\n";    $doc = new DOMDocument('1.0', 'utf-8');    $comment = new DOMComment(' Generated by '      . basename(__FILE__, '.php')      . " version " . Writer::FORMAT_VERSION      . " on FSM version " . Machine::VERSION      . " ");    $doc->appendChild($comment);    $fsm = new DOMElement('fsm');    $doc->appendChild($fsm);    $fsm->setAttribute('fsm_version',   "1.3");    $fsm->setAttribute('idle_work',     "1");    $fsm->setAttribute('allow_actions', "1");    $fsm->setAttribute('init',  $t['#' . Machine::INIT_STATE]);    $fsm->setAttribute('final', $t['#' . Machine::FINAL_STATE]);    foreach ($this->fTransitions as $stateName => $state) {      if ($stateName[0] == '#') {        continue; // ignore special state definitions      }      $stateNode = new DOMElement('state', "\n");      $fsm->appendChild($stateNode);      $stateNode->setAttribute('id', $stateName);      foreach ($state as $eventName => $event) {        $eventNode = new DOMElement('event', "\n");        $stateNode->appendChild($eventNode);        $eventNode->setAttribute('name', $eventName);        $eventNode->setAttribute('type', $event['#type']);        foreach ($event as $outcomeName => $outcome) {          if ($outcomeName[0] == '#') {            continue; // ignore special outcome definitions (event type)          }          $outcomeNode = new DOMElement('next', "\n");          $eventNode->appendChild($outcomeNode);          /**           * Generated FSMs currently always use the "string" event handler type,           * meaning they always have outcome results. This will not always be           * the case, hence this test (think ftp.xml)           */          if (!empty($outcomeName)) {            $outcomeNode->setAttribute('result', $outcomeName);          }          if (!empty($outcome[0])) {            $outcomeNode->setAttribute('state', $outcome[0]);          }          if (!empty($outcome[1])) {            $outcomeNode->setAttribute('action', $outcome[1]);          }        }      }    }    $doc->save("$prefix.xml");  }}
 |