This repository has been archived on 2023-08-20. You can view files and clone it, but cannot push or open issues or pull requests.
symfony/src/Symfony/Component/Workflow/Workflow.php

311 lines
9.6 KiB
PHP
Raw Normal View History

<?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;
use Symfony\Component\Workflow\MarkingStore\MultipleStateMarkingStore;
/**
* @author Fabien Potencier <fabien@symfony.com>
2016-03-25 15:43:30 +00:00
* @author Grégoire Pineau <lyrixx@lyrixx.info>
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
class Workflow
{
2016-03-25 15:43:30 +00:00
private $definition;
private $markingStore;
private $dispatcher;
2016-03-25 15:43:30 +00:00
private $name;
public function __construct(Definition $definition, MarkingStoreInterface $markingStore = null, EventDispatcherInterface $dispatcher = null, $name = 'unnamed')
{
2016-03-25 15:43:30 +00:00
$this->definition = $definition;
$this->markingStore = $markingStore ?: new MultipleStateMarkingStore();
$this->dispatcher = $dispatcher;
2016-03-25 15:43:30 +00:00
$this->name = $name;
}
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)
{
2016-03-25 15:43:30 +00:00
$marking = $this->markingStore->getMarking($subject);
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));
}
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());
// update the subject with the new marking
$this->markingStore->setMarking($subject, $marking);
2016-03-25 15:43:30 +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.';
}
2016-03-25 15:43:30 +00:00
throw new LogicException($message);
}
}
2016-03-25 15:43:30 +00:00
return $marking;
}
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)
{
$transitions = $this->getEnabledTransitions($subject);
foreach ($transitions as $transition) {
if ($transitionName === $transition->getName()) {
return true;
}
}
return false;
}
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)
{
$transitions = $this->getEnabledTransitions($subject);
// 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);
$applied = false;
foreach ($transitions as $transition) {
if ($transitionName !== $transition->getName()) {
continue;
}
$applied = true;
2016-03-25 15:43:30 +00:00
$this->leave($subject, $transition, $marking);
2016-03-25 15:43:30 +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);
$this->announce($subject, $transition, $marking);
}
if (!$applied) {
throw new LogicException(sprintf('Unable to apply transition "%s" for workflow "%s".', $transitionName, $this->name));
}
2016-03-25 15:43:30 +00:00
return $marking;
}
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)
{
2016-03-25 15:43:30 +00:00
$enabled = array();
$marking = $this->getMarking($subject);
2016-03-25 15:43:30 +00:00
foreach ($this->definition->getTransitions() as $transition) {
if ($this->doCan($subject, $marking, $transition)) {
$enabled[] = $transition;
}
}
2016-03-25 15:43:30 +00:00
return $enabled;
}
2016-03-25 15:43:30 +00:00
public function getName()
{
2016-03-25 15:43:30 +00:00
return $this->name;
}
2016-11-08 19:01:05 +00:00
/**
* @return Definition
*/
public function getDefinition()
{
return $this->definition;
}
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;
}
/**
* @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)
{
if (null === $this->dispatcher) {
return;
}
$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();
}
2016-03-25 15:43:30 +00:00
private function leave($subject, Transition $transition, Marking $marking)
{
$places = $transition->getFroms();
if (null !== $this->dispatcher) {
$event = new Event($subject, $marking, $transition, $this->name);
2016-03-25 15:43:30 +00:00
$this->dispatcher->dispatch('workflow.leave', $event);
$this->dispatcher->dispatch(sprintf('workflow.%s.leave', $this->name), $event);
foreach ($places as $place) {
2016-03-25 15:43:30 +00:00
$this->dispatcher->dispatch(sprintf('workflow.%s.leave.%s', $this->name, $place), $event);
}
2016-03-25 15:43:30 +00:00
}
foreach ($places as $place) {
$marking->unmark($place);
}
2016-03-25 15:43:30 +00:00
}
2016-03-25 15:43:30 +00:00
private function transition($subject, Transition $transition, Marking $marking)
{
if (null === $this->dispatcher) {
return;
}
$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);
}
2016-03-25 15:43:30 +00:00
private function enter($subject, Transition $transition, Marking $marking)
{
$places = $transition->getTos();
if (null !== $this->dispatcher) {
$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);
foreach ($places as $place) {
2016-03-25 15:43:30 +00:00
$this->dispatcher->dispatch(sprintf('workflow.%s.enter.%s', $this->name, $place), $event);
}
}
foreach ($places as $place) {
$marking->mark($place);
}
}
2016-12-06 15:23:39 +00:00
private function entered($subject, Transition $transition, Marking $marking)
{
if (null === $this->dispatcher) {
return;
}
$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);
}
}
2016-03-25 15:43:30 +00:00
private function announce($subject, Transition $initialTransition, Marking $marking)
{
2016-03-25 15:43:30 +00:00
if (null === $this->dispatcher) {
return;
}
$event = new Event($subject, $marking, $initialTransition);
$this->dispatcher->dispatch('workflow.announce', $event);
$this->dispatcher->dispatch(sprintf('workflow.%s.announce', $this->name), $event);
foreach ($this->getEnabledTransitions($subject) as $transition) {
$this->dispatcher->dispatch(sprintf('workflow.%s.announce.%s', $this->name, $transition->getName()), $event);
}
}
}