diff --git a/src/Symfony/Component/Workflow/Tests/StateMachineTest.php b/src/Symfony/Component/Workflow/Tests/StateMachineTest.php index 9224f7cb129d9..a6c7362f79568 100644 --- a/src/Symfony/Component/Workflow/Tests/StateMachineTest.php +++ b/src/Symfony/Component/Workflow/Tests/StateMachineTest.php @@ -5,6 +5,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\Workflow\Event\GuardEvent; +use Symfony\Component\Workflow\Exception\NotEnabledTransitionException; use Symfony\Component\Workflow\StateMachine; use Symfony\Component\Workflow\TransitionBlocker; @@ -84,27 +85,52 @@ public function testBuildTransitionBlockerListReturnsExpectedReasonOnBranchMerge $subject = new Subject(); // There may be multiple transitions with the same name. Make sure that transitions - // that are not enabled by the marking are evaluated. + // that are enabled by the marking are evaluated. // see https://github.com/symfony/symfony/issues/28432 - // Test if when you are in place "a"trying transition "t1" then returned + // Test if when you are in place "a" and trying to apply "t1" then it returns // blocker list contains guard blocker instead blockedByMarking $subject->setMarking('a'); $transitionBlockerList = $net->buildTransitionBlockerList($subject, 't1'); $this->assertCount(1, $transitionBlockerList); $blockers = iterator_to_array($transitionBlockerList); - $this->assertSame('Transition blocker of place a', $blockers[0]->getMessage()); $this->assertSame('blocker', $blockers[0]->getCode()); - // Test if when you are in place "d" trying transition "t1" then - // returned blocker list contains guard blocker instead blockedByMarking + // Test if when you are in place "d" and trying to apply "t1" then + // it returns blocker list contains guard blocker instead blockedByMarking $subject->setMarking('d'); $transitionBlockerList = $net->buildTransitionBlockerList($subject, 't1'); $this->assertCount(1, $transitionBlockerList); $blockers = iterator_to_array($transitionBlockerList); - $this->assertSame('Transition blocker of place d', $blockers[0]->getMessage()); $this->assertSame('blocker', $blockers[0]->getCode()); } + + public function testApplyReturnsExpectedReasonOnBranchMerge() + { + $definition = $this->createComplexStateMachineDefinition(); + + $dispatcher = new EventDispatcher(); + $net = new StateMachine($definition, null, $dispatcher); + + $dispatcher->addListener('workflow.guard', function (GuardEvent $event) { + $event->addTransitionBlocker(new TransitionBlocker(sprintf('Transition blocker of place %s', $event->getTransition()->getFroms()[0]), 'blocker')); + }); + + $subject = new Subject(); + + // There may be multiple transitions with the same name. Make sure that all transitions + // that are enabled by the marking are evaluated. + // see https://github.com/symfony/symfony/issues/34489 + + try { + $net->apply($subject, 't1'); + $this->fail(); + } catch (NotEnabledTransitionException $e) { + $blockers = iterator_to_array($e->getTransitionBlockerList()); + $this->assertSame('Transition blocker of place a', $blockers[0]->getMessage()); + $this->assertSame('blocker', $blockers[0]->getCode()); + } + } } diff --git a/src/Symfony/Component/Workflow/Workflow.php b/src/Symfony/Component/Workflow/Workflow.php index 2d56bd9ee5087..53fef69274acc 100644 --- a/src/Symfony/Component/Workflow/Workflow.php +++ b/src/Symfony/Component/Workflow/Workflow.php @@ -159,25 +159,47 @@ public function apply($subject, $transitionName/*, array $context = []*/) $marking = $this->getMarking($subject); - $transitionBlockerList = null; - $applied = false; - $approvedTransitionQueue = []; + $transitionExist = false; + $approvedTransitions = []; + $bestTransitionBlockerList = null; foreach ($this->definition->getTransitions() as $transition) { if ($transition->getName() !== $transitionName) { continue; } - $transitionBlockerList = $this->buildTransitionBlockerListForTransition($subject, $marking, $transition); - if (!$transitionBlockerList->isEmpty()) { + $transitionExist = true; + + $tmpTransitionBlockerList = $this->buildTransitionBlockerListForTransition($subject, $marking, $transition); + + if ($tmpTransitionBlockerList->isEmpty()) { + $approvedTransitions[] = $transition; + continue; + } + + if (!$bestTransitionBlockerList) { + $bestTransitionBlockerList = $tmpTransitionBlockerList; continue; } - $approvedTransitionQueue[] = $transition; + + // We prefer to return transitions blocker by something else than + // marking. Because it means the marking was OK. Transitions are + // deterministic: it's not possible to have many transitions enabled + // at the same time that match the same marking with the same name + if (!$tmpTransitionBlockerList->has(TransitionBlocker::BLOCKED_BY_MARKING)) { + $bestTransitionBlockerList = $tmpTransitionBlockerList; + } + } + + if (!$transitionExist) { + throw new UndefinedTransitionException($subject, $transitionName, $this); } - foreach ($approvedTransitionQueue as $transition) { - $applied = true; + if (!$approvedTransitions) { + throw new NotEnabledTransitionException($subject, $transitionName, $this, $bestTransitionBlockerList); + } + foreach ($approvedTransitions as $transition) { $this->leave($subject, $transition, $marking); $context = $this->transition($subject, $transition, $marking, $context); @@ -193,14 +215,6 @@ public function apply($subject, $transitionName/*, array $context = []*/) $this->announce($subject, $transition, $marking); } - if (!$transitionBlockerList) { - throw new UndefinedTransitionException($subject, $transitionName, $this); - } - - if (!$applied) { - throw new NotEnabledTransitionException($subject, $transitionName, $this, $transitionBlockerList); - } - return $marking; }