From dbd772ca2e0bacbb67c688c9b1c888af59187345 Mon Sep 17 00:00:00 2001 From: Jerzy Zawadzki Date: Sat, 8 Dec 2018 15:55:06 +0100 Subject: [PATCH 1/2] Change wording and order of "Dumping workflows" page --- workflow/dumping-workflows.rst | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/workflow/dumping-workflows.rst b/workflow/dumping-workflows.rst index b8709f722ee..01d7afc69a3 100644 --- a/workflow/dumping-workflows.rst +++ b/workflow/dumping-workflows.rst @@ -12,8 +12,6 @@ Use the ``GraphvizDumper`` or ``StateMachineGraphvizDumper`` to create DOT files, or use ``PlantUmlDumper`` for PlantUML files. Both types can be converted to PNG or SVG images. -Images of the workflow defined above: - .. code-block:: php // dump-graph-dot.php @@ -34,6 +32,15 @@ Images of the workflow defined above: # run this command if you prefer SVG images: # $ php dump-graph-dot.php | dot -Tsvg -o dot_graph.svg + +Inside a Symfony application, you can dump the files with those commands using +``workflow:dump`` command: + +.. 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 + The DOT result will look like this: .. image:: /_images/components/workflow/blogpost.png @@ -42,13 +49,7 @@ The PUML result: .. image:: /_images/components/workflow/blogpost_puml.png -Inside a Symfony application, you can dump the files with those commands using -``workflow:dump`` command: - -.. 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 .. note:: From a16868c888c5b50eb485ca7ab04b6b308a480fb2 Mon Sep 17 00:00:00 2001 From: Jerzy Zawadzki Date: Sat, 8 Dec 2018 15:56:08 +0100 Subject: [PATCH 2/2] Create Workflow Events page and merge Workflow and Usage pages --- workflow.rst | 299 ++++++++++++++++++++++++++- workflow/events.rst | 195 ++++++++++++++++++ workflow/usage.rst | 477 -------------------------------------------- 3 files changed, 490 insertions(+), 481 deletions(-) create mode 100644 workflow/events.rst delete mode 100644 workflow/usage.rst diff --git a/workflow.rst b/workflow.rst index b25e62b8379..db1062a10d5 100644 --- a/workflow.rst +++ b/workflow.rst @@ -16,9 +16,13 @@ to a property of the object to remember the current place. The terminology above is commonly used when discussing workflows and `Petri nets`_ -The Workflow component does also support state machines. A state machine is a subset -of a workflow and its purpose is to hold a state of your model. Read more about the -differences and specific features of state machine in :doc:`/workflow/state-machines`. +The Workflow component helps you handle different kind of workflows in your application. + +.. tip:: + + Component does also support state machines. A state machine is a subset + of a workflow and its purpose is to hold a state of your model. Read more about the + differences and specific features of state machine in :doc:`/workflow/state-machines`. Examples -------- @@ -41,13 +45,300 @@ By defining a workflow like this, there is an overview how the process looks lik logic is not mixed with the controllers, models or view. The order of the steps can be changed by changing the configuration only. + +Installation +------------ + +In applications using :doc:`Symfony Flex `, run this command to +install the workflow feature before using it: + +.. code-block:: terminal + + $ composer require symfony/workflow + +Defining a Workflow +------------------- + +Consider the following example for a blog post that can have these places: +``draft``, ``review``, ``rejected``, ``published``. You can define the workflow +like this: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/workflow.yaml + framework: + workflows: + blog_publishing: + type: 'workflow' # or 'state_machine' + audit_trail: + enabled: true + marking_store: + type: 'single_state' # or 'multiple_state' + arguments: + - 'currentPlace' + supports: + - App\Entity\BlogPost + initial_place: draft + places: + - draft + - review + - rejected + - published + transitions: + to_review: + from: draft + to: review + publish: + from: review + to: published + reject: + from: review + to: rejected + + .. code-block:: xml + + + + + + + + + + + currentPlace + + + App\Entity\BlogPost + + draft + review + rejected + published + + + draft + + review + + + + review + + published + + + + review + + rejected + + + + + + + + .. code-block:: php + + // config/packages/workflow.php + + $container->loadFromExtension('framework', array( + // ... + 'workflows' => array( + 'blog_publishing' => array( + 'type' => 'workflow', // or 'state_machine' + 'audit_trail' => array( + 'enabled' => true + ), + 'marking_store' => array( + 'type' => 'single_state', // or 'multiple_state' + 'arguments' => array('currentPlace') + ), + 'supports' => array('App\Entity\BlogPost'), + 'places' => array( + 'draft', + 'review', + 'rejected', + 'published', + ), + 'transitions' => array( + 'to_review' => array( + 'from' => 'draft', + 'to' => 'review', + ), + 'publish' => array( + 'from' => 'review', + 'to' => 'published', + ), + 'reject' => array( + 'from' => 'review', + 'to' => 'rejected', + ), + ), + ), + ), + )); + +.. code-block:: php + + class BlogPost + { + public $currentPlace; // This property is used by the marking store + public $title; + public $content; + } + +.. note:: + + The marking store type could be "single_state" or "multiple_state". + A multiple state marking store allow your model to be on multiple places + at the same time. + +.. tip:: + + The ``type`` (default value ``single_state``) and ``arguments`` (default + value ``marking``) attributes of the ``marking_store`` option are optional. + If omitted, their default values will be used. + +.. tip:: + + Setting the ``audit_trail.enabled`` option to ``true`` makes the application + generate detailed log messages for the workflow activity. + +.. tip:: + + You can easily visualize your workflow by using ``workflow:dump`` command. + Read more on :doc:`/workflow/dumping-workflows` + + +Workflow Events +--------------- + +To make your workflows more flexible, Workflow component allows you to listen on +several events raised during transitions. You can create event listeners to block them +(i.e. depending on the data in the blog post) and do additional actions when a workflow +operation happened (e.g. sending announcements). Read more on different kind of events +on :doc:`/workflow/events` + + +Using a Workflow +---------------- + +Once the ``blog_publishing`` workflow has been defined, you can now use it to +decide what actions are allowed on a blog post. For example, inside a controller +of an application using the :ref:`default services.yaml configuration `, +you can get the workflow by injecting the Workflow registry service:: + + // ... + use Symfony\Component\Workflow\Registry; + use App\Entity\BlogPost; + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\Workflow\Exception\TransitionException; + + class BlogController extends AbstractController + { + public function edit(Registry $workflows) + { + $post = new BlogPost(); + $workflow = $workflows->get($post); + + // if there are multiple workflows for the same class, + // pass the workflow name as the second argument + // $workflow = $workflows->get($post, 'blog_publishing'); + + // you can also get all workflows associated with an object, which is useful + // for example to show the status of all those workflows in a backend + $postWorkflows = $workflows->all($post); + + $workflow->can($post, 'publish'); // False + $workflow->can($post, 'to_review'); // True + + // Update the currentPlace on the post + try { + $workflow->apply($post, 'to_review'); + } catch (TransitionException $exception) { + // ... if the transition is not allowed + } + + // See all the available transitions for the post in the current state + $transitions = $workflow->getEnabledTransitions($post); + } + } + +.. versionadded:: 4.1 + The :class:`Symfony\\Component\\Workflow\\Exception\\TransitionException` + class was introduced in Symfony 4.1. + +.. versionadded:: 4.1 + The :method:`Symfony\\Component\\Workflow\\Registry::all` method was + introduced in Symfony 4.1. + + +Usage in Twig +------------- + +Symfony defines several Twig functions to manage workflows and reduce the need +of domain logic in your templates: + +``workflow_can()`` + Returns ``true`` if the given object can make the given transition. + +``workflow_transitions()`` + Returns an array with all the transitions enabled for the given object. + +``workflow_marked_places()`` + Returns an array with the place names of the given marking. + +``workflow_has_marked_place()`` + Returns ``true`` if the marking of the given object has the given state. + +The following example shows these functions in action: + +.. code-block:: twig + +

Actions

+ {% if workflow_can(post, 'publish') %} + Publish article + {% endif %} + {% if workflow_can(post, 'to_review') %} + Submit to review + {% endif %} + {% if workflow_can(post, 'reject') %} + Reject article + {% endif %} + + {# Or loop through the enabled transitions #} + {% for transition in workflow_transitions(post) %} + {{ transition.name }} + {% else %} + No actions available. + {% endfor %} + + {# Check if the object is in some specific place #} + {% if workflow_has_marked_place(post, 'review') %} +

This post is ready for review.

+ {% endif %} + + {# Check if some place has been marked on the object #} + {% if 'waiting_some_approval' in workflow_marked_places(post) %} + PENDING + {% endif %} + Learn more ---------- .. toctree:: :maxdepth: 1 - workflow/usage + workflow/events workflow/state-machines workflow/dumping-workflows diff --git a/workflow/events.rst b/workflow/events.rst new file mode 100644 index 00000000000..4c1dd0b5209 --- /dev/null +++ b/workflow/events.rst @@ -0,0 +1,195 @@ +.. index:: + single: Workflow; Usage + +Using Workflow Events +=============================== + +To make your workflows more flexible, you can construct the ``Workflow`` +object with an ``EventDispatcher``. You can now create event listeners to +block transitions (i.e. depending on the data in the blog post) and do +additional actions when a workflow operation happened (e.g. sending +announcements). + +Each step has three events that are fired in order: + +* An event for every workflow; +* An event for the workflow concerned; +* An event for the workflow concerned with the specific transition or place name. + +When a state transition is initiated, the events are dispatched in the following +order: + +``workflow.guard`` + Validate whether the transition is allowed at all (:ref:`see below `). + + The three events being dispatched are: + + * ``workflow.guard`` + * ``workflow.[workflow name].guard`` + * ``workflow.[workflow name].guard.[transition name]`` + +``workflow.leave`` + The subject is about to leave a place. + + The three events being dispatched are: + + * ``workflow.leave`` + * ``workflow.[workflow name].leave`` + * ``workflow.[workflow name].leave.[place name]`` + +``workflow.transition`` + The subject is going through this transition. + + The three events being dispatched are: + + * ``workflow.transition`` + * ``workflow.[workflow name].transition`` + * ``workflow.[workflow name].transition.[transition name]`` + +``workflow.enter`` + The subject is about to enter a new place. This event is triggered just + before the subject places are updated, which means that the marking of the + subject is not yet updated with the new places. + + The three events being dispatched are: + + * ``workflow.enter`` + * ``workflow.[workflow name].enter`` + * ``workflow.[workflow name].enter.[place name]`` + +``workflow.entered`` + The subject has entered in the places and the marking is updated (making it a good + place to flush data in Doctrine). + + The three events being dispatched are: + + * ``workflow.entered`` + * ``workflow.[workflow name].entered`` + * ``workflow.[workflow name].entered.[place name]`` + +``workflow.completed`` + The object has completed this transition. + + The three events being dispatched are: + + * ``workflow.completed`` + * ``workflow.[workflow name].completed`` + * ``workflow.[workflow name].completed.[transition name]`` + + +``workflow.announce`` + Triggered for each transition that now is accessible for the subject. + + The three events being dispatched are: + + * ``workflow.announce`` + * ``workflow.[workflow name].announce`` + * ``workflow.[workflow name].announce.[transition name]`` + +.. note:: + + The leaving and entering events are triggered even for transitions that stay + in same place. + +Here is an example of how to enable logging for every time the ``blog_publishing`` +workflow leaves a place:: + + use Psr\Log\LoggerInterface; + use Symfony\Component\EventDispatcher\EventSubscriberInterface; + use Symfony\Component\Workflow\Event\Event; + + class WorkflowLogger implements EventSubscriberInterface + { + public function __construct(LoggerInterface $logger) + { + $this->logger = $logger; + } + + public function onLeave(Event $event) + { + $this->logger->alert(sprintf( + 'Blog post (id: "%s") performed transaction "%s" from "%s" to "%s"', + $event->getSubject()->getId(), + $event->getTransition()->getName(), + implode(', ', array_keys($event->getMarking()->getPlaces())), + implode(', ', $event->getTransition()->getTos()) + )); + } + + public static function getSubscribedEvents() + { + return array( + 'workflow.blog_publishing.leave' => 'onLeave', + ); + } + } + +.. _workflow-usage-guard-events: + +Guard Events +~~~~~~~~~~~~ + +There are a special kind of events called "Guard events". Their event listeners +are invoked every time a call to ``Workflow::can``, ``Workflow::apply`` or +``Workflow::getEnabledTransitions`` is executed. With the guard events you may +add custom logic to decide what transitions that are valid or not. Here is a list +of the guard event names. + +* ``workflow.guard`` +* ``workflow.[workflow name].guard`` +* ``workflow.[workflow name].guard.[transition name]`` + +See example to make sure no blog post without title is moved to "review":: + + use Symfony\Component\Workflow\Event\GuardEvent; + use Symfony\Component\EventDispatcher\EventSubscriberInterface; + + class BlogPostReviewListener implements EventSubscriberInterface + { + public function guardReview(GuardEvent $event) + { + /** @var \App\Entity\BlogPost $post */ + $post = $event->getSubject(); + $title = $post->title; + + if (empty($title)) { + // Posts with no title should not be allowed + $event->setBlocked(true); + } + } + + public static function getSubscribedEvents() + { + return array( + 'workflow.blogpost.guard.to_review' => array('guardReview'), + ); + } + } + +Event Methods +~~~~~~~~~~~~~ + +Each workflow event is an instance of :class:`Symfony\\Component\\Workflow\\Event\\Event`. +This means that each event +Defining a Workflowhas access to the following information: + +:method:`Symfony\\Component\\Workflow\\Event\\Event::getMarking` + Returns the :class:`Symfony\\Component\\Workflow\\Marking` of the workflow. + +:method:`Symfony\\Component\\Workflow\\Event\\Event::getSubject` + Returns the object that dispatches the event. + +:method:`Symfony\\Component\\Workflow\\Event\\Event::getTransition` + Returns the :class:`Symfony\\Component\\Workflow\\Transition` that dispatches the event. + +:method:`Symfony\\Component\\Workflow\\Event\\Event::getWorkflowName` + Returns a string with the name of the workflow that triggered the event. + +For Guard Events, there is an extended class :class:`Symfony\\Component\\Workflow\\Event\\GuardEvent`. +This class has two more methods: + +:method:`Symfony\\Component\\Workflow\\Event\\GuardEvent::isBlocked` + Returns if transition is blocked. + +:method:`Symfony\\Component\\Workflow\\Event\\GuardEvent::setBlocked` + Sets the blocked value. diff --git a/workflow/usage.rst b/workflow/usage.rst deleted file mode 100644 index 5373c6e1345..00000000000 --- a/workflow/usage.rst +++ /dev/null @@ -1,477 +0,0 @@ -.. index:: - single: Workflow; Usage - -How to Create and Use Workflows -=============================== - -Installation ------------- - -In applications using :doc:`Symfony Flex `, run this command to -install the workflow feature before using it: - -.. code-block:: terminal - - $ composer require symfony/workflow - -Creating a Workflow -------------------- - -A workflow is a process or a lifecycle that your objects go through. Each -step or stage in the process is called a *place*. You do also define *transitions* -to that describes the action to get from one place to another. - -.. image:: /_images/components/workflow/states_transitions.png - -A set of places and transitions creates a **definition**. A workflow needs -a ``Definition`` and a way to write the states to the objects (i.e. an -instance of a :class:`Symfony\\Component\\Workflow\\MarkingStore\\MarkingStoreInterface`.) - -Consider the following example for a blog post that can have these places: -``draft``, ``review``, ``rejected``, ``published``. You can define the workflow -like this: - -.. configuration-block:: - - .. code-block:: yaml - - # config/packages/workflow.yaml - framework: - workflows: - blog_publishing: - type: 'workflow' # or 'state_machine' - audit_trail: - enabled: true - marking_store: - type: 'multiple_state' # or 'single_state' - arguments: - - 'currentPlace' - supports: - - App\Entity\BlogPost - initial_place: draft - places: - - draft - - review - - rejected - - published - transitions: - to_review: - from: draft - to: review - publish: - from: review - to: published - reject: - from: review - to: rejected - - .. code-block:: xml - - - - - - - - - - - currentPlace - - - App\Entity\BlogPost - - draft - review - rejected - published - - - draft - - review - - - - review - - published - - - - review - - rejected - - - - - - - - .. code-block:: php - - // config/packages/workflow.php - - $container->loadFromExtension('framework', array( - // ... - 'workflows' => array( - 'blog_publishing' => array( - 'type' => 'workflow', // or 'state_machine' - 'audit_trail' => array( - 'enabled' => true - ), - 'marking_store' => array( - 'type' => 'multiple_state', // or 'single_state' - 'arguments' => array('currentPlace') - ), - 'supports' => array('App\Entity\BlogPost'), - 'places' => array( - 'draft', - 'review', - 'rejected', - 'published', - ), - 'transitions' => array( - 'to_review' => array( - 'from' => 'draft', - 'to' => 'review', - ), - 'publish' => array( - 'from' => 'review', - 'to' => 'published', - ), - 'reject' => array( - 'from' => 'review', - 'to' => 'rejected', - ), - ), - ), - ), - )); - -.. code-block:: php - - class BlogPost - { - // This property is used by the marking store - public $currentPlace; - public $title; - public $content; - } - -.. note:: - - The marking store type could be "multiple_state" or "single_state". - A single state marking store does not support a model being on multiple places - at the same time. - -.. tip:: - - The ``type`` (default value ``single_state``) and ``arguments`` (default - value ``marking``) attributes of the ``marking_store`` option are optional. - If omitted, their default values will be used. - -.. tip:: - - Setting the ``audit_trail.enabled`` option to ``true`` makes the application - generate detailed log messages for the workflow activity. - -Using a Workflow ----------------- - -Once the ``blog_publishing`` workflow has been created, you can now use it to -decide what actions are allowed on a blog post. For example, inside a controller -of an application using the :ref:`default services.yaml configuration `, -you can get the workflow by injecting the Workflow registry service:: - - // ... - use Symfony\Component\Workflow\Registry; - use App\Entity\BlogPost; - use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; - use Symfony\Component\Workflow\Exception\TransitionException; - - class BlogController extends AbstractController - { - public function edit(Registry $workflows) - { - $post = new BlogPost(); - $workflow = $workflows->get($post); - - // if there are multiple workflows for the same class, - // pass the workflow name as the second argument - // $workflow = $workflows->get($post, 'blog_publishing'); - - // you can also get all workflows associated with an object, which is useful - // for example to show the status of all those workflows in a backend - $postWorkflows = $workflows->all($post); - - $workflow->can($post, 'publish'); // False - $workflow->can($post, 'to_review'); // True - - // Update the currentState on the post - try { - $workflow->apply($post, 'to_review'); - } catch (TransitionException $exception) { - // ... if the transition is not allowed - } - - // See all the available transitions for the post in the current state - $transitions = $workflow->getEnabledTransitions($post); - } - } - -.. versionadded:: 4.1 - The :class:`Symfony\\Component\\Workflow\\Exception\\TransitionException` - class was introduced in Symfony 4.1. - -.. versionadded:: 4.1 - The :method:`Symfony\\Component\\Workflow\\Registry::all` method was - introduced in Symfony 4.1. - -Using Events ------------- - -To make your workflows more flexible, you can construct the ``Workflow`` -object with an ``EventDispatcher``. You can now create event listeners to -block transitions (i.e. depending on the data in the blog post) and do -additional actions when a workflow operation happened (e.g. sending -announcements). - -Each step has three events that are fired in order: - -* An event for every workflow; -* An event for the workflow concerned; -* An event for the workflow concerned with the specific transition or place name. - -When a state transition is initiated, the events are dispatched in the following -order: - -``workflow.guard`` - Validate whether the transition is allowed at all (:ref:`see below `). - - The three events being dispatched are: - - * ``workflow.guard`` - * ``workflow.[workflow name].guard`` - * ``workflow.[workflow name].guard.[transition name]`` - -``workflow.leave`` - The subject is about to leave a place. - - The three events being dispatched are: - - * ``workflow.leave`` - * ``workflow.[workflow name].leave`` - * ``workflow.[workflow name].leave.[place name]`` - -``workflow.transition`` - The subject is going through this transition. - - The three events being dispatched are: - - * ``workflow.transition`` - * ``workflow.[workflow name].transition`` - * ``workflow.[workflow name].transition.[transition name]`` - -``workflow.enter`` - The subject is about to enter a new place. This event is triggered just - before the subject places are updated, which means that the marking of the - subject is not yet updated with the new places. - - The three events being dispatched are: - - * ``workflow.enter`` - * ``workflow.[workflow name].enter`` - * ``workflow.[workflow name].enter.[place name]`` - -``workflow.entered`` - The subject has entered in the places and the marking is updated (making it a good - place to flush data in Doctrine). - - The three events being dispatched are: - - * ``workflow.entered`` - * ``workflow.[workflow name].entered`` - * ``workflow.[workflow name].entered.[place name]`` - -``workflow.completed`` - The object has completed this transition. - - The three events being dispatched are: - - * ``workflow.completed`` - * ``workflow.[workflow name].completed`` - * ``workflow.[workflow name].completed.[transition name]`` - - -``workflow.announce`` - Triggered for each transition that now is accessible for the subject. - - The three events being dispatched are: - - * ``workflow.announce`` - * ``workflow.[workflow name].announce`` - * ``workflow.[workflow name].announce.[transition name]`` - -.. note:: - - The leaving and entering events are triggered even for transitions that stay - in same place. - -Here is an example of how to enable logging for every time the ``blog_publishing`` -workflow leaves a place:: - - use Psr\Log\LoggerInterface; - use Symfony\Component\EventDispatcher\EventSubscriberInterface; - use Symfony\Component\Workflow\Event\Event; - - class WorkflowLogger implements EventSubscriberInterface - { - public function __construct(LoggerInterface $logger) - { - $this->logger = $logger; - } - - public function onLeave(Event $event) - { - $this->logger->alert(sprintf( - 'Blog post (id: "%s") performed transaction "%s" from "%s" to "%s"', - $event->getSubject()->getId(), - $event->getTransition()->getName(), - implode(', ', array_keys($event->getMarking()->getPlaces())), - implode(', ', $event->getTransition()->getTos()) - )); - } - - public static function getSubscribedEvents() - { - return array( - 'workflow.blog_publishing.leave' => 'onLeave', - ); - } - } - -.. _workflow-usage-guard-events: - -Guard Events -~~~~~~~~~~~~ - -There are a special kind of events called "Guard events". Their event listeners -are invoked every time a call to ``Workflow::can``, ``Workflow::apply`` or -``Workflow::getEnabledTransitions`` is executed. With the guard events you may -add custom logic to decide what transitions that are valid or not. Here is a list -of the guard event names. - -* ``workflow.guard`` -* ``workflow.[workflow name].guard`` -* ``workflow.[workflow name].guard.[transition name]`` - -See example to make sure no blog post without title is moved to "review":: - - use Symfony\Component\Workflow\Event\GuardEvent; - use Symfony\Component\EventDispatcher\EventSubscriberInterface; - - class BlogPostReviewListener implements EventSubscriberInterface - { - public function guardReview(GuardEvent $event) - { - /** @var \App\Entity\BlogPost $post */ - $post = $event->getSubject(); - $title = $post->title; - - if (empty($title)) { - // Posts with no title should not be allowed - $event->setBlocked(true); - } - } - - public static function getSubscribedEvents() - { - return array( - 'workflow.blogpost.guard.to_review' => array('guardReview'), - ); - } - } - -Event Methods -~~~~~~~~~~~~~ - -Each workflow event is an instance of :class:`Symfony\\Component\\Workflow\\Event\\Event`. -This means that each event has access to the following information: - -:method:`Symfony\\Component\\Workflow\\Event\\Event::getMarking` - Returns the :class:`Symfony\\Component\\Workflow\\Marking` of the workflow. - -:method:`Symfony\\Component\\Workflow\\Event\\Event::getSubject` - Returns the object that dispatches the event. - -:method:`Symfony\\Component\\Workflow\\Event\\Event::getTransition` - Returns the :class:`Symfony\\Component\\Workflow\\Transition` that dispatches the event. - -:method:`Symfony\\Component\\Workflow\\Event\\Event::getWorkflowName` - Returns a string with the name of the workflow that triggered the event. - -For Guard Events, there is an extended class :class:`Symfony\\Component\\Workflow\\Event\\GuardEvent`. -This class has two more methods: - -:method:`Symfony\\Component\\Workflow\\Event\\GuardEvent::isBlocked` - Returns if transition is blocked. - -:method:`Symfony\\Component\\Workflow\\Event\\GuardEvent::setBlocked` - Sets the blocked value. - -Usage in Twig -------------- - -Symfony defines several Twig functions to manage workflows and reduce the need -of domain logic in your templates: - -``workflow_can()`` - Returns ``true`` if the given object can make the given transition. - -``workflow_transitions()`` - Returns an array with all the transitions enabled for the given object. - -``workflow_marked_places()`` - Returns an array with the place names of the given marking. - -``workflow_has_marked_place()`` - Returns ``true`` if the marking of the given object has the given state. - -The following example shows these functions in action: - -.. code-block:: twig - -

Actions

- {% if workflow_can(post, 'publish') %} - Publish article - {% endif %} - {% if workflow_can(post, 'to_review') %} - Submit to review - {% endif %} - {% if workflow_can(post, 'reject') %} - Reject article - {% endif %} - - {# Or loop through the enabled transitions #} - {% for transition in workflow_transitions(post) %} - {{ transition.name }} - {% else %} - No actions available. - {% endfor %} - - {# Check if the object is in some specific place #} - {% if workflow_has_marked_place(post, 'review') %} -

This post is ready for review.

- {% endif %} - - {# Check if some place has been marked on the object #} - {% if 'waiting_some_approval' in workflow_marked_places(post) %} - PENDING - {% endif %}