Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork5.3k
Added docs for Workflow component#6871
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
d0701f091867c269bca59f99fbb2e797f0c5b2a029196baf9763950dcead2f7e0089c548de43d805b237f57ec1403925ffe6bdee6c7464c783d26c16e7a35f4415466866b25adceebecb959f8ab45edf2fefdb5f7f0f5b0c68128386ecf0ad002a8bb0a88554e7cf113aa433d4f277dc2511c21c9b16562cc293447dc11d3250621c0bd6daFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -5,8 +5,8 @@ | ||
| The Workflow Component | ||
| ====================== | ||
| The Workflow component provides tools for managing a workflow or finite | ||
Contributor 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. isn't a Petri net instead ? Member 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. @mickaelandrieu a workflow is a subset of a petri net (and a state machine a subset of workflow). The component does not support all features necessary to implement a petri net Contributor
| ||
| statemachine. | ||
| .. 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 </components/using_components>` (``symfony/workflow`` on `Packagist`_); | ||
| * Use the official Git repository (https://github.com/symfony/workflow). | ||
| .. include:: /components/require_autoload.rst.inc | ||
| Creating a Workflow | ||
| ------------------- | ||
| The workflow component gives you an object oriented way to define a process | ||
Contributor
| ||
| 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`). | ||
Member 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. please remove the empty lines (except from one) Contributor 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. You should also the initial place with something like: | ||
| 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, | ||
Contributor 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. I suggest to use | ||
| 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'); | ||
Contributor 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. What about | ||
| $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 | ||
Contributor 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. It's not about domain logic, it's more about Workflow management logic, isn'it ? I'd say instead "This will keep your Workflow logic in one place and not spread all over your applications.", what do you think ? MemberAuthor 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. Interesting. I would always consider this the domain logic because the "workflow management logic" is a part of your domain. Contributor 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. The theory behind "Workflow management" is to put the Workflow managementoutside of your domain, if I've well understood. BlogPost entity shouldn't be modified every time the rules of publishing evolve, for instance. I'm pretty noob of this field, so correct me if I'm wrong :) MemberAuthor 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.
Correct, you just edit the workflow when the rules of publishing changes.
Outside the domain or outside yourmodel? The way I see it there is only three places you can put code/config:
Can@lyrixx give some input? Member 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. I'm not sure to understand the question / debate here. Contributor 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.
I can't do it, because the primary purpose of this Workflow component is not obvious for me:
The component is designed as very generic thing, so in theory it probably may have some interesting applications. However, providedexamples don't impress. Compare it with rich domain model: finalclass Article{private$id;private$title;private$approvedByJournalist;private$approvedBySpellchecker;private$published;publicstaticfunctionpostDraft(int$articleId,ArticleTitle$title):Article {$draft =newself();$draft->apply(newDraftArticleWasPosted($articleId,$title));$draft->apply(newArticleWasSentForApproval($this->id));return$draft; }privatefunction__construct() {}publicfunctionapproveByJournalist() {if ($this->approvedByJournalist) {return; }$this->apply(newArticleWasApprovedByJournalist($this->id));$this->publishIfItIsReady(); }publicfunctionapproveBySpellchecker() {if ($this->approvedBySpellchecker) {return; }$this->apply(newArticleWasApprovedBySpellchecker($this->id));$this->publishIfItIsReady(); }publicfunctionchangeTitle(ArticleTitle$newTitle) {if ($this->title->equals($newTitle)) {return; }$this->apply(newArticleTitleWasChanged($this->id,$newTitle));$this->apply(newArticleWasSentForApproval($this->id)); }privatefunctionpublishIfItIsReady() {if ($this->approvedByJournalist &&$this->approvedBySpellchecker) {$this->apply(newArticleWasPublished($this->id)); } }// ...privatefunctiononArticleWasApprovedByJournalist(ArticleWasApprovedByJournalist$event) {$this->approvedByJournalist =true; }privatefunctiononArticleWasSentForApproval(ArticleWasSentForApproval$event) {$this->approvedByJournalist =false;$this->approvedBySpellchecker =false;$this->published =false; }privatefunctiononArticleTitleWasChanged(ArticleTitleWasChanged$event) {$this->title =$event->getNewTitle(); }} We see use-cases ( Further, that sample projectchecks permissions with publicfunctiononTransitionJournalist(GuardEvent$event) {if (!$this->checker->isGranted('ROLE_JOURNALIST')) {$event->setBlocked(true); } } ... and how you can solve it with commands: finalclass ApproveArticleByJournalistHandler{/** * @acl_role ROLE_JOURNALIST */publicfunctionhandle(ApproveArticleByJournalist$command) {$blogPost =$this->blogPostRepository->find($command->getArticleId());$blogPost->approveByJournalist();$this->blogPostRepository->save($blogPost); }} And again we lose expressiveness by generic I would like to know if I'm wrong. Member 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. IMHO, all this debate is totally out of the scope of this PR. Contributor 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. How is it possible to write docs for the component without clear applications, examples? Member 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. I did not said that. But you you are telling us the Workflow component is useless and it's better to write code like your example. It's really a matter of taste (I have a strong opinion on that ; but I let people making their own choices). So I'm juste saying: The component is here, and it's useless to discuss if it's better to use CQRS / ES over Workflow Component. For the record, the discussion is about the following sentence:
IMHO, it's more important to document how the component works. I like the@Nyholm's work ; so let's focus on what matter. MemberAuthor 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. @unkind: I hear you. Your arguments are valid. This is, as Grégoire says, out of scope for this PR.@unkind, I've asked you to create an issue on the symfony/symfony repository. I amhappy to make a PR to try to improve the Workflow component regarding your input. Let's work together on this, but not in this PR. | ||
| 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(); | ||
Member 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. please prefix the example with // ...$post =newBlogPost(); | ||
| $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 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -62,6 +62,7 @@ Topics | ||
| testing | ||
| translation | ||
| validation | ||
| workflow | ||
| Best Practices | ||
| -------------- | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
Contributor 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. "drafted, reviewed and published"? | ||
| 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 | ||
Member 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. "That marking score [...]" -> "The marking store [...]" MemberAuthor 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. I do not see where I've written "score". It supposed to be "store" everywhere. Member 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. Oh, sorry, typo from my side. I meant "That" should become "The". | ||
| 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 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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: | ||
Member 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. please add | ||
| workflows: | ||
| pull_request: | ||
| type:'state_machine' | ||
| supports: | ||
| -AppBundle\Entity\PullRequest | ||
| places: | ||
| -start | ||
Contributor 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. I've started with many comments, so here a resume of what I suggest for the following: pull_request:type:'state_machine'supports: -AppBundle\Entity\PullRequestplaces: -started -coding -travis -needs_review -reviewed -merged -closedtransitions:test:from:[started, coding]to:travissubmit:from:travisto:needs_reviewrequest_change:from:[travis, needs_review, reviewed]to:codingaccept:from:needs_reviewto:reviewedreject:from:[coding, needs_review, reviewed]to:closedreopen:from:closedto:needs_reviewmerge:from:reviewedto:merged Wdyt? Actually when trying to dump it I've found a bug and submittedsymfony/symfony#20497 :) | ||
| -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 | ||
Member 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. please add XML and PHP formats. <!-- app/config/config.xml--><?xml version="1.0" encoding="utf-8" ?><containerxmlns="http://symfony.com/schema/dic/services"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:framework="http://symfony.com/schema/dic/symfony"xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> <framework:config> <framework:workflowname="pull_request"type="state_machine"> <framework:marking-storetype="scalar"/> <framework:support>AppBundle\Entity\PullRequest</framework:support> <framework:place>start</framework:place> <framework:place>coding</framework:place> <framework:place>travis</framework:place> <framework:place>review</framework:place> <framework:place>merged</framework:place> <framework:place>closed</framework:place> <framework:transitionname="submit"> <framework:from>start</framework:from> <framework:to>travis</framework:to> </framework:transition> <framework:transitionname="update"> <framework:from>coding</framework:from> <framework:from>travis</framework:from> <framework:from>review</framework:from> <framework:to>travis</framework:to> </framework:transition> <framework:transitionname="wait_for_review"> <framework:from>travis</framework:from> <framework:to>review</framework:to> </framework:transition> <framework:transitionname="change_needed"> <framework:from>review</framework:from> <framework:to>coding</framework:to> </framework:transition> <framework:transitionname="accepted"> <framework:from>review</framework:from> <framework:to>merged</framework:to> </framework:transition> <framework:transitionname="rejected"> <framework:from>review</framework:from> <framework:to>closed</framework:to> </framework:transition> <framework:transitionname="reopened"> <framework:from>closed</framework:from> <framework:to>review</framework:to> </framework:transition> </framework:workflow> </framework:config></container> MemberAuthor 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. Thank you. Im embarrassingly bad at the XML syntax for the config. | ||
| ..code-block::xml | ||
| <!-- app/config/config.xml--> | ||
| <?xml version="1.0" encoding="utf-8" ?> | ||
| <containerxmlns="http://symfony.com/schema/dic/services" | ||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| xmlns:framework="http://symfony.com/schema/dic/symfony" | ||
| xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd | ||
| http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd" | ||
| > | ||
| <framework:config> | ||
| <framework:workflowname="pull_request"type="state_machine"> | ||
| <framework:marking-storetype="single_state"/> | ||
| <framework:support>AppBundle\Entity\PullRequest</framework:support> | ||
| <framework:place>start</framework:place> | ||
| <framework:place>coding</framework:place> | ||
| <framework:place>travis</framework:place> | ||
| <framework:place>review</framework:place> | ||
| <framework:place>merged</framework:place> | ||
| <framework:place>closed</framework:place> | ||
| <framework:transitionname="submit"> | ||
| <framework:from>start</framework:from> | ||
| <framework:to>travis</framework:to> | ||
| </framework:transition> | ||
| <framework:transitionname="update"> | ||
| <framework:from>coding</framework:from> | ||
| <framework:from>travis</framework:from> | ||
| <framework:from>review</framework:from> | ||
| <framework:to>travis</framework:to> | ||
| </framework:transition> | ||
| <framework:transitionname="wait_for_review"> | ||
| <framework:from>travis</framework:from> | ||
| <framework:to>review</framework:to> | ||
| </framework:transition> | ||
| <framework:transitionname="request_change"> | ||
| <framework:from>review</framework:from> | ||
| <framework:to>coding</framework:to> | ||
| </framework:transition> | ||
| <framework:transitionname="accept"> | ||
| <framework:from>review</framework:from> | ||
| <framework:to>merged</framework:to> | ||
| </framework:transition> | ||
| <framework:transitionname="reject"> | ||
| <framework:from>review</framework:from> | ||
| <framework:to>closed</framework:to> | ||
| </framework:transition> | ||
| <framework:transitionname="reopen"> | ||
| <framework:from>closed</framework:from> | ||
| <framework:to>review</framework:to> | ||
| </framework:transition> | ||
| </framework:workflow> | ||
| </framework:config> | ||
| </container> | ||
| ..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', | ||
| ), | ||
| ), | ||
| ), | ||
| ), | ||
| )); | ||
Member 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. multiple blank lines should always be just one | ||
| You can now use this state machine by getting the ``state_machine.pull_request`` service:: | ||
| $stateMachine = $this->container->get('state_machine.pull_request'); | ||
MemberAuthor 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. Ping@rufinus. Thank you for the feedback. I've added this example now. | ||
| .. _Petri net:https://en.wikipedia.org/wiki/Petri_net | ||
Uh oh!
There was an error while loading.Please reload this page.