Writer.php 10.0 KB

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