How to secure and perpetuate your business processes with the Workflow component of Symfony?

Image article with vector character

We will start this article by imagining and immersing ourselves in a little scenario.

Imagine that you are a young PHP/Symfony developer in a development team and that you are starting a project to create an E-commerce website for your client specialized in the distribution of Christmas packages.

Your entire team is invited to participate in a preparatory meeting with your customer to define their needs and address their business process, which begins with the assembly of packages in the factory and ends with delivery to the customer. You are challenged to design and implement a solution that will reflect your customer's core business.

However, you have no idea how to get started, the different rules laid out get mixed up and drowned out in your brain, and you get scared by the ton of complicated and specific information your client gives you.

Yet at night, when you can't sleep, you think of a catch-all word that could be the solution to your problem: " Workflow ".

What a chance! You type this great word in your favorite search engine, and you come across results that talk about Symfony's Workflow component and they look super interesting!

That's when you ask yourself a lot of questions:

  • What is a workflow?
  • Where does this notion of workflow come from?
  • How do I install the Workflow component of Symfony?
  • What is the purpose of this component and what does it allow me to do?

If you are also asking these questions, I invite you to read this article that will allow you to get answers to implement this component in your own project and this time in real life.


1 - What is a workflow?

That's the first question you have to ask yourself, right?

We can say that a workflow represents "the set of tasks to be accomplished and the different actors involved in the realization of a business process".

In other words, it is the set of sequenced tasks that would allow our imaginary client to fulfill his Christmas package order to his customers.

We can see immediately how the business process should be articulated, right?

If you still don't see it, it could be summed up in 6 simple steps:

  • The creation of the order by the customer
  • Preparation of the package in the factory
  • The packaging of the package
  • Shipping the package to the customer
  • Delivery of the package to the customer
  • The reception of the package by the customer

These are the different steps or tasks that allow us to carry out the business process, and it also allows us to associate reports to our order to ensure its follow-up.

This can be modeled by a state machine, which is a mathematical model that is the basis of Symfony's Workflow component that we will explore in the next part.


2 - Theory of automata and state machines

So we're going to do a little bit of theory, even though I know it's not what you prefer, but it's important to understand how to articulate our business process.

So we were talking about a state machine, but we still don't know what it is and also the link it can have with the Workflow component of Symfony.

A state machine is a mathematical object that allows us to design algorithms for specific business processes. Great, that's just what we need!

It is composed of two important elements that can be seen on the diagram below:

  • States (represented by circles) that describe the status of an object of which only one can be the current state. For our example, either the light bulb is on or off.
  • Transitions (represented by arrows) that describe the actions to be performed under certain conditions to move from one state to another. For our example, either the light bulb can be turned on if it is off, or the light bulb can be turned off if it is on.

Example of a simple state machine, a switch.

Thus, with this tool, we are able to make an exhaustive diagram of all the possible actions according to the current states, which is important to frame the business rules of a process.

This is the principle on which the Workflow component of Symfony is based, that's why we will install this component.


3 - Installing the Workflow component of Symfony

If you have already started a Symfony project and you have the Composer dependency manager, it is very easy to install the Workflow component, just type the following command:

composer require symfony/workflow

3.1 - Business process design and state machine configuration

Once your project is installed with the Workflow component, it's time to rack your brains, well not that much!
Indeed, we have already isolated the main steps of our business process before and so it becomes a piece of cake to set up our state machine.

The first step in setting up a state machine is to create an entity in our project with a string which we will call state. This is the field that will be able to tell us where in the state machine our package is located using the function getState().

Here is an example of the entity Colis to better visualize:

<?php

namespace App\Entity;

use App\Repository\ColisRepository;
use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity(repositoryClass=ColisRepository::class)
*/
class Colis
{
    /**
    * @ORM\Id
    * @ORM\GeneratedValue
    * @ORM\Column(type="integer")
    */
    private $id;

    /**
    * @ORM\Column(type="string", length=255, nullable=true)
    */
    private $state;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getState(): ?string
    {
        return $this->state;
    }

    public function setState(?string $state): self
    {
        $this->state = $state;

        return $this;
    }
}

Then, the second step is to go to the configuration file ./config/packages/workflow.yaml.

The rest of the process is relatively simple.
You just need to have your¬†states (places) and in particular its¬†initial state (initial_marking), and its¬†transitions (transitions) allowing to go from one state to another.

Here is an example of the configuration of our state machine for packages:

framework:
    workflows:
        commande_colis:
            type: 'state_machine'
            marking_store:
                type: 'method'
                property: 'state'
            supports:
                - App\Entity\Colis
            initial_marking: nouvelle_commande
            places:
                - nouvelle_commande
                - en_preparation
                - emballe
                - expedie
                - livre
                - retourne
                - perdu
            transitions:
                preparation:
                    from: nouvelle_commande
                    to:   en_preparation
                emballage:
                    from: en_preparation
                    to:   emballe
                expedition:
                    from: emballe
                    to:   expedie
                livraison:
                    from: expedie
                    to:   livre
                retour:
                    from: [livre, expedie]
                    to:   retourne
                perte:
                    from: [expedie, livre]
                    to:   perdu

Once our configuration is correctly filled, we will be able to see what we can do with it and you will see that you will like it!

3.2 - Exporting an image of the state machine

And if we could see what our splendid state machine looks like to manage our parcels, wouldn't you like it?

This is possible with this command:

php bin/console workflow:dump commande_colis | dot -Tpng -o gestion_colis.png

And thanks to this, we have our beautiful state machine with our business process:

But you must say to yourself, it is cute him to display his state machine, but what does he actually do with it!

You are right, let's get down to business!

3.3 - Using the state machine

The main interest of our state machine is to follow the status of each of our orders and to be able to update them individually.

To do this, we simply apply the transitions we have defined in our configuration to move from the state the package is currently in to a new state.

For example, a package is in the state emballe and we want to send it to the client, we just have to check and apply the transition expedition as follows:

$workflow = $this->get('workflow.commande_colis');

if ($workflow->can($colis, 'expedition')) {
    $workflow->apply($colis, 'expedition');
}

Once the transition is applied, if your package was in the state emballe its current state will be expedie. If this was not the case, then the package will remain in its original state.

As you can see, this is relatively simple and allows us to go from one state to another of the business process, but sometimes our business process can be complicated and we need more tools to transcribe it accurately. This is what we will see in the next part.

3.4 - Application of complex business rules

Indeed, as described above, our business process uses some specific business rules.

Let's imagine that we offer our customer the possibility to return his package within 7 days after the shipping date if he is not satisfied.

Currently, our state machine as it is defined does not allow us to implement this rule. But fortunately, it is possible to implement this constraint thanks to events.

So we just need to create a class that implements the EventSubscriberInterface like this:

use Symfony\Component\Workflow\Event\GuardEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class RetourColisListener implements EventSubscriberInterface
{
    public function guardRetourColis(GuardEvent $event)
    {
        /** @var \App\Entity\Colis $colis */
        $colis = $event->getSubject();
        $dateExpedition = $colis->getDateExpedition();
        $now = new \DateTime();

        if ($now->diff($dateExpedition)->format('%a') < 7) {
           $event->setBlocked(true);
        }
    }

    public static function getSubscribedEvents()
    {
        return [
            'workflow.commande_colis.guard.retour' => ['guardRetourColis'],
        ];
    }
}

Once this class is written, thanks to the event guardIn order to ensure that the package is returned to us in a timely manner, each time the customer attempts to return a package, a check will be made to see if the package was shipped within the last 7 days. If this is not the case, then it will no longer be possible for the customer to return the package and obtain a refund.

There are other events that also allow us to better transcribe the business process of our applications, here is the list:

  • guard¬†Allows to constrain and apply a transition under certain conditions.
  • leave¬†Allows to execute code when leaving a state of our state machine.
  • transition¬†: Allows to execute code when the transition has been applied.
  • enter¬†Allows you to execute code when you are about to enter the destination state, just before the current state is modified.
  • entered¬†Allows you to execute code when you have entered the destination state, just after the current state has been modified.
  • completed¬†Allows you to execute code when the transition has been made, such as sending emails for example.
  • announce¬†Announce that one or more transitions are now available from the new state.

4 - Conclusion

In this article, I talked about workflow, automata theory, more specifically about state machines and also about the Workflow component of Symfony.

This is probably very theoretical, but I hope you will remember that this component can help you take the design part of a business process seriously so that you can better frame it and better realize your solution.

I am of course available to guide you on the implementation of your workflow, or even to answer any questions you may have.


Sources

Workflow definition and state machine
Symfony Workflow component documentation
Example of the use of the workflow
Illustration
Share this article
Photo of a man from the Progicar team
Written by Pierre Petillon

Suggested items