-
-
Notifications
You must be signed in to change notification settings - Fork 9.7k
Description
Description
The workflows are now private in the container, which means it's deprecated to perform $container->get("<workflow>")
. To counter this the Registry allows retrieving a workflow based on an object you wish to manipulate. However, this is problematic in case you want to inspect a workflow without having an object to manipulate.
#31506 somewhat hinted at this by requesting fetching workflows by class names. Instead I want to be able to get the definition of a workflow by its name.
The WorkflowDumpCommand
in symfony/framework-bundle
is an example use-case. Though it implements a lot of code to provide all the workflows as array $workflows
to the command. For alternative dumper implementations replicating the logic to create that argument isn't feasible and it'd be great if we could do Registry::getByName
or even just Registry::all()
without subject.
My use case is a documentation controller that dumps the definition similar to WorkflowDumpCommand
and renders it in a Twig template using JavaScript. This allows visualising the various workflows on the platform which also makes it visible to users who may not have the Symfony site installed locally, or know how to use another visualisation tool.
The controller uses the name as path argument to load the workflow. A potential workaround for my use-case would be to manually inject every defined workflow, but this requires updating the controller every time a new workflow is defined or a workflow is removed. Additionally this does not allow creating an overview of all available workflows on a page automatically.
Example
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Workflow\Registry;
class DocumentationController extends AbstractController {
/**
* Visualise a workflow using vis.js.
*
* Documentation about vis.js can be found at:
* https://visjs.github.io/vis-network/docs/network/
*
* Check the corresponding template for the options that are in use.
*
* @Route("/docs/workflows/{workflow}/visualise", name="documentation_workflow_visualise", methods={"GET"})
*
* @param string $workflow
* The name of the workflow to visualise in hyphenated-form.
*/
public function visualiseWorkflowAction(string $workflow, Registry $registry): Response
{
// We use hyphens for our URL and templates but underscores for the workflow
// machine names.
$workflow = str_replace('-', '_', $workflow);
if (!$registry->has($workflow))
{
throw new NotFoundHttpException(sprintf('Workflow "%1$s" does not exist.".', $workflow));
}
$workflow = $registry->getByName($workflow);
$options = [
'nodes' => [
// Our places are boxes by default.
'shape' => 'box',
],
'edges' => [
// Our workflows are directional so we show arrows to indicate where
// they're going.
'arrows' => 'to',
'color' => [
// The color of the transition matches where we're going to. This
// ensures transitions to errors have the error color.
'inherit' => 'to',
],
],
];
$definition = $workflow->getDefinition();
$nodes = [];
foreach ($definition->getPlaces() as $place)
{
$node = ['id' => $place, 'label' => $place];
// Make the initial place stand out.
if (in_array($place, $definition->getInitialPlaces(), TRUE))
{
$node['shape'] = 'circle';
}
// Differentiate error states.
if (substr($place, 0, 6) === 'error_')
{
$node['color'] = '#ffb5a7';
}
$nodes[] = $node;
}
$edges = [];
foreach ($definition->getTransitions() as $transition)
{
// A transition may specify multiple origins and destinations which means
// We need to plot all of them.
foreach ($transition->getFroms() as $from)
{
foreach ($transition->getTos() as $to)
{
$edges[] = ['label' => $transition->getName(), 'from' => $from, 'to' => $to];
}
}
}
return $this->render(
'documentation/workflows/visualise.html.twig',
[
'workflow_slug' => str_replace('_', '-', $workflow->getName()),
'workflow_label' => ucwords(str_replace('_', ' ', $workflow->getName())),
'nodes' => $nodes,
'edges' => $edges,
'options' => $options,
]
);
}
}
The following (shortened) template is used for visualisation.
{% block main %}
<h1>Visualise</h1>
<div id="visualisation-container">
<div id="visualisation"></div>
</div>
{% endblock %}
{% block stylesheets %}
{{ parent() }}
<style type="text/css">
#main {
display: flex;
flex-direction: column;
}
#main h1 {
flex: 0 0 auto;
}
#visualisation-container {
flex: auto;
position: relative;
}
{#
The vis.js library doesn't handle dynamic full-height well so we require
a bit of a trick to make it take up all space on the page.
#}
#visualisation {
position: absolute;
inset: 0;
}
</style>
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script type="text/javascript" src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
<script type="text/javascript">
// For documentation check: https://visjs.github.io/vis-network/docs/network/
const container = document.getElementById('visualisation');
const nodes = new vis.DataSet({{ nodes|json_encode|raw }});
const edges = new vis.DataSet({{ edges|json_encode|raw }});
const data = {
nodes: nodes,
edges: edges
};
const options = {{ options|json_encode|raw }};
const network = new vis.Network(container, data, options);
</script>
{% endblock %}