Skip to content
Insights

Flow control with state machines in Laravel

e-commerce flow

Recently I came into contact with finite state machines in Laravel for the first time while researching an implementation for one of our customers. In this blog I want to briefly explain what a state machine is and what we can use its functionalities for.

So what is it, exactly?

According to wikipedia: a finite state machine is an abstract, mathematical model for the behavior of a system where the model consists of a finite number of states, transitions between those states and actions. 

Simply put, we want our application to offer a limited number of outcomes to the user. At the same time, we also want to limit the possibilities to achieve these outcomes. A good example is the flow you have to go through when you want to complete an online order. For example: a logical flow would be a user that first enters its credentials, then is being forwarded to the payment platform and afterwards receives a confirmation in his mailbox.

Of course, you do not want the user to be able to skip the payment step and confirm their order immediately without paying.

Sounds good, I want an example!

Let's work out the example above. To implement the state machine in our laravel application I use the package laravel-state-machine from sebDesign. The first thing we need to set up is a state machine graph, this lets our application know which flow it needs to follow and thanks to the package we can also link it directly to our laravel model. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php

return [
    'order_graph' => [
        'class' => Order::class,
        'graph' => 'order_graph',
        'property_path' => 'state',
        'metadata' => [],
        'states' => [
            'checkout',
            'pending-user-data',
            'pending-payment',
            'finished',
            'cancelled'
        ],
        'transitions' => [
            'create' => [
                'from' => ['checkout'],
                'to' => 'input-user-data'
            ],
            'input-user-data' => [
                'from' => ['pending-user-data'],
                'to' => 'pending-payment'
            ],
            'payment-succeeded' => [
                'from' => ['pending-payment'],
                'to' => 'finished'
            ],
            'payment-failed' => [
                'from' => ['pending-payment'],
                'to' => 'cancelled'
            ]
        ],
    ],
];

For this simple use case we can create five states and define the corresponding transitions. We can now use these transitions to update our state and maintain our logical flow. This creates readable and easily maintainable entry points for our functionalities. 

Extra functionalities for each transition

In many cases you will want to add extra functionality before or after your transitions. In the state graph it is possible to set callbacks on which you can hook in to execute extra code. You might want to validate the order before proceeding to the next step or maybe you want to send an automatic confirmation email when the payment is successful? Let's take a closer look at these two examples. 

    'callbacks' => [
        'guard' => [
            'quard_on_creating' => [
                'on' => 'create',
                'do' => ,
                'args' => ['object'],
            ],
        ],
        'after' => [
            [
                'on' => 'payment-succeeded',
                'do' => 'event',
                'args' => ['"payment-succeeded"', 'event', 'object'],
            ],
        ]
    ]

In this piece of code we have worked out these two examples. In the first part we want to validate the order before asking for the personal data. To do this, we set up a guard that will check the order again for errors in MyService. As an argument, we send the Order Object that we wish to validate. When certain products are no longer available, we can possibly stop an order in this way and inform the buyer...  

In the second part, we send out an event when our payment is successful. We can easily set up multiple subscribers that will apply different functionalities. One of these subscribers can handle the confirmation email while another one notifies the new order to our external accounting program. 

So, when would I use it?

Whenever an entity in your code has more than two states! 

After my first contact with this architecture, I implemented state machines in several projects. In my experience, they help to clarify a complicated flow. The callbacks offer me a good way to split up complex code and make it easily extensible as well. This results in more maintainable code and ensures a clear separation of concerns. 

 

More insights

  • SymfonyCon 2024: code in harmony

    The 2024 edition took place in beautiful Vienna, so one of our experts went to check it out. A quick night train journey and some culture later, they were ready to focus on two days packed with Symfony. What insights did we bring back as souvenirs? You can read all about it in this report! 

    SymfonyCon 2024: code in harmony
  • Stepping into something new: Lore’s journey at Codana

    Lore Vanderlinden tells you all about her journey at Codana. She combines her technical background as a frontend developer with a passion for entrepreneurship in her role as project manager. Find out how by reading the blog!

    Stepping into something new: Lore’s journey at Codana
  • Qodo: an AI-copiloot for coding and testing

    We recently came across Qodo: a tool that uses Artificial Intelligence (AI) to help us code and test. In this blog post, you can read all about our initial experiences. 

    Qodo: an AI-copiloot for coding and testing
  • Lunar and Codana merge into one brand

    Lunar and Codana join hands and from today will continue together under the Codana brand name. This merger creates a digital product studio with more than 30 experts and a clear ambition: to become a leading player in the Belgian and European market.

    Lunar and Codana merge into one brand
  • From Intern to Digital Project Manager: My Journey at Codana

    Jelmer Krux tells you all about his journey at Codana. He joined our team fresh out of university and combines the roles of digital project manager and UX/UI Designer. How? Find out by reading his story in this blog! 

    From Intern to Digital Project Manager: My Journey at Codana
  • Cross-platform applicaties with React Native

    Never before has developing native mobile applications been as accessible as it is today. At Codana, we do this by using the React Native, an open-source framework developed by Meta.

    Cross-platform applicaties with React Native