PHP state machine framework

18,448

Solution 1

I don't know a framework like this (which doesn't mean it does not exist). But while not as feature packed as the linked framework, the State pattern is rather simple to implement. Consider this naive implementation below:

interface EngineState
{
    public function startEngine();
    public function moveForward();
}

class EngineTurnedOffState implements EngineState
{
    public function startEngine()
    {
        echo "Started Engine\n";
        return new EngineTurnedOnState;
    }
    public function moveForward()
    {
        throw new LogicException('Have to start engine first');
    }
}

class EngineTurnedOnState implements EngineState
{
    public function startEngine()
    {
        throw new LogicException('Engine already started');
    }
    public function moveForward()
    {
        echo "Moved Car forward";
        return $this;
    }
}

After you defined the states, you just have to apply them to your main object:

class Car implements EngineState
{
    protected $state;
    public function __construct()
    {
        $this->state = new EngineTurnedOffState;
    }
    public function startEngine()
    {
        $this->state = $this->state->startEngine();
    }
    public function moveForward()
    {
        $this->state = $this->state->moveForward();
    }
}

And then you can do

$car = new Car;
try {
    $car->moveForward(); // throws Exception
} catch(LogicException $e) {
    echo $e->getMessage();
}

$car = new Car;
$car->startEngine();
$car->moveForward();

For reducing overly large if/else statements, this should be sufficient. Note that returning a new state instance on each transition is somewhat inefficient. Like I said, it's a naive implementation to illustrate the point.

Solution 2

FSM Package and FSM Parser.

Solution 3

I've been working on a simple PHP finite state machine library, kind of similar to the rails state_machine. The code is here: https://github.com/chriswoodford/techne/tree/v0.1

A car example, similar to the selected answer (above) would look something like this:

Initialization

  $machine = new StateMachine\FiniteStateMachine();
  $machine->addEvent('start', array('parked' => 'idling'));
  $machine->addEvent('drive', array('idling' => 'driving'));
  $machine->addEvent('stop', array('driving' => 'idling'));
  $machine->addEvent('park', array('idling' => 'parked'));
  $machine->setInitialState('parked');

Usage

  $machine->start();
  echo $machine->getCurrentStatus();
  // prints "idling"     

  $machine->drive();
  echo $machine->getCurrentStatus();
  // prints "driving"

  $machine->stop();
  echo $machine->getCurrentStatus();
  // prints "idling"

  $machine->park();
  echo $machine->getCurrentStatus();
  // prints "parked"

It lacks an explicitly defined interface that since it uses the __call() magic method to accept messages, but that could easily be resolved with an adapter.

Solution 4

Symfony has a workflow component since 2016 which includes all the state machine features you can imagine and seems both flexible and mature by now. It is tied in to Symfony, so if you are using Symfony and Doctrine it is very easy to integrate (but you can use it standalone too). I also like how you can dump visual representations of your workflows to show it to people or discuss it with a team, and there are many excellent tutorials available online or in real-world workshops.

There are a few good videos on Youtube about both state machines and how you use them with the workflow component, like this one by Michelle Sanver or this one by Tobias Nyholm.

That being said, the workflow component does assume many things about your state machine and works best if you follow those principles and combine it with other adjacent components (Symfony Event Dispatcher and Doctrine). If you are using Domain Driven Design or CQRS or an event store or anything advanced like that it might make sense to just handle your own states. First draw your state machine (so you know the states and transitions) and then implement classes to handle changes or access the current state - the complex thing about state machines is the logic (which you should think about in advance anyway), not necessarily the representation in code. Get a good grasp of how state machines work, be inspired by libraries like the Symfony workflow component (you can even prototype in those or try it with them first), and then try to adapt it to your situation and see where your most important state machine logic is and how you can enforce it.

Solution 5

I have used this one https://github.com/yohang/Finite which is quite powerfull, however the docs are not so detailed. If you are familliar with state machines then you shouldn't have any problem.

Share:
18,448
noomz
Author by

noomz

Internet Application Developer PHP, Python, Groovy, Music, Painting, Drawing

Updated on June 06, 2022

Comments

  • noomz
    noomz about 2 years

    I doubt that is there any state machine framework like https://github.com/pluginaweek/state_machine for PHP.

    I've had to define many if-else logical clauses, and I would like something to help make it more fun by just defining:

    1. Condition required to transition
    2. State after transition

    Then this can be reused to check if conditions match or not, for example

    $customer->transition('platinum');
    

    I expect this line of code to implicitly check if the customer can transition or not. Or explicitly check by:

    $customer->canTransitTo('platinum');
    

    Thanks in advance, noomz

  • Dave Kiss
    Dave Kiss about 13 years
    For us n00bs, what would be a more efficient and practical way of handling transition points?
  • Gordon
    Gordon about 13 years
    @Dave try the FSM Package given in Elzo's answer
  • Marijn Huizendveld
    Marijn Huizendveld over 12 years
    Thanks @Gordon, although it's a naive implementation it's very powerful and easy to comprehend. For anyone looking for another example: gist.github.com/2137684
  • relipse
    relipse about 10 years
    This was very helpful, additionally a context is provided in this other example which helped: sourcemaking.com/design_patterns/state/php