123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335 |
- <?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\FiniteStateMachine;
- /**
- * 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 FiniteStateMachine 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);
- $state_node->setIdAttribute('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 FiniteStateMachine::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->setIdAttribute('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");
- }
- }
|