Doctrine2 behavior adding a finite state machine in your entities.
The state machine implementation used is Finite.
This project is DEPRECATED and should NOT be used.
If someone magically appears and wants to maintain this project, I'll gladly give access to this repository.
In your app/config/config.yml
file, define your state machines:
k_phoen_doctrine_state_machine:
auto_injection: true # should we automatically inject state machines into hydrated objects?
auto_validation: true # should we validate any status change before the persistence happens?
state_machines:
article_state_machine:
class: \Acme\FooBundle\Entity\Article
property: state
states:
new: {type: initial}
reviewed: ~
accepted: ~
published: {type: final, properties: {printable: true}}
rejected: {type: final}
transitions:
review: {from: [new], to: reviewed}
accept: {from: [reviewed], to: accepted}
publish: {from: [accepted], to: published}
reject: {from: [new, reviewed, accepted, published], to: rejected}
The state machines configuration is pretty straightforward. In addition to the states and transitions, you just have to define the entity class and the column used to store the state.
Important: the entity has to implement the Stateful
interface.
To ease the implementation, you can use the StatefulTrait
that comes bundled
with the behavior.
Stateful
entities have access to their own state machine. See Finite's
documentation for more details about it.
The Article
entity below is ready to be used as a Stateful
entity.
<?php
namespace Acme\FooBundle\Entity;
use KPhoen\DoctrineStateMachineBehavior\Entity\Stateful;
use KPhoen\DoctrineStateMachineBehavior\Entity\StatefulTrait;
class Article implements Stateful
{
use StatefulTrait;
/**
* define your fields here
*/
/**
* @var string
*/
protected $state = 'new';
/**
* Set state
*
* @param string $state
* @return Article
*/
public function setState($state)
{
$this->state = $state;
return $this;
}
/**
* Get state
*
* @return string
*/
public function getState()
{
return $this->state;
}
/**
* Sets the object state.
* Used by the StateMachine behavior
*
* @return string
*/
public function getFiniteState()
{
return $this->getState();
}
/**
* Sets the object state.
* Used by the StateMachine behavior
*
* @param string $state
*/
public function setFiniteState($state)
{
return $this->setState($state);
}
}
Entities using the StatefulTrait
see the setStateMachine()
and
getStateMachine()
methods implemented and gain access to the following
methods:
can($transition)
: indicating if the given transition is allowed ;- and a few magic methods, based on the transition allowed by the
state-machine:
{TransitionName}()
: apply the transition {TransitionName} (ie:accept()
,reject()
, etc) ;can{TransitionName}()
: test if the transition {TransitionName} can be applied (ie:canAccept()
,canReject()
, etc).is{StateName}()
: test if the current state is {StatusName} (ie:isAccepted()
,isRejected()
, etc).
<?php
$article = new Article();
$article->canAccept();
$article->canReject();
$article->can('accept');
$article->accept();
$article->publish();
$article->isAccepted();
$article->isRejected();
If you use the event-aware state-machine (which is the default one used by the bundle), the extension provides a listener implementing "lifecyle callbacks" for stateful entities.
For each available transition, three methods can be executed:
pre{TransitionName}()
: called before the transition {TransitionName} is applied ;post{TransitionName}()
: called after the transition {TransitionName} is applied ;can{TransitionName}()
: called when the state-machine tests if the transition {TransitionName} can be applied.
<?php
namespace Acme\FooBundle\Entity;
use KPhoen\DoctrineStateMachineBehavior\Entity\Stateful;
use KPhoen\DoctrineStateMachineBehavior\Entity\StatefulTrait;
class Article implements Stateful
{
// previous code
public function preAccept()
{
// your logic here
}
public function postAccept()
{
// your logic here
}
public function onCanAccept()
{
// your logic here
}
}
You can put the state logic outside of the entity using a listener on Finite events. The extension provides an abstract EventSubcriber with the same methods as the "lifecyle callbacks" listener.
You need to declare a service and extend the AbstractSubcriber.
services:
article_workflow_subscriber:
class: Acme\FooBundle\Workflow\ArticleSubscriber
tags:
- { name: kernel.event_subscriber }
<?php
namespace Acme\FooBundle\Workflow;
use KPhoen\DoctrineStateMachineBundle\Listener\AbstractSubscriber;
class ArticleSubscriber extends AbstractSubscriber
{
public function supportsObject($object)
{
return $object instanceof \Acme\FooBundle\Entity\Article;
}
public function preAccept()
{
// your logic here
}
public function postAccept()
{
// your logic here
}
public function canAccept()
{
// your logic here
}
}
The bundle also exposes a few Twig helpers:
{# your template ... #}
{% if article|can('reject') %}
<a class="btn btn-danger" href="{{ path('article_delete', article) }}">
<i class="icon-trash"></i>
{{ 'link_reject'|trans }}
</a>
{% endif %}
{# this is strictly equivalent #}
{% if can(article, 'reject') %}
<a class="btn btn-danger" href="{{ path('article_delete', article) }}">
<i class="icon-trash"></i>
{{ 'link_reject'|trans }}
</a>
{% endif %}
{% if current_state(article).isFinal %}
blabla
{% endif %}
{% if article|is_status('rejected') %}
blabla
{% endif %}
{# this is strictly equivalent #}
{% if is_status(article, 'rejected') %}
blabla
{% endif %}
{% if article|has_property('printable') %}
{{ article|property('printable') ? 'I can print' : 'I CANNOT print' }}
{% endif %}
{# this is strictly equivalent #}
{% if has_property(article, 'printable') %}
{{ property(article, 'printable') ? 'I can print' : 'I CANNOT print' }}
{% endif %}
Install the behavior adding kphoen/doctrine-state-machine-bundle
to your composer.json or from CLI:
composer require kphoen/doctrine-state-machine-bundle
Than register the bundle in your app/AppKernel.php
file:
// app/AppKernel.php
public function registerBundles()
{
$bundles = array(
// ...
new KPhoen\DoctrineStateMachineBundle\KPhoenDoctrineStateMachineBundle(),
// ...
);
}
This bundle is released under the MIT license.