Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[Workflow] Made GraphvizDumper supports StateMachine #20497

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

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions src/Symfony/Component/Workflow/Dumper/GraphvizDumper.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ private function findTransitions(Definition $definition)
$transitions = array();

foreach ($definition->getTransitions() as $transition) {
$transitions[] = array(
$transitions[$transition->getName()] = array(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks wrong to me. Transition names are not unique in the definition (the only requirement is that transition starting from a given node have unique names)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stof This is what this key is about, removing duplicated transition names, since they generate useless code in the dot file.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If they have the same name, they have different from/to, so they should both be rendered on the graph separately (otherwise, the graph will lie).

Btw, for a finite state machine, we could change the graph to render transitions as edges directly (optionally, as rendering the graph in workflow mode is still useful), as they are known to have a single from and a single to

Copy link
Contributor Author

@HeahDude HeahDude Nov 14, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If they have the same name, they have different from/to, so they should both be rendered on the graph separately (otherwise, the graph will lie).

That not true for the way state machines have been implemented. It splits the transition that has many tos in many transitions with the same name.

See https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php#L416.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@HeahDude But this class is also able to dump Workflow graph.

'attributes' => array('shape' => 'box', 'regular' => true),
'name' => $transition->getName(),
);
Expand Down Expand Up @@ -126,21 +126,20 @@ private function addTransitions(array $transitions)
private function findEdges(Definition $definition)
{
$dotEdges = array();
$getEdge = function ($from, $to, $direction) use (&$dotEdges) {
$dotEdges[$from.$to.$direction] = array(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This kind of "hash" could collide.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They should, this is the point of it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the thing is that your dumping of the StateMachine graph does not represent the workflow being defined by the state machine, as you represent a single transition with multiple input and output instead of several transitions (the fact that the DI config of the StateMachine allows to define these transitions in a shorted way by grouping them does not mean they are the same transition in the StateMachine)
Your dumping is lying about the actual workflow being run.
The bug in the GraphvizDumper currently is that it uses transition names to identify them in the dot file, while they are not unique identifiers anymore (they were at the time the dumper was written).

Btw, this is why I said we should have another dumper being tailored only at state machines, and dumping them into a graph being more readable. This graph would represent transitions as edges on the graph (which is not possible in generic workflows, as transitions can have multiple input and output). But I would make this another dumper, or an option in this dumper, because dumping the StateMachine as a generic workflow graph can be useful too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright! Thank you again for your explanation, I'll work on that soon!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw, this is why I said we should have another dumper being tailored only at state machines, and dumping them into a graph being more readable

👍

'from' => $from,
'to' => $to,
'direction' => $direction,
);
};

foreach ($definition->getTransitions() as $transition) {
foreach ($transition->getFroms() as $from) {
$dotEdges[] = array(
'from' => $from,
'to' => $transition->getName(),
'direction' => 'from',
);
$getEdge($from, $transition->getName(), 'from');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getEdge is a weird name, as it does not get anything. It adds something

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok l'll change it to addName, it seems you all agree with that :)

}
foreach ($transition->getTos() as $to) {
$dotEdges[] = array(
'from' => $transition->getName(),
'to' => $to,
'direction' => 'to',
);
$getEdge($transition->getName(), $to, 'to');
}
}

Expand All @@ -152,10 +151,11 @@ private function addEdges($edges)
$code = '';

foreach ($edges as $edge) {
$from = 'from' === $edge['direction'];
$code .= sprintf(" %s_%s -> %s_%s [style=\"solid\"];\n",
'from' === $edge['direction'] ? 'place' : 'transition',
$from ? 'place' : 'transition',
$this->dotize($edge['from']),
'from' === $edge['direction'] ? 'transition' : 'place',
$from ? 'transition' : 'place',
$this->dotize($edge['to'])
);
}
Expand Down
74 changes: 74 additions & 0 deletions src/Symfony/Component/Workflow/Tests/Dumper/GraphvizDumperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@

class GraphvizDumperTest extends \PHPUnit_Framework_TestCase
{
/**
* @var GraphvizDumper
*/
private $dumper;

public function setUp()
Expand Down Expand Up @@ -36,6 +39,16 @@ public function testWorkflowWithMarking($definition, $marking, $expected)
$this->assertEquals($expected, $dump);
}

/**
* @dataProvider provideStateMachineDefinition
*/
public function testStateMachine($definition, $expected)
{
$dump = $this->dumper->dump($definition);

$this->assertEquals($expected, $dump);
}

public function provideWorkflowDefinitionWithMarking()
{
yield array(
Expand All @@ -57,6 +70,11 @@ public function provideWorkflowDefinitionWithoutMarking()
yield array($this->provideSimpleWorkflowDefinition(), $this->provideSimpleWorkflowDumpWithoutMarking());
}

public function provideStateMachineDefinition()
{
yield array($this->provideComplexStateMachineDefinition(), $this->provideComplexStateMachineDump());
}

public function provideComplexWorkflowDefinition()
{
$builder = new DefinitionBuilder();
Expand Down Expand Up @@ -85,6 +103,25 @@ public function provideSimpleWorkflowDefinition()
return $builder->build();
}

public function provideComplexStateMachineDefinition()
{
$builder = new DefinitionBuilder();

$builder->addPlaces(range('a', 'g'));

$builder->addTransition(new Transition('t1', 'a', 'c'));
$builder->addTransition(new Transition('t1', 'b', 'c'));
$builder->addTransition(new Transition('t2', 'c', 'd'));
$builder->addTransition(new Transition('t2', 'e', 'd'));
$builder->addTransition(new Transition('t2', 'f', 'd'));
$builder->addTransition(new Transition('t3', 'd', 'g'));
$builder->addTransition(new Transition('t4', 'f', 'e'));
$builder->addTransition(new Transition('t4', 'g', 'e'));
$builder->addTransition(new Transition('t5', 'f', 'b'));

return $builder->build();
}

public function createComplexWorkflowDumpWithMarking()
{
return 'digraph workflow {
Expand Down Expand Up @@ -198,6 +235,43 @@ public function provideSimpleWorkflowDumpWithoutMarking()
place_b -> transition_t2 [style="solid"];
transition_t2 -> place_c [style="solid"];
}
';
}

public function provideComplexStateMachineDump()
{
return '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_e [label="e", shape=circle];
place_f [label="f", shape=circle];
place_g [label="g", shape=circle];
transition_t1 [label="t1", shape=box, shape="box", regular="1"];
transition_t2 [label="t2", shape=box, shape="box", regular="1"];
transition_t3 [label="t3", shape=box, shape="box", regular="1"];
transition_t4 [label="t4", shape=box, shape="box", regular="1"];
transition_t5 [label="t5", shape=box, shape="box", regular="1"];
place_a -> transition_t1 [style="solid"];
transition_t1 -> place_c [style="solid"];
place_b -> transition_t1 [style="solid"];
place_c -> transition_t2 [style="solid"];
transition_t2 -> place_d [style="solid"];
place_e -> transition_t2 [style="solid"];
place_f -> transition_t2 [style="solid"];
place_d -> transition_t3 [style="solid"];
transition_t3 -> place_g [style="solid"];
place_f -> transition_t4 [style="solid"];
transition_t4 -> place_e [style="solid"];
place_g -> transition_t4 [style="solid"];
place_f -> transition_t5 [style="solid"];
transition_t5 -> place_b [style="solid"];
}
';
}
}