- converted class names and directories for PSR0 - replaced the basic __autoload() by the canonical PSR0 loader example. - added a GraphViz-based Grapher class to plot Machine structures.
138 lines
3.7 KiB
PHP
138 lines
3.7 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Output converter for FSM from GraphViz
|
|
*
|
|
* This class converts a loaded FSM to a GraphViz DOT file.
|
|
*
|
|
* @copyright (c) 2007-2012 Ouest Systèmes Informatiques
|
|
* @author Frederic G. MARAND
|
|
* @license CeCILL 2.0
|
|
* @link http://wiki.audean.com/fsm/fsm
|
|
* @since FSM 1.6
|
|
*/
|
|
|
|
namespace OSInet\Finite_State_Machine;
|
|
|
|
class Grapher {
|
|
const STATE_SHAPE = 'octagon';
|
|
const EVENT_SHAPE = 'egg';
|
|
|
|
/**
|
|
* The FSM being plotted.
|
|
*
|
|
* @var Machine
|
|
*/
|
|
public $m;
|
|
|
|
/**
|
|
* The Image_Graphviz canvas.
|
|
*
|
|
* @var Image_Graphviz
|
|
*/
|
|
public $g;
|
|
|
|
/**
|
|
* Debug level. >= 0.
|
|
*
|
|
* @var int
|
|
*/
|
|
public $debug = NULL;
|
|
/**
|
|
* A hash of state name to GraphViz name.
|
|
*
|
|
* @var array
|
|
*/
|
|
public $states = array();
|
|
|
|
/**
|
|
* @param Machine $m
|
|
*/
|
|
public function __construct(Machine $m, $values = array()) {
|
|
$this->m = $m;
|
|
|
|
$default_values = array(
|
|
'debug' => 0,
|
|
);
|
|
$values = array_intersect_key($values, $default_values);
|
|
$values += $default_values;
|
|
foreach ($values as $key => $value) {
|
|
$this->$key = $value;
|
|
}
|
|
|
|
// PEAR is not currently PSR-0 compliant.
|
|
// As a consequence, strict PSR-0 autoloading will not work for it.
|
|
require 'Image/GraphViz.php';
|
|
$this->g = new \Image_Graphviz();
|
|
}
|
|
|
|
public function render() {
|
|
$i = 0;
|
|
$g = &$this->g;
|
|
// First make sure all states have labeled nodes, so that forward
|
|
// declarations of states occurring in transitions find the proper state id.
|
|
foreach ($this->m->fTransitions as $state => $events) {
|
|
$label = $state;
|
|
$state .= "_$i";
|
|
$this->states[$label] = $state;
|
|
$g->addNode($state, array(
|
|
'label' => $label,
|
|
'shape' => self::STATE_SHAPE,
|
|
));
|
|
$i++;
|
|
}
|
|
foreach ($this->m->fTransitions as $state => $events) {
|
|
$state_id = $this->states[$state];
|
|
foreach ($events as $event => $outcomes) {
|
|
if ($this->debug) {
|
|
echo "State $state Event $event " . count($outcomes) . " outcomes\n";
|
|
print_r($outcomes);
|
|
}
|
|
if (0 && count($outcomes) == 1) {
|
|
$outcome = reset($outcomes);
|
|
// Only one outcome: we do not care for the result.
|
|
echo "State $state event $event, outcome:"; print_r($outcome); echo "\n";
|
|
$edge = array($state_id => $this->states[$outcome[0]]);
|
|
$edge_label = "\n:" . $outcome[1];
|
|
$edge_attributes = empty($outcome[1])
|
|
? array()
|
|
: array('label' => $edge_label);
|
|
$g->addEdge($edge, $edge_attributes);
|
|
}
|
|
else {
|
|
$event_label = $event;
|
|
$event_id = "{$state_id}_$event";
|
|
$g->addNode($event_id, array(
|
|
'label' => $event_label,
|
|
'shape' => self::EVENT_SHAPE,
|
|
));
|
|
$g->addEdge(array($state_id => $event_id));
|
|
foreach ($outcomes as $result => $next) {
|
|
list($next_state, $next_action) = $next;
|
|
$edge = array($event_id => $this->states[$next_state]) ;
|
|
$edge_label_parts = array();
|
|
if (!empty($result)) {
|
|
$edge_label_parts[] = "[$result]";
|
|
}
|
|
if (!empty($next_action)) {
|
|
$edge_label_parts[] = ":$next_action";
|
|
}
|
|
if (empty($edge_label_parts)) {
|
|
echo "No label!\n";
|
|
$g->addEdge($edge);
|
|
}
|
|
else {
|
|
$edge_label = implode("\n", $edge_label_parts);
|
|
$edge_attributes = array(
|
|
'label' => $edge_label,
|
|
);
|
|
$g->addEdge($edge, $edge_attributes);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $g->parse();
|
|
}
|
|
}
|