php__phplib/OSInet/Finite_State_Machine/Grapher.php
Frederic G. MARAND a28ed41155 FSM: PSR-0 conversion, Grapher added.
- 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.
2012-04-18 00:10:46 +02:00

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