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"); } }