campaign-creators---kQ4tBklJI-unsplash 1.jpg

Managing Workflows in Symfony

Guillem

4 reading minutes

There are many situations in the development of software projects in which you have to manage a workflow, a process, a series of states that a certain entity can go through. Entity, as we say, that something you are modeling can go through. An easy example would be an order in an online store, the order starts in a new state and, if certain conditions are met, it goes to pending payment, paid, shipped, etc. An order is something direct, easy to understand that follows a flow of states, but there are actually many other things that can only be in a series of states and that, naturally, may or may not move from one state to another. A task in a task management system, a course that someone wants to take, etc. In Symfony there is a specific component for process management called Workflow. You can add it to your project with:
composer require symfony/workflow
Symfony processes are based on places and transitions between those places. From the Symfony website, this flow explains it well:
states_transitions.png

Diagram showing places as circles, transitions as squares, and arrows between them.

State machines and workflows

There is a subtype of workflow that is a state machine. The fundamental difference is that in a state machine the process can only be in one place or state at a time. That is, using previous examples, an order will only be in one state at a time (new, paid, shipped, etc.), and only in one. In a generic workflow, the process could be multi-state and be in several places at once. In our case, almost everything we do is with state machines.

How to get started

We like to draw the process in some software that helps with this, like Whimsical, Figma FigJam or similar. Besides using it for ourselves and as internal documentation, it's generally a very good tool to sit down with clients and see what needs to happen at each step, from which state you should be able to go to which other state, etc. We end up with something like the following image:
flujoestados.png
Once we have modeled the process, either in this way or on paper, we can transfer it to a configuration .yaml file in the project.
framework:
  workflows:
    article_lifecycle:
      type: 'state_machine'
      marking_store:
        type: 'single_state'
        arguments:
          - 'status'
      supports:
        - App\Entity\Article
      places:
        - draft
        - review
        - published
      transitions:
        to_review:
          from: draft
          to: review
        publish:
          from: review
          to: published
        unpublish:
          from: published
          to: review
      guards:
        to_review:
          - "subject.isEditable() === true"
        publish:
          - "subject.isReviewed() === true"
The above is an example of how a workflow would look for an Article that can have draft, review and published states and how the transitions would be between one state and another. You can see that there are 3 places or states available, draft, review and published. You can see in transitions that you can go from draft to review, from review to published and you can also unpublish, going from published back to review.

Events

If you build your workflow with an EventDispatcher, events will be fired in a bunch of situations that the workflow goes through, specifically:

  • workflow.guard To validate whether the transition is blocked or not.
  • workflow.leave When the object is about to leave a state, or place.
  • workflow.transition The object is going through this transition.
  • workflow.enter The object is entering a state or place.
  • workflow.entered The object has already entered this state or place.
  • workflow.completed The object has already completed this transition.
  • workflow.announce Fired for each transition currently available for the object.

Since announce can fire a bunch of events, it is common to disable it.

We can also decide which events to fire in the yaml (in case you only want to fire a subset of the events):

# you can pass one or more event names
            events_to_dispatch: ['workflow.leave', 'workflow.completed']
In this way, with events and listening with listeners that interest us, it is very easy to execute in the transitions whatever is necessary to do.

Guards

There is a special type of event, and if it is not as an event it can be configured directly in the yaml, as in the example above or directly in each place, which are the guards:

completed: 
	guard: "subject.hasPaid()"
    	from: [in_progress]
    	to: completed
Guards allow you to define restrictions for states, so that a transition cannot be executed or a state cannot be passed to, if a series of conditions are not met. This means that, by configuration in the Workflow yaml, we have defined our entire flow of states, from which state to which state we can pass, under what conditions, what transition occurs in that process and what events are launched when that happens. The simplicity in terms of the amount of code, ease of reading and robustness, since we are using a Symfony component already tested for something that, in many cases, can be critical in the platform we are developing, make Workflows something fundamental in many developments, at least for us.
📫
Hasta aquí el artículo de hoy. ¡Si quieres puedes escribirnos por redes sociales como siempre, o a hola@softspring.es con cualquier duda o sugerencia!

Let’s work together!

Do you want to tell us your idea?

CONTACT US