Partial State Machine
I had to implement a game state in PHP. Usually, in that language, I was going to “hard code” all the behaviors in the code itself as I usually do. But I told to myself: “Wait a minute! Am I going to really do that?”. I went to sleep and next day I came up with this idea.
PHP Rendering State Machine
What I wanted to achieve was to render pages from a state and only test the transition from that state. I didn’t want to create the whole state machine in memory every time I was going to load a script.
Partial State Machine
From there, the idea of a partial state machine came to me.
- Create states with a unique identifier (I would store it in the session)
- In those states, create the appropriate transitions and set this unique identifier as the target.
- Create a factory that can create those states based on those unique ids.
As you can see, if you’re familiar with state machines, it’s nothing too complex to implement and it can be incredibly powerful.
My simple PHP framework here contains my naive implementation of this state machine: https://github.com/angedelamort/php-sun-framework. You can have a look at the sample where I created a simple hangman that implements it.
Example
Using the state machine
// When the script loads in your route controller (if using slim) // The code was over-simplified to show the idea. // 1 - initialize the state machine with the current system state. $sm = new StateMachine($_SESSION['current_state], 'namespace\to\states'); // 2 - Create a context for the state machine $context = new StateMachineContext($request, $args, []); // 3 - if needed, call step() to make the state machine test the conditions for each transitions registered in the current state. $sm->step($context) // 4 - get the current state (might be the same as the previous one if no conditions were met $state = $sm->getState(); // 5 - Render the page content using the view and model from the state (since it's a rendering state machine. $this->view->render($response, $state->getView(), $state- >getModel($context));
Creating a new state
// I won't go into details here neither, but only show the idea class MyFirstState extends BaseState { private function initStateMachine(): void { // Create a first condition that will start by testing the final transition. If the condition is true, it will go to the final state. $finalTransition = new Transition(FinalState::name(), [$this, 'setLetter'], 'success'); $finalTransition->addCondition(function (StateMachineContext $context) { return self::isCompleted($context->getRequest()->getParsedBodyParam('isCompleted')); }); $this->addTransition($finalTransition); // the default transition that does nothing for now. $failTransition = new Transition(InitState::name(), [$this, 'showWarning'], 'fail'); $this->addTransition($failTransition); } }
I would suggest you to go and browse the code for a complete example, but in here, as you can see, when the state will be instantiated by the state machine, only the transitions from the state will be instantiated, and until you do a StateMachine::step(), no new allocation will be done.
Conclusion
I don’t know if this concept is already used in some other framework, but I think in this kind of use case, it’s really useful if you care about performance and loading time. The state machine can be as complex as you want and it will always take the same time to initialize it. The only disadvantage is that it might be hard to get the full overview of what your state machine is doing since it’s not defined all at the same place. But big state machines tend to be incomprehensible anyway if you don’t use proper hierarchy.
I hope you’ll like the idea and use it in your future projects!