Grapher.php 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. <?php
  2. /**
  3. * Output converter for FSM from GraphViz
  4. *
  5. * This class converts a loaded FSM to a GraphViz DOT file.
  6. *
  7. * @copyright (c) 2007-2012 Ouest Systèmes Informatiques
  8. * @author Frederic G. MARAND
  9. * @license CeCILL 2.0
  10. * @link http://wiki.audean.com/fsm/fsm
  11. * @since FSM 1.6
  12. */
  13. namespace Osinet\FiniteStateMachine;
  14. class Grapher {
  15. const STATE_SHAPE = 'octagon';
  16. const EVENT_SHAPE = 'egg';
  17. /**
  18. * The FSM being plotted.
  19. *
  20. * @var Machine
  21. */
  22. public $m;
  23. /**
  24. * The Image_Graphviz canvas.
  25. *
  26. * @var Image_Graphviz
  27. */
  28. public $g;
  29. /**
  30. * Debug level. >= 0.
  31. *
  32. * @var int
  33. */
  34. public $debug = NULL;
  35. /**
  36. * A hash of state name to GraphViz name.
  37. *
  38. * @var array
  39. */
  40. public $states = array();
  41. /**
  42. * @param Machine $m
  43. */
  44. public function __construct(Machine $m, $values = array()) {
  45. $this->m = $m;
  46. $default_values = array(
  47. 'debug' => 0,
  48. );
  49. $values = array_intersect_key($values, $default_values);
  50. $values += $default_values;
  51. foreach ($values as $key => $value) {
  52. $this->$key = $value;
  53. }
  54. // PEAR is not currently PSR-0 compliant.
  55. // As a consequence, strict PSR-0 autoloading will not work for it.
  56. require 'Image/GraphViz.php';
  57. $this->g = new \Image_Graphviz();
  58. }
  59. public function render() {
  60. $i = 0;
  61. $g = &$this->g;
  62. // First make sure all states have labeled nodes, so that forward
  63. // declarations of states occurring in transitions find the proper state id.
  64. foreach ($this->m->fTransitions as $state => $events) {
  65. $label = $state;
  66. $state .= "_$i";
  67. $this->states[$label] = $state;
  68. $g->addNode($state, array(
  69. 'label' => $label,
  70. 'shape' => self::STATE_SHAPE,
  71. ));
  72. $i++;
  73. }
  74. foreach ($this->m->fTransitions as $state => $events) {
  75. $state_id = $this->states[$state];
  76. foreach ($events as $event => $outcomes) {
  77. if ($this->debug) {
  78. echo "State $state Event $event " . count($outcomes) . " outcomes\n";
  79. print_r($outcomes);
  80. }
  81. if (0 && count($outcomes) == 1) {
  82. $outcome = reset($outcomes);
  83. // Only one outcome: we do not care for the result.
  84. echo "State $state event $event, outcome:"; print_r($outcome); echo "\n";
  85. $edge = array($state_id => $this->states[$outcome[0]]);
  86. $edge_label = "\n:" . $outcome[1];
  87. $edge_attributes = empty($outcome[1])
  88. ? array()
  89. : array('label' => $edge_label);
  90. $g->addEdge($edge, $edge_attributes);
  91. }
  92. else {
  93. $event_label = $event;
  94. $event_id = "{$state_id}_$event";
  95. $g->addNode($event_id, array(
  96. 'label' => $event_label,
  97. 'shape' => self::EVENT_SHAPE,
  98. ));
  99. $g->addEdge(array($state_id => $event_id));
  100. foreach ($outcomes as $result => $next) {
  101. list($next_state, $next_action) = $next;
  102. $edge = array($event_id => $this->states[$next_state]) ;
  103. $edge_label_parts = array();
  104. if (!empty($result)) {
  105. $edge_label_parts[] = "[$result]";
  106. }
  107. if (!empty($next_action)) {
  108. $edge_label_parts[] = ":$next_action";
  109. }
  110. if (empty($edge_label_parts)) {
  111. echo "No label!\n";
  112. $g->addEdge($edge);
  113. }
  114. else {
  115. $edge_label = implode("\n", $edge_label_parts);
  116. $edge_attributes = array(
  117. 'label' => $edge_label,
  118. );
  119. $g->addEdge($edge, $edge_attributes);
  120. }
  121. }
  122. }
  123. }
  124. }
  125. return $g->parse();
  126. }
  127. }