Skip to content

Commit f01da98

Browse files
feature #50621 [FrameworkBundle][Workflow] Add metadata dumping support for GraphvizDumper (Louis-Proffit)
This PR was merged into the 6.4 branch. Discussion ---------- [FrameworkBundle][Workflow] Add metadata dumping support for `GraphvizDumper` | Q | A | ------------- | --- | Branch? | 6.4 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | Fix #50604 | License | MIT | Doc PR | symfony/symfony-docs#18398 Add `--with-metadata` option to `workflow:dump` command. It includes places, transitions and workflow's metadata in the dumped graph. Currently only supported for **GraphvizDumper** and **StateMachineGraphvizDumper**. When used, the `label` metadata is not included in the dumped metadata because it is already the title. This could be enlarged to all styling metadata. Commits ------- 457059d [FrameworkBundle][Workflow] Add metadata dumping support for GraphvizDumper
2 parents d3c26bb + 457059d commit f01da98

File tree

5 files changed

+257
-30
lines changed

5 files changed

+257
-30
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ protected function configure(): void
7373
new InputArgument('name', InputArgument::REQUIRED, 'A workflow name'),
7474
new InputArgument('marking', InputArgument::IS_ARRAY, 'A marking (a list of places)'),
7575
new InputOption('label', 'l', InputOption::VALUE_REQUIRED, 'Label a graph'),
76+
new InputOption('with-metadata', null, InputOption::VALUE_NONE, 'Include the workflow\'s metadata in the dumped graph', null),
7677
new InputOption('dump-format', null, InputOption::VALUE_REQUIRED, 'The dump format ['.implode('|', self::DUMP_FORMAT_OPTIONS).']', 'dot'),
7778
])
7879
->setHelp(<<<'EOF'
@@ -134,10 +135,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int
134135

135136
$options = [
136137
'name' => $workflowName,
138+
'with-metadata' => $input->getOption('with-metadata'),
137139
'nofooter' => true,
138-
'graph' => [
139-
'label' => $input->getOption('label'),
140-
],
140+
'label' => $input->getOption('label'),
141141
];
142142
$output->writeln($dumper->dump($definition, $marking, $options));
143143

src/Symfony/Component/Workflow/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
CHANGELOG
22
=========
33

4+
6.4
5+
---
6+
7+
* Add `with-metadata` option to the `workflow:dump` command to include places,
8+
transitions and workflow's metadata into dumped graph
9+
410
6.2
511
---
612

src/Symfony/Component/Workflow/Dumper/GraphvizDumper.php

Lines changed: 97 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -44,23 +44,27 @@ class GraphvizDumper implements DumperInterface
4444
*/
4545
public function dump(Definition $definition, Marking $marking = null, array $options = []): string
4646
{
47-
$places = $this->findPlaces($definition, $marking);
48-
$transitions = $this->findTransitions($definition);
47+
$withMetadata = $options['with-metadata'] ?? false;
48+
49+
$places = $this->findPlaces($definition, $withMetadata, $marking);
50+
$transitions = $this->findTransitions($definition, $withMetadata);
4951
$edges = $this->findEdges($definition);
5052

5153
$options = array_replace_recursive(self::$defaultOptions, $options);
5254

53-
return $this->startDot($options)
54-
.$this->addPlaces($places)
55-
.$this->addTransitions($transitions)
55+
$label = $this->formatLabel($definition, $withMetadata, $options);
56+
57+
return $this->startDot($options, $label)
58+
.$this->addPlaces($places, $withMetadata)
59+
.$this->addTransitions($transitions, $withMetadata)
5660
.$this->addEdges($edges)
5761
.$this->endDot();
5862
}
5963

6064
/**
6165
* @internal
6266
*/
63-
protected function findPlaces(Definition $definition, Marking $marking = null): array
67+
protected function findPlaces(Definition $definition, bool $withMetadata, Marking $marking = null): array
6468
{
6569
$workflowMetadata = $definition->getMetadataStore();
6670

@@ -80,9 +84,16 @@ protected function findPlaces(Definition $definition, Marking $marking = null):
8084
$attributes['style'] = 'filled';
8185
$attributes['fillcolor'] = $backgroundColor;
8286
}
87+
if ($withMetadata) {
88+
$attributes['metadata'] = $workflowMetadata->getPlaceMetadata($place);
89+
}
8390
$label = $workflowMetadata->getMetadata('label', $place);
8491
if (null !== $label) {
8592
$attributes['name'] = $label;
93+
if ($withMetadata) {
94+
// Don't include label in metadata if already used as name
95+
unset($attributes['metadata']['label']);
96+
}
8697
}
8798
$places[$place] = [
8899
'attributes' => $attributes,
@@ -95,7 +106,7 @@ protected function findPlaces(Definition $definition, Marking $marking = null):
95106
/**
96107
* @internal
97108
*/
98-
protected function findTransitions(Definition $definition): array
109+
protected function findTransitions(Definition $definition, bool $withMetadata): array
99110
{
100111
$workflowMetadata = $definition->getMetadataStore();
101112

@@ -111,9 +122,16 @@ protected function findTransitions(Definition $definition): array
111122
}
112123
$name = $workflowMetadata->getMetadata('label', $transition) ?? $transition->getName();
113124

125+
$metadata = [];
126+
if ($withMetadata) {
127+
$metadata = $workflowMetadata->getTransitionMetadata($transition);
128+
unset($metadata['label']);
129+
}
130+
114131
$transitions[] = [
115132
'attributes' => $attributes,
116133
'name' => $name,
134+
'metadata' => $metadata,
117135
];
118136
}
119137

@@ -123,7 +141,7 @@ protected function findTransitions(Definition $definition): array
123141
/**
124142
* @internal
125143
*/
126-
protected function addPlaces(array $places): string
144+
protected function addPlaces(array $places, float $withMetadata): string
127145
{
128146
$code = '';
129147

@@ -135,7 +153,15 @@ protected function addPlaces(array $places): string
135153
$placeName = $id;
136154
}
137155

138-
$code .= sprintf(" place_%s [label=\"%s\", shape=circle%s];\n", $this->dotize($id), $this->escape($placeName), $this->addAttributes($place['attributes']));
156+
if ($withMetadata) {
157+
$escapedLabel = sprintf('<<B>%s</B>%s>', $this->escape($placeName), $this->addMetadata($place['attributes']['metadata']));
158+
// Don't include metadata in default attributes used to format the place
159+
unset($place['attributes']['metadata']);
160+
} else {
161+
$escapedLabel = sprintf('"%s"', $this->escape($placeName));
162+
}
163+
164+
$code .= sprintf(" place_%s [label=%s, shape=circle%s];\n", $this->dotize($id), $escapedLabel, $this->addAttributes($place['attributes']));
139165
}
140166

141167
return $code;
@@ -144,12 +170,18 @@ protected function addPlaces(array $places): string
144170
/**
145171
* @internal
146172
*/
147-
protected function addTransitions(array $transitions): string
173+
protected function addTransitions(array $transitions, bool $withMetadata): string
148174
{
149175
$code = '';
150176

151177
foreach ($transitions as $i => $place) {
152-
$code .= sprintf(" transition_%s [label=\"%s\",%s];\n", $this->dotize($i), $this->escape($place['name']), $this->addAttributes($place['attributes']));
178+
if ($withMetadata) {
179+
$escapedLabel = sprintf('<<B>%s</B>%s>', $this->escape($place['name']), $this->addMetadata($place['metadata']));
180+
} else {
181+
$escapedLabel = '"'.$this->escape($place['name']).'"';
182+
}
183+
184+
$code .= sprintf(" transition_%s [label=%s,%s];\n", $this->dotize($i), $escapedLabel, $this->addAttributes($place['attributes']));
153185
}
154186

155187
return $code;
@@ -215,10 +247,11 @@ protected function addEdges(array $edges): string
215247
/**
216248
* @internal
217249
*/
218-
protected function startDot(array $options): string
250+
protected function startDot(array $options, string $label): string
219251
{
220-
return sprintf("digraph workflow {\n %s\n node [%s];\n edge [%s];\n\n",
252+
return sprintf("digraph workflow {\n %s%s\n node [%s];\n edge [%s];\n\n",
221253
$this->addOptions($options['graph']),
254+
'""' !== $label && '<>' !== $label ? sprintf(' label=%s', $label) : '',
222255
$this->addOptions($options['node']),
223256
$this->addOptions($options['edge'])
224257
);
@@ -248,6 +281,9 @@ protected function escape(string|bool $value): string
248281
return \is_bool($value) ? ($value ? '1' : '0') : addslashes($value);
249282
}
250283

284+
/**
285+
* @internal
286+
*/
251287
protected function addAttributes(array $attributes): string
252288
{
253289
$code = [];
@@ -259,6 +295,33 @@ protected function addAttributes(array $attributes): string
259295
return $code ? ' '.implode(' ', $code) : '';
260296
}
261297

298+
/**
299+
* Handles the label of the graph depending on whether a label was set in CLI,
300+
* if metadata should be included and if there are any.
301+
*
302+
* The produced label must be escaped.
303+
*
304+
* @internal
305+
*/
306+
protected function formatLabel(Definition $definition, string $withMetadata, array $options): string
307+
{
308+
$currentLabel = $options['label'] ?? '';
309+
310+
if (!$withMetadata) {
311+
// Only currentLabel to handle. If null, will be translated to empty string
312+
return sprintf('"%s"', $this->escape($currentLabel));
313+
}
314+
$workflowMetadata = $definition->getMetadataStore()->getWorkflowMetadata();
315+
316+
if ('' === $currentLabel) {
317+
// Only metadata to handle
318+
return sprintf('<%s>', $this->addMetadata($workflowMetadata, false));
319+
}
320+
321+
// currentLabel and metadata to handle
322+
return sprintf('<<B>%s</B>%s>', $this->escape($currentLabel), $this->addMetadata($workflowMetadata));
323+
}
324+
262325
private function addOptions(array $options): string
263326
{
264327
$code = [];
@@ -269,4 +332,25 @@ private function addOptions(array $options): string
269332

270333
return implode(' ', $code);
271334
}
335+
336+
/**
337+
* @param bool $lineBreakFirstIfNotEmpty Whether to add a separator in the first place when metadata is not empty
338+
*/
339+
private function addMetadata(array $metadata, bool $lineBreakFirstIfNotEmpty = true): string
340+
{
341+
$code = [];
342+
343+
$skipSeparator = !$lineBreakFirstIfNotEmpty;
344+
345+
foreach ($metadata as $key => $value) {
346+
if ($skipSeparator) {
347+
$code[] = sprintf('%s: %s', $this->escape($key), $this->escape($value));
348+
$skipSeparator = false;
349+
} else {
350+
$code[] = sprintf('%s%s: %s', '<BR/>', $this->escape($key), $this->escape($value));
351+
}
352+
}
353+
354+
return $code ? implode('', $code) : '';
355+
}
272356
}

src/Symfony/Component/Workflow/Dumper/StateMachineGraphvizDumper.php

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,19 @@ class StateMachineGraphvizDumper extends GraphvizDumper
2727
*/
2828
public function dump(Definition $definition, Marking $marking = null, array $options = []): string
2929
{
30-
$places = $this->findPlaces($definition, $marking);
30+
$withMetadata = $options['with-metadata'] ?? false;
31+
32+
$places = $this->findPlaces($definition, $withMetadata, $marking);
3133
$edges = $this->findEdges($definition);
3234

3335
$options = array_replace_recursive(self::$defaultOptions, $options);
3436

35-
return $this->startDot($options)
36-
.$this->addPlaces($places)
37+
$label = $this->formatLabel($definition, $withMetadata, $options);
38+
39+
return $this->startDot($options, $label)
40+
.$this->addPlaces($places, $withMetadata)
3741
.$this->addEdges($edges)
38-
.$this->endDot()
39-
;
42+
.$this->endDot();
4043
}
4144

4245
/**

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy