Skip to content

Commit 855fb39

Browse files
lyrixxfabpot
authored andcommitted
[Workflow] Move the dump command to the component
1 parent 11e8902 commit 855fb39

File tree

4 files changed

+176
-0
lines changed

4 files changed

+176
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ CHANGELOG
66

77
* Add support for `BackedEnum` in `MethodMarkingStore`
88
* Add support for weighted transitions
9+
* Add a command to dump a workflow definition in different formats (dot, plantuml, mermaid)
910

1011
7.3
1112
---

Command/WorkflowDumpCommand.php

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Command;
13+
14+
use Symfony\Component\Console\Attribute\AsCommand;
15+
use Symfony\Component\Console\Command\Command;
16+
use Symfony\Component\Console\Completion\CompletionInput;
17+
use Symfony\Component\Console\Completion\CompletionSuggestions;
18+
use Symfony\Component\Console\Exception\InvalidArgumentException;
19+
use Symfony\Component\Console\Input\InputArgument;
20+
use Symfony\Component\Console\Input\InputInterface;
21+
use Symfony\Component\Console\Input\InputOption;
22+
use Symfony\Component\Console\Output\OutputInterface;
23+
use Symfony\Component\Workflow\Debug\TraceableWorkflow;
24+
use Symfony\Component\Workflow\Dumper\GraphvizDumper;
25+
use Symfony\Component\Workflow\Dumper\MermaidDumper;
26+
use Symfony\Component\Workflow\Dumper\PlantUmlDumper;
27+
use Symfony\Component\Workflow\Dumper\StateMachineGraphvizDumper;
28+
use Symfony\Component\Workflow\Marking;
29+
use Symfony\Component\Workflow\StateMachine;
30+
use Symfony\Contracts\Service\ServiceProviderInterface;
31+
32+
/**
33+
* @author Grégoire Pineau <lyrixx@lyrixx.info>
34+
*
35+
* @final
36+
*/
37+
#[AsCommand(name: 'workflow:dump', description: 'Dump a workflow')]
38+
class WorkflowDumpCommand extends Command
39+
{
40+
private const DUMP_FORMAT_OPTIONS = [
41+
'puml',
42+
'mermaid',
43+
'dot',
44+
];
45+
46+
public function __construct(
47+
private ServiceProviderInterface $workflows,
48+
) {
49+
parent::__construct();
50+
}
51+
52+
protected function configure(): void
53+
{
54+
$this
55+
->setDefinition([
56+
new InputArgument('name', InputArgument::REQUIRED, 'A workflow name'),
57+
new InputArgument('marking', InputArgument::IS_ARRAY, 'A marking (a list of places)'),
58+
new InputOption('label', 'l', InputOption::VALUE_REQUIRED, 'Label a graph'),
59+
new InputOption('with-metadata', null, InputOption::VALUE_NONE, 'Include the workflow\'s metadata in the dumped graph', null),
60+
new InputOption('dump-format', null, InputOption::VALUE_REQUIRED, 'The dump format ['.implode('|', self::DUMP_FORMAT_OPTIONS).']', 'dot'),
61+
])
62+
->setHelp(<<<'EOF'
63+
The <info>%command.name%</info> command dumps the graphical representation of a
64+
workflow in different formats
65+
66+
<info>DOT</info>: %command.full_name% <workflow name> | dot -Tpng > workflow.png
67+
<info>PUML</info>: %command.full_name% <workflow name> --dump-format=puml | java -jar plantuml.jar -p > workflow.png
68+
<info>MERMAID</info>: %command.full_name% <workflow name> --dump-format=mermaid | mmdc -o workflow.svg
69+
EOF
70+
)
71+
;
72+
}
73+
74+
protected function execute(InputInterface $input, OutputInterface $output): int
75+
{
76+
$workflowName = $input->getArgument('name');
77+
78+
if (!$this->workflows->has($workflowName)) {
79+
throw new InvalidArgumentException(\sprintf('The workflow named "%s" cannot be found.', $workflowName));
80+
}
81+
$workflow = $this->workflows->get($workflowName);
82+
if ($workflow instanceof TraceableWorkflow) {
83+
$workflow = $workflow->getInner();
84+
}
85+
$type = $workflow instanceof StateMachine ? 'state_machine' : 'workflow';
86+
$definition = $workflow->getDefinition();
87+
88+
switch ($input->getOption('dump-format')) {
89+
case 'puml':
90+
$transitionType = 'workflow' === $type ? PlantUmlDumper::WORKFLOW_TRANSITION : PlantUmlDumper::STATEMACHINE_TRANSITION;
91+
$dumper = new PlantUmlDumper($transitionType);
92+
break;
93+
94+
case 'mermaid':
95+
$transitionType = 'workflow' === $type ? MermaidDumper::TRANSITION_TYPE_WORKFLOW : MermaidDumper::TRANSITION_TYPE_STATEMACHINE;
96+
$dumper = new MermaidDumper($transitionType);
97+
break;
98+
99+
case 'dot':
100+
default:
101+
$dumper = ('workflow' === $type) ? new GraphvizDumper() : new StateMachineGraphvizDumper();
102+
}
103+
104+
$marking = new Marking();
105+
106+
foreach ($input->getArgument('marking') as $place) {
107+
$marking->mark($place);
108+
}
109+
110+
$options = [
111+
'name' => $workflowName,
112+
'with-metadata' => $input->getOption('with-metadata'),
113+
'nofooter' => true,
114+
'label' => $input->getOption('label'),
115+
];
116+
$output->writeln($dumper->dump($definition, $marking, $options));
117+
118+
return 0;
119+
}
120+
121+
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
122+
{
123+
if ($input->mustSuggestArgumentValuesFor('name')) {
124+
$suggestions->suggestValues(array_keys($this->workflows->getProvidedServices()));
125+
}
126+
127+
if ($input->mustSuggestOptionValuesFor('dump-format')) {
128+
$suggestions->suggestValues(self::DUMP_FORMAT_OPTIONS);
129+
}
130+
}
131+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Tests\Command;
13+
14+
use PHPUnit\Framework\Attributes\DataProvider;
15+
use PHPUnit\Framework\TestCase;
16+
use Symfony\Component\Console\Application;
17+
use Symfony\Component\Console\Tester\CommandCompletionTester;
18+
use Symfony\Component\DependencyInjection\ServiceLocator;
19+
use Symfony\Component\Workflow\Command\WorkflowDumpCommand;
20+
21+
class WorkflowDumpCommandTest extends TestCase
22+
{
23+
#[DataProvider('provideCompletionSuggestions')]
24+
public function testComplete(array $input, array $expectedSuggestions)
25+
{
26+
$application = new Application();
27+
$command = new WorkflowDumpCommand(new ServiceLocator([]));
28+
if (method_exists($application, 'addCommand')) {
29+
$application->addCommand($command);
30+
} else {
31+
$application->add($command);
32+
}
33+
34+
$tester = new CommandCompletionTester($application->find('workflow:dump'));
35+
$suggestions = $tester->complete($input, 2);
36+
$this->assertSame($expectedSuggestions, $suggestions);
37+
}
38+
39+
public static function provideCompletionSuggestions(): iterable
40+
{
41+
yield 'option --dump-format' => [['--dump-format', ''], ['puml', 'mermaid', 'dot']];
42+
}
43+
}

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"require-dev": {
2727
"psr/log": "^1|^2|^3",
2828
"symfony/config": "^6.4|^7.0|^8.0",
29+
"symfony/console": "^6.4|^7.0|^8.0",
2930
"symfony/dependency-injection": "^6.4|^7.0|^8.0",
3031
"symfony/error-handler": "^6.4|^7.0|^8.0",
3132
"symfony/event-dispatcher": "^6.4|^7.0|^8.0",

0 commit comments

Comments
 (0)