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

Skip to content

Commit 1497d36

Browse files
committed
Add option to the workflow:dump command to allow PlantUML format dump
1 parent 07766b3 commit 1497d36

19 files changed

+816
-4
lines changed

src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\Console\Input\InputOption;
1818
use Symfony\Component\Console\Output\OutputInterface;
1919
use Symfony\Component\Workflow\Dumper\GraphvizDumper;
20+
use Symfony\Component\Workflow\Dumper\PlantUmlDumper;
2021
use Symfony\Component\Workflow\Dumper\StateMachineGraphvizDumper;
2122
use Symfony\Component\Workflow\Marking;
2223

@@ -39,13 +40,15 @@ protected function configure()
3940
new InputArgument('name', InputArgument::REQUIRED, 'A workflow name'),
4041
new InputArgument('marking', InputArgument::IS_ARRAY, 'A marking (a list of places)'),
4142
new InputOption('label', 'l', InputArgument::OPTIONAL, 'Labels a graph'),
43+
new InputOption('dump-format', null, InputOption::VALUE_REQUIRED, 'The dump format [dot|puml]', 'dot'),
4244
))
4345
->setDescription('Dump a workflow')
4446
->setHelp(<<<'EOF'
4547
The <info>%command.name%</info> command dumps the graphical representation of a
46-
workflow in DOT format
48+
workflow in different formats
4749
48-
%command.full_name% <workflow name> | dot -Tpng > workflow.png
50+
<info>DOT</info>: %command.full_name% <workflow name> | dot -Tpng > workflow.png
51+
<info>PUML</info>: %command.full_name% <workflow name> --dump-format=puml | java -jar plantuml.jar -p > workflow.png
4952

5053
EOF
5154
)
@@ -59,16 +62,27 @@ protected function execute(InputInterface $input, OutputInterface $output)
5962
{
6063
$container = $this->getApplication()->getKernel()->getContainer();
6164
$serviceId = $input->getArgument('name');
65+
6266
if ($container->has('workflow.'.$serviceId)) {
6367
$workflow = $container->get('workflow.'.$serviceId);
64-
$dumper = new GraphvizDumper();
68+
$type = 'workflow';
6569
} elseif ($container->has('state_machine.'.$serviceId)) {
6670
$workflow = $container->get('state_machine.'.$serviceId);
67-
$dumper = new StateMachineGraphvizDumper();
71+
$type = 'state_machine';
6872
} else {
6973
throw new \InvalidArgumentException(sprintf('No service found for "workflow.%1$s" nor "state_machine.%1$s".', $serviceId));
7074
}
7175

76+
if ('puml' === $input->getOption('dump-format')) {
77+
$dumper = new PlantUmlDumper(
78+
'workflow' === $type ? PlantUmlDumper::WORKFLOW_TRANSITION : PlantUmlDumper::STATEMACHINE_TRANSITION
79+
);
80+
} elseif ('workflow' === $type) {
81+
$dumper = new GraphvizDumper();
82+
} else {
83+
$dumper = new StateMachineGraphvizDumper();
84+
}
85+
7286
$marking = new Marking();
7387

7488
foreach ($input->getArgument('marking') as $place) {
@@ -80,6 +94,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
8094
if (null !== $label && '' !== trim($label)) {
8195
$options = array('graph' => array('label' => $label));
8296
}
97+
$options = array_replace($options, array('name' => $serviceId, 'nofooter' => true));
8398
$output->writeln($dumper->dump($workflow->getDefinition(), $marking, $options));
8499
}
85100
}
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Workflow\Dumper;
13+
14+
use InvalidArgumentException;
15+
use Symfony\Component\Workflow\Definition;
16+
use Symfony\Component\Workflow\Marking;
17+
18+
/**
19+
* PlantUmlDumper dumps a workflow as a PlantUML file.
20+
*
21+
* You can convert the generated puml file with the plantuml.jar utility (http://plantuml.com/):
22+
*
23+
* php bin/console workflow:dump pull_request travis --dump-format=puml | java -jar plantuml.jar -p > workflow.png
24+
*
25+
* @author Sébastien Morel <[email protected]>
26+
*/
27+
class PlantUmlDumper implements DumperInterface
28+
{
29+
private const SYMFONY_LOGO = 'sprite $sf_logo [81x20/16z] {
30+
hPNRaYiX24K1xwBo_tyx6-qaCtDEJ-KXLYMTLbp0HWcHZr3KRDJ8z94HG3jZn4_mijbQ2ryJoFePtXLWA_qxyGy19DpdY_10z11ZAbGjFHRwcEbcKx5-wqsV
31+
yIMo8StMCHKh8ZUxnEwrZiwRAUOvy1lLcPQF4lEFAjhzMd5WOAqvKflS0Enx8PbihiSYXM8ClGVAseIWTAjCgVSAcnYbQG79xKFsZ0VnDCNc7AVBoPSMcTsX
32+
UnrujbYjjz0NnsObkTgnmolqJD4QgGUYTQiNe8eIjtx4b6Vv8nPGpncn3NJ8Geo9W9VW2wGACm_JzgIO8A8KXr2jUBCVGEAAJSZ6JUlsNnmOzmIYti9G7bjL
33+
8InaHM9G40NkwTG7OxrggvNIejA8AZuqyWjOzTIKi-wwYvjeHYesSWuPiTGDN5THzkYLU4MD5r2_0PDhG7LIUG33z5HtM6CP3icyWEVOS61sD_2ZsBfJdbVA
34+
qM53XHDUwhY0TAwPug3OG9NonRFhO8ynF3I4unuAMDHmSrXH57V1RGvl9jafuZF9ZhqjWOEh98y0tUYGsUxkBSllIyBdT2oM5Fn2-ut-fzsq_cQNuL6Uvwqr
35+
knh4RrvOKzxZfLV3s0rs_R_1SdYt3VxeQ1_y2_W2
36+
}';
37+
private const INITIAL = 'initial';
38+
private const MARKED = 'marked';
39+
40+
const STATEMACHINE_TRANSITION = 'arrow';
41+
const WORKFLOW_TRANSITION = 'square';
42+
const TRANSITION_TYPES = array(self::STATEMACHINE_TRANSITION, self::WORKFLOW_TRANSITION);
43+
const DEFAULT_OPTIONS = array(
44+
'skinparams' => array(
45+
'titleBorderRoundCorner' => 15,
46+
'titleBorderThickness' => 2,
47+
'state' => array(
48+
'BackgroundColor<<'.self::INITIAL.'>>' => '#87b741',
49+
'BackgroundColor<<'.self::MARKED.'>>' => '#3887C6',
50+
'BorderColor' => '#3887C6',
51+
'BorderColor<<'.self::MARKED.'>>' => 'Black',
52+
'FontColor<<'.self::MARKED.'>>' => 'White',
53+
),
54+
'agent' => array(
55+
'BackgroundColor' => '#ffffff',
56+
'BorderColor' => '#3887C6',
57+
),
58+
),
59+
);
60+
61+
private $transitionType = self::STATEMACHINE_TRANSITION;
62+
63+
public function __construct(string $transitionType = null)
64+
{
65+
if (!in_array($transitionType, self::TRANSITION_TYPES)) {
66+
throw new InvalidArgumentException("Transition type '{$transitionType}' does not exist.");
67+
}
68+
$this->transitionType = $transitionType;
69+
}
70+
71+
public function dump(Definition $definition, Marking $marking = null, array $options = array()): string
72+
{
73+
$options = array_replace_recursive(self::DEFAULT_OPTIONS, $options);
74+
$code = $this->initialize($options);
75+
foreach ($definition->getPlaces() as $place) {
76+
$code[] =
77+
"state {$place}".
78+
($definition->getInitialPlace() === $place ? ' <<'.self::INITIAL.'>>' : '').
79+
($marking && $marking->has($place) ? ' <<'.self::MARKED.'>>' : '');
80+
}
81+
if ($this->isWorkflowTransitionType()) {
82+
foreach ($definition->getTransitions() as $transition) {
83+
$code[] = "agent {$transition->getName()}";
84+
}
85+
}
86+
foreach ($definition->getTransitions() as $transition) {
87+
foreach ($transition->getFroms() as $from) {
88+
foreach ($transition->getTos() as $to) {
89+
if ($this->isWorkflowTransitionType()) {
90+
$lines = array(
91+
"{$from} --> {$transition->getName()}",
92+
"{$transition->getName()} --> {$to}",
93+
);
94+
foreach ($lines as $line) {
95+
if (!in_array($line, $code)) {
96+
$code[] = $line;
97+
}
98+
}
99+
} else {
100+
$code[] = "{$from} --> {$to}: {$transition->getName()}";
101+
}
102+
}
103+
}
104+
}
105+
106+
return $this->startPuml($options).$this->getLines($code).$this->endPuml($options);
107+
}
108+
109+
private function isWorkflowTransitionType(): bool
110+
{
111+
return self::WORKFLOW_TRANSITION === $this->transitionType;
112+
}
113+
114+
private function startPuml(array $options): string
115+
{
116+
$start = '@startuml'.PHP_EOL;
117+
118+
if ($this->isWorkflowTransitionType()) {
119+
$start .= 'allow_mixing'.PHP_EOL;
120+
}
121+
122+
if ($options['nofooter'] ?? false) {
123+
return $start;
124+
}
125+
126+
return $start.self::SYMFONY_LOGO.PHP_EOL;
127+
}
128+
129+
private function endPuml(array $options): string
130+
{
131+
$end = PHP_EOL.'@enduml';
132+
if ($options['nofooter'] ?? false) {
133+
return $end;
134+
}
135+
136+
return PHP_EOL.'footer \nGenerated by <$sf_logo> **Workflow Component** and **PlantUML**'.$end;
137+
}
138+
139+
private function getLines(array $code): string
140+
{
141+
return implode(PHP_EOL, $code);
142+
}
143+
144+
private function initialize(array $options): array
145+
{
146+
$code = array();
147+
if (isset($options['title'])) {
148+
$code[] = "title {$options['title']}";
149+
}
150+
if (isset($options['name'])) {
151+
$code[] = "title {$options['name']}";
152+
}
153+
if (isset($options['skinparams']) && is_array($options['skinparams'])) {
154+
foreach ($options['skinparams'] as $skinparamKey => $skinparamValue) {
155+
if (!$this->isWorkflowTransitionType() && 'agent' === $skinparamKey) {
156+
continue;
157+
}
158+
if (!is_array($skinparamValue)) {
159+
$code[] = "skinparam {$skinparamKey} $skinparamValue";
160+
continue;
161+
}
162+
$code[] = "skinparam {$skinparamKey} {";
163+
foreach ($skinparamValue as $key => $value) {
164+
$code[] = " {$key} $value";
165+
}
166+
$code[] = '}';
167+
}
168+
}
169+
170+
return $code;
171+
}
172+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
/*
3+
* This file is part of the Symfony package.
4+
*
5+
* (c) Fabien Potencier <[email protected]>
6+
*
7+
* For the full copyright and license information, please view the LICENSE
8+
* file that was distributed with this source code.
9+
*/
10+
11+
namespace Symfony\Component\Workflow\Tests\Dumper;
12+
13+
use PHPUnit\Framework\TestCase;
14+
use Symfony\Component\Workflow\Dumper\DumperInterface;
15+
use Symfony\Component\Workflow\Dumper\PlantUmlDumper;
16+
use Symfony\Component\Workflow\Marking;
17+
use Symfony\Component\Workflow\Tests\WorkflowBuilderTrait;
18+
19+
class PlantUmlDumperTest extends TestCase
20+
{
21+
use WorkflowBuilderTrait;
22+
23+
/**
24+
* @var DumperInterface[]
25+
*/
26+
private $dumpers;
27+
28+
protected function setUp()
29+
{
30+
$this->dumpers =
31+
array(
32+
PlantUmlDumper::STATEMACHINE_TRANSITION => new PlantUmlDumper(PlantUmlDumper::STATEMACHINE_TRANSITION),
33+
PlantUmlDumper::WORKFLOW_TRANSITION => new PlantUmlDumper(PlantUmlDumper::WORKFLOW_TRANSITION),
34+
);
35+
}
36+
37+
/**
38+
* @dataProvider provideWorkflowDefinitionWithoutMarking
39+
*/
40+
public function testDumpWithoutMarking($definition, $expectedFileName, $title, $nofooter)
41+
{
42+
foreach ($this->dumpers as $transitionType => $dumper) {
43+
$dump = $dumper->dump($definition, null, array('title' => $title, 'nofooter' => $nofooter));
44+
// handle windows, and avoid to create more fixtures
45+
$dump = str_replace(PHP_EOL, "\n", $dump.PHP_EOL);
46+
$this->assertStringEqualsFile($this->getFixturePath($expectedFileName, $transitionType), $dump);
47+
}
48+
}
49+
50+
/**
51+
* @dataProvider provideWorkflowDefinitionWithMarking
52+
*/
53+
public function testDumpWithMarking($definition, $marking, $expectedFileName, $title, $footer)
54+
{
55+
foreach ($this->dumpers as $transitionType => $dumper) {
56+
$dump = $dumper->dump($definition, $marking, array('title' => $title, 'nofooter' => $footer));
57+
// handle windows, and avoid to create more fixtures
58+
$dump = str_replace(PHP_EOL, "\n", $dump.PHP_EOL);
59+
$this->assertStringEqualsFile($this->getFixturePath($expectedFileName, $transitionType), $dump);
60+
}
61+
}
62+
63+
public function provideWorkflowDefinitionWithoutMarking()
64+
{
65+
$title = 'SimpleDiagram';
66+
yield array($this->createSimpleWorkflowDefinition(), 'simple-workflow-nomarking-nofooter', $title, true);
67+
yield array($this->createSimpleWorkflowDefinition(), 'simple-workflow-nomarking', $title, false);
68+
$title = 'ComplexDiagram';
69+
yield array($this->createComplexWorkflowDefinition(), 'complex-workflow-nomarking-nofooter', $title, true);
70+
yield array($this->createComplexWorkflowDefinition(), 'complex-workflow-nomarking', $title, false);
71+
}
72+
73+
public function provideWorkflowDefinitionWithMarking()
74+
{
75+
$title = 'SimpleDiagram';
76+
$marking = new Marking(array('b' => 1));
77+
yield array(
78+
$this->createSimpleWorkflowDefinition(), $marking, 'simple-workflow-marking-nofooter', $title, true,
79+
);
80+
yield array(
81+
$this->createSimpleWorkflowDefinition(), $marking, 'simple-workflow-marking', $title, false,
82+
);
83+
$title = 'ComplexDiagram';
84+
$marking = new Marking(array('c' => 1, 'e' => 1));
85+
yield array(
86+
$this->createComplexWorkflowDefinition(), $marking, 'complex-workflow-marking-nofooter', $title, true,
87+
);
88+
yield array(
89+
$this->createComplexWorkflowDefinition(), $marking, 'complex-workflow-marking', $title, false,
90+
);
91+
}
92+
93+
private function getFixturePath($name, $transitionType)
94+
{
95+
return __DIR__.'/../fixtures/puml/'.$transitionType.'/'.$name.'.puml';
96+
}
97+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
@startuml
2+
title ComplexDiagram
3+
skinparam titleBorderRoundCorner 15
4+
skinparam titleBorderThickness 2
5+
skinparam state {
6+
BackgroundColor<<initial>> #87b741
7+
BackgroundColor<<marked>> #3887C6
8+
BorderColor #3887C6
9+
BorderColor<<marked>> Black
10+
FontColor<<marked>> White
11+
}
12+
state a <<initial>>
13+
state b
14+
state c <<marked>>
15+
state d
16+
state e <<marked>>
17+
state f
18+
state g
19+
a --> b: t1
20+
a --> c: t1
21+
b --> d: t2
22+
c --> d: t2
23+
d --> e: t3
24+
d --> f: t4
25+
e --> g: t5
26+
f --> g: t6
27+
@enduml
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
@startuml
2+
sprite $sf_logo [81x20/16z] {
3+
hPNRaYiX24K1xwBo_tyx6-qaCtDEJ-KXLYMTLbp0HWcHZr3KRDJ8z94HG3jZn4_mijbQ2ryJoFePtXLWA_qxyGy19DpdY_10z11ZAbGjFHRwcEbcKx5-wqsV
4+
yIMo8StMCHKh8ZUxnEwrZiwRAUOvy1lLcPQF4lEFAjhzMd5WOAqvKflS0Enx8PbihiSYXM8ClGVAseIWTAjCgVSAcnYbQG79xKFsZ0VnDCNc7AVBoPSMcTsX
5+
UnrujbYjjz0NnsObkTgnmolqJD4QgGUYTQiNe8eIjtx4b6Vv8nPGpncn3NJ8Geo9W9VW2wGACm_JzgIO8A8KXr2jUBCVGEAAJSZ6JUlsNnmOzmIYti9G7bjL
6+
8InaHM9G40NkwTG7OxrggvNIejA8AZuqyWjOzTIKi-wwYvjeHYesSWuPiTGDN5THzkYLU4MD5r2_0PDhG7LIUG33z5HtM6CP3icyWEVOS61sD_2ZsBfJdbVA
7+
qM53XHDUwhY0TAwPug3OG9NonRFhO8ynF3I4unuAMDHmSrXH57V1RGvl9jafuZF9ZhqjWOEh98y0tUYGsUxkBSllIyBdT2oM5Fn2-ut-fzsq_cQNuL6Uvwqr
8+
knh4RrvOKzxZfLV3s0rs_R_1SdYt3VxeQ1_y2_W2
9+
}
10+
title ComplexDiagram
11+
skinparam titleBorderRoundCorner 15
12+
skinparam titleBorderThickness 2
13+
skinparam state {
14+
BackgroundColor<<initial>> #87b741
15+
BackgroundColor<<marked>> #3887C6
16+
BorderColor #3887C6
17+
BorderColor<<marked>> Black
18+
FontColor<<marked>> White
19+
}
20+
state a <<initial>>
21+
state b
22+
state c <<marked>>
23+
state d
24+
state e <<marked>>
25+
state f
26+
state g
27+
a --> b: t1
28+
a --> c: t1
29+
b --> d: t2
30+
c --> d: t2
31+
d --> e: t3
32+
d --> f: t4
33+
e --> g: t5
34+
f --> g: t6
35+
footer \nGenerated by <$sf_logo> **Workflow Component** and **PlantUML**
36+
@enduml

0 commit comments

Comments
 (0)