diff --git a/_images/components/workflow/blogpost.png b/_images/components/workflow/blogpost.png new file mode 100644 index 00000000000..38e29250eb1 Binary files /dev/null and b/_images/components/workflow/blogpost.png differ diff --git a/_images/components/workflow/job_application.png b/_images/components/workflow/job_application.png new file mode 100644 index 00000000000..9c5e6792ae9 Binary files /dev/null and b/_images/components/workflow/job_application.png differ diff --git a/_images/components/workflow/pull_request.png b/_images/components/workflow/pull_request.png new file mode 100644 index 00000000000..3b98078099a Binary files /dev/null and b/_images/components/workflow/pull_request.png differ diff --git a/_images/components/workflow/simple.png b/_images/components/workflow/simple.png new file mode 100644 index 00000000000..ed158d5cc7a Binary files /dev/null and b/_images/components/workflow/simple.png differ diff --git a/_images/components/workflow/states_transitions.png b/_images/components/workflow/states_transitions.png new file mode 100644 index 00000000000..1e68f9ca597 Binary files /dev/null and b/_images/components/workflow/states_transitions.png differ diff --git a/components/workflow.rst b/components/workflow.rst index 656d37f2e65..367bff2d41d 100644 --- a/components/workflow.rst +++ b/components/workflow.rst @@ -5,8 +5,8 @@ The Workflow Component ====================== - The Workflow component provides tools for managing a workflow or finite state - machine. + The Workflow component provides tools for managing a workflow or finite + state machine. .. versionadded:: 3.2 The Workflow component was introduced in Symfony 3.2. @@ -19,6 +19,87 @@ You can install the component in 2 different ways: * :doc:`Install it via Composer ` (``symfony/workflow`` on `Packagist`_); * Use the official Git repository (https://github.com/symfony/workflow). -For more information, see the code in the Git Repository. +.. include:: /components/require_autoload.rst.inc + +Creating a Workflow +------------------- + +The workflow component gives you an object oriented way to define a process +or a life cycle that your object goes through. Each step or stage in the +process is called a *place*. You do also define *transitions* that describe +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. A post can have one of a number +of predefined statuses (`draft`, `review`, `rejected`, `published`). In a workflow, +these statuses are called **places**. You can define the workflow like this:: + + use Symfony\Component\Workflow\DefinitionBuilder; + use Symfony\Component\Workflow\Transition; + use Symfony\Component\Workflow\Workflow; + use Symfony\Component\Workflow\MarkingStore\SingleStateMarkingStore; + + $builder = new DefinitionBuilder(); + $builder->addPlaces(['draft', 'review', 'rejected', 'published']); + + // Transitions are defined with a unique name, an origin place and a destination place + $builder->addTransition(new Transition('to_review', 'draft', 'review')); + $builder->addTransition(new Transition('publish', 'review', 'published')); + $builder->addTransition(new Transition('reject', 'review', 'rejected')); + + $definition = $builder->build(); + + $marking = new SingleStateMarkingStore('currentState'); + $workflow = new Workflow($definition, $marking); + +The ``Workflow`` can now help you to decide what actions are allowed +on a blog post depending on what *place* it is in. This will keep your domain +logic in one place and not spread all over your application. + +When you define multiple workflows you should consider using a ``Registry``, +which is an object that stores and provides access to different workflows. +A registry will also help you to decide if a workflow supports the object you +are trying to use it with:: + + use Symfony\Component\Workflow\Registry; + use Acme\Entity\BlogPost; + use Acme\Entity\Newsletter; + + $blogWorkflow = ... + $newsletterWorkflow = ... + + $registry = new Registry(); + $registry->add($blogWorkflow, BlogPost::class); + $registry->add($newsletterWorkflow, Newsletter::class); + +Usage +----- + +When you have configured a ``Registry`` with your workflows, you may use it as follows:: + + // ... + $post = new BlogPost(); + $workflow = $registry->get($post); + + $workflow->can($post, 'publish'); // False + $workflow->can($post, 'to_review'); // True + + $workflow->apply($post, 'to_review'); + $workflow->can($post, 'publish'); // True + $workflow->getEnabledTransitions($post); // ['publish', 'reject'] + +Learn more +---------- + +.. toctree:: + :maxdepth: 1 + :glob: + + /workflow/* .. _Packagist: https://packagist.org/packages/symfony/workflow diff --git a/index.rst b/index.rst index eed3f3f5200..47468efcfc8 100644 --- a/index.rst +++ b/index.rst @@ -62,6 +62,7 @@ Topics testing translation validation + workflow Best Practices -------------- diff --git a/workflow.rst b/workflow.rst new file mode 100644 index 00000000000..766c8ab2a7c --- /dev/null +++ b/workflow.rst @@ -0,0 +1,50 @@ +Workflow +======== + +A workflow is a model of a process in your application. It may be the process +of how a blog post goes from draft, review and publish. Another example is when +a user submits a series of different forms to complete a task. Such processes are +best kept away from your models and should be defined in configuration. + +A **definition** of a workflow consist of places and actions to get from one +place to another. The actions are called **transistions**. A workflow does also +need to know each object's position in the workflow. That **marking store** writes +to a property of the object to remember the current place. + +.. note:: + + 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`. + +Examples +-------- + +The simples workflow looks like this. It contains two places and one transition. + +.. image:: /_images/components/workflow/simple.png + +Workflows could be more complicated when they describe a real business case. The +workflow below describes the process to fill in a job application. + +.. image:: /_images/components/workflow/job_application.png + +When you fill in a job application in this example there are 4 to 7 steps depending +on the what job you are applying for. Some jobs require personality tests, logic tests +and/or formal requirements to be answered by the user. Some jobs don't. The +``GuardEvent`` is used to decide what next steps are allowed for a specific application. + +By defining a workflow like this, there is an overview how the process looks like. The process +logic is not mixed with the controllers, models or view. The order of the steps can be changed +by changing the configuration only. + +.. toctree:: + :maxdepth: 1 + :glob: + + workflow/* + +.. _Petri nets: https://en.wikipedia.org/wiki/Petri_net diff --git a/workflow/dumping-workflows.rst b/workflow/dumping-workflows.rst new file mode 100644 index 00000000000..56b1d1f7d56 --- /dev/null +++ b/workflow/dumping-workflows.rst @@ -0,0 +1,37 @@ +.. index:: + single: Workflow; Dumping Workflows + +How to Dump Workflows +===================== + +To help you debug your workflows, you can dump a representation of your workflow with +the use of a ``DumperInterface``. Use the ``GraphvizDumper`` to create a +PNG image of the workflow defined above:: + + // dump-graph.php + $dumper = new GraphvizDumper(); + echo $dumper->dump($definition); + +.. code-block:: terminal + + $ php dump-graph.php > out.dot + $ dot -Tpng out.dot -o graph.png + +The result will look like this: + +.. image:: /_images/components/workflow/blogpost.png + +If you have configured your workflow with the Symfony framework, you may dump the dot file +with the ``WorkflowDumpCommand``: + +.. code-block:: terminal + + $ php bin/console workflow:dump name > out.dot + $ dot -Tpng out.dot -o graph.png + +.. note:: + + The ``dot`` command is part of Graphviz. You can download it and read + more about it on `Graphviz.org`_. + +.. _Graphviz.org: http://www.graphviz.org diff --git a/workflow/state-machines.rst b/workflow/state-machines.rst new file mode 100644 index 00000000000..6139e696be8 --- /dev/null +++ b/workflow/state-machines.rst @@ -0,0 +1,197 @@ +.. index:: + single: Workflow; Workflows as State Machines + +Workflows as State Machines +=========================== + +The workflow component is modelled after a *Workflow net* which is a subclass +of a `Petri net`_. By adding further restrictions you can get a state machine. +The most important one being that a state machine cannot be in more than +one place simultaneously. It is also worth noting that a workflow does not +commonly have cyclic path in the definition graph, but it is common for a state +machine. + +Example of a State Machine +-------------------------- + +A pull request starts in an intial "start" state, a state for e.g. running +tests on Travis. When this is finished, the pull request is in the "review" +state, where contributors can require changes, reject or accept the +pull request. At any time, you can also "update" the pull request, which +will result in another Travis run. + +.. image:: /_images/components/workflow/pull_request.png + +Below is the configuration for the pull request state machine. + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/config.yml + framework: + workflows: + pull_request: + type: 'state_machine' + supports: + - AppBundle\Entity\PullRequest + places: + - start + - coding + - travis + - review + - merged + - closed + transitions: + submit: + from: start + to: travis + update: + from: [coding, travis, review] + to: travis + wait_for_reivew: + from: travis + to: review + request_change: + from: review + to: coding + accept: + from: review + to: merged + reject: + from: review + to: closed + reopen: + from: closed + to: review + + .. code-block:: xml + + + + + + + + + + AppBundle\Entity\PullRequest + + start + coding + travis + review + merged + closed + + + start + + travis + + + + coding + travis + review + + travis + + + + travis + + review + + + + review + + coding + + + + review + + merged + + + + review + + closed + + + + closed + + review + + + + + + + + .. code-block:: php + + // app/config/config.php + + $container->loadFromExtension('framework', array( + // ... + 'workflows' => array( + 'pull_request' => array( + 'type' => 'state_machine', + 'supports' => array('AppBundle\Entity\PullRequest'), + 'places' => array( + 'start', + 'coding', + 'travis', + 'review', + 'merged', + 'closed', + ), + 'transitions' => array( + 'start'=> array( + 'form' => 'start', + 'to' => 'travis', + ), + 'update'=> array( + 'form' => array('coding','travis','review'), + 'to' => 'travis', + ), + 'wait_for_reivew'=> array( + 'form' => 'travis', + 'to' => 'review', + ), + 'request_change'=> array( + 'form' => 'review', + 'to' => 'coding', + ), + 'accept'=> array( + 'form' => 'review', + 'to' => 'merged', + ), + 'reject'=> array( + 'form' => 'review', + 'to' => 'closed', + ), + 'reopen'=> array( + 'form' => 'start', + 'to' => 'review', + ), + ), + ), + ), + )); + +You can now use this state machine by getting the ``state_machine.pull_request`` service:: + + $stateMachine = $this->container->get('state_machine.pull_request'); + +.. _Petri net: https://en.wikipedia.org/wiki/Petri_net diff --git a/workflow/usage.rst b/workflow/usage.rst new file mode 100644 index 00000000000..a47743ca458 --- /dev/null +++ b/workflow/usage.rst @@ -0,0 +1,244 @@ +.. index:: + single: Workflow; Usage + +How to Use the 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. A post can have places: +'draft', 'review', 'rejected', 'published'. You can define the workflow +like this: + +.. configuration-block:: + + .. code-block:: yaml + + framework: + workflows: + blog_publishing: + type: 'workflow' # or 'state_machine' + marking_store: + type: 'multiple_state' # or 'single_state' + arguments: + - 'currentPlace' + supports: + - AppBundle\Entity\BlogPost + 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 + + + AppBundle\Entity\BlogPost + + draft + review + rejected + published + + + draft + + review + + + + review + + published + + + + review + + rejected + + + + + + + + .. code-block:: php + + // app/config/config.php + + $container->loadFromExtension('framework', array( + // ... + 'workflows' => array( + 'blog_publishing' => array( + 'type' => 'workflow', // or 'state_machine' + 'marking_store' => array( + 'type' => 'multiple_state', // or 'single_state' + 'arguments' => array('currentPlace') + ), + 'supports' => array('AppBundle\Entity\BlogPost'), + 'places' => array( + 'draft', + 'review', + 'rejected', + 'published', + ), + 'transitions' => array( + 'to_review'=> array( + 'form' => 'draft', + 'to' => 'review', + ), + 'publish'=> array( + 'form' => 'review', + 'to' => 'published', + ), + 'reject'=> array( + 'form' => '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. + +With this workflow named ``blog_publishing``, you can get help to decide +what actions that are allowed on a blog post. :: + + $post = new \AppBundle\Entity\BlogPost(); + + $workflow = $this->container->get('workflow.blog_publishing'); + $workflow->can($post, 'publish'); // False + $workflow->can($post, 'to_review'); // True + + // Update the currentState on the post + try { + $workflow->apply($post, 'to_review'); + } catch (LogicException $e) { + // ... + } + + // See all the available transition for the post in the current state + $transitions = $workflow->getEnabledTransitions($post); + +Using Events +------------ + +To make your workflows even more powerful you could 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). The following +events are dispatched: + +* ``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 Acme\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'), + ); + } + } + +With help from the ``EventDispatcher`` and the ``AuditTrailListener`` you +could easily enable logging:: + + use Symfony\Component\Workflow\EventListener\AuditTrailListener; + + $logger = new AnyPsr3Logger(); + $subscriber = new AuditTrailListener($logger); + $dispatcher->addSubscriber($subscriber); + +Usage in Twig +------------- + +Using your workflow in your Twig templates reduces the need of domain logic +in the view layer. Consider this example of the control panel of the blog. +The links below will only be displayed when the action is allowed: + +.. 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 transistions #} + {% for transition in workflow_transitions(post) %} + {{ transition.name }} + {% else %} + No actions available. + {% endfor %} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy