2013-12-01 09:16:07 +00:00
|
|
|
<?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;
|
2016-03-25 15:43:30 +00:00
|
|
|
use Symfony\Component\Workflow\Exception\LogicException;
|
|
|
|
use Symfony\Component\Workflow\MarkingStore\MarkingStoreInterface;
|
2016-11-09 13:38:18 +00:00
|
|
|
use Symfony\Component\Workflow\MarkingStore\MultipleStateMarkingStore;
|
2013-12-01 09:16:07 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @author Fabien Potencier <fabien@symfony.com>
|
2016-03-25 15:43:30 +00:00
|
|
|
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
2016-08-16 12:13:16 +01:00
|
|
|
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
2013-12-01 09:16:07 +00:00
|
|
|
*/
|
|
|
|
class Workflow
|
|
|
|
{
|
2016-03-25 15:43:30 +00:00
|
|
|
private $definition;
|
|
|
|
private $markingStore;
|
2013-12-01 09:16:07 +00:00
|
|
|
private $dispatcher;
|
2016-03-25 15:43:30 +00:00
|
|
|
private $name;
|
|
|
|
|
2016-11-07 17:57:54 +00:00
|
|
|
public function __construct(Definition $definition, MarkingStoreInterface $markingStore = null, EventDispatcherInterface $dispatcher = null, $name = 'unnamed')
|
2013-12-01 09:16:07 +00:00
|
|
|
{
|
2016-03-25 15:43:30 +00:00
|
|
|
$this->definition = $definition;
|
2016-11-09 13:38:18 +00:00
|
|
|
$this->markingStore = $markingStore ?: new MultipleStateMarkingStore();
|
2013-12-01 09:16:07 +00:00
|
|
|
$this->dispatcher = $dispatcher;
|
2016-03-25 15:43:30 +00:00
|
|
|
$this->name = $name;
|
2013-12-01 09:16:07 +00:00
|
|
|
}
|
|
|
|
|
2016-03-25 15:43:30 +00:00
|
|
|
/**
|
|
|
|
* Returns the object's Marking.
|
|
|
|
*
|
|
|
|
* @param object $subject A subject
|
|
|
|
*
|
|
|
|
* @return Marking The Marking
|
|
|
|
*
|
|
|
|
* @throws LogicException
|
|
|
|
*/
|
|
|
|
public function getMarking($subject)
|
2013-12-01 09:16:07 +00:00
|
|
|
{
|
2016-03-25 15:43:30 +00:00
|
|
|
$marking = $this->markingStore->getMarking($subject);
|
2013-12-01 09:16:07 +00:00
|
|
|
|
2016-03-25 15:43:30 +00:00
|
|
|
if (!$marking instanceof Marking) {
|
|
|
|
throw new LogicException(sprintf('The value returned by the MarkingStore is not an instance of "%s" for workflow "%s".', Marking::class, $this->name));
|
2013-12-01 09:16:07 +00:00
|
|
|
}
|
|
|
|
|
2016-03-25 15:43:30 +00:00
|
|
|
// check if the subject is already in the workflow
|
|
|
|
if (!$marking->getPlaces()) {
|
|
|
|
if (!$this->definition->getInitialPlace()) {
|
|
|
|
throw new LogicException(sprintf('The Marking is empty and there is no initial place for workflow "%s".', $this->name));
|
|
|
|
}
|
|
|
|
$marking->mark($this->definition->getInitialPlace());
|
2017-02-27 15:36:29 +00:00
|
|
|
|
|
|
|
// update the subject with the new marking
|
|
|
|
$this->markingStore->setMarking($subject, $marking);
|
2016-03-25 15:43:30 +00:00
|
|
|
}
|
2013-12-01 09:16:07 +00:00
|
|
|
|
2016-03-25 15:43:30 +00:00
|
|
|
// check that the subject has a known place
|
|
|
|
$places = $this->definition->getPlaces();
|
|
|
|
foreach ($marking->getPlaces() as $placeName => $nbToken) {
|
|
|
|
if (!isset($places[$placeName])) {
|
|
|
|
$message = sprintf('Place "%s" is not valid for workflow "%s".', $placeName, $this->name);
|
|
|
|
if (!$places) {
|
|
|
|
$message .= ' It seems you forgot to add places to the current workflow.';
|
|
|
|
}
|
2013-12-01 09:16:07 +00:00
|
|
|
|
2016-03-25 15:43:30 +00:00
|
|
|
throw new LogicException($message);
|
2013-12-01 09:16:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-25 15:43:30 +00:00
|
|
|
return $marking;
|
2013-12-01 09:16:07 +00:00
|
|
|
}
|
|
|
|
|
2016-03-25 15:43:30 +00:00
|
|
|
/**
|
|
|
|
* Returns true if the transition is enabled.
|
|
|
|
*
|
|
|
|
* @param object $subject A subject
|
|
|
|
* @param string $transitionName A transition
|
|
|
|
*
|
|
|
|
* @return bool true if the transition is enabled
|
|
|
|
*/
|
|
|
|
public function can($subject, $transitionName)
|
2013-12-01 09:16:07 +00:00
|
|
|
{
|
2017-08-03 19:22:22 +01:00
|
|
|
$transitions = $this->definition->getTransitions();
|
|
|
|
$marking = $this->getMarking($subject);
|
2017-01-13 14:53:55 +00:00
|
|
|
|
|
|
|
foreach ($transitions as $transition) {
|
2017-08-03 19:22:22 +01:00
|
|
|
foreach ($transition->getFroms() as $place) {
|
|
|
|
if (!$marking->has($place)) {
|
|
|
|
// do not emit guard events for transitions where the marking does not contain
|
|
|
|
// all "from places" (thus the transition couldn't be applied anyway)
|
|
|
|
continue 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($transitionName === $transition->getName() && $this->doCan($subject, $marking, $transition)) {
|
2017-01-13 14:53:55 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2013-12-01 09:16:07 +00:00
|
|
|
|
2017-01-13 14:53:55 +00:00
|
|
|
return false;
|
2013-12-01 09:16:07 +00:00
|
|
|
}
|
|
|
|
|
2016-03-25 15:43:30 +00:00
|
|
|
/**
|
|
|
|
* Fire a transition.
|
|
|
|
*
|
|
|
|
* @param object $subject A subject
|
|
|
|
* @param string $transitionName A transition
|
|
|
|
*
|
|
|
|
* @return Marking The new Marking
|
|
|
|
*
|
|
|
|
* @throws LogicException If the transition is not applicable
|
|
|
|
* @throws LogicException If the transition does not exist
|
|
|
|
*/
|
|
|
|
public function apply($subject, $transitionName)
|
2013-12-01 09:16:07 +00:00
|
|
|
{
|
2017-02-27 15:36:29 +00:00
|
|
|
$transitions = $this->getEnabledTransitions($subject);
|
2016-08-16 12:13:16 +01:00
|
|
|
|
2017-01-13 14:53:55 +00:00
|
|
|
// We can shortcut the getMarking method in order to boost performance,
|
|
|
|
// since the "getEnabledTransitions" method already checks the Marking
|
|
|
|
// state
|
|
|
|
$marking = $this->markingStore->getMarking($subject);
|
2013-12-01 09:16:07 +00:00
|
|
|
|
2017-01-13 14:53:55 +00:00
|
|
|
$applied = false;
|
2013-12-01 09:16:07 +00:00
|
|
|
|
2017-01-13 14:53:55 +00:00
|
|
|
foreach ($transitions as $transition) {
|
|
|
|
if ($transitionName !== $transition->getName()) {
|
|
|
|
continue;
|
|
|
|
}
|
2013-12-01 09:16:07 +00:00
|
|
|
|
2017-01-13 14:53:55 +00:00
|
|
|
$applied = true;
|
2016-03-25 15:43:30 +00:00
|
|
|
|
2017-01-13 14:53:55 +00:00
|
|
|
$this->leave($subject, $transition, $marking);
|
2016-03-25 15:43:30 +00:00
|
|
|
|
2017-01-13 14:53:55 +00:00
|
|
|
$this->transition($subject, $transition, $marking);
|
|
|
|
|
|
|
|
$this->enter($subject, $transition, $marking);
|
|
|
|
|
|
|
|
$this->markingStore->setMarking($subject, $marking);
|
|
|
|
|
2016-12-06 15:23:39 +00:00
|
|
|
$this->entered($subject, $transition, $marking);
|
|
|
|
|
2017-04-30 16:11:37 +01:00
|
|
|
$this->completed($subject, $transition, $marking);
|
|
|
|
|
2017-01-13 14:53:55 +00:00
|
|
|
$this->announce($subject, $transition, $marking);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$applied) {
|
|
|
|
throw new LogicException(sprintf('Unable to apply transition "%s" for workflow "%s".', $transitionName, $this->name));
|
|
|
|
}
|
2016-11-09 16:43:55 +00:00
|
|
|
|
2016-03-25 15:43:30 +00:00
|
|
|
return $marking;
|
2013-12-01 09:16:07 +00:00
|
|
|
}
|
|
|
|
|
2016-03-25 15:43:30 +00:00
|
|
|
/**
|
|
|
|
* Returns all enabled transitions.
|
|
|
|
*
|
|
|
|
* @param object $subject A subject
|
|
|
|
*
|
|
|
|
* @return Transition[] All enabled transitions
|
|
|
|
*/
|
|
|
|
public function getEnabledTransitions($subject)
|
2013-12-01 09:16:07 +00:00
|
|
|
{
|
2016-03-25 15:43:30 +00:00
|
|
|
$enabled = array();
|
|
|
|
$marking = $this->getMarking($subject);
|
2013-12-01 09:16:07 +00:00
|
|
|
|
2016-03-25 15:43:30 +00:00
|
|
|
foreach ($this->definition->getTransitions() as $transition) {
|
2017-01-13 14:53:55 +00:00
|
|
|
if ($this->doCan($subject, $marking, $transition)) {
|
2016-08-16 12:13:16 +01:00
|
|
|
$enabled[] = $transition;
|
2013-12-01 09:16:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-25 15:43:30 +00:00
|
|
|
return $enabled;
|
2013-12-01 09:16:07 +00:00
|
|
|
}
|
|
|
|
|
2016-03-25 15:43:30 +00:00
|
|
|
public function getName()
|
2013-12-01 09:16:07 +00:00
|
|
|
{
|
2016-03-25 15:43:30 +00:00
|
|
|
return $this->name;
|
2013-12-01 09:16:07 +00:00
|
|
|
}
|
|
|
|
|
2016-11-08 19:01:05 +00:00
|
|
|
/**
|
|
|
|
* @return Definition
|
|
|
|
*/
|
|
|
|
public function getDefinition()
|
|
|
|
{
|
|
|
|
return $this->definition;
|
|
|
|
}
|
|
|
|
|
2017-07-28 10:47:13 +01:00
|
|
|
/**
|
|
|
|
* @return MarkingStoreInterface
|
|
|
|
*/
|
|
|
|
public function getMarkingStore()
|
|
|
|
{
|
|
|
|
return $this->markingStore;
|
|
|
|
}
|
|
|
|
|
2017-01-13 14:53:55 +00:00
|
|
|
private function doCan($subject, Marking $marking, Transition $transition)
|
|
|
|
{
|
|
|
|
foreach ($transition->getFroms() as $place) {
|
|
|
|
if (!$marking->has($place)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (true === $this->guardTransition($subject, $marking, $transition)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-08-16 12:13:16 +01:00
|
|
|
/**
|
|
|
|
* @param object $subject
|
|
|
|
* @param Marking $marking
|
|
|
|
* @param Transition $transition
|
|
|
|
*
|
2016-11-07 17:57:54 +00:00
|
|
|
* @return bool|void boolean true if this transition is guarded, ie you cannot use it
|
2016-08-16 12:13:16 +01:00
|
|
|
*/
|
2016-03-25 15:43:30 +00:00
|
|
|
private function guardTransition($subject, Marking $marking, Transition $transition)
|
2013-12-01 09:16:07 +00:00
|
|
|
{
|
|
|
|
if (null === $this->dispatcher) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-03-08 10:41:18 +00:00
|
|
|
$event = new GuardEvent($subject, $marking, $transition, $this->name);
|
2016-03-25 15:43:30 +00:00
|
|
|
|
|
|
|
$this->dispatcher->dispatch('workflow.guard', $event);
|
|
|
|
$this->dispatcher->dispatch(sprintf('workflow.%s.guard', $this->name), $event);
|
|
|
|
$this->dispatcher->dispatch(sprintf('workflow.%s.guard.%s', $this->name, $transition->getName()), $event);
|
|
|
|
|
|
|
|
return $event->isBlocked();
|
2013-12-01 09:16:07 +00:00
|
|
|
}
|
|
|
|
|
2016-03-25 15:43:30 +00:00
|
|
|
private function leave($subject, Transition $transition, Marking $marking)
|
2013-12-01 09:16:07 +00:00
|
|
|
{
|
2017-03-05 20:06:20 +00:00
|
|
|
$places = $transition->getFroms();
|
|
|
|
|
2013-12-01 09:16:07 +00:00
|
|
|
if (null !== $this->dispatcher) {
|
2017-03-08 10:41:18 +00:00
|
|
|
$event = new Event($subject, $marking, $transition, $this->name);
|
2013-12-01 09:16:07 +00:00
|
|
|
|
2016-03-25 15:43:30 +00:00
|
|
|
$this->dispatcher->dispatch('workflow.leave', $event);
|
|
|
|
$this->dispatcher->dispatch(sprintf('workflow.%s.leave', $this->name), $event);
|
|
|
|
|
2017-03-05 20:06:20 +00:00
|
|
|
foreach ($places as $place) {
|
2016-03-25 15:43:30 +00:00
|
|
|
$this->dispatcher->dispatch(sprintf('workflow.%s.leave.%s', $this->name, $place), $event);
|
2013-12-01 09:16:07 +00:00
|
|
|
}
|
2016-03-25 15:43:30 +00:00
|
|
|
}
|
2017-03-05 20:06:20 +00:00
|
|
|
|
|
|
|
foreach ($places as $place) {
|
|
|
|
$marking->unmark($place);
|
|
|
|
}
|
2016-03-25 15:43:30 +00:00
|
|
|
}
|
2013-12-01 09:16:07 +00:00
|
|
|
|
2016-03-25 15:43:30 +00:00
|
|
|
private function transition($subject, Transition $transition, Marking $marking)
|
|
|
|
{
|
|
|
|
if (null === $this->dispatcher) {
|
|
|
|
return;
|
2013-12-01 09:16:07 +00:00
|
|
|
}
|
|
|
|
|
2017-03-08 10:41:18 +00:00
|
|
|
$event = new Event($subject, $marking, $transition, $this->name);
|
2016-03-25 15:43:30 +00:00
|
|
|
|
|
|
|
$this->dispatcher->dispatch('workflow.transition', $event);
|
|
|
|
$this->dispatcher->dispatch(sprintf('workflow.%s.transition', $this->name), $event);
|
|
|
|
$this->dispatcher->dispatch(sprintf('workflow.%s.transition.%s', $this->name, $transition->getName()), $event);
|
2013-12-01 09:16:07 +00:00
|
|
|
}
|
|
|
|
|
2016-03-25 15:43:30 +00:00
|
|
|
private function enter($subject, Transition $transition, Marking $marking)
|
2013-12-01 09:16:07 +00:00
|
|
|
{
|
2017-03-05 20:06:20 +00:00
|
|
|
$places = $transition->getTos();
|
|
|
|
|
2013-12-01 09:16:07 +00:00
|
|
|
if (null !== $this->dispatcher) {
|
2017-03-08 10:41:18 +00:00
|
|
|
$event = new Event($subject, $marking, $transition, $this->name);
|
2016-03-25 15:43:30 +00:00
|
|
|
|
|
|
|
$this->dispatcher->dispatch('workflow.enter', $event);
|
|
|
|
$this->dispatcher->dispatch(sprintf('workflow.%s.enter', $this->name), $event);
|
|
|
|
|
2017-03-05 20:06:20 +00:00
|
|
|
foreach ($places as $place) {
|
2016-03-25 15:43:30 +00:00
|
|
|
$this->dispatcher->dispatch(sprintf('workflow.%s.enter.%s', $this->name, $place), $event);
|
|
|
|
}
|
2013-12-01 09:16:07 +00:00
|
|
|
}
|
2017-03-05 20:06:20 +00:00
|
|
|
|
|
|
|
foreach ($places as $place) {
|
|
|
|
$marking->mark($place);
|
|
|
|
}
|
2013-12-01 09:16:07 +00:00
|
|
|
}
|
|
|
|
|
2016-12-06 15:23:39 +00:00
|
|
|
private function entered($subject, Transition $transition, Marking $marking)
|
|
|
|
{
|
|
|
|
if (null === $this->dispatcher) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-03-08 10:41:18 +00:00
|
|
|
$event = new Event($subject, $marking, $transition, $this->name);
|
2016-12-06 15:23:39 +00:00
|
|
|
|
|
|
|
$this->dispatcher->dispatch('workflow.entered', $event);
|
|
|
|
$this->dispatcher->dispatch(sprintf('workflow.%s.entered', $this->name), $event);
|
|
|
|
|
|
|
|
foreach ($transition->getTos() as $place) {
|
|
|
|
$this->dispatcher->dispatch(sprintf('workflow.%s.entered.%s', $this->name, $place), $event);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-30 16:11:37 +01:00
|
|
|
private function completed($subject, Transition $transition, Marking $marking)
|
|
|
|
{
|
|
|
|
if (null === $this->dispatcher) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$event = new Event($subject, $marking, $transition, $this->name);
|
|
|
|
|
|
|
|
$this->dispatcher->dispatch('workflow.completed', $event);
|
|
|
|
$this->dispatcher->dispatch(sprintf('workflow.%s.completed', $this->name), $event);
|
|
|
|
$this->dispatcher->dispatch(sprintf('workflow.%s.completed.%s', $this->name, $transition->getName()), $event);
|
|
|
|
}
|
|
|
|
|
2016-03-25 15:43:30 +00:00
|
|
|
private function announce($subject, Transition $initialTransition, Marking $marking)
|
2013-12-01 09:16:07 +00:00
|
|
|
{
|
2016-03-25 15:43:30 +00:00
|
|
|
if (null === $this->dispatcher) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-07-19 15:45:25 +01:00
|
|
|
$event = new Event($subject, $marking, $initialTransition, $this->name);
|
2016-03-25 15:43:30 +00:00
|
|
|
|
2017-06-25 19:04:17 +01:00
|
|
|
$this->dispatcher->dispatch('workflow.announce', $event);
|
|
|
|
$this->dispatcher->dispatch(sprintf('workflow.%s.announce', $this->name), $event);
|
|
|
|
|
2017-01-13 14:53:55 +00:00
|
|
|
foreach ($this->getEnabledTransitions($subject) as $transition) {
|
|
|
|
$this->dispatcher->dispatch(sprintf('workflow.%s.announce.%s', $this->name, $transition->getName()), $event);
|
2016-08-16 12:13:16 +01:00
|
|
|
}
|
|
|
|
}
|
2013-12-01 09:16:07 +00:00
|
|
|
}
|