Fsm_Writer.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. <?php
  2. /**
  3. * Finite state machine writer.
  4. *
  5. * Keeping the FSM writer (generation) functions out of the root class minimize
  6. * the code volume for FSM readers, which should represent the majority of FSM
  7. * users.
  8. *
  9. * @copyright (c) 2007 OSI
  10. * @author Frédéric G. MARAND
  11. * @license Licensed under the CeCILL 2.0
  12. * @version CVS: $Id: Fsm_Writer.php,v 1.1 2007-06-10 19:42:35 marand Exp $
  13. * @link http://wiki.audean.com/fsm/fsm
  14. * @since Not applicable yet
  15. * @package fsm
  16. * @subpackage fsm.core
  17. */
  18. /**
  19. * needed notably for autoload and func_name()
  20. */
  21. require_once('misc.php');
  22. $erFiniteStateMachine = error_reporting(E_ALL|E_STRICT);
  23. /**
  24. * Read/Write concrete FSM class.
  25. *
  26. * This (concrete) class extends the default abstract
  27. * Finite_State_Machine to add write behaviours. It is to be used by
  28. * applications wishing to write FSM files. Other applications should not
  29. * use it to avoid code bloat.
  30. * @package fsm
  31. * @subpackage fsm.core
  32. */
  33. class Fsm_Writer extends Finite_State_Machine
  34. {
  35. const VERSION = '$Id: Fsm_Writer.php,v 1.1 2007-06-10 19:42:35 marand Exp $';
  36. public function __construct()
  37. {
  38. $this->f_transitions = array();
  39. parent::__construct();
  40. }
  41. /**
  42. * Return the initial and final states from an abstract FSM graph
  43. *
  44. * @param array $graph Abstract FSM
  45. * @return array
  46. */
  47. protected function getSpecialStates($graph)
  48. {
  49. $ret[0] = NULL;
  50. $ret[1] = NULL;
  51. foreach ($graph['states'] as $state)
  52. {
  53. if (isset($state['type']) && $state['type'] == 'initial')
  54. {
  55. $ret[0] = $state['name'];
  56. }
  57. if (isset($state['type']) && $state['type'] == 'final')
  58. {
  59. $ret[1] = $state['name'];
  60. }
  61. if (isset($ret[0]) && isset($ret[1]))
  62. {
  63. break;
  64. }
  65. }
  66. return $ret;
  67. }
  68. /**
  69. * Return the transitions (triggers) available from a given state
  70. *
  71. * @param array $graph Abstract FSM
  72. * @param string $id State id in the abstract graph (not id in the XML FSM)
  73. */
  74. protected function getTransitionsForState($graph, $id)
  75. {
  76. $ret = array();
  77. foreach ($graph['transitions'] as $txid => $transition)
  78. {
  79. // echo "id = $id, txid = $txid from = " . $transition['from'] . PHP_EOL;
  80. if ($transition['from'] == $id)
  81. {
  82. $ret[] = $txid;
  83. }
  84. }
  85. return $ret;
  86. }
  87. /**
  88. * Generate the XML FSM file and optionally PHP stubs file from an abstract
  89. * FSM representation as an array of states and transitions
  90. *
  91. * @param array $graph
  92. * @param string $prefix
  93. * @param boolean $php
  94. * @param boolean $overwrite
  95. * @todo take $overwrite and $php into account
  96. * @deprecated
  97. */
  98. public function old_save_fsm($graph, $prefix = 'fsm', $php = FALSE, $overwrite = FALSE)
  99. {
  100. echo "This is a dump for FSM $prefix"
  101. . ', ' . ($php ? 'with' : 'without') . ' PHP generation'
  102. . ', ' . ($overwrite ? 'with' : 'without') . ' overwrite mode'
  103. . ".\n";
  104. $doc = new DOMDocument('1.0', 'utf-8');
  105. $comment = new DOMComment(' Generated by '
  106. . basename(__FILE__, '.php')
  107. . " version " . Fsm_Writer::VERSION
  108. . " ");
  109. $doc->appendChild($comment);
  110. $fsm = new DOMElement('fsm');
  111. $doc->appendChild($fsm);
  112. $fsm->setAttribute('fsm_version', "1.3");
  113. $fsm->setAttribute('idle_work', "1");
  114. $fsm->setAttribute('allow_actions', "1");
  115. list($init, $final) = $this->getSpecialStates($graph);
  116. $fsm->setAttribute('init', $init);
  117. $fsm->setAttribute('final', $final);
  118. foreach ($graph['states'] as $id => $state)
  119. {
  120. $state_node = new DOMElement('state');
  121. $fsm->appendChild($state_node);
  122. $state_node->setAttribute('id', $state['name']); // setIdAttribute not yet implemented in php 5.1
  123. $transitions = $this->getTransitionsForState($graph, $id);
  124. /**
  125. * FGM on ne peut pas simplement boucler ainsi: on a dans le graphe
  126. * abstrait une transition par évènement, alors que dans la FSM les
  127. * transitions partant du même état sont groupées. En se contentant
  128. * de boucler ainsi, on crée un "event" pour chaque transition alors
  129. * qu'il faut créer 1 event et "n" next.
  130. */
  131. foreach ($transitions as $transition_id)
  132. {
  133. $event_node = new DOMElement('event');
  134. $state_node->appendChild($event_node);
  135. $transition = $graph['transitions'][$transition_id];
  136. $event_node->setAttribute('name', $transition['trigger']);
  137. /**
  138. * @todo support other event types
  139. */
  140. $event_node->setAttribute('type', 'string');
  141. }
  142. }
  143. $doc->save("$prefix.xml");
  144. print_r($graph['transitions']);
  145. }
  146. /**
  147. * Add a new state to the transitions table
  148. *
  149. * @param string $name
  150. * @return integer 0 on success, < 0 on failure
  151. */
  152. public function addState($name)
  153. {
  154. // echo func_name() . "($name)";
  155. if (array_key_exists($name, $this->f_transitions))
  156. {
  157. $ret = -1;
  158. }
  159. else
  160. {
  161. $this->f_transitions[$name] = array();
  162. $ret = 0;
  163. }
  164. // echo ", ret = $ret\n";
  165. return $ret;
  166. }
  167. /**
  168. * Add an event definition (name + optional type of handler) to an existing state
  169. *
  170. * @param string $state
  171. * @param string $event
  172. * @param string $type
  173. * @return integer 0 on success, < 0 on failure
  174. */
  175. public function addEvent($state, $event, $type = 'string')
  176. {
  177. if (!array_key_exists($state, $this->f_transitions))
  178. {
  179. $ret = -1;
  180. }
  181. elseif (array_key_exists($event, $this->f_transitions[$state]))
  182. {
  183. $ret = -2;
  184. }
  185. else // state exists, event doesn't: OK
  186. {
  187. $this->f_transitions[$state][$event] = array();
  188. $this->f_transitions[$state][$event]["#type"] = $type;
  189. $ret = 0;
  190. }
  191. return $ret;
  192. }
  193. /**
  194. * Add an outcome definition to an existing event:
  195. * - event handler result
  196. * - next state
  197. * - event to fire after transition to next state
  198. * All outcome fields are optional
  199. *
  200. * @param name $state
  201. * @param name $event
  202. * @return integer 0 on success, < 0 on failure
  203. */
  204. public function addOutcome($state, $event,
  205. $result = NULL, // if null, single possible outcome; only possible on void events
  206. $next_state = NULL, // if null, stay on same state
  207. $action = NULL) // if null, do not send an event after transitioning
  208. {
  209. $t = &$this->f_transitions;
  210. if (!array_key_exists($state, $t))
  211. {
  212. $ret = -1;
  213. }
  214. elseif (!array_key_exists($event, $t[$state]))
  215. {
  216. $ret = -2;
  217. }
  218. elseif (($result == NULL) && ($t[$state][$event]['#type'] <> 'void'))
  219. {
  220. $ret = -3; // Undefined, single, results are only available for void events
  221. }
  222. elseif (($result != NULL) && array_key_exists($result, $t[$state][$event]))
  223. {
  224. $ret = -4; // results must be unique per event
  225. }
  226. else
  227. {
  228. $t[$state][$event][$result] = array($next_state, $action);
  229. $ret = 0;
  230. }
  231. return $ret;
  232. }
  233. /**
  234. * Defines the special states in the FSM
  235. *
  236. * @param string $kind Finite_State_Machine::INIT_STATE or ..FINAL_STATE
  237. * @param string $name
  238. */
  239. public function setSpecialState($kind, $name)
  240. {
  241. // echo func_name() . "($kind, $name)\n";
  242. if (($kind != Finite_State_Machine::INIT_STATE) && ($kind != Finite_State_Machine::FINAL_STATE))
  243. {
  244. $ret = -1; // unknown special state
  245. }
  246. elseif (!array_key_exists($name, $this->f_transitions))
  247. {
  248. $ret = -2; // non existent state declared as special
  249. }
  250. else
  251. {
  252. $this->f_transitions["#$kind"] = $name;
  253. $ret = 0;
  254. }
  255. return $ret;
  256. }
  257. /*
  258. * Generate the XML FSM file and optionally PHP stubs file from a valid
  259. * instanc of Finite_State_Machine
  260. *
  261. * @param string $prefix
  262. * @param boolean $php
  263. * @param boolean $overwrite
  264. * @todo take $overwrite and $php into account
  265. * @throws Exception from within _check_transitions
  266. */
  267. public function save_fsm($prefix = 'fsm', $php = FALSE, $overwrite = FALSE)
  268. {
  269. $this->_check_transitions();
  270. $t = &$this->f_transitions;
  271. echo "This is a dump for FSM $prefix"
  272. . ', ' . ($php ? 'with' : 'without') . ' PHP generation'
  273. . ', ' . ($overwrite ? 'with' : 'without') . ' overwrite mode'
  274. . ".\n";
  275. $doc = new DOMDocument('1.0', 'utf-8');
  276. $comment = new DOMComment(' Generated by '
  277. . basename(__FILE__, '.php')
  278. . " version " . Fsm_Writer::VERSION
  279. . " on FSM version " . Finite_State_Machine::VERSION
  280. . " ");
  281. $doc->appendChild($comment);
  282. $fsm = new DOMElement('fsm');
  283. $doc->appendChild($fsm);
  284. $fsm->setAttribute('fsm_version', "1.3");
  285. $fsm->setAttribute('idle_work', "1");
  286. $fsm->setAttribute('allow_actions', "1");
  287. $fsm->setAttribute('init', $t['#' . Finite_State_Machine::INIT_STATE]);
  288. $fsm->setAttribute('final', $t['#' . Finite_State_Machine::FINAL_STATE]);
  289. foreach ($this->f_transitions as $state_name => $state)
  290. {
  291. if ($state_name[0] == '#')
  292. {
  293. continue; // ignore special state definitions
  294. }
  295. $state_node = new DOMElement('state', "\n");
  296. $fsm->appendChild($state_node);
  297. $state_node->setAttribute('id', $state_name);
  298. foreach ($state as $event_name => $event)
  299. {
  300. $event_node = new DOMElement('event', "\n");
  301. $state_node->appendChild($event_node);
  302. $event_node->setAttribute('name', $event_name);
  303. $event_node->setAttribute('type', $event['#type']);
  304. foreach ($event as $outcome_name => $outcome)
  305. {
  306. if ($outcome_name[0] == '#')
  307. {
  308. continue; // ignore special outcome definitions (event type)
  309. }
  310. $outcome_node = new DOMElement('next', "\n");
  311. $event_node->appendChild($outcome_node);
  312. /**
  313. * Generated FSMs currently always use the "string" event handler type,
  314. * meaning they always have outcome results. This will not always be
  315. * the case, hence this test (think ftp.xml)
  316. */
  317. if (!empty($outcome_name))
  318. {
  319. $outcome_node->setAttribute('result', $outcome_name);
  320. }
  321. if (!empty($outcome[0]))
  322. {
  323. $outcome_node->setAttribute('state', $outcome[0]);
  324. }
  325. if (!empty($outcome[1]))
  326. {
  327. $outcome_node->setAttribute('action', $outcome[1]);
  328. }
  329. }
  330. }
  331. }
  332. $doc->save("$prefix.xml");
  333. }
  334. }