2017 © Pedro Peláez
 

library fluent-state-machine

image

konsulting/fluent-state-machine

  • Thursday, January 11, 2018
  • by konsulting
  • Repository
  • 1 Watchers
  • 2 Stars
  • 40 Installations
  • PHP
  • 0 Dependents
  • 0 Suggesters
  • 0 Forks
  • 0 Open issues
  • 4 Versions
  • 33 % Grown

The README.md

Fluent State Machine

A simple fluent implementation of a state machine. If you've ever battled with trying to control and report state for an object in your php project - a state machine will help., (*1)

There are a few notable php state machine libraries around already - but didn't quite fit right, although we've used them in projects before. This is not an exhaustive list., (*2)

Installation

composer require konsulting/state-machine, (*3)

Simple Example

We can construct a basic state machine easily, using a door as an example:, (*4)

    use Konsulting\StateMachine\StateMachine;

    $door = new StateMachine(['closed', 'open'])
        ->addTransition('open')->from('closed')->to('open')
        ->addTransition('close')->from('open')->to('closed');

When constructing a StateMachine, the first state is assumed to be the default., (*5)

We can then try to transition the state machine to a new state:, (*6)

    $door->transition('open'); // will complete successfully

    $door->transition('close'); // will throw a TransitionFailed Exception

We can also check if a transition is possible:, (*7)

    $door->can('open'); // returns true

    $door->can('close'); // returns false

Real usage

In real usage, we will have a object (model) where the state machine is responsible for controlling the transitions that can be applied (and therefore controlling the models behaviour), (*8)

This can be accomplished two ways with this library., (*9)

  1. We can attach a model to the state machine, and the state machine can manipulate the model. In very simple cases, this may be enough., (*10)

  2. We can attach the state machine to a model, and the model's methods use the state machine to determine if it is able to proceed with an action., (*11)

Real usage 1 - attach a model to the state machine

As you will see, only the state machine retains the state information and we use it to control the flow in the script., (*12)

    use Konsulting\StateMachine\StateMachine;

    $simpleDoor = new SimpleDoor();

    $sm = new StateMachine(['closed', 'open'])
        ->setModel($state)
        ->addTransition('open')->from('closed')->to('open')
        ->addTransition('close')->from('open')->to('closed');

    $sm->transition('open');     // outputs opening
    echo $sm->getCurrentState(); // outputs open
    $sm->transition('close');    // outputs closing
    echo $sm->getCurrentState(); // outputs closed
    class SimpleDoor
    {
        public function open()
        {
            echo "opening";
        }

        public function close()
        {
            echo "closing";
        }
    }

Side note: This example makes use of automatic wiring to use a model method called the same name as the transition (in camelCase). We can also define a method specifically by passing a string, or any other callable., (*13)

Real usage 2 - attach the state machine to a model

For this we extend the AttachableStateMachine which is set up to allow us to programmatically define the state machine, and accepts a model as its' constructor., (*14)

The end point is that we use the model in the natural manner we want to., (*15)

    $door = new Door('closed');

    $door->close(); // throws TransitionFailed Exception.

    $door->open();  // outputs "I am opening"
    $door->close(); // outputs "I am closing"

In the door class' methods we pass through a callback to be run as part of the transition. We are also able to pass through a callback to be run if the transition fails (instead of throwing an exception)., (*16)


class Door { public $state; protected $stateMachine; public function __construct($state) { $this->state = $state; $this->stateMachine = new AttachedStateMachine($this); } public function open() { $this->stateMachine->transition('open', function () { echo "I am opening"; }); } public function close() { $this->stateMachine->transition('close', function () { echo "I am closing"; }); } }

The AttachedStateMachine defines itself during construction. It grabs the current status from the model, and makes sure to stamp it back when setting the current status., (*17)

We also stop the auto wiring, so the state machine doesn't end up in an infinite loop trying to call it's calling method., (*18)

use Konsulting\StateMachine\AttachableStateMachine;
use Konsulting\StateMachine\StateMachine;
use Konsulting\StateMachine\TransitionFactory;
use Konsulting\StateMachine\Transitions;

class AttachedStateMachine extends AttachableStateMachine
{
    protected function define()
    {
        $transitionFactory = (new TransitionFactory)->useDefaultCall(false);
        $transitions = new Transitions($transitionFactory);

        $this->setTransitions($transitions)
            ->setStates(['closed', 'open'])
            ->setCurrentState($this->model->state ?? 'closed')
            ->addTransition('open')->from('closed')->to('open')
            ->addTransition('close')->from('open')->to('closed');
    }

    public function setCurrentState($state)
    {
        if ($this->model) {
            $this->model->state = $state;
        }

        return parent::setCurrentState($state);
    }
}

Contributing

Contributions are welcome and will be fully credited. We will accept contributions by Pull Request., (*19)

Please:, (*20)

  • Use the PSR-2 Coding Standard
  • Add tests, if you’re not sure how, please ask.
  • Document changes in behaviour, including readme.md.

Testing

We use PHPUnit, (*21)

Run tests using PHPUnit: vendor/bin/phpunit, (*22)

The Versions

11/01 2018
10/01 2018

dev-analysis-X0eYoL

dev-analysis-X0eYoL

  Sources   Download

MIT

The Requires

 

The Development Requires

03/10 2017

dev-master

9999999-dev

  Sources   Download

MIT

The Requires

 

The Development Requires

03/10 2017