State Machine Theory
In the domain of software design, there are multiple patterns that support creating efficient, flexible, and comprehensible applications. One of these patterns is the ‘State Machine’. In this blog, we will take a closer look at this pattern, what benefits it brings, and how it works in Sylius.
What is a State Machine as a design pattern?
State Machine is a design pattern that helps describe how an object behaves based on its internal state and the events that affect it. This pattern is built on the idea that an application can be in specific states, and events lead to changing those states and causing appropriate reactions. While initially used in automation systems, the state machine concept has found broad use in various types of application programming.
Benefits of using a State Machine:
Modularity and readability
This pattern assists in breaking down different states and their related reactions and transitions, which makes code more readable and behaviors more modular.
Scalability and flexibility
State Machine simplifies adding new states and defining permissible actions within each state. This allows applications to easily grow with new features.
The pattern helps maintain consistency in the application’s behavior based on the current state. This prevents inconsistencies and unpredictable behaviors.
State Machine enables clear responses to various events, leading to more predictable and controlled application behavior.
State Machine in Sylius
In Sylius, we use a tool called WinzouStateMachine, which you can find here. This tool allows us to define different states and how to move between them, and it also provides the ability to use ‘callbacks’ during these transitions.
With this tool, we can set up various states and determine how they can change from one to another. It also lets us add actions that can happen before or after a state change, giving us full control over the process. We can even prioritize these actions to manage their order precisely.
In the context of Sylius, the state machine based on WinzouStateMachine plays a key role in handling orders and related elements like payments and deliveries. It’s also used for rating products and managing promotions in the product catalog.
By using this state machine, Sylius gains the ability to create more complex processes that can be customized to suit the unique needs of both users and administrators. This approach results in increased flexibility, control, and efficiency in managing states and processes within the system.
State Machines in the order object
The ‘order’ object, responsible for representing the journey of an order from the cart to its completion, encompasses several state machines:
a) order state
When a product is added to the cart, an order is created with the ‘cart’ status. As the user progresses through the ‘checkout’ process, the order’s status changes to ‘new’. Upon successful payment and order shipment, its status transitions to ‘fulfilled’. However, there is also a possibility that the order may be canceled, in which case its status changes to ‘canceled’.
b) checkout state
Starting from the ‘cart’ state, the user assembles products in their shopping cart. Subsequently, upon progressing to the ‘addressed’ state, they provide their necessary address information for order delivery. In the following stage, namely ‘shipping_selected,’ they choose their preferred shipping option. However, an alternative choice is ‘shipping_skipped,’ which allows skipping the shipping step if unnecessary.
After selecting shipping, we move to the ‘payment_selected’ state, where the user enters their payment details. Flexibility is also present here, with the option to transition to ‘payment_skipped’ if payment is not required or if payment upon delivery is anticipated.
Finally, upon completing the information, we arrive at the ‘completed’ state. This last stage confirms order placement and presents an overview of the entire process.
c) shipment state
The shipment state machine creates a structured model that considers the various situations where a single order might involve multiple shipments. Each shipment has its own status, showing its progress, and these shipment statuses affect the overall shipment status within the order.
When the order progresses through the ‘checkout’ stage, the shipment status shifts to ‘ready.’ At this point, there are three scenarios:
- If the order includes one shipment and this shipment is completed, the shipment status of the order changes to ‘shipped.’
- In the case of orders with multiple shipments, if at least one of them is completed, the shipment status of the order becomes ‘partially_shipped’. Once all shipments for the order are completed, the shipment status of the order becomes ‘shipped.’
- If an order is cancelled, the shipment status of the order is also cancelled.
d) payment state
The payment order state machine is one of the more advanced solutions, primarily due to the sensitivity of payment matters and the necessity of ensuring security. Additionally, it accounts for various scenarios related to potential refunds.
Also, in orders where multiple payments can exist, each of them has its own state machine, which adds another element to consider. This explains the existence of statuses like ‘partial_paid’ and ‘partial_refunded,’ precisely reflecting different stages of the payment process in multi-payment orders.
Upon progressing through the ‘checkout’ stage, the payment order status changes to ‘awaiting_payment.’ At this point, there are three scenarios:
- If the order includes one payment and this payment is paid, the payment status of the order changes to ‘paid’.
- In the case of orders with multiple payments, if at least one of them is paid, the payment status of the order becomes ‘partially_paid’. Once all payments for the order are paid, the payment status of the order becomes ‘paid.’
- If an order is cancelled, the payment status of the order is also cancelled.
When an order reaches the stage of partial or full payment, an option related to refunds becomes available.
Example of adding a new state machine to Sylius
Let’s consider this scenario: We want to add the ability to track the status of an invoice in the context of orders. After a customer places and pays for an order, we plan to allow attaching an invoice to the order, but not every order will require this. Subsequently, when the order is shipped to the customer, we want to send the invoice to the customer at the same time. During a specified period, the customer has the opportunity to provide feedback on the invoice and request revisions. After this time period elapses, we assume the invoice is accepted. Of course, if such an order gets cancelled, the invoice status will also be cancelled.
Based on these assumptions, a state diagram has been created:
Let’s do this in code
To begin with, we start by creating an interface that will act as a container for all available states. This will help us manage the states more easily. The next step is to add the necessary properties (invoice status, invoice creation date) to the entities and add them into the mapping process.
Next, we’ll extend the orders table in the admin panel by adding new fields. While it’s not a necessary step, it’ll certainly make it easier to see invoice statuses. So, we need to add the fields to the ‘sylius_admin_order’ grid.
Now, here comes the most important step. We need to define a state machine using ‘winzou_state_machine.’ We create a configuration file and add the basic info.
- ‘class’ – class of your object
- ‘property_path’ – the path to the field where the state is stored
- ‘graph’ – the name of the graph
- ‘state_machine_class’ – the state machine class; here we put the class modified in Sylius
Then, we add the states and transitions:
After we update the database, we’ll see the result, which looks like this:
To simplify the example, let’s skip the full implementation of adding files to an order. Instead, we’ll focus on creating a button that simulates this situation and sets the status to ‘created.’ All we need to do is add the right ‘route’ and button to the ‘sylius_admin_order’ grid.
To save the invoice creation date, we’ll add a ‘callback’ that automatically sets the creation date when the invoice status changes to ‘created.’ For this purpose, we’ll create a class (InvoiceCreatedAtAssigner) that lets us implement this feature. Let’s make sure this class is public, and then we’ll add the ‘callback’ to the invoice state machine.
Now, when we add an invoice to an order, the creation date will be automatically set.
The next step is adding a ‘callback’ to the order shipping state machine. If the shipment is successfully sent, the ‘callback’ will trigger the ‘send’ transition on the order invoice state machine. We also add a ‘callback’ to the invoice state machine that, when transitioning to the ‘sent’ status, will activate the appropriate class responsible for sending invoices.
So, invoice information will be automatically sent every time an order is shipped.
In the end, using state machines on websites, especially in online stores, is crucial for effective process management. It provides clarity, automation, and consistency in actions while allowing personalized customer communication. As a result, it contributes to improving customer service, enhancing process efficiency, and building a positive brand image.
Want to know more about Sylius?