



  

Finite 1.1 documentation

 [Finite](index.html#document-index) 

 stable

 

* [1. Use with Symfony](index.html#document-usage/symfony)

* [2. Basic graph](index.html#document-examples/basic_graph)

* [3. Events / Callbacks](index.html#document-examples/callbacks)

* [4. Transitions properties](index.html#document-examples/transition-properties)

[Finite](index.html#document-index)

* [Docs](index.html#document-index) »

* Finite 1.1 documentation

* [Edit on GitHub](https://github.com/yohang/Finite/blob/8f06be8c08875e04f8ad25e4c9c8f49b6f24e0d1/docs/index.rst)

---

Finite[¶](#finite "Permalink to this headline")

===============================================

Use with Symfony[¶](#use-with-symfony "Permalink to this headline")

-------------------------------------------------------------------

### Installation[¶](#installation "Permalink to this headline")

```

$ composer require yohang/finite

```

#### Register the bundle[¶](#register-the-bundle "Permalink to this headline")

Register the bundle in your AppKernel:

```

<?php

// app/AppKernel.php

public function registerBundles()

{

    $bundles = array(

        // ...

        new\Finite\Bundle\FiniteBundle\FiniteFiniteBundle(),

        // ...

    );

}

```

### Defining your stateful class[¶](#defining-your-stateful-class "Permalink to this headline")

As we want to track the state of an object (or the multiples states, but this example will focus on

object with single state-graph), create your class if it doesn’t already exists. This class has simply

to implements `Finite\StatefulInterface`.

This part is covered in [Define your object](index.html#index-define-your-object).

### Configuration[¶](#configuration "Permalink to this headline")

```

finite\_finite:

 document\_workflow:

 class: MyDocument # You class FQCN

 states:

 draft: { type: initial, properties: { visible: false } }

 proposed: { type: normal, properties: { visible: false } }

 accepted: { type: final, properties: { visible: true } }

 refused: { type: final, properties: { visible: false } }

 transitions:

 propose: { from: draft, to: proposed }

 accept: { from: proposed, to: accepted }

 refuse: { from: proposed, to: refused }

```

At this point, your graph is ready and you can start using your workflow on your object.

### Controller / Service usage[¶](#controller-service-usage "Permalink to this headline")

Finite define several services into the Symfony DIC. The easier to use is `finite.context`.

#### Example[¶](#example "Permalink to this headline")

```

<?php

$context = $this->get('finite.context');

$context->getState($document); // return "draft", or… the current state if different

$context->getProperties($document); // array:1 [ 'visible' => false ]

$context->getTransitions($document); // array:2 [ 0 => "propose", 1 => "refuse" ]

$context->hasProperty($document, 'visible'); // true

$context->getFactory(); // Return an instance of FiniteFactory, used to instantiate the state machine

$context->getStateMachine($document); // Returns a initialized StateMachine instance for $document

// Throw a 404 if document isn't visible

if (!$this->get('finite.context')->getProperties($document)['visible']) {

    throw $this->createNotFoundException(

        sprintf('The document "%s" is not in a visible state.', $document->getName())

    );

}

```

### Twig usage[¶](#twig-usage "Permalink to this headline")

Although the Twig Extension is not Symfony-specific at all, when using the Symfony Bundle, Finite functions are

automatically accessible in your templates.

```

{{ dump(finite\_state(document)) }} {# "draft" #}

{{ dump(finite\_transitions(document)) }} {# array:2 [ 0 => "propose", 1 => "refuse" ] #}

{{ dump(finite\_properties(document)) }} {# array:1 [ 'visible' => false ] #}

{{ dump(finite\_has(document, 'visible')) }} {# true #}

{{ dump(finite\_can(document, 'accept')) }} {# true #}

{# Display reachable transitions #}

{% for transition in finite\_transitions(document) %}

 <a href="{{ path('document\_apply\_transition', {transition: transition}) }}">

 {{ transition }}

 </a>

{% endfor %}

{# Display an action if available #}

{% if finite\_can(document, 'accept') %}

 <button type="submit" name="accept">

 Accept this document

 </button>

{% endif %}

```

#### Example[¶](#id1 "Permalink to this headline")

### Using callbacks[¶](#using-callbacks "Permalink to this headline")

The state machine is built around a a very flexible and powerful events / callbacks system.

Events dispatched with the EventDispatcher and works as the Symfony kernel events.

#### Events[¶](#events "Permalink to this headline")

finite.set\_initial\_state:

This event is fired when initializing a state machine with an object which does not have a defined state.

It allows you to manage the default initial state of your object.

finite.initialize:

Fired when the StateMachine is initialized for an object (event if the current object state is known)

finite.test\_transition:

Fired when testing if a transition can be applied, when you call `StateMachine#can` or `StateMachine#apply`.

This event is an instance of `Finite\Event\TransitionEvent` and can be rejected, which leads to a

non-appliable transition. This is one of the most useful event, as it allows you to introduce business code

for allowing / rejecting transitions

finite.test\_transition.[transition\_name]:

Same as `finite.test\_transition` but with the concerned transition in the event name.

finite.test\_transition.[graph].[transition\_name]:

Same as `finite.test\_transition` but with the concerned graph and transition in the event name.

finite.pre\_transition:

Fired before applying a transition. You can use it to prepare your object for a transition.

finite.pre\_transition.[transition\_name]:

Same as `finite.pre\_transition` but with the concerned transition in the event name.

finite.pre\_transition.[graph].[transition\_name]:

Same as `finite.pre\_transition` but with the concerned graph and transition in the event name.

finite.post\_transition:

Fired after applying a transition. You can use it to execute the business code you have to execute when

a transition is applied.

finite.post\_transition.[post\_transition]:

Same as `finite.post\_transition` but with the concerned transition in the event name.

finite.post\_transition.[graph].[transition\_name]:

Same as `finite.post\_transition` but with the concerned graph and transition in the event name.

#### Callbacks[¶](#callbacks "Permalink to this headline")

Callbacks are a simplified mechanism allowing you to plug your domain services on the finite events.

You can see it as a way to listen to events without defining a listener class that just redirects the events to

your services.

##### Using YAML configuration[¶](#using-yaml-configuration "Permalink to this headline")

```

finite_finite:

    document_workflow:

        class: MyDocument

        states:

            # ...

        transitions:

            # ...

        callbacks:

            before:

                # Will call the `sendPublicationMail` method of `@app.mailer.document` service

                # When the `accept` transition is applied

                send_publication_mail:

                    disabled: false # default value

                    on: accept

                    do: [ @app.mailer.document, 'sendPublicationMail' ]

                # Will call the `sendNotAnymoreProposedEmail` method of `@app.mailer.document` service

                # When any transition from the `proposed` state is applied.

                # This condition can be negated by prefixing a `-` before the state name

                # And the same exists for the destination transitions (with `to: `)

                send_publication_mail:

                    disabled: false # default value

                    from: ['proposed']

                    do: [ @app.mailer.document, 'sendNotAnymoreProposedEmail' ]

```

### Configuration reference[¶](#configuration-reference "Permalink to this headline")

```

finite\_finite:

    # Prototype

    name: # internal name of your graph, not used

        class:                ~           # Required, FQCN of your class

        graph:                default     # Name of your graph, keep default if using a single graph

        property\_path:        finiteState # The property of your class used to store the state

        states:

            # Prototype

            name:            # Required, Name of your state

                type: normal # State type, in "initial", "normal", "final"

                properties:  # Properties array.

                    # Prototype

                    name:                 ~

        transitions:

            # Prototype

            name:           # Required, Name of your transition

                from: []    # Required, states the transition can come from

                to:   ~     # Required, state where the transition go

                properties: # Properties array.

                    # Prototype

                    name:                 ~

        callbacks:

            before: # Pre-transition callbacks

                # Prototype

                name:

                    do:       ~ # Required. The callback.

                    on:       ~ # On which transition to trigger the callback. Default null

                    from:     ~ # From which states are we triggering the callback. Default null

                    to:       ~ # To which states are we triggering the callback. Default null

                    disabled: false

            after: # Post-transition callbacks

                # Prototype

                name:

                    on:                   ~

                    do:                   ~

                    from:                 ~

                    to:                   ~

                    disabled:             false

```

Basic graph[¶](#basic-graph "Permalink to this headline")

---------------------------------------------------------

### Goal[¶](#goal "Permalink to this headline")

In this example, we’ll see a basic Document workflow, following this graph :

```

                            Reject

                     |-----------------|

Transitions          |                 |

                     v    Propose      |       Accept

States            Draft ----------> Proposed ----------> Accepted

Properties     * Deletable                              * Printable

               * Editable

```

### Implement the document class[¶](#implement-the-document-class "Permalink to this headline")

```

<?php

class Document implements Finite\StatefulInterface

{

    private $state;

    public function getFiniteState()

    {

        return $this->state;

    }

    public function setFiniteState($state)

    {

        $this->state = $state;

    }

}

```

### Configure your graph[¶](#configure-your-graph "Permalink to this headline")

```

<?php

$loader = new Finite\Loader\ArrayLoader([

    'class'   => 'Document',

    'states'  => [

        'draft' => [

            'type'       => Finite\State\StateInterface::TYPE\_INITIAL,

            'properties' => ['deletable' => true, 'editable' => true],

        ],

        'proposed' => [

            'type'       => Finite\State\StateInterface::TYPE\_NORMAL,

            'properties' => [],

        ],

        'accepted' => [

            'type'       => Finite\State\StateInterface::TYPE\_FINAL,

            'properties' => ['printable' => true],

        ]

    ],

    'transitions' => [

        'propose' => ['from' => ['draft'], 'to' => 'proposed'],

        'accept'  => ['from' => ['proposed'], 'to' => 'accepted'],

        'reject'  => ['from' => ['proposed'], 'to' => 'draft'],

    ],

]);

$document = new Document;

$stateMachine = new Finite\StateMachine\StateMachine($document);

$loader->load($stateMachine);

$stateMachine->initialize();

```

At this point, your Workflow / State graph is fully accessible to the state machine, and you can start to work with your workflow.

### Working with workflow[¶](#working-with-workflow "Permalink to this headline")

#### Current state[¶](#current-state "Permalink to this headline")

```

<?php

// Get the name of the current state

$stateMachine->getCurrentState()->getName();

// string(5) "draft"

// List the currently accessible properties, and their values

$stateMachine->getCurrentState()->getProperties();

// array(2) {

// 'deletable' => bool(true)

// 'editable' => bool(true)

// }

// Checks if "deletable" property is defined

$stateMachine->getCurrentState()->has('deletable');

// bool(true)

// Checks if "printable" property is defined

$stateMachine->getCurrentState()->has('printable');

// bool(false)

```

#### Available transitions[¶](#available-transitions "Permalink to this headline")

```

<?php

// Retrieve available transitions

var\_dump($stateMachine->getCurrentState()->getTransitions());

// array(1) {

// [0] => string(7) "propose"

// }

// Check if we can apply the "propose" transition

var\_dump($stateMachine->getCurrentState()->can('propose'));

// bool(true)

// Check if we can apply the "accept" transition

var\_dump($stateMachine->getCurrentState()->can('accept'));

// bool(false)

```

#### Apply transition[¶](#apply-transition "Permalink to this headline")

```

<?php

// Trying to apply a not accessible transition

try {

    $stateMachine->apply('accept');

} catch (\Finite\Exception\StateException $e) {

    echo $e->getMessage();

}

// The "accept" transition can not be applied to the "draft" state.

// Applying a transition

$stateMachine->apply('propose');

$stateMachine->getCurrentState()->getName();

// string(7) "proposed"

$document->getFiniteState();

// string(7) "proposed"

```

Events / Callbacks[¶](#events-callbacks "Permalink to this headline")

---------------------------------------------------------------------

### Overview[¶](#overview "Permalink to this headline")

Finite use the Symfony EventDispatcher component to notify each actions done by the State Machine.

You can use the event system directly with callbacks in your configuration, or by attaching listeners

to the event dispatcher.

### Implement your document class and define your workflow[¶](#implement-your-document-class-and-define-your-workflow "Permalink to this headline")

See [Basic graph](index.html#document-examples/basic_graph).

### Use callbacks[¶](#use-callbacks "Permalink to this headline")

Callbacks can be defined directly in your State Machine configuration. The can be called

before or after the transition apply, and their definition use the following pattern :

```

<?php

$definition = [

    'from' => [],  // a string or an array of string that represent the initial states that trigger the callback. Empty for All.

    'to'   => [],  // a string or an array of string that represent the target states that trigger the callback. Empty for All.

    'on'   => [],  // a string or an array of string that represent the transition names that trigger the callback. Empty for All.

    'do'   => function($object, Finite\Event\TransitionEvent $e) {

        // The callback

    }

];

```

from and to parameters can be any state names. Prefix by - to process an exclusion.

By default, callbacks matchs all the events.

#### Example :[¶](#example "Permalink to this headline")

```

<?php

[

    'from' => ['all', '-proposed'],

    'do'   => function($object, Finite\Event\TransitionEvent $e) {

        // callback code

    }

];

```

Will match any transition that don’t begin on the proposed state.

#### Full example :[¶](#full-example "Permalink to this headline")

```

<?php

$loader       = new Finite\Loader\ArrayLoader([

    'class'       => 'Document',

    'states'      => [

        'draft'    => [

            'type'       => Finite\State\StateInterface::TYPE\_INITIAL,

            'properties' => ['deletable' => true, 'editable' => true],

        ],

        'proposed' => [

            'type'       => Finite\State\StateInterface::TYPE\_NORMAL,

            'properties' => [],

        ],

        'accepted' => [

            'type'       => Finite\State\StateInterface::TYPE\_FINAL,

            'properties' => ['printable' => true],

        ]

    ],

    'transitions' => [

        'propose' => ['from' => ['draft'], 'to' => 'proposed', 'properties' => ['foo' => 'bar']],

        'accept'  => ['from' => ['proposed'], 'to' => 'accepted'],

        'reject'  => ['from' => ['proposed'], 'to' => 'draft'],

    ],

    'callbacks' => [

        'before' => [

            [

                'from' => '-proposed',

                'do'   => function(\Finite\Event\TransitionEvent $e) {

                    echo 'Applying transition '.$e->getTransition()->getName(), "\n";

                    if ($e->has('foo')) {

                        echo "Parameter \"foo\" is defined\n";

                    }

                }

            ],

            [

                'from' => 'proposed',

                'do'   => function() {

                    echo 'Applying transition from proposed state', "\n";

                }

            ]

        ],

        'after' => [

            [

                'to' => ['accepted'], 'do' => [$document, 'display']

            ]

        ]

    ]

]);

$stateMachine->apply('propose');

// => "Applying transition propose"

// => "Parameter "foo" is defined"

$stateMachine->apply('reject');

// => "Applying transition from proposed state"

$stateMachine->apply('propose');

// => "Applying transition propose"

// => "Parameter "foo" is defined"

$stateMachine->apply('accept');

// => "Applying transition from proposed state"

// => "Hello, I'm a document and I'm currently at the accepted state."

```

### Use event dispatcher[¶](#use-event-dispatcher "Permalink to this headline")

If you prefer, you can use directly the event dispatcher.

Here is the available events :

```

finite.initialize      => Dispatched at State Machine initialization

finite.test_transition => Dispatched when testing if a transition can be applied

finite.pre_transition  => Dispatched before a transition

finite.post_transition => Dispatched after a transition

finite.test_transition.{transitionName} => Dispatched when testing if a specific transition can be applied

finite.pre_transition.{transitionName}  => Dispatched before a specific transition

finite.post_transition.{transitionName} => Dispatched after a specific transition

finite.test_transition.{graph}.{transitionName} => Dispatched when testing if a specific transition  in a specific graph can be applied

finite.pre_transition.{graph}.{transitionName}  => Dispatched before a specific transition in a specific graph

finite.post_transition.{graph}.{transitionName} => Dispatched after a specific transition in a specific graph

```

#### Example :[¶](#id1 "Permalink to this headline")

```

<?php

$stateMachine->getDispatcher()->addListener('finite.pre\_transition', function(\Finite\Event\TransitionEvent $e) {

    echo 'This is a pre transition', "\n";

});

$stateMachine->apply('propose');

// => "This is a pre transition"

```

#### Example testing transitions:[¶](#example-testing-transitions "Permalink to this headline")

```

<?php

$stateMachine->getDispatcher()->addListener('finite.test\_transition', function(\Finite\Event\TransitionEvent $e) {

    $e->reject();

});

try {

    $stateMachine->apply('propose');

}

catch (Finite\StateMachine\Exception\StateException $e) {

    echo 'The transition did not apply', "\n";

}

// => "The transition did not apply"

```

Transitions properties[¶](#transitions-properties "Permalink to this headline")

-------------------------------------------------------------------------------

As the second argument, StateMachine#apply and StateMachine#test will accept an array of properties to be passed

to the dispatched event, and accessible by the listeners.

Default properties can be defined with your state graph.

```

$stateManager->apply('some\_event', array('something' => $value));

```

In your listeners you just have to call ``$event->getProperties()`` to access the passed data.

```

<?php

namespace My\AwesomeBundle\EventListener;

use Finite\Event\TransitionEvent;

class TransitionListener

{

    /\*\*

 \* @param TransitionEvent $event

 \*/

    public function someEvent(TransitionEvent $event)

    {

        $entity = $event->getStateMachine()->getObject();

        $params = $event->getProperties();

        $entity->setSomething($params['something']);

    }

}

```

### Default properties[¶](#default-properties "Permalink to this headline")

```

'transitions' => array(

 'finish' => array(

 'from' => array('middle'),

 'to' => 'end',

 'properties' => array('foo' => 'bar'),

 'configure\_properties' => function (OptionsResolver $resolver) {

 $resolver->setRequired('baz');

 }

 )

)

```

A PHP Finite State Machine[¶](#a-php-finite-state-machine "Permalink to this headline")

---------------------------------------------------------------------------------------

Finite is a state machine library that gives you ability to manage the state of a PHP object through

a graph of states and transitions.

Overview[¶](#overview "Permalink to this headline")

---------------------------------------------------

### Define your workflow / state graph[¶](#define-your-workflow-state-graph "Permalink to this headline")

```

<?php

$document     = new MyDocument;

$stateMachine = new Finite\StateMachine\StateMachine;

$loader       = new Finite\Loader\ArrayLoader([

    'class'  => 'MyDocument',

    'states' => [

        'draft'    => ['type' => 'initial', 'properties' => []],

        'proposed' => ['type' => 'normal',  'properties' => []],

        'accepted' => ['type' => 'final',   'properties' => []],

        'refused'  => ['type' => 'final',   'properties' => []],

    ],

    'transitions' => [

        'propose' => ['from' => ['draft'],    'to' => 'proposed'],

        'accept'  => ['from' => ['proposed'], 'to' => 'accepted'],

        'refuse'  => ['from' => ['proposed'], 'to' => 'refused'],

    ]

]);

$loader->load($stateMachine);

$stateMachine->setObject($document);

$stateMachine->initialize();

```

### Define your object[¶](#define-your-object "Permalink to this headline")

```

<?php

class MyDocument implements Finite\StatefulInterface

{

    private $state;

    public function getFiniteState()

    {

        return $this->state;

    }

    public function setFiniteState($state)

    {

        $this->state = $state;

    }

}

```

### Work with states & transitions[¶](#work-with-states-transitions "Permalink to this headline")

```

<?php

echo $stateMachine->getCurrentState();

// => "draft"

var\_dump($stateMachine->can('accept'));

// => bool(false)

var\_dump($stateMachine->can('propose'));

// => bool(true)

$stateMachine->apply('propose');

echo $stateMachine->getCurrentState();

// => "proposed"

```

Contribute[¶](#contribute "Permalink to this headline")

-------------------------------------------------------

Contributions are welcome !

Finite follows PSR-2 code, and accept pull-requests on the [GitHub repository](https://github.com/yohang/Finite).

If you’re a beginner, you will find some guidelines about code contributions at [Symfony](http://symfony.com/doc/current/contributing/code/patches.html).

[Follow @rouKs](https://twitter.com/rouKs)

[Tweeter](https://twitter.com/share)

