D7net
Home
Console
Upload
information
Create File
Create Folder
About
Tools
:
/
opt
/
lampp
/
lib
/
php
/
FSM
/
Filename :
GraphViz.php
back
Copy
<?php /* vim: set expandtab softtabstop=4 tabstop=4 shiftwidth=4: */ /** * Copyright (c) 2007-2008 Philippe Jausions / 11abacus * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. * * @package FSM * @author Philippe Jausions <jausions@php.net> * @copyright 2007 Philippe Jausions / 11abacus * @license http://www.11abacus.com/license/NewBSD.php New BSD License * @version CVS: $Id: GraphViz.php 252630 2008-02-10 22:07:09Z jon $ */ /** * Requires PEAR packages */ require_once 'FSM.php'; require_once 'Image/GraphViz.php'; /** * FSM to Image_GraphViz converter * * This class extends the FSM class to have access to private properties. * It is not intended to be used as a FSM instance. * * PHP 5 or later is recommended to be able to handle action that return a new * state. * * @package FSM * @author Philippe Jausions <jausions@php.net> * @copyright (c) 2007 by Philippe Jausions / 11abacus * @since 1.3.0 */ class FSM_GraphViz extends FSM { /** * Machine instance * * @var FSM * @access protected */ var $_fsm; /** * Action name callback * * @var string * @access protected */ var $_actionNameCallback; /** * Constructor * * @param FSM &$machine instance to convert * * @access public */ function FSM_GraphViz(&$machine) { $this->_fsm =& $machine; $this->_actionNameCallback = array(&$this, '_getActionName'); } /** * Sets the callback for the action name * * @param mixed $callback * * @return boolean TRUE on success, PEAR_Error on error * @access public */ function setActionNameCallback($callback) { if (!is_callable($callback)) { return PEAR::raiseError('Not a valid callback'); } $this->_actionNameCallback = $callback; return true; } /** * Converts an FSM to an instance of Image_GraphViz * * @param string $name Name for the graph * @param boolean $strict Whether to collapse multiple edges between * same nodes. * * @return Image_GraphViz instance or PEAR_Error on failure * @access public */ function &export($name = 'FSM', $strict = true) { if (!is_a($this->_fsm, 'FSM')) { $error = PEAR::raiseError('Not a FSM instance'); return $error; } $g = new Image_GraphViz(true, null, $name, $strict); // Initial state $attr = array('shape' => 'invhouse'); $g->addNode($this->_fsm->_initialState, $attr); $nodes = array($this->_fsm->_initialState => $this->_fsm->_initialState); $_t = '_transitions'; do { foreach ($this->_fsm->$_t as $input => $t) { if ($_t == '_transitions') { list($symbol, $state) = explode(',', $input, 2); } else { $state = $input; $symbol = ''; } list($nextState, $action) = $t; if (!array_key_exists($nextState, $nodes)) { $g->addNode($nextState); $nodes[$nextState] = $nextState; } if (!array_key_exists($state, $nodes)) { $g->addNode($state); $nodes[$state] = $state; } if (strlen($symbol)) { $g->addEdge(array($state => $nextState), array('label' => $symbol)); } else { $g->addEdge(array($state => $nextState)); } $this->_addAction($g, $nodes, $action, $nextState); } if ($_t == '_transitions') { $_t = '_transitionsAny'; } else { $_t = false; } } while ($_t); // Add default transition if ($this->_defaultTransition) { list($nextState, $action) = $this->_defaultTransition; if (!array_key_exists($nextState, $nodes)) { $g->addNode($nextState, array('style' => 'dotted')); $nodes[$nextState] = $nextState; } $this->_addAction($g, $nodes, $action, $nextState, true); } return $g; } /** * Adds an action into the graph * * @param Image_GraphViz &$graph instance to add the action to * @param array &$nodes list of nodes * @param mixed $action callback * @param string $state start state * @param boolean $default whether this is the action tied to the default * transition * * @return void * @access protected */ function _addAction(&$graph, &$nodes, $action, $state, $default = false) { $actionName = call_user_func($this->_actionNameCallback, $action); if (strlen($actionName)) { $attr = array(); if ($default) { $attr['style'] = 'dotted'; } if (!array_key_exists($actionName, $nodes)) { $graph->addNode($actionName, array_merge($attr, array('shape' => 'box'))); $nodes[$actionName] = $actionName; } $graph->addEdge(array($state => $actionName), $attr); // Any new states out of action? $states = $this->_getStatesReturnedByAction($action); foreach ($states as $state) { if (!array_key_exists($state, $nodes)) { $graph->addNode($state, $attr); $nodes[$state] = $state; } $graph->addEdge(array($actionName => $state), $attr); } } } /** * Returns an symbol-node name * * @param string $symbol * @param string $state * * @return string * @access protected */ function _getSymbolName($symbol, $state) { return $symbol.', '.$state; } /** * Returns an action as string * * @param mixed $callback action * * @return string * @access protected */ function _getActionName($callback) { if (!is_callable($callback)) { return null; } if (!is_array($callback)) { return $callback.'()'; } if (is_object($callback[0])) { return get_class($callback[0]).'::'.$callback[1].'()'; } return $callback[0].'::'.$callback[1].'()'; } /** * Analyzes callback for possible new state(s) returned * * PHP version 5 * * This methods requires the use of the Reflection API to parse the * doc block and looks for the @return declaration. * * If the callback shall return new states, @return should specify a string * followed by a <ul></ul> containing a list of new states inside <li></li> * tags. * * Example of doc block for action callback: * <code> * /** * * This method does something then returns a new status * * * * \@param string $symbol * * \@param mixed $payload * * * * \@return string One of * * <ul> * * <li>RIPE</li> * * <li>NOT_RIPE</li> * * <ul> * * \@access public * {@*} * function checkRipeness($symbol, $payload) * { * return ($symbol == 'Orange') ? 'RIPE' : 'NOT_RIPE'; * } * </code> * * @param mixed $callback callback to analyze * * @return array a list of possible new states returned by the callback * @access protected */ function _getStatesReturnedByAction($callback) { if (version_compare(PHP_VERSION, '5.1.0') < 0 || !is_callable($callback)) { return array(); } if (!is_array($callback)) { $reflector = new ReflectionFunction($callback); } else { $reflector = new ReflectionMethod($callback[0], $callback[1]); } $doc = $reflector->getDocComment(); // We're only interested in the docBlock from @return and on $returnPos = strpos($doc, '* @return'); if ($returnPos === false) { return array(); } $returnDoc = trim(substr($doc, $returnPos + 9)); // Returning a string (i.e. new state name)? if (strncasecmp($returnDoc, 'string', 6) != 0) { return array(); } // Get the list of possible new states $length = strpos($returnDoc, '* @'); if (!$length) { $length = strlen($returnDoc); } $listDoc = substr($returnDoc, 0, $length); if (!preg_match_all('~<li>([^\s<]+?).*</li>~Uis', $listDoc, $list)) { return array(); } return $list[1]; } }