-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Workflow] Fixed graphviz dumper for state machine #20583
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,7 +26,7 @@ | |
*/ | ||
class GraphvizDumper implements DumperInterface | ||
{ | ||
private static $defaultOptions = array( | ||
protected static $defaultOptions = array( | ||
'graph' => array('ratio' => 'compress', 'rankdir' => 'LR'), | ||
'node' => array('fontsize' => 9, 'fontname' => 'Arial', 'color' => '#333333', 'fillcolor' => 'lightblue', 'fixedsize' => true, 'width' => 1), | ||
'edge' => array('fontsize' => 9, 'fontname' => 'Arial', 'color' => '#333333', 'arrowhead' => 'normal', 'arrowsize' => 0.5), | ||
|
@@ -58,7 +58,10 @@ public function dump(Definition $definition, Marking $marking = null, array $opt | |
.$this->endDot(); | ||
} | ||
|
||
private function findPlaces(Definition $definition, Marking $marking = null) | ||
/** | ||
* @internal | ||
*/ | ||
protected function findPlaces(Definition $definition, Marking $marking = null) | ||
{ | ||
$places = array(); | ||
|
||
|
@@ -79,7 +82,10 @@ private function findPlaces(Definition $definition, Marking $marking = null) | |
return $places; | ||
} | ||
|
||
private function findTransitions(Definition $definition) | ||
/** | ||
* @internal | ||
*/ | ||
protected function findTransitions(Definition $definition) | ||
{ | ||
$transitions = array(); | ||
|
||
|
@@ -93,37 +99,38 @@ private function findTransitions(Definition $definition) | |
return $transitions; | ||
} | ||
|
||
private function addPlaces(array $places) | ||
/** | ||
* @internal | ||
*/ | ||
protected function addPlaces(array $places) | ||
{ | ||
$code = ''; | ||
|
||
foreach ($places as $id => $place) { | ||
$code .= sprintf(" place_%s [label=\"%s\", shape=circle%s];\n", | ||
$this->dotize($id), | ||
$id, | ||
$this->addAttributes($place['attributes']) | ||
); | ||
$code .= sprintf(" place_%s [label=\"%s\", shape=circle%s];\n", $this->dotize($id), $id, $this->addAttributes($place['attributes'])); | ||
} | ||
|
||
return $code; | ||
} | ||
|
||
private function addTransitions(array $transitions) | ||
/** | ||
* @internal | ||
*/ | ||
protected function addTransitions(array $transitions) | ||
{ | ||
$code = ''; | ||
|
||
foreach ($transitions as $place) { | ||
$code .= sprintf(" transition_%s [label=\"%s\", shape=box%s];\n", | ||
$this->dotize($place['name']), | ||
$place['name'], | ||
$this->addAttributes($place['attributes']) | ||
); | ||
$code .= sprintf(" transition_%s [label=\"%s\", shape=box%s];\n", $this->dotize($place['name']), $place['name'], $this->addAttributes($place['attributes'])); | ||
} | ||
|
||
return $code; | ||
} | ||
|
||
private function findEdges(Definition $definition) | ||
/** | ||
* @internal | ||
*/ | ||
protected function findEdges(Definition $definition) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO, this should not be protected extension points. This seems like the wrong way to reuse part of the GraphvizDumper (fixing the dumping of transitions to stop assuming unique names might require refactoring private methods, but making them protected forbids this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't really understand what you mean. Could you elaborate? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you are talking about composition, it's not really possible. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added |
||
{ | ||
$dotEdges = array(); | ||
|
||
|
@@ -147,7 +154,10 @@ private function findEdges(Definition $definition) | |
return $dotEdges; | ||
} | ||
|
||
private function addEdges($edges) | ||
/** | ||
* @internal | ||
*/ | ||
protected function addEdges(array $edges) | ||
{ | ||
$code = ''; | ||
|
||
|
@@ -163,7 +173,10 @@ private function addEdges($edges) | |
return $code; | ||
} | ||
|
||
private function startDot(array $options) | ||
/** | ||
* @internal | ||
*/ | ||
protected function startDot(array $options) | ||
{ | ||
return sprintf("digraph workflow {\n %s\n node [%s];\n edge [%s];\n\n", | ||
$this->addOptions($options['graph']), | ||
|
@@ -172,12 +185,23 @@ private function startDot(array $options) | |
); | ||
} | ||
|
||
private function endDot() | ||
/** | ||
* @internal | ||
*/ | ||
protected function endDot() | ||
{ | ||
return "}\n"; | ||
} | ||
|
||
private function addAttributes($attributes) | ||
/** | ||
* @internal | ||
*/ | ||
protected function dotize($id) | ||
{ | ||
return strtolower(preg_replace('/[^\w]/i', '_', $id)); | ||
} | ||
|
||
private function addAttributes(array $attributes) | ||
{ | ||
$code = array(); | ||
|
||
|
@@ -188,7 +212,7 @@ private function addAttributes($attributes) | |
return $code ? ', '.implode(', ', $code) : ''; | ||
} | ||
|
||
private function addOptions($options) | ||
private function addOptions(array $options) | ||
{ | ||
$code = array(); | ||
|
||
|
@@ -198,9 +222,4 @@ private function addOptions($options) | |
|
||
return implode(' ', $code); | ||
} | ||
|
||
private function dotize($id) | ||
{ | ||
return strtolower(preg_replace('/[^\w]/i', '_', $id)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <[email protected]> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Workflow\Dumper; | ||
|
||
use Symfony\Component\Workflow\Definition; | ||
use Symfony\Component\Workflow\Marking; | ||
|
||
class StateMachineGraphvizDumper extends GraphvizDumper | ||
{ | ||
/** | ||
* {@inheritdoc} | ||
* | ||
* Dumps the workflow as a graphviz graph. | ||
* | ||
* Available options: | ||
* | ||
* * graph: The default options for the whole graph | ||
* * node: The default options for nodes (places) | ||
* * edge: The default options for edges | ||
*/ | ||
public function dump(Definition $definition, Marking $marking = null, array $options = array()) | ||
{ | ||
$places = $this->findPlaces($definition, $marking); | ||
$edges = $this->findEdges($definition); | ||
|
||
$options = array_replace_recursive(self::$defaultOptions, $options); | ||
|
||
return $this->startDot($options) | ||
.$this->addPlaces($places) | ||
.$this->addEdges($edges) | ||
.$this->endDot() | ||
; | ||
} | ||
|
||
/** | ||
* @internal | ||
*/ | ||
protected function findEdges(Definition $definition) | ||
{ | ||
$edges = array(); | ||
|
||
foreach ($definition->getTransitions() as $transition) { | ||
foreach ($transition->getFroms() as $from) { | ||
foreach ($transition->getTos() as $to) { | ||
$edges[$from][] = array( | ||
'name' => $transition->getName(), | ||
'to' => $to, | ||
); | ||
} | ||
} | ||
} | ||
|
||
return $edges; | ||
} | ||
|
||
/** | ||
* @internal | ||
*/ | ||
protected function addEdges(array $edges) | ||
{ | ||
$code = ''; | ||
|
||
foreach ($edges as $id => $edges) { | ||
foreach ($edges as $edge) { | ||
$code .= sprintf(" place_%s -> place_%s [label=\"%s\" style=\"%s\"];\n", $this->dotize($id), $this->dotize($edge['to']), $edge['name'], 'solid'); | ||
} | ||
} | ||
|
||
return $code; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
<?php | ||
|
||
namespace Symfony\Component\Workflow\Tests\Dumper; | ||
|
||
use Symfony\Component\Workflow\Dumper\StateMachineGraphvizDumper; | ||
use Symfony\Component\Workflow\Marking; | ||
use Symfony\Component\Workflow\Tests\WorkflowBuilderTrait; | ||
|
||
class StateMachineGraphvizDumperTest extends \PHPUnit_Framework_TestCase | ||
{ | ||
use WorkflowBuilderTrait; | ||
|
||
private $dumper; | ||
|
||
public function setUp() | ||
{ | ||
$this->dumper = new StateMachineGraphvizDumper(); | ||
} | ||
|
||
public function testDumpWithoutMarking() | ||
{ | ||
$definition = $this->createComplexStateMachineDefinition(); | ||
|
||
$dump = $this->dumper->dump($definition); | ||
|
||
$expected = <<<'EOGRAPH' | ||
digraph workflow { | ||
ratio="compress" rankdir="LR" | ||
node [fontsize="9" fontname="Arial" color="#333333" fillcolor="lightblue" fixedsize="1" width="1"]; | ||
edge [fontsize="9" fontname="Arial" color="#333333" arrowhead="normal" arrowsize="0.5"]; | ||
|
||
place_a [label="a", shape=circle, style="filled"]; | ||
place_b [label="b", shape=circle]; | ||
place_c [label="c", shape=circle]; | ||
place_d [label="d", shape=circle]; | ||
place_a -> place_b [label="t1" style="solid"]; | ||
place_d -> place_b [label="t1" style="solid"]; | ||
place_b -> place_c [label="t2" style="solid"]; | ||
place_b -> place_d [label="t3" style="solid"]; | ||
} | ||
|
||
EOGRAPH; | ||
|
||
$this->assertEquals($expected, $dump); | ||
} | ||
|
||
public function testDumpWithMarking() | ||
{ | ||
$definition = $this->createComplexStateMachineDefinition(); | ||
$marking = new Marking(array('b' => 1)); | ||
|
||
$expected = <<<'EOGRAPH' | ||
digraph workflow { | ||
ratio="compress" rankdir="LR" | ||
node [fontsize="9" fontname="Arial" color="#333333" fillcolor="lightblue" fixedsize="1" width="1"]; | ||
edge [fontsize="9" fontname="Arial" color="#333333" arrowhead="normal" arrowsize="0.5"]; | ||
|
||
place_a [label="a", shape=circle, style="filled"]; | ||
place_b [label="b", shape=circle, color="#FF0000", shape="doublecircle"]; | ||
place_c [label="c", shape=circle]; | ||
place_d [label="d", shape=circle]; | ||
place_a -> place_b [label="t1" style="solid"]; | ||
place_d -> place_b [label="t1" style="solid"]; | ||
place_b -> place_c [label="t2" style="solid"]; | ||
place_b -> place_d [label="t3" style="solid"]; | ||
} | ||
|
||
EOGRAPH; | ||
|
||
$dump = $this->dumper->dump($definition, $marking); | ||
|
||
$this->assertEquals($expected, $dump); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we have a way to dump the state machine in workflow mode ? It is a subset after all.
But anyway, I agree it should not be the default dumping for state machines.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this of course means that it should be fixed to work properly, but I know how to do this (stop assuming that transition names are unique identifiers of the transition, as we stopped doing this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now, it's not really readable to dump a state machine with the workflow dumper. So IMHO, it's not useful to add a way to do it.