added the first more-or-less working version of the Workflow component
This commit is contained in:
parent
9af416d096
commit
17d59a7c66
87
src/Symfony/Component/Workflow/Definition.php
Normal file
87
src/Symfony/Component/Workflow/Definition.php
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Workflow;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class Definition
|
||||||
|
{
|
||||||
|
private $class;
|
||||||
|
private $states = array();
|
||||||
|
private $transitions = array();
|
||||||
|
private $initialState;
|
||||||
|
|
||||||
|
public function __construct($class)
|
||||||
|
{
|
||||||
|
$this->class = $class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClass()
|
||||||
|
{
|
||||||
|
return $this->class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStates()
|
||||||
|
{
|
||||||
|
return $this->states;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTransitions()
|
||||||
|
{
|
||||||
|
return $this->transitions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getInitialState()
|
||||||
|
{
|
||||||
|
return $this->initialState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setInitialState($name)
|
||||||
|
{
|
||||||
|
if (!isset($this->states[$name])) {
|
||||||
|
throw new \LogicException(sprintf('State "%s" cannot be the initial state as it does not exist.', $name));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->initialState = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addState($name)
|
||||||
|
{
|
||||||
|
if (!count($this->states)) {
|
||||||
|
$this->initialState = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->states[$name] = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addTransition(Transition $transition)
|
||||||
|
{
|
||||||
|
if (isset($this->transitions[$transition->getName()])) {
|
||||||
|
throw new \LogicException(sprintf('Transition "%s" is already defined.', $transition->getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($transition->getFroms() as $from) {
|
||||||
|
if (!isset($this->states[$from])) {
|
||||||
|
throw new \LogicException(sprintf('State "%s" referenced in transition "%s" does not exist.', $from, $name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($transition->getTos() as $to) {
|
||||||
|
if (!isset($this->states[$to])) {
|
||||||
|
throw new \LogicException(sprintf('State "%s" referenced in transition "%s" does not exist.', $to, $name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->transitions[$transition->getName()] = $transition;
|
||||||
|
}
|
||||||
|
}
|
32
src/Symfony/Component/Workflow/Dumper/DumperInterface.php
Normal file
32
src/Symfony/Component/Workflow/Dumper/DumperInterface.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Workflow\Dumper;
|
||||||
|
|
||||||
|
use Symfony\Component\Workflow\Definition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DumperInterface is the interface implemented by workflow dumper classes.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
interface DumperInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Dumps a workflow definition.
|
||||||
|
*
|
||||||
|
* @param Definition $definition A Definition instance
|
||||||
|
* @param array $options An array of options
|
||||||
|
*
|
||||||
|
* @return string The representation of the workflow
|
||||||
|
*/
|
||||||
|
public function dump(Definition $definition, array $options = array());
|
||||||
|
}
|
198
src/Symfony/Component/Workflow/Dumper/GraphvizDumper.php
Normal file
198
src/Symfony/Component/Workflow/Dumper/GraphvizDumper.php
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Workflow\Dumper;
|
||||||
|
|
||||||
|
use Symfony\Component\Workflow\Definition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GraphvizDumper dumps a workflow as a graphviz file.
|
||||||
|
*
|
||||||
|
* You can convert the generated dot file with the dot utility (http://www.graphviz.org/):
|
||||||
|
*
|
||||||
|
* dot -Tpng workflow.dot > workflow.png
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class GraphvizDumper implements DumperInterface
|
||||||
|
{
|
||||||
|
private $nodes;
|
||||||
|
private $edges;
|
||||||
|
private $options = array(
|
||||||
|
'graph' => array('ratio' => 'compress', 'rankdir' => 'LR'),
|
||||||
|
'node' => array('fontsize' => 9, 'fontname' => 'Arial', 'color' => '#333', 'shape' => 'circle', 'fillcolor' => 'lightblue', 'fixedsize' => true, 'width' => 1),
|
||||||
|
'edge' => array('fontsize' => 9, 'fontname' => 'Arial', 'color' => '#333', 'arrowhead' => 'normal', 'arrowsize' => 0.5),
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dumps the workflow as a graphviz graph.
|
||||||
|
*
|
||||||
|
* Available options:
|
||||||
|
*
|
||||||
|
* * graph: The default options for the whole graph
|
||||||
|
* * node: The default options for nodes
|
||||||
|
* * edge: The default options for edges
|
||||||
|
*
|
||||||
|
* @param Definition $definition A Definition instance
|
||||||
|
* @param array $options An array of options
|
||||||
|
*
|
||||||
|
* @return string The dot representation of the workflow
|
||||||
|
*/
|
||||||
|
public function dump(Definition $definition, array $options = array())
|
||||||
|
{
|
||||||
|
foreach (array('graph', 'node', 'edge') as $key) {
|
||||||
|
if (isset($options[$key])) {
|
||||||
|
$this->options[$key] = array_merge($this->options[$key], $options[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->nodes = $this->findNodes($definition);
|
||||||
|
$this->edges = $this->findEdges($definition);
|
||||||
|
|
||||||
|
return $this->startDot().$this->addNodes().$this->addEdges().$this->endDot();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds all nodes.
|
||||||
|
*
|
||||||
|
* @return array An array of all nodes
|
||||||
|
*/
|
||||||
|
private function findNodes(Definition $definition)
|
||||||
|
{
|
||||||
|
$nodes = array();
|
||||||
|
foreach ($definition->getStates() as $state) {
|
||||||
|
$nodes[$state] = array(
|
||||||
|
'attributes' => array_merge($this->options['node'], array('style' => $state == $definition->getInitialState() ? 'filled' : 'solid'))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all nodes.
|
||||||
|
*
|
||||||
|
* @return string A string representation of all nodes
|
||||||
|
*/
|
||||||
|
private function addNodes()
|
||||||
|
{
|
||||||
|
$code = '';
|
||||||
|
foreach ($this->nodes as $id => $node) {
|
||||||
|
$code .= sprintf(" node_%s [label=\"%s\", shape=%s%s];\n", $this->dotize($id), $id, $this->options['node']['shape'], $this->addAttributes($node['attributes']));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $code;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function findEdges(Definition $definition)
|
||||||
|
{
|
||||||
|
$edges = array();
|
||||||
|
foreach ($definition->getTransitions() as $transition) {
|
||||||
|
foreach ($transition->getFroms() as $from) {
|
||||||
|
foreach ($transition->getTos() as $to) {
|
||||||
|
$edges[$from][] = array(
|
||||||
|
'name' => $transition->getName(),
|
||||||
|
'to' => $to,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $edges;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all edges.
|
||||||
|
*
|
||||||
|
* @return string A string representation of all edges
|
||||||
|
*/
|
||||||
|
private function addEdges()
|
||||||
|
{
|
||||||
|
$code = '';
|
||||||
|
foreach ($this->edges as $id => $edges) {
|
||||||
|
foreach ($edges as $edge) {
|
||||||
|
$code .= sprintf(" node_%s -> node_%s [label=\"%s\" style=\"%s\"];\n", $this->dotize($id), $this->dotize($edge['to']), $edge['name'], 'solid');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the start dot.
|
||||||
|
*
|
||||||
|
* @return string The string representation of a start dot
|
||||||
|
*/
|
||||||
|
private function startDot()
|
||||||
|
{
|
||||||
|
return sprintf("digraph workflow {\n %s\n node [%s];\n edge [%s];\n\n",
|
||||||
|
$this->addOptions($this->options['graph']),
|
||||||
|
$this->addOptions($this->options['node']),
|
||||||
|
$this->addOptions($this->options['edge'])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the end dot.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function endDot()
|
||||||
|
{
|
||||||
|
return "}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds attributes
|
||||||
|
*
|
||||||
|
* @param array $attributes An array of attributes
|
||||||
|
*
|
||||||
|
* @return string A comma separated list of attributes
|
||||||
|
*/
|
||||||
|
private function addAttributes($attributes)
|
||||||
|
{
|
||||||
|
$code = array();
|
||||||
|
foreach ($attributes as $k => $v) {
|
||||||
|
$code[] = sprintf('%s="%s"', $k, $v);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $code ? ', '.implode(', ', $code) : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds options
|
||||||
|
*
|
||||||
|
* @param array $options An array of options
|
||||||
|
*
|
||||||
|
* @return string A space separated list of options
|
||||||
|
*/
|
||||||
|
private function addOptions($options)
|
||||||
|
{
|
||||||
|
$code = array();
|
||||||
|
foreach ($options as $k => $v) {
|
||||||
|
$code[] = sprintf('%s="%s"', $k, $v);
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode(' ', $code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dotizes an identifier.
|
||||||
|
*
|
||||||
|
* @param string $id The identifier to dotize
|
||||||
|
*
|
||||||
|
* @return string A dotized string
|
||||||
|
*/
|
||||||
|
private function dotize($id)
|
||||||
|
{
|
||||||
|
return strtolower(preg_replace('/[^\w]/i', '_', $id));
|
||||||
|
}
|
||||||
|
}
|
51
src/Symfony/Component/Workflow/Event/Event.php
Normal file
51
src/Symfony/Component/Workflow/Event/Event.php
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Workflow\Event;
|
||||||
|
|
||||||
|
use Symfony\Component\EventDispatcher\Event as BaseEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class Event extends BaseEvent
|
||||||
|
{
|
||||||
|
private $object;
|
||||||
|
private $state;
|
||||||
|
private $attributes;
|
||||||
|
|
||||||
|
public function __construct($object, $state, array $attributes = array())
|
||||||
|
{
|
||||||
|
$this->object = $object;
|
||||||
|
$this->state = $state;
|
||||||
|
$this->attributes = $attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getState()
|
||||||
|
{
|
||||||
|
return $this->state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getObject()
|
||||||
|
{
|
||||||
|
return $this->object;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAttribute($key)
|
||||||
|
{
|
||||||
|
return isset($this->attributes[$key]) ? $this->attributes[$key] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hastAttribute($key)
|
||||||
|
{
|
||||||
|
return isset($this->attributes[$key]);
|
||||||
|
}
|
||||||
|
}
|
30
src/Symfony/Component/Workflow/Event/GuardEvent.php
Normal file
30
src/Symfony/Component/Workflow/Event/GuardEvent.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Workflow\Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class GuardEvent extends Event
|
||||||
|
{
|
||||||
|
private $allowed = null;
|
||||||
|
|
||||||
|
public function isAllowed()
|
||||||
|
{
|
||||||
|
return $this->allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setAllowed($allowed)
|
||||||
|
{
|
||||||
|
$this->allowed = (Boolean) $allowed;
|
||||||
|
}
|
||||||
|
}
|
30
src/Symfony/Component/Workflow/Event/TransitionEvent.php
Normal file
30
src/Symfony/Component/Workflow/Event/TransitionEvent.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Workflow\Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class TransitionEvent extends Event
|
||||||
|
{
|
||||||
|
private $nextState;
|
||||||
|
|
||||||
|
public function setNextState($state)
|
||||||
|
{
|
||||||
|
$this->nextState = $state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNextState()
|
||||||
|
{
|
||||||
|
return $this->nextState;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Workflow\EventListener;
|
||||||
|
|
||||||
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
|
use Symfony\Component\Workflow\Event\Event;
|
||||||
|
|
||||||
|
class AuditTrailListener implements EventSubscriberInterface
|
||||||
|
{
|
||||||
|
public function onEnter(Event $event)
|
||||||
|
{
|
||||||
|
// FIXME: object "identity", timestamp, who, ...
|
||||||
|
error_log('entering "'.$event->getState().'" generic for object of class '.get_class($event->getObject()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onLeave(Event $event)
|
||||||
|
{
|
||||||
|
error_log('leaving "'.$event->getState().'" generic for object of class '.get_class($event->getObject()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onTransition(Event $event)
|
||||||
|
{
|
||||||
|
error_log('transition "'.$event->getState().'" generic for object of class '.get_class($event->getObject()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getSubscribedEvents()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
// FIXME: add a way to listen to workflow.XXX.*
|
||||||
|
'workflow.transition' => array('onTransition'),
|
||||||
|
'workflow.leave' => array('onLeave'),
|
||||||
|
'workflow.enter' => array('onEnter'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
43
src/Symfony/Component/Workflow/Registry.php
Normal file
43
src/Symfony/Component/Workflow/Registry.php
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Workflow;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class Registry
|
||||||
|
{
|
||||||
|
private $workflows = array();
|
||||||
|
|
||||||
|
public function __construct(array $workflows = array())
|
||||||
|
{
|
||||||
|
foreach ($workflows as $workflow) {
|
||||||
|
$this->add($workflow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function add(Workflow $workflow)
|
||||||
|
{
|
||||||
|
$this->workflows[] = $workflow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get($object)
|
||||||
|
{
|
||||||
|
foreach ($this->workflows as $workflow) {
|
||||||
|
if ($workflow->supports($object)) {
|
||||||
|
return $workflow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \InvalidArgumentException(sprintf('Unable to find a workflow for class "%s".', get_class($object)));
|
||||||
|
}
|
||||||
|
}
|
44
src/Symfony/Component/Workflow/Transition.php
Normal file
44
src/Symfony/Component/Workflow/Transition.php
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Workflow;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class Transition
|
||||||
|
{
|
||||||
|
private $name;
|
||||||
|
private $froms = array();
|
||||||
|
private $tos = array();
|
||||||
|
|
||||||
|
public function __construct($name, $froms, $tos)
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
$this->froms = (array) $froms;
|
||||||
|
$this->tos = (array) $tos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFroms()
|
||||||
|
{
|
||||||
|
return $this->froms;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTos()
|
||||||
|
{
|
||||||
|
return $this->tos;
|
||||||
|
}
|
||||||
|
}
|
208
src/Symfony/Component/Workflow/Workflow.php
Normal file
208
src/Symfony/Component/Workflow/Workflow.php
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Workflow;
|
||||||
|
|
||||||
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
|
use Symfony\Component\Workflow\Event\Event;
|
||||||
|
use Symfony\Component\Workflow\Event\GuardEvent;
|
||||||
|
use Symfony\Component\Workflow\Event\TransitionEvent;
|
||||||
|
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||||
|
use Symfony\Component\PropertyAccess\PropertyAccessor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class Workflow
|
||||||
|
{
|
||||||
|
private $name;
|
||||||
|
private $dispatcher;
|
||||||
|
private $propertyAccessor;
|
||||||
|
private $property = 'state';
|
||||||
|
private $stateTransitions = array();
|
||||||
|
private $states;
|
||||||
|
private $initialState;
|
||||||
|
private $class;
|
||||||
|
|
||||||
|
public function __construct($name, Definition $definition, EventDispatcherInterface $dispatcher = null)
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
$this->dispatcher = $dispatcher;
|
||||||
|
$this->propertyAccessor = PropertyAccess::createPropertyAccessor();
|
||||||
|
|
||||||
|
$this->states = $definition->getStates();
|
||||||
|
$this->class = $definition->getClass();
|
||||||
|
$this->initialState = $definition->getInitialState();
|
||||||
|
foreach ($definition->getTransitions() as $name => $transition) {
|
||||||
|
$this->transitions[$name] = $transition;
|
||||||
|
foreach ($transition->getFroms() as $from) {
|
||||||
|
$this->stateTransitions[$from][$name] = $name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supports($class)
|
||||||
|
{
|
||||||
|
return $class instanceof $this->class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function can($object, $transition)
|
||||||
|
{
|
||||||
|
if (!isset($this->transitions[$transition])) {
|
||||||
|
throw new \LogicException(sprintf('Transition "%s" does not exist for workflow "%s".', $transition, $this->name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== $this->dispatcher) {
|
||||||
|
$event = new GuardEvent($object, $this->getState($object));
|
||||||
|
|
||||||
|
$this->dispatcher->dispatch(sprintf('workflow.%s.guard.%s', $this->name, $transition), $event);
|
||||||
|
|
||||||
|
if (null !== $ret = $event->isAllowed()) {
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isset($this->stateTransitions[$this->getState($object)][$transition]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getState($object)
|
||||||
|
{
|
||||||
|
$state = $this->propertyAccessor->getValue($object, $this->property);
|
||||||
|
|
||||||
|
// check if the object is already in the workflow
|
||||||
|
if (null === $state) {
|
||||||
|
$this->enter($object, $this->initialState, array());
|
||||||
|
|
||||||
|
$state = $this->propertyAccessor->getValue($object, $this->property);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that the object has a known state
|
||||||
|
if (!isset($this->states[$state])) {
|
||||||
|
throw new \LogicException(sprintf('State "%s" is not valid for workflow "%s".', $transition, $this->name));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function apply($object, $transition, array $attributes = array())
|
||||||
|
{
|
||||||
|
$current = $this->getState($object);
|
||||||
|
|
||||||
|
if (!$this->can($object, $transition)) {
|
||||||
|
throw new \LogicException(sprintf('Unable to apply transition "%s" from state "%s" for workflow "%s".', $transition, $current, $this->name));
|
||||||
|
}
|
||||||
|
|
||||||
|
$transition = $this->determineTransition($current, $transition);
|
||||||
|
|
||||||
|
$this->leave($object, $current, $attributes);
|
||||||
|
|
||||||
|
$state = $this->transition($object, $current, $transition, $attributes);
|
||||||
|
|
||||||
|
$this->enter($object, $state, $attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAvailableTransitions($object)
|
||||||
|
{
|
||||||
|
return array_keys($this->stateTransitions[$this->getState($object)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNextStates($object)
|
||||||
|
{
|
||||||
|
if (!$stateTransitions = $this->stateTransitions[$this->getState($object)]) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$states = array();
|
||||||
|
foreach ($stateTransitions as $transition) {
|
||||||
|
foreach ($this->transitions[$transition]->getTos() as $to) {
|
||||||
|
$states[] = $to;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $states;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setStateProperty($property)
|
||||||
|
{
|
||||||
|
$this->property = $property;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPropertyAccessor(PropertyAccessor $propertyAccessor)
|
||||||
|
{
|
||||||
|
$this->propertyAccessor = $propertyAccessor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __call($method, $arguments)
|
||||||
|
{
|
||||||
|
if (!count($arguments)) {
|
||||||
|
throw new BadMethodCallException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->apply($arguments[0], $method, array_slice($arguments, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function leave($object, $state, $attributes)
|
||||||
|
{
|
||||||
|
if (null === $this->dispatcher) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->dispatcher->dispatch(sprintf('workflow.leave', $this->name), new Event($object, $state, $attributes));
|
||||||
|
$this->dispatcher->dispatch(sprintf('workflow.%s.leave', $this->name), new Event($object, $state, $attributes));
|
||||||
|
$this->dispatcher->dispatch(sprintf('workflow.%s.leave.%s', $this->name, $state), new Event($object, $state, $attributes));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function transition($object, $current, Transition $transition, $attributes)
|
||||||
|
{
|
||||||
|
$state = null;
|
||||||
|
$tos = $transition->getTos();
|
||||||
|
|
||||||
|
if (null !== $this->dispatcher) {
|
||||||
|
// the generic event cannot change the next state
|
||||||
|
$this->dispatcher->dispatch(sprintf('workflow.transition', $this->name), new Event($object, $current, $attributes));
|
||||||
|
$this->dispatcher->dispatch(sprintf('workflow.%s.transition', $this->name), new Event($object, $current, $attributes));
|
||||||
|
|
||||||
|
$event = new TransitionEvent($object, $current, $attributes);
|
||||||
|
$this->dispatcher->dispatch(sprintf('workflow.%s.transition.%s', $this->name, $transition->getName()), $event);
|
||||||
|
$state = $event->getNextState();
|
||||||
|
|
||||||
|
if (null !== $state && !in_array($state, $tos)) {
|
||||||
|
throw new \LogicException(sprintf('Transition "%s" cannot go to state "%s" for workflow "%s"', $transition->getName(), $state, $this->name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $state) {
|
||||||
|
if (count($tos) > 1) {
|
||||||
|
throw new \LogicException(sprintf('Unable to apply transition "%s" as the new state is not unique for workflow "%s".', $transition->getName(), $this->name));
|
||||||
|
}
|
||||||
|
|
||||||
|
$state = $tos[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $state;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function enter($object, $state, $attributes)
|
||||||
|
{
|
||||||
|
$this->propertyAccessor->setValue($object, $this->property, $state);
|
||||||
|
|
||||||
|
if (null !== $this->dispatcher) {
|
||||||
|
$this->dispatcher->dispatch(sprintf('workflow.enter', $this->name), new Event($object, $state, $attributes));
|
||||||
|
$this->dispatcher->dispatch(sprintf('workflow.%s.enter', $this->name), new Event($object, $state, $attributes));
|
||||||
|
$this->dispatcher->dispatch(sprintf('workflow.%s.enter.%s', $this->name, $state), new Event($object, $state, $attributes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function determineTransition($current, $transition)
|
||||||
|
{
|
||||||
|
return $this->transitions[$transition];
|
||||||
|
}
|
||||||
|
}
|
36
src/Symfony/Component/Workflow/composer.json
Normal file
36
src/Symfony/Component/Workflow/composer.json
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "symfony/workflow",
|
||||||
|
"type": "library",
|
||||||
|
"description": "Symfony Workflow Component",
|
||||||
|
"keywords": [],
|
||||||
|
"homepage": "http://symfony.com",
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "http://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3.3",
|
||||||
|
"symfony/event-dispatcher": "~2.1",
|
||||||
|
"symfony/property-access": "~2.3"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"twig/twig": "~1.14"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-0": { "Symfony\\Component\\Workflow\\": "" }
|
||||||
|
},
|
||||||
|
"target-dir": "Symfony/Component/Workflow",
|
||||||
|
"minimum-stability": "dev",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "2.5-dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user