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;
|
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;
|
|
|
|
|
|
|
|
public function __construct(Definition $definition, MarkingStoreInterface $markingStore, EventDispatcherInterface $dispatcher = null, $name = 'unnamed')
|
2013-12-01 09:16:07 +00:00
|
|
|
{
|
2016-03-25 15:43:30 +00:00
|
|
|
$this->definition = $definition;
|
|
|
|
$this->markingStore = $markingStore;
|
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());
|
|
|
|
}
|
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
|
|
|
// Because the marking could have been initialized, we update the subject
|
|
|
|
$this->markingStore->setMarking($subject, $marking);
|
|
|
|
|
|
|
|
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
|
|
|
|
*
|
|
|
|
* @throws LogicException If the transition does not exist
|
|
|
|
*/
|
|
|
|
public function can($subject, $transitionName)
|
2013-12-01 09:16:07 +00:00
|
|
|
{
|
2016-08-16 12:13:16 +01:00
|
|
|
$transitions = $this->getTransitions($transitionName);
|
2016-03-25 15:43:30 +00:00
|
|
|
$marking = $this->getMarking($subject);
|
2013-12-01 09:16:07 +00:00
|
|
|
|
2016-08-16 12:13:16 +01:00
|
|
|
return null !== $this->getTransitionForSubject($subject, $marking, $transitions);
|
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
|
|
|
{
|
2016-08-16 12:13:16 +01:00
|
|
|
$transitions = $this->getTransitions($transitionName);
|
|
|
|
$marking = $this->getMarking($subject);
|
|
|
|
|
|
|
|
if (null === $transition = $this->getTransitionForSubject($subject, $marking, $transitions)) {
|
2016-03-25 15:43:30 +00:00
|
|
|
throw new LogicException(sprintf('Unable to apply transition "%s" for workflow "%s".', $transitionName, $this->name));
|
2013-12-01 09:16:07 +00:00
|
|
|
}
|
|
|
|
|
2016-03-25 15:43:30 +00:00
|
|
|
$this->leave($subject, $transition, $marking);
|
2013-12-01 09:16:07 +00:00
|
|
|
|
2016-03-25 15:43:30 +00:00
|
|
|
$this->transition($subject, $transition, $marking);
|
2013-12-01 09:16:07 +00:00
|
|
|
|
2016-03-25 15:43:30 +00:00
|
|
|
$this->enter($subject, $transition, $marking);
|
|
|
|
|
|
|
|
$this->announce($subject, $transition, $marking);
|
|
|
|
|
|
|
|
$this->markingStore->setMarking($subject, $marking);
|
|
|
|
|
|
|
|
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) {
|
2016-08-16 12:13:16 +01:00
|
|
|
if (null !== $this->getTransitionForSubject($subject, $marking, array($transition))) {
|
|
|
|
$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-08-16 12:13:16 +01:00
|
|
|
/**
|
|
|
|
* @param object $subject
|
|
|
|
* @param Marking $marking
|
|
|
|
* @param Transition $transition
|
|
|
|
*
|
|
|
|
* @return bool|void boolean true if this transition is guarded, ie you cannot use it.
|
|
|
|
*/
|
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;
|
|
|
|
}
|
|
|
|
|
2016-03-25 15:43:30 +00:00
|
|
|
$event = new GuardEvent($subject, $marking, $transition);
|
|
|
|
|
|
|
|
$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
|
|
|
{
|
|
|
|
if (null !== $this->dispatcher) {
|
2016-03-25 15:43:30 +00:00
|
|
|
$event = new Event($subject, $marking, $transition);
|
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);
|
2013-12-01 09:16:07 +00:00
|
|
|
}
|
|
|
|
|
2016-03-25 15:43:30 +00:00
|
|
|
foreach ($transition->getFroms() as $place) {
|
|
|
|
$marking->unmark($place);
|
|
|
|
|
|
|
|
if (null !== $this->dispatcher) {
|
|
|
|
$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
|
|
|
}
|
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
2016-03-25 15:43:30 +00:00
|
|
|
$event = new Event($subject, $marking, $transition);
|
|
|
|
|
|
|
|
$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
|
|
|
{
|
|
|
|
if (null !== $this->dispatcher) {
|
2016-03-25 15:43:30 +00:00
|
|
|
$event = new Event($subject, $marking, $transition);
|
|
|
|
|
|
|
|
$this->dispatcher->dispatch('workflow.enter', $event);
|
|
|
|
$this->dispatcher->dispatch(sprintf('workflow.%s.enter', $this->name), $event);
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($transition->getTos() as $place) {
|
|
|
|
$marking->mark($place);
|
|
|
|
|
|
|
|
if (null !== $this->dispatcher) {
|
|
|
|
$this->dispatcher->dispatch(sprintf('workflow.%s.enter.%s', $this->name, $place), $event);
|
|
|
|
}
|
2013-12-01 09:16:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
$event = new Event($subject, $marking, $initialTransition);
|
|
|
|
|
|
|
|
foreach ($this->definition->getTransitions() as $transition) {
|
2016-08-16 12:13:16 +01:00
|
|
|
if (null !== $this->getTransitionForSubject($subject, $marking, array($transition))) {
|
2016-03-25 15:43:30 +00:00
|
|
|
$this->dispatcher->dispatch(sprintf('workflow.%s.announce.%s', $this->name, $transition->getName()), $event);
|
|
|
|
}
|
|
|
|
}
|
2013-12-01 09:16:07 +00:00
|
|
|
}
|
2016-08-16 12:13:16 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param $transitionName
|
|
|
|
*
|
|
|
|
* @return Transition[]
|
|
|
|
*/
|
|
|
|
private function getTransitions($transitionName)
|
|
|
|
{
|
|
|
|
$transitions = $this->definition->getTransitions();
|
|
|
|
|
|
|
|
$namedTransitions = array_filter(
|
|
|
|
$transitions,
|
|
|
|
function (Transition $transition) use ($transitionName) {
|
|
|
|
return $transitionName === $transition->getName();
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
if (empty($namedTransitions)) {
|
|
|
|
throw new LogicException(
|
|
|
|
sprintf('Transition "%s" does not exist for workflow "%s".', $transitionName, $this->name)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $namedTransitions;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the first Transition in $transitions that is valid for the $subject and $marking. null is returned when
|
|
|
|
* you cannot do any Transition in $transitions on the $subject.
|
|
|
|
*
|
|
|
|
* @param object $subject
|
|
|
|
* @param Marking $marking
|
|
|
|
* @param Transition[] $transitions
|
|
|
|
*
|
|
|
|
* @return Transition|null
|
|
|
|
*/
|
|
|
|
private function getTransitionForSubject($subject, Marking $marking, array $transitions)
|
|
|
|
{
|
|
|
|
foreach ($transitions as $transition) {
|
|
|
|
foreach ($transition->getFroms() as $place) {
|
|
|
|
if (!$marking->has($place)) {
|
|
|
|
continue 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (true !== $this->guardTransition($subject, $marking, $transition)) {
|
|
|
|
return $transition;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
2013-12-01 09:16:07 +00:00
|
|
|
}
|