— 4 reading minutes
There are many situations in the development of software projects in which you have to manage a workflow, a process, or a series of states that a certain entity can go through. This is especially common in complex Symfony applications where business processes often involve multiple steps and transitions between different states.
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 and 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 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
Diagram showing places as circles, transitions as squares, and arrows between them.
State machines and workflows
How to get started
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"
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']
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 reached if a series of conditions are not met. This means that, through configuration in the Workflow YAML, we have defined the entire flow of states: from which state to which state we can move, under what conditions, what transition occurs in that process, and what events are triggered when that happens.
The simplicity in terms of the amount of code, ease of reading, and robustness — since we are using a Symfony component that has already been thoroughly tested for something that, in many cases, can be critical in the platform we are developing — makes Workflows fundamental in many projects. This is particularly true when building complex and scalable Symfony applications where business processes need to be clearly modeled and reliably executed.
