Skip to content

Commit 59c2f8b

Browse files
committed
[Workflow] Cleaned the transition blocker implementations
1 parent 4d10e10 commit 59c2f8b

File tree

9 files changed

+246
-332
lines changed

9 files changed

+246
-332
lines changed

src/Symfony/Component/Workflow/Event/GuardEvent.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,18 @@ public function __construct($subject, Marking $marking, Transition $transition,
3636

3737
public function isBlocked(): bool
3838
{
39-
return 0 !== count($this->transitionBlockerList);
39+
return !$this->transitionBlockerList->isEmpty();
4040
}
4141

4242
public function setBlocked(bool $blocked): void
4343
{
4444
if (!$blocked) {
45-
$this->transitionBlockerList = new TransitionBlockerList();
45+
$this->transitionBlockerList->reset();
4646

4747
return;
4848
}
4949

50-
$this->transitionBlockerList->add(TransitionBlocker::createUnknownReason($this->getTransition()->getName()));
50+
$this->transitionBlockerList->add(TransitionBlocker::createUnknown());
5151
}
5252

5353
public function getTransitionBlockerList(): TransitionBlockerList

src/Symfony/Component/Workflow/EventListener/GuardListener.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Symfony\Component\Validator\Validator\ValidatorInterface;
1919
use Symfony\Component\Workflow\Event\GuardEvent;
2020
use Symfony\Component\Workflow\Exception\InvalidTokenConfigurationException;
21+
use Symfony\Component\Workflow\TransitionBlocker;
2122

2223
/**
2324
* @author Grégoire Pineau <lyrixx@lyrixx.info>
@@ -49,8 +50,11 @@ public function onTransition(GuardEvent $event, $eventName)
4950
return;
5051
}
5152

52-
if (!$this->expressionLanguage->evaluate($this->configuration[$eventName], $this->getVariables($event))) {
53-
$event->setBlocked(true);
53+
$expression = $this->configuration[$eventName];
54+
55+
if (!$this->expressionLanguage->evaluate($expression, $this->getVariables($event))) {
56+
$blocker = TransitionBlocker::createBlockedByExpressionGuardListener($expression);
57+
$event->addTransitionBlocker($blocker);
5458
}
5559
}
5660

src/Symfony/Component/Workflow/Exception/BlockedTransitionException.php renamed to src/Symfony/Component/Workflow/Exception/NotEnabledTransitionException.php

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,17 @@
1414
use Symfony\Component\Workflow\TransitionBlockerList;
1515

1616
/**
17-
* Thrown by the workflow when a transition is not enabled.
17+
* Thrown by Workflow when a not enabled transition is applied on a subject.
18+
*
19+
* @author Grégoire Pineau <lyrixx@lyrixx.info>
1820
*/
19-
class BlockedTransitionException extends LogicException
21+
class NotEnabledTransitionException extends LogicException
2022
{
2123
private $transitionBlockerList;
2224

23-
public function __construct(string $message, TransitionBlockerList $transitionBlockerList)
25+
public function __construct(string $transitionName, string $workflowName, TransitionBlockerList $transitionBlockerList)
2426
{
25-
parent::__construct($message);
27+
parent::__construct(sprintf('The transition "%s" is not enabled for the workflow "%s".', $transitionName, $workflowName));
2628

2729
$this->transitionBlockerList = $transitionBlockerList;
2830
}

src/Symfony/Component/Workflow/Exception/UndefinedTransitionException.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,13 @@
1313

1414
/**
1515
* Thrown by Workflow when an undefined transition is applied on a subject.
16+
*
17+
* @author Grégoire Pineau <lyrixx@lyrixx.info>
1618
*/
1719
class UndefinedTransitionException extends LogicException
1820
{
21+
public function __construct(string $transitionName, string $workflowName)
22+
{
23+
parent::__construct(sprintf('The transition "%s" is not defined for the workflow "%s".', $transitionName, $workflowName));
24+
}
1925
}

src/Symfony/Component/Workflow/Tests/WorkflowTest.php

Lines changed: 97 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
use Symfony\Component\Workflow\Definition;
88
use Symfony\Component\Workflow\Event\Event;
99
use Symfony\Component\Workflow\Event\GuardEvent;
10-
use Symfony\Component\Workflow\Exception\BlockedTransitionException;
10+
use Symfony\Component\Workflow\Exception\NotEnabledTransitionException;
11+
use Symfony\Component\Workflow\Exception\UndefinedTransitionException;
1112
use Symfony\Component\Workflow\Marking;
1213
use Symfony\Component\Workflow\MarkingStore\MarkingStoreInterface;
1314
use Symfony\Component\Workflow\MarkingStore\MultipleStateMarkingStore;
@@ -164,20 +165,6 @@ public function testCanDoesNotTriggerGuardEventsForNotEnabledTransitions()
164165
$this->assertSame(array('workflow_name.guard.t3'), $dispatchedEvents);
165166
}
166167

167-
/**
168-
* @expectedException \Symfony\Component\Workflow\Exception\LogicException
169-
* @expectedExceptionMessage Unable to apply transition "t2" for workflow "unnamed".
170-
*/
171-
public function testApplyWithImpossibleTransition()
172-
{
173-
$definition = $this->createComplexWorkflowDefinition();
174-
$subject = new \stdClass();
175-
$subject->marking = null;
176-
$workflow = new Workflow($definition, new MultipleStateMarkingStore());
177-
178-
$workflow->apply($subject, 't2');
179-
}
180-
181168
public function testCanWithSameNameTransition()
182169
{
183170
$definition = $this->createWorkflowWithSameNameTransition();
@@ -195,6 +182,101 @@ public function testCanWithSameNameTransition()
195182
$this->assertTrue($workflow->can($subject, 'to_a'));
196183
}
197184

185+
public function testBuildTransitionBlockerListReturnsUndefinedTransition()
186+
{
187+
$definition = $this->createSimpleWorkflowDefinition();
188+
$subject = new \stdClass();
189+
$subject->marking = null;
190+
$workflow = new Workflow($definition);
191+
192+
try {
193+
$workflow->buildTransitionBlockerList($subject, 'not defined');
194+
195+
$this->fail('Workflow failed to throw undefined transition exception.');
196+
} catch (UndefinedTransitionException $e) {
197+
$this->assertSame('The transition "not defined" is not defined for the workflow "unnamed".', $e->getMessage());
198+
}
199+
}
200+
201+
public function testBuildTransitionBlockerListReturnsReasonsProvidedByMarking()
202+
{
203+
$definition = $this->createComplexWorkflowDefinition();
204+
$subject = new \stdClass();
205+
$subject->marking = null;
206+
$workflow = new Workflow($definition, new MultipleStateMarkingStore());
207+
208+
$transitionBlockerList = $workflow->buildTransitionBlockerList($subject, 't2');
209+
$this->assertCount(1, $transitionBlockerList);
210+
$blockers = iterator_to_array($transitionBlockerList);
211+
$this->assertSame('The marking does not enable the transition.', $blockers[0]->getMessage());
212+
$this->assertSame('19beefc8-6b1e-4716-9d07-a39bd6d16e34', $blockers[0]->getCode());
213+
}
214+
215+
public function testBuildTransitionBlockerListReturnsReasonsProvidedInGuards()
216+
{
217+
$definition = $this->createSimpleWorkflowDefinition();
218+
$subject = new \stdClass();
219+
$subject->marking = null;
220+
$dispatcher = new EventDispatcher();
221+
$workflow = new Workflow($definition, new MultipleStateMarkingStore(), $dispatcher);
222+
223+
$dispatcher->addListener('workflow.guard', function (GuardEvent $event) {
224+
$event->addTransitionBlocker(new TransitionBlocker('Transition blocker 1', 'blocker_1'));
225+
$event->addTransitionBlocker(new TransitionBlocker('Transition blocker 2', 'blocker_2'));
226+
});
227+
$dispatcher->addListener('workflow.guard', function (GuardEvent $event) {
228+
$event->addTransitionBlocker(new TransitionBlocker('Transition blocker 3', 'blocker_3'));
229+
});
230+
$dispatcher->addListener('workflow.guard', function (GuardEvent $event) {
231+
$event->setBlocked(true);
232+
});
233+
234+
$transitionBlockerList = $workflow->buildTransitionBlockerList($subject, 't1');
235+
$this->assertCount(4, $transitionBlockerList);
236+
$blockers = iterator_to_array($transitionBlockerList);
237+
$this->assertSame('Transition blocker 1', $blockers[0]->getMessage());
238+
$this->assertSame('blocker_1', $blockers[0]->getCode());
239+
$this->assertSame('Transition blocker 2', $blockers[1]->getMessage());
240+
$this->assertSame('blocker_2', $blockers[1]->getCode());
241+
$this->assertSame('Transition blocker 3', $blockers[2]->getMessage());
242+
$this->assertSame('blocker_3', $blockers[2]->getCode());
243+
$this->assertSame('Unknown reason.', $blockers[3]->getMessage());
244+
$this->assertSame('e8b5bbb9-5913-4b98-bfa6-65dbd228a82a', $blockers[3]->getCode());
245+
}
246+
247+
/**
248+
* @expectedException \Symfony\Component\Workflow\Exception\UndefinedTransitionException
249+
* @expectedExceptionMessage The transition "404 Not Found" is not defined for the workflow "unnamed".
250+
*/
251+
public function testApplyWithNotExisingTransition()
252+
{
253+
$definition = $this->createComplexWorkflowDefinition();
254+
$subject = new \stdClass();
255+
$subject->marking = null;
256+
$workflow = new Workflow($definition, new MultipleStateMarkingStore());
257+
258+
$workflow->apply($subject, '404 Not Found');
259+
}
260+
261+
public function testApplyWithNotEnabledTransition()
262+
{
263+
$definition = $this->createComplexWorkflowDefinition();
264+
$subject = new \stdClass();
265+
$subject->marking = null;
266+
$workflow = new Workflow($definition, new MultipleStateMarkingStore());
267+
268+
try {
269+
$workflow->apply($subject, 't2');
270+
271+
$this->fail('Should throw an exception');
272+
} catch (NotEnabledTransitionException $e) {
273+
$this->assertSame('The transition "t2" is not enabled for the workflow "unnamed".', $e->getMessage());
274+
$this->assertCount(1, $e->getTransitionBlockerList());
275+
$list = iterator_to_array($e->getTransitionBlockerList());
276+
$this->assertSame('The marking does not enable the transition.', $list[0]->getMessage());
277+
}
278+
}
279+
198280
public function testApply()
199281
{
200282
$definition = $this->createComplexWorkflowDefinition();
@@ -413,116 +495,6 @@ public function testGetEnabledTransitionsWithSameNameTransition()
413495
$this->assertSame('to_a', $transitions[1]->getName());
414496
$this->assertSame('to_a', $transitions[2]->getName());
415497
}
416-
417-
public function testWhyCannotReturnsReasonsProvidedInGuards()
418-
{
419-
$definition = $this->createSimpleWorkflowDefinition();
420-
$subject = new \stdClass();
421-
$subject->marking = null;
422-
$dispatcher = new EventDispatcher();
423-
$workflow = new Workflow($definition, new MultipleStateMarkingStore(), $dispatcher);
424-
425-
$guardsAddingTransitionBlockers = array(
426-
function (GuardEvent $event) {
427-
$event->addTransitionBlocker(new TransitionBlocker('Transition blocker 1', 'blocker_1'));
428-
$event->addTransitionBlocker(new TransitionBlocker('Transition blocker 2', 'blocker_2'));
429-
},
430-
function (GuardEvent $event) {
431-
$event->addTransitionBlocker(new TransitionBlocker('Transition blocker 3', 'blocker_3'));
432-
},
433-
);
434-
435-
foreach ($guardsAddingTransitionBlockers as $guard) {
436-
$dispatcher->addListener('workflow.guard', $guard);
437-
}
438-
439-
$transitionBlockerList = $workflow->buildTransitionBlockerList($subject, 't1');
440-
441-
$this->assertCount(3, $transitionBlockerList);
442-
443-
$assertTransitionBlockerPresentByCodeFn = function (string $code) use ($transitionBlockerList) {
444-
$this->assertNotNull(
445-
$transitionBlockerList->findByCode($code),
446-
sprintf('Workflow did not produce transition blocker with code "%s"', $code)
447-
);
448-
};
449-
450-
$assertTransitionBlockerPresentByCodeFn('blocker_1');
451-
$assertTransitionBlockerPresentByCodeFn('blocker_2');
452-
$assertTransitionBlockerPresentByCodeFn('blocker_3');
453-
}
454-
455-
public function testWhyCannotReturnsTransitionNotDefinedReason()
456-
{
457-
$definition = $this->createSimpleWorkflowDefinition();
458-
$subject = new \stdClass();
459-
$subject->marking = null;
460-
$workflow = new Workflow($definition);
461-
462-
$transitionBlockerList = $workflow->buildTransitionBlockerList($subject, 'undefined_transition_name');
463-
464-
$this->assertCount(1, $transitionBlockerList);
465-
$this->assertEquals(
466-
TransitionBlocker::REASON_TRANSITION_NOT_DEFINED,
467-
$transitionBlockerList->get(0)->getCode()
468-
);
469-
}
470-
471-
public function testWhyCannotReturnsTransitionNotApplicableReason()
472-
{
473-
$definition = $this->createSimpleWorkflowDefinition();
474-
$subject = new \stdClass();
475-
$subject->marking = null;
476-
$workflow = new Workflow($definition);
477-
478-
$transitionBlockerList = $workflow->buildTransitionBlockerList($subject, 't2');
479-
480-
$this->assertCount(1, $transitionBlockerList);
481-
$this->assertEquals(
482-
TransitionBlocker::REASON_TRANSITION_NOT_APPLICABLE,
483-
$transitionBlockerList->get(0)->getCode()
484-
);
485-
}
486-
487-
public function testApplyConveysTheTransitionBlockers()
488-
{
489-
$definition = $this->createSimpleWorkflowDefinition();
490-
$subject = new \stdClass();
491-
$subject->marking = null;
492-
$dispatcher = new EventDispatcher();
493-
$workflow = new Workflow($definition, new MultipleStateMarkingStore(), $dispatcher);
494-
495-
$dispatcher->addListener('workflow.guard', function (GuardEvent $event) {
496-
$event->addTransitionBlocker(new TransitionBlocker('Transition blocker 3', 'blocker_1'));
497-
});
498-
499-
try {
500-
$workflow->apply($subject, 't1');
501-
} catch (BlockedTransitionException $exception) {
502-
$this->assertNotNull(
503-
$exception->getTransitionBlockerList()->findByCode('blocker_1'),
504-
'Workflow failed to convey it could not transition subject because of expected blocker'
505-
);
506-
507-
return;
508-
}
509-
510-
$this->fail('Workflow failed to prevent a transition from happening');
511-
}
512-
513-
/**
514-
* @expectedException \Symfony\Component\Workflow\Exception\UndefinedTransitionException
515-
* @expectedExceptionMessage Transition "undefined_transition" is not defined in workflow "unnamed".
516-
*/
517-
public function testApplyWithUndefinedTransition()
518-
{
519-
$definition = $this->createSimpleWorkflowDefinition();
520-
$subject = new \stdClass();
521-
$subject->marking = null;
522-
$workflow = new Workflow($definition);
523-
524-
$workflow->apply($subject, 'undefined_transition');
525-
}
526498
}
527499

528500
class EventDispatcherMock implements \Symfony\Component\EventDispatcher\EventDispatcherInterface

0 commit comments

Comments
 (0)