-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[FrameworkBundle][Workflow] Add metadata dumping support for GraphvizDumper
#50621
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
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,23 +44,27 @@ class GraphvizDumper implements DumperInterface | |
*/ | ||
public function dump(Definition $definition, Marking $marking = null, array $options = []): string | ||
{ | ||
$places = $this->findPlaces($definition, $marking); | ||
$transitions = $this->findTransitions($definition); | ||
$withMetadata = $options['with-metadata'] ?? false; | ||
|
||
$places = $this->findPlaces($definition, $withMetadata, $marking); | ||
$transitions = $this->findTransitions($definition, $withMetadata); | ||
$edges = $this->findEdges($definition); | ||
|
||
$options = array_replace_recursive(self::$defaultOptions, $options); | ||
|
||
return $this->startDot($options) | ||
.$this->addPlaces($places) | ||
.$this->addTransitions($transitions) | ||
$label = $this->formatLabel($definition, $withMetadata, $options); | ||
|
||
return $this->startDot($options, $label) | ||
.$this->addPlaces($places, $withMetadata) | ||
.$this->addTransitions($transitions, $withMetadata) | ||
.$this->addEdges($edges) | ||
.$this->endDot(); | ||
} | ||
|
||
/** | ||
* @internal | ||
*/ | ||
protected function findPlaces(Definition $definition, Marking $marking = null): array | ||
protected function findPlaces(Definition $definition, bool $withMetadata, Marking $marking = null): array | ||
{ | ||
$workflowMetadata = $definition->getMetadataStore(); | ||
|
||
|
@@ -80,9 +84,16 @@ protected function findPlaces(Definition $definition, Marking $marking = null): | |
$attributes['style'] = 'filled'; | ||
$attributes['fillcolor'] = $backgroundColor; | ||
} | ||
if ($withMetadata) { | ||
$attributes['metadata'] = $workflowMetadata->getPlaceMetadata($place); | ||
} | ||
$label = $workflowMetadata->getMetadata('label', $place); | ||
if (null !== $label) { | ||
$attributes['name'] = $label; | ||
if ($withMetadata) { | ||
// Don't include label in metadata if already used as name | ||
unset($attributes['metadata']['label']); | ||
} | ||
} | ||
$places[$place] = [ | ||
'attributes' => $attributes, | ||
|
@@ -95,7 +106,7 @@ protected function findPlaces(Definition $definition, Marking $marking = null): | |
/** | ||
* @internal | ||
*/ | ||
protected function findTransitions(Definition $definition): array | ||
protected function findTransitions(Definition $definition, bool $withMetadata): array | ||
{ | ||
$workflowMetadata = $definition->getMetadataStore(); | ||
|
||
|
@@ -111,9 +122,16 @@ protected function findTransitions(Definition $definition): array | |
} | ||
$name = $workflowMetadata->getMetadata('label', $transition) ?? $transition->getName(); | ||
|
||
$metadata = []; | ||
if ($withMetadata) { | ||
$metadata = $workflowMetadata->getTransitionMetadata($transition); | ||
unset($metadata['label']); | ||
} | ||
|
||
$transitions[] = [ | ||
'attributes' => $attributes, | ||
'name' => $name, | ||
'metadata' => $metadata, | ||
]; | ||
} | ||
|
||
|
@@ -123,7 +141,7 @@ protected function findTransitions(Definition $definition): array | |
/** | ||
* @internal | ||
*/ | ||
protected function addPlaces(array $places): string | ||
protected function addPlaces(array $places, float $withMetadata): string | ||
{ | ||
$code = ''; | ||
|
||
|
@@ -135,7 +153,15 @@ protected function addPlaces(array $places): string | |
$placeName = $id; | ||
} | ||
|
||
$code .= sprintf(" place_%s [label=\"%s\", shape=circle%s];\n", $this->dotize($id), $this->escape($placeName), $this->addAttributes($place['attributes'])); | ||
if ($withMetadata) { | ||
$escapedLabel = sprintf('<<B>%s</B>%s>', $this->escape($placeName), $this->addMetadata($place['attributes']['metadata'])); | ||
// Don't include metadata in default attributes used to format the place | ||
unset($place['attributes']['metadata']); | ||
} else { | ||
$escapedLabel = sprintf('"%s"', $this->escape($placeName)); | ||
} | ||
|
||
$code .= sprintf(" place_%s [label=%s, shape=circle%s];\n", $this->dotize($id), $escapedLabel, $this->addAttributes($place['attributes'])); | ||
} | ||
|
||
return $code; | ||
|
@@ -144,12 +170,18 @@ protected function addPlaces(array $places): string | |
/** | ||
* @internal | ||
*/ | ||
protected function addTransitions(array $transitions): string | ||
protected function addTransitions(array $transitions, bool $withMetadata): string | ||
{ | ||
$code = ''; | ||
|
||
foreach ($transitions as $i => $place) { | ||
$code .= sprintf(" transition_%s [label=\"%s\",%s];\n", $this->dotize($i), $this->escape($place['name']), $this->addAttributes($place['attributes'])); | ||
if ($withMetadata) { | ||
$escapedLabel = sprintf('<<B>%s</B>%s>', $this->escape($place['name']), $this->addMetadata($place['metadata'])); | ||
} else { | ||
$escapedLabel = '"'.$this->escape($place['name']).'"'; | ||
} | ||
|
||
$code .= sprintf(" transition_%s [label=%s,%s];\n", $this->dotize($i), $escapedLabel, $this->addAttributes($place['attributes'])); | ||
} | ||
|
||
return $code; | ||
|
@@ -215,10 +247,11 @@ protected function addEdges(array $edges): string | |
/** | ||
* @internal | ||
*/ | ||
protected function startDot(array $options): string | ||
protected function startDot(array $options, string $label): string | ||
{ | ||
return sprintf("digraph workflow {\n %s\n node [%s];\n edge [%s];\n\n", | ||
return sprintf("digraph workflow {\n %s%s\n node [%s];\n edge [%s];\n\n", | ||
$this->addOptions($options['graph']), | ||
'""' !== $label && '<>' !== $label ? sprintf(' label=%s', $label) : '', | ||
$this->addOptions($options['node']), | ||
$this->addOptions($options['edge']) | ||
); | ||
|
@@ -248,6 +281,9 @@ protected function escape(string|bool $value): string | |
return \is_bool($value) ? ($value ? '1' : '0') : addslashes($value); | ||
} | ||
|
||
/** | ||
* @internal | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why make this internal? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. because all methods are in this class |
||
*/ | ||
protected function addAttributes(array $attributes): string | ||
{ | ||
$code = []; | ||
|
@@ -259,6 +295,33 @@ protected function addAttributes(array $attributes): string | |
return $code ? ' '.implode(' ', $code) : ''; | ||
} | ||
|
||
/** | ||
* Handles the label of the graph depending on whether a label was set in CLI, | ||
* if metadata should be included and if there are any. | ||
* | ||
* The produced label must be escaped. | ||
* | ||
* @internal | ||
*/ | ||
protected function formatLabel(Definition $definition, string $withMetadata, array $options): string | ||
{ | ||
$currentLabel = $options['label'] ?? ''; | ||
|
||
if (!$withMetadata) { | ||
// Only currentLabel to handle. If null, will be translated to empty string | ||
return sprintf('"%s"', $this->escape($currentLabel)); | ||
} | ||
$workflowMetadata = $definition->getMetadataStore()->getWorkflowMetadata(); | ||
|
||
if ('' === $currentLabel) { | ||
// Only metadata to handle | ||
return sprintf('<%s>', $this->addMetadata($workflowMetadata, false)); | ||
} | ||
|
||
// currentLabel and metadata to handle | ||
return sprintf('<<B>%s</B>%s>', $this->escape($currentLabel), $this->addMetadata($workflowMetadata)); | ||
} | ||
|
||
private function addOptions(array $options): string | ||
{ | ||
$code = []; | ||
|
@@ -269,4 +332,25 @@ private function addOptions(array $options): string | |
|
||
return implode(' ', $code); | ||
} | ||
|
||
/** | ||
* @param bool $lineBreakFirstIfNotEmpty Whether to add a separator in the first place when metadata is not empty | ||
*/ | ||
private function addMetadata(array $metadata, bool $lineBreakFirstIfNotEmpty = true): string | ||
{ | ||
$code = []; | ||
|
||
$skipSeparator = !$lineBreakFirstIfNotEmpty; | ||
|
||
foreach ($metadata as $key => $value) { | ||
if ($skipSeparator) { | ||
$code[] = sprintf('%s: %s', $this->escape($key), $this->escape($value)); | ||
$skipSeparator = false; | ||
} else { | ||
$code[] = sprintf('%s%s: %s', '<BR/>', $this->escape($key), $this->escape($value)); | ||
} | ||
} | ||
|
||
return $code ? implode('', $code) : ''; | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this a BC break if someone uses the dumper in their own code (as they would have to change the options being passed to the
dump()
method)?