/src/Symfony/Component/Workflow/Workflow.php

https://github.com/deviantintegral/symfony · PHP · 322 lines · 206 code · 67 blank · 49 comment · 20 complexity · 57123285fb1be2e8e492cb3b98e66d9b MD5 · raw file

  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Workflow;
  11. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  12. use Symfony\Component\Workflow\Event\Event;
  13. use Symfony\Component\Workflow\Event\GuardEvent;
  14. use Symfony\Component\Workflow\Exception\LogicException;
  15. use Symfony\Component\Workflow\MarkingStore\MarkingStoreInterface;
  16. use Symfony\Component\Workflow\MarkingStore\MultipleStateMarkingStore;
  17. /**
  18. * @author Fabien Potencier <fabien@symfony.com>
  19. * @author Grégoire Pineau <lyrixx@lyrixx.info>
  20. * @author Tobias Nyholm <tobias.nyholm@gmail.com>
  21. */
  22. class Workflow implements WorkflowInterface
  23. {
  24. private $definition;
  25. private $markingStore;
  26. private $dispatcher;
  27. private $name;
  28. public function __construct(Definition $definition, MarkingStoreInterface $markingStore = null, EventDispatcherInterface $dispatcher = null, string $name = 'unnamed')
  29. {
  30. $this->definition = $definition;
  31. $this->markingStore = $markingStore ?: new MultipleStateMarkingStore();
  32. $this->dispatcher = $dispatcher;
  33. $this->name = $name;
  34. }
  35. /**
  36. * {@inheritdoc}
  37. */
  38. public function getMarking($subject)
  39. {
  40. $marking = $this->markingStore->getMarking($subject);
  41. if (!$marking instanceof Marking) {
  42. throw new LogicException(sprintf('The value returned by the MarkingStore is not an instance of "%s" for workflow "%s".', Marking::class, $this->name));
  43. }
  44. // check if the subject is already in the workflow
  45. if (!$marking->getPlaces()) {
  46. if (!$this->definition->getInitialPlace()) {
  47. throw new LogicException(sprintf('The Marking is empty and there is no initial place for workflow "%s".', $this->name));
  48. }
  49. $marking->mark($this->definition->getInitialPlace());
  50. // update the subject with the new marking
  51. $this->markingStore->setMarking($subject, $marking);
  52. }
  53. // check that the subject has a known place
  54. $places = $this->definition->getPlaces();
  55. foreach ($marking->getPlaces() as $placeName => $nbToken) {
  56. if (!isset($places[$placeName])) {
  57. $message = sprintf('Place "%s" is not valid for workflow "%s".', $placeName, $this->name);
  58. if (!$places) {
  59. $message .= ' It seems you forgot to add places to the current workflow.';
  60. }
  61. throw new LogicException($message);
  62. }
  63. }
  64. return $marking;
  65. }
  66. /**
  67. * {@inheritdoc}
  68. */
  69. public function can($subject, $transitionName)
  70. {
  71. $transitions = $this->definition->getTransitions();
  72. $marking = $this->getMarking($subject);
  73. foreach ($transitions as $transition) {
  74. foreach ($transition->getFroms() as $place) {
  75. if (!$marking->has($place)) {
  76. // do not emit guard events for transitions where the marking does not contain
  77. // all "from places" (thus the transition couldn't be applied anyway)
  78. continue 2;
  79. }
  80. }
  81. if ($transitionName === $transition->getName() && $this->doCan($subject, $marking, $transition)) {
  82. return true;
  83. }
  84. }
  85. return false;
  86. }
  87. /**
  88. * {@inheritdoc}
  89. */
  90. public function apply($subject, $transitionName)
  91. {
  92. $transitions = $this->getEnabledTransitions($subject);
  93. // We can shortcut the getMarking method in order to boost performance,
  94. // since the "getEnabledTransitions" method already checks the Marking
  95. // state
  96. $marking = $this->markingStore->getMarking($subject);
  97. $applied = false;
  98. foreach ($transitions as $transition) {
  99. if ($transitionName !== $transition->getName()) {
  100. continue;
  101. }
  102. $applied = true;
  103. $this->leave($subject, $transition, $marking);
  104. $this->transition($subject, $transition, $marking);
  105. $this->enter($subject, $transition, $marking);
  106. $this->markingStore->setMarking($subject, $marking);
  107. $this->entered($subject, $transition, $marking);
  108. $this->completed($subject, $transition, $marking);
  109. $this->announce($subject, $transition, $marking);
  110. }
  111. if (!$applied) {
  112. throw new LogicException(sprintf('Unable to apply transition "%s" for workflow "%s".', $transitionName, $this->name));
  113. }
  114. return $marking;
  115. }
  116. /**
  117. * {@inheritdoc}
  118. */
  119. public function getEnabledTransitions($subject)
  120. {
  121. $enabled = array();
  122. $marking = $this->getMarking($subject);
  123. foreach ($this->definition->getTransitions() as $transition) {
  124. if ($this->doCan($subject, $marking, $transition)) {
  125. $enabled[] = $transition;
  126. }
  127. }
  128. return $enabled;
  129. }
  130. /**
  131. * {@inheritdoc}
  132. */
  133. public function getName()
  134. {
  135. return $this->name;
  136. }
  137. /**
  138. * {@inheritdoc}
  139. */
  140. public function getDefinition()
  141. {
  142. return $this->definition;
  143. }
  144. /**
  145. * {@inheritdoc}
  146. */
  147. public function getMarkingStore()
  148. {
  149. return $this->markingStore;
  150. }
  151. private function doCan($subject, Marking $marking, Transition $transition)
  152. {
  153. foreach ($transition->getFroms() as $place) {
  154. if (!$marking->has($place)) {
  155. return false;
  156. }
  157. }
  158. if (true === $this->guardTransition($subject, $marking, $transition)) {
  159. return false;
  160. }
  161. return true;
  162. }
  163. /**
  164. * @param object $subject
  165. * @param Marking $marking
  166. * @param Transition $transition
  167. *
  168. * @return bool|void boolean true if this transition is guarded, ie you cannot use it
  169. */
  170. private function guardTransition($subject, Marking $marking, Transition $transition)
  171. {
  172. if (null === $this->dispatcher) {
  173. return;
  174. }
  175. $event = new GuardEvent($subject, $marking, $transition, $this->name);
  176. $this->dispatcher->dispatch('workflow.guard', $event);
  177. $this->dispatcher->dispatch(sprintf('workflow.%s.guard', $this->name), $event);
  178. $this->dispatcher->dispatch(sprintf('workflow.%s.guard.%s', $this->name, $transition->getName()), $event);
  179. return $event->isBlocked();
  180. }
  181. private function leave($subject, Transition $transition, Marking $marking)
  182. {
  183. $places = $transition->getFroms();
  184. if (null !== $this->dispatcher) {
  185. $event = new Event($subject, $marking, $transition, $this->name);
  186. $this->dispatcher->dispatch('workflow.leave', $event);
  187. $this->dispatcher->dispatch(sprintf('workflow.%s.leave', $this->name), $event);
  188. foreach ($places as $place) {
  189. $this->dispatcher->dispatch(sprintf('workflow.%s.leave.%s', $this->name, $place), $event);
  190. }
  191. }
  192. foreach ($places as $place) {
  193. $marking->unmark($place);
  194. }
  195. }
  196. private function transition($subject, Transition $transition, Marking $marking)
  197. {
  198. if (null === $this->dispatcher) {
  199. return;
  200. }
  201. $event = new Event($subject, $marking, $transition, $this->name);
  202. $this->dispatcher->dispatch('workflow.transition', $event);
  203. $this->dispatcher->dispatch(sprintf('workflow.%s.transition', $this->name), $event);
  204. $this->dispatcher->dispatch(sprintf('workflow.%s.transition.%s', $this->name, $transition->getName()), $event);
  205. }
  206. private function enter($subject, Transition $transition, Marking $marking)
  207. {
  208. $places = $transition->getTos();
  209. if (null !== $this->dispatcher) {
  210. $event = new Event($subject, $marking, $transition, $this->name);
  211. $this->dispatcher->dispatch('workflow.enter', $event);
  212. $this->dispatcher->dispatch(sprintf('workflow.%s.enter', $this->name), $event);
  213. foreach ($places as $place) {
  214. $this->dispatcher->dispatch(sprintf('workflow.%s.enter.%s', $this->name, $place), $event);
  215. }
  216. }
  217. foreach ($places as $place) {
  218. $marking->mark($place);
  219. }
  220. }
  221. private function entered($subject, Transition $transition, Marking $marking)
  222. {
  223. if (null === $this->dispatcher) {
  224. return;
  225. }
  226. $event = new Event($subject, $marking, $transition, $this->name);
  227. $this->dispatcher->dispatch('workflow.entered', $event);
  228. $this->dispatcher->dispatch(sprintf('workflow.%s.entered', $this->name), $event);
  229. foreach ($transition->getTos() as $place) {
  230. $this->dispatcher->dispatch(sprintf('workflow.%s.entered.%s', $this->name, $place), $event);
  231. }
  232. }
  233. private function completed($subject, Transition $transition, Marking $marking)
  234. {
  235. if (null === $this->dispatcher) {
  236. return;
  237. }
  238. $event = new Event($subject, $marking, $transition, $this->name);
  239. $this->dispatcher->dispatch('workflow.completed', $event);
  240. $this->dispatcher->dispatch(sprintf('workflow.%s.completed', $this->name), $event);
  241. $this->dispatcher->dispatch(sprintf('workflow.%s.completed.%s', $this->name, $transition->getName()), $event);
  242. }
  243. private function announce($subject, Transition $initialTransition, Marking $marking)
  244. {
  245. if (null === $this->dispatcher) {
  246. return;
  247. }
  248. $event = new Event($subject, $marking, $initialTransition, $this->name);
  249. $this->dispatcher->dispatch('workflow.announce', $event);
  250. $this->dispatcher->dispatch(sprintf('workflow.%s.announce', $this->name), $event);
  251. foreach ($this->getEnabledTransitions($subject) as $transition) {
  252. $this->dispatcher->dispatch(sprintf('workflow.%s.announce.%s', $this->name, $transition->getName()), $event);
  253. }
  254. }
  255. }