Skip to content

[Workflow] doc improvements #11578

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 1 commit into from
May 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified _images/components/workflow/pull_request.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 5 additions & 2 deletions components/workflow.rst
Original file line number Diff line number Diff line change
Expand Up @@ -76,16 +76,19 @@ are trying to use it with::
Usage
-----

When you have configured a ``Registry`` with your workflows, you may use it as follows::
When you have configured a ``Registry`` with your workflows,
you can retreive a workflow from it and use it as follows::

// ...
// Consider that $post is in state "draft" by default
$post = new BlogPost();
$workflow = $registry->get($post);

$workflow->can($post, 'publish'); // False
$workflow->can($post, 'to_review'); // True

$workflow->apply($post, 'to_review');
$workflow->apply($post, 'to_review'); // $post is now in state "review"

$workflow->can($post, 'publish'); // True
$workflow->getEnabledTransitions($post); // ['publish', 'reject']

Expand Down
169 changes: 104 additions & 65 deletions workflow.rst
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,10 @@ As configured, the following property is used by the marking store::
With this workflow named ``blog_publishing``, you can get help to decide
what actions are allowed on a blog post::

$post = new App\Entity\BlogPost();
use Symfony\Component\Workflow\Exception\LogicException;
use App\Entity\BlogPost;

$post = BlogPost();

$workflow = $this->container->get('workflow.blog_publishing');
$workflow->can($post, 'publish'); // False
Expand Down Expand Up @@ -401,6 +404,9 @@ This means that each event has access to the following information:
:method:`Symfony\\Component\\Workflow\\Event\\Event::getWorkflowName`
Returns a string with the name of the workflow that triggered the event.

:method:`Symfony\\Component\\Workflow\\Event\\Event::getMetadata`
Returns a metadata.

For Guard Events, there is an extended class :class:`Symfony\\Component\\Workflow\\Event\\GuardEvent`.
This class has two more methods:

Expand All @@ -410,6 +416,13 @@ This class has two more methods:
:method:`Symfony\\Component\\Workflow\\Event\\GuardEvent::setBlocked`
Sets the blocked value.

:method:`Symfony\\Component\\Workflow\\Event\\GuardEvent::getTransitionBlockerList`
Returns the event :class:`Symfony\\Component\\Workflow\\TransitionBlockerList`.
See :ref:`blocking transitions <workflow-blocking-transitions>`.

:method:`Symfony\\Component\\Workflow\\Event\\GuardEvent::addTransitionBlocker`
Add a :class:`Symfony\\Component\\Workflow\\TransitionBlocker` instance.

.. _workflow-blocking-transitions:

Blocking Transitions
Expand Down Expand Up @@ -438,16 +451,61 @@ transition. The value of this option is any valid expression created with the
from: draft
to: reviewed
publish:
# or "is_anonymous", "is_remember_me", "is_fully_authenticated", "is_granted"
# or "is_anonymous", "is_remember_me", "is_fully_authenticated", "is_granted", "is_valid"
guard: "is_authenticated"
from: reviewed
to: published
reject:
# or any valid expression language with "subject" referring to the post
guard: "has_role('ROLE_ADMIN') and subject.isStatusReviewed()"
# or any valid expression language with "subject" referring to the supported object
guard: "has_role('ROLE_ADMIN') and subject.isRejectable()"
from: reviewed
to: rejected

You can also use transition blockers to block and return a user-friendly error
message when you stop a transition from happening.
In the example we get this message from the
:class:`Symfony\\Component\\Workflow\\Event\\Event`'s metadata, giving you a
central place to manage the text.

This example has been simplified; in production you may prefer to use the
:doc:`Translation </components/translation>` component to manage messages in one
place::

namespace App\Listener\Workflow\Task;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Workflow\Event\GuardEvent;
use Symfony\Component\Workflow\TransitionBlocker;

class BlogPostPublishListener implements EventSubscriberInterface
{
public function guardPublish(GuardEvent $event)
{
$eventTransition = $event->getTransition();
$hourLimit = $event->getMetadata('hour_limit', $eventTransition);

if (date('H') <= $hourLimit) {
return;
}

// Block the transition "publish" if it is more than 8 PM
// with the message for end user
$explanation = $event->getMetadata('explanation', $eventTransition);
$event->addTransitionBlocker(new TransitionBlocker($explanation , 0));
}

public static function getSubscribedEvents()
{
return [
'workflow.blog_publishing.guard.publish' => ['guardPublish'],
];
}
}

.. versionadded:: 4.1

The transition blockers were introduced in Symfony 4.1.

Usage in Twig
-------------

Expand All @@ -470,15 +528,15 @@ The following example shows these functions in action:

.. code-block:: html+twig

<h3>Actions</h3>
<h3>Actions on Blog Post</h3>
{% if workflow_can(post, 'publish') %}
<a href="...">Publish article</a>
<a href="...">Publish</a>
{% endif %}
{% if workflow_can(post, 'to_review') %}
<a href="...">Submit to review</a>
{% endif %}
{% if workflow_can(post, 'reject') %}
<a href="...">Reject article</a>
<a href="...">Reject</a>
{% endif %}

{# Or loop through the enabled transitions #}
Expand All @@ -494,8 +552,8 @@ The following example shows these functions in action:
{% endif %}

{# Check if some place has been marked on the object #}
{% if 'waiting_some_approval' in workflow_marked_places(post) %}
<span class="label">PENDING</span>
{% if 'reviewed' in workflow_marked_places(post) %}
<span class="label">Reviewed</span>
{% endif %}

Storing Metadata
Expand Down Expand Up @@ -532,7 +590,12 @@ requires:
to: review
metadata:
priority: 0.5
# ...
publish:
from: reviewed
to: published
metadata:
hour_limit: 20
explanation: 'You can not publish after 8 PM.'

.. code-block:: xml

Expand Down Expand Up @@ -563,7 +626,14 @@ requires:
<framework:priority>0.5</framework:priority>
</framework:metadata>
</framework:transition>
<!-- ... -->
<framework:transition name="publish">
<framework:from>reviewed</framework:from>
<framework:to>published</framework:to>
<framework:metadata>
<framework:hour_limit>20</framework:priority>
<framework:explanation>You can not publish after 8 PM.</framework:priority>
</framework:metadata>
</framework:transition>
</framework:workflow>
</framework:config>
</container>
Expand Down Expand Up @@ -595,6 +665,14 @@ requires:
'priority' => 0.5,
],
],
'publish' => [
'from' => 'reviewed',
'to' => 'published',
'metadata' => [
'hour_limit' => 20,
'explanation' => 'You can not publish after 8 PM.',
],
],
],
],
],
Expand All @@ -603,27 +681,29 @@ requires:
Then you can access this metadata in your controller as follows::

use Symfony\Component\Workflow\Registry;
use App\Entity\BlogPost;

public function myController(Registry $registry, Article $article)
public function myController(Registry $registry, BlogPost $post)
{
$workflow = $registry->get($article);
$workflow = $registry->get($post);

$title = $workflow
->getMetadataStore()
->getWorkflowMetadata()['title'] ?? false
->getWorkflowMetadata()['title'] ?? 'Default title'
;

// or
$aTransition = $workflow->getDefinition()->getTransitions()[0];
$transitionTitle = $workflow
->getMetadataStore()
->getTransitionMetadata($aTransition)['title'] ?? false
->getTransitionMetadata($aTransition)['priority'] ?? 0
;
}

There is a shortcut that works with everything::
There is a shortcut that works with every metadata level::

$title = $workflow->getMetadataStore()->getMetadata('title');
$priority = $workflow->getMetadataStore()->getMetadata('priority');

In a :ref:`flash message <flash-messages>` in your controller::

Expand All @@ -633,76 +713,35 @@ In a :ref:`flash message <flash-messages>` in your controller::
$title = $workflow->getMetadataStore()->getMetadata('title', $transition);
$this->addFlash('info', "You have successfully applied the transition with title: '$title'");

Metadata can also be accessed in a Listener, from the Event object.

Using transition blockers you can return a user-friendly error message when you
stop a transition from happening. In the example we get this message from the
:class:`Symfony\\Component\\Workflow\\Event\\Event`'s metadata, giving you a
central place to manage the text.

This example has been simplified; in production you may prefer to use the
:doc:`Translation </components/translation>` component to manage messages in one
place::

namespace App\Listener\Workflow\Task;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Workflow\Event\GuardEvent;
use Symfony\Component\Workflow\TransitionBlocker;

class OverdueGuard implements EventSubscriberInterface
{
public function guardPublish(GuardEvent $event)
{
$timeLimit = $event->getMetadata('time_limit', $event->getTransition());

if (date('Hi') <= $timeLimit) {
return;
}

$explanation = $event->getMetadata('explanation', $event->getTransition());
$event->addTransitionBlocker(new TransitionBlocker($explanation , 0));
}

public static function getSubscribedEvents()
{
return [
'workflow.task.guard.done' => 'guardPublish',
];
}
}

.. versionadded:: 4.1

The transition blockers were introduced in Symfony 4.1.
Metadata can also be accessed in a Listener, from the :class:`Symfony\\Component\\Workflow\\Event\\Event` object.

In Twig templates, metadata is available via the ``workflow_metadata()`` function:

.. code-block:: html+twig

<h2>Metadata</h2>
<h2>Metadata of Blog Post</h2>
<p>
<strong>Workflow</strong>:<br >
<code>{{ workflow_metadata(article, 'title') }}</code>
<strong>Workflow</strong>:<br>
<code>{{ workflow_metadata(blog_post, 'title') }}</code>
</p>
<p>
<strong>Current place(s)</strong>
<ul>
{% for place in workflow_marked_places(article) %}
{% for place in workflow_marked_places(blog_post) %}
<li>
{{ place }}:
<code>{{ workflow_metadata(article, 'max_num_of_words', place) ?: 'Unlimited'}}</code>
<code>{{ workflow_metadata(blog_post, 'max_num_of_words', place) ?: 'Unlimited'}}</code>
</li>
{% endfor %}
</ul>
</p>
<p>
<strong>Enabled transition(s)</strong>
<ul>
{% for transition in workflow_transitions(article) %}
{% for transition in workflow_transitions(blog_post) %}
<li>
{{ transition.name }}:
<code>{{ workflow_metadata(article, 'priority', transition) ?: '0' }}</code>
<code>{{ workflow_metadata(blog_post, 'priority', transition) ?: '0' }}</code>
</li>
{% endfor %}
</ul>
Expand Down
18 changes: 11 additions & 7 deletions workflow/dumping-workflows.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ How to Dump Workflows
=====================

To help you debug your workflows, you can dump a representation of your workflow
or state machine with the use of a ``DumperInterface``. Symfony provides 2
different dumpers both based on Dot.
or state machine with the use of a ``DumperInterface``. Symfony provides two
different dumpers, both based on Dot (see below).

Use the ``GraphvizDumper`` or ``StateMachineGraphvizDumper`` to create DOT
files, or use ``PlantUmlDumper`` for PlantUML files. Both types can be converted
Expand All @@ -24,17 +24,20 @@ Images of the workflow defined above::

.. code-block:: terminal

# dump DOT file in PNG image:
$ php dump-graph-dot.php | dot -Tpng -o dot_graph.png
$ php dump-graph-puml.php | java -jar plantuml.jar -p > puml_graph.png

# run this command if you prefer SVG images:
# dump DOT file in SVG image:
# $ php dump-graph-dot.php | dot -Tsvg -o dot_graph.svg

# dump PlantUML in PNG image:
$ php dump-graph-puml.php | java -jar plantuml.jar -p > puml_graph.png
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this grouping is a bit confusing now, because it generates a png file, but follows a comment/description which is talking about svg 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wanted to "group" dot cli vs java
i propose a new way


The DOT result will look like this:

.. image:: /_images/components/workflow/blogpost.png

The PUML result:
The PlantUML result:

.. image:: /_images/components/workflow/blogpost_puml.png

Expand All @@ -43,8 +46,9 @@ Inside a Symfony application, you can dump the files with those commands using

.. code-block:: terminal

$ php bin/console workflow:dump name | dot -Tsvg -o graph.svg
$ php bin/console workflow:dump name --dump-format=puml | java -jar plantuml.jar -p > workflow.png
$ php bin/console workflow:dump workflow_name | dot -Tpng -o workflow_name.png
$ php bin/console workflow:dump workflow_name | dot -Tsvg -o workflow_name.svg
$ php bin/console workflow:dump workflow_name --dump-format=puml | java -jar plantuml.jar -p > workflow_name.png
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above


.. note::

Expand Down
Loading