8000 feature #50621 [FrameworkBundle][Workflow] Add metadata dumping suppo… · symfony/symfony@f01da98 · GitHub
[go: up one dir, main page]

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

+3-3
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

+6
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

+97-13
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
*/ F42D
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

+8-5
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)
0