8000 [FrameworkBundle] fixed guard event names for transitions · symfony/symfony@fb88bfc · GitHub
[go: up one dir, main page]

Skip to content

Commit fb88bfc

Browse files
destillatlyrixx
authored andcommitted
[FrameworkBundle] fixed guard event names for transitions
1 parent 6006448 commit fb88bfc

File tree

8 files changed

+315
-14
lines changed

8 files changed

+315
-14
lines changed

src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@
300300
<xsd:sequence>
301301
<xsd:element name="from" type="xsd:string" minOccurs="1" maxOccurs="unbounded" />
302302
<xsd:element name="to" type="xsd:string" minOccurs="1" maxOccurs="unbounded" />
303+
<xsd:element name="guard" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
303304
</xsd:sequence>
304305
<xsd:attribute name="name" type="xsd:string" use="required" />
305306
</xsd:complexType>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
use Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTest;
4+
5+
$container->loadFromExtension('framework', array(
6+
'workflows' => array(
7+
'article' => array(
8+
'type' => 'workflow',
9+
'marking_store' => array(
10+
'type' => 'multiple_state',
11+
),
12+
'supports' => array(
13+
FrameworkExtensionTest::class,
14+
),
15+
'initial_place' => 'draft',
16+
'places' => array(
17+
'draft',
18+
'wait_for_journalist',
19+
'approved_by_journalist',
20+
'wait_for_spellchecker',
21+
'approved_by_spellchecker',
22+
'published',
23+
),
24+
'transitions' => array(
25+
'request_review' => array(
26+
'from' => 'draft',
27+
'to' => array('wait_for_journalist', 'wait_for_spellchecker'),
28+
),
29+
'journalist_approval' => array(
30+
'from' => 'wait_for_journalist',
31+
'to' => 'approved_by_journalist',
32+
),
33+
'spellchecker_approval' => array(
34+
'from' => 'wait_for_spellchecker',
35+
'to' => 'approved_by_spellchecker',
36+
),
37+
'publish' => array(
38+
'from' => array('approved_by_journalist', 'approved_by_spellchecker'),
39+
'to' => 'published',
40+
'guard' => '!!true',
41+
),
42+
'publish_editor_in_chief' => array(
43+
'name' => 'publish',
44+
'from' => 'draft',
45+
'to' => 'published',
46+
'guard' => '!!false',
47+
),
48+
),
49+
),
50+
),
51+
));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?xml version="1.0" ?>
2+
3+
<container xmlns="http://symfony.com/schema/dic/services"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xmlns:framework="http://symfony.com/schema/dic/symfony"
6+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
7+
http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
8+
9+
<framework:config>
10+
<framework:workflow name="article" type="workflow" initial-place="draft">
11+
<framework:marking-store type="multiple_state">
12+
<framework:argument>a</framework:argument>
13+
<framework:argument>a</framework:argument>
14+
</framework:marking-store>
15+
<framework:support>Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTest</framework:support>
16+
<framework:place name="draft" />
17+
<framework:place name="wait_for_journalist" />
18+
<framework:place name="approved_by_journalist" />
19+
<framework:place name="wait_for_spellchecker" />
20+
<framework:place name="approved_by_spellchecker" />
21+
<framework:place name="published" />
22+
<framework:transition name="request_review">
23+
<framework:from>draft</framework:from>
24+
<framework:to>wait_for_journalist</framework:to>
25+
<framework:to>wait_for_spellchecker</framework:to>
26+
</framework:transition>
27+
<framework:transition name="journalist_approval">
28+
<framework:from>wait_for_journalist</framework:from>
29+
<framework:to>approved_by_journalist</framework:to>
30+
</framework:transition>
31+
<framework:transition name="spellchecker_approval">
32+
<framework:from>wait_for_spellchecker</framework:from>
33+
<framework:to>approved_by_spellchecker</framework:to>
34+
</framework:transition>
35+
<framework:transition name="publish">
36+
<framework:from>approved_by_journalist</framework:from>
37+
<framework:from>approved_by_spellchecker</framework:from>
38+
<framework:to>published</framework:to>
39+
<framework:guard>!!true</framework:guard>
40+
</framework:transition>
41+
<framework:transition name="publish">
42+
<framework:from>draft</framework:from>
43+
<framework:to>published</framework:to>
44+
<framework:guard>!!false</framework:guard>
45+
</framework:transition>
46+
</framework:workflow>
47+
</framework:config>
48+
</container>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
framework:
2+
workflows:
3+
article:
4+
type: workflow
5+
marking_store:
6+
type: multiple_state
7+
supports:
8+
- Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTest
9+
initial_place: draft
10+
places:
11+
- draft
12+
- wait_for_journalist
13+
- approved_by_journalist
14+
- wait_for_spellchecker
15+
- approved_by_spellchecker
16+
- published
17+
transitions:
18+
request_review:
19+
from: [draft]
20+
to: [wait_for_journalist, wait_for_spellchecker]
21+
journalist_approval:
22+
from: [wait_for_journalist]
23+
to: [approved_by_journalist]
24+
spellchecker_approval:
25+
from: [wait_for_spellchecker]
26+
to: [approved_by_spellchecker]
27+
publish:
28+
from: [approved_by_journalist, approved_by_spellchecker]
29+
to: [published]
30+
guard: "!!true"
31+
publish_editor_in_chief:
32+
name: publish
33+
from: [draft]
34+
to: [published]
35+
guard: "!!false"

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php

Lines changed: 78 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -302,14 +302,84 @@ public function testWorkflowMultipleTransitionsWithSameName()
302302

303303
$this->assertCount(5, $transitions);
304304

305-
$this->assertSame('request_review', $transitions[0]->getArgument(0));
306-
$this->assertSame('journalist_approval', $transitions[1]->getArgument(0));
307-
$this->assertSame('spellchecker_approval', $transitions[2]->getArgument(0));
308-
$this->assertSame('publish', $transitions[3]->getArgument(0));
309-
$this->assertSame('publish', $transitions[4]->getArgument(0));
310-
311-
$this->assertSame(array('approved_by_journalist', 'approved_by_spellchecker'), $transitions[3]->getArgument(1));
312-
$this->assertSame(array('draft'), $transitions[4]->getArgument(1));
305+
$this->assertSame('workflow.article.transition.0', (string) $transitions[0]);
306+
$this->assertSame(array(
307+
'request_review',
308+
array(
309+
'draft',
310+
),
311+
array(
312+
'wait_for_journalist', 'wait_for_spellchecker',
313+
),
314+
), $container->getDefinition($transitions[0])->getArguments());
315+
316+
$this->assertSame('workflow.article.transition.1', (string) $transitions[1]);
317+
$this->assertSame(array(
318+
'journalist_approval',
319+
array(
320+
'wait_for_journalist',
321+
),
322+
array(
323+
'approved_by_journalist',
324+
),
325+
), $container->getDefinition($transitions[1])->getArguments());
326+
327+
$this->assertSame('workflow.article.transition.2', (string) $transitions[2]);
328+
$this->assertSame(array(
329+
'spellchecker_approval',
330+
array(
331+
'wait_for_spellchecker',
332+
),
333+
array(
334+
'approved_by_spellchecker',
335+
),
336+
), $container->getDefinition($transitions[2])->getArguments());
337+
338+
$this->assertSame('workflow.article.transition.3', (string) $transitions[3]);
339+
$this->assertSame(array(
340+
'publish',
341+
array(
342+
'approved_by_journalist',
343+
'approved_by_spellchecker',
344+
),
345+
array(
346+
'published',
347+
),
348+
), $container->getDefinition($transitions[3])->getArguments());
349+
350+
$this->assertSame('workflow.article.transition.4', (string) $transitions[4]);
351+
$this->assertSame(array(
352+
'publish',
353+
array(
354+
'draft',
355+
),
356+
array(
357+
'published',
358+
),
359+
), $container->getDefinition($transitions[4])->getArguments());
360+
}
361+
362+
public function testGuardExpressions()
363+
{
364+
$container = $this->createContainerFromFile('workflow_with_guard_expression');
365+
366+
$this->assertTrue($container->hasDefinition('workflow.article.listener.guard'), 'Workflow guard listener is registered as a service');
367+
$this->assertTrue($container->hasParameter('workflow.has_guard_listeners'), 'Workflow guard listeners parameter exists');
368+
$this->assertTrue(true === $container->getParameter('workflow.has_guard_listeners'), 'Workflow guard listeners parameter is enabled');
369+
$guardDefinition = $container->getDefinition('workflow.article.listener.guard');
370+
$this->assertSame(array(
371+
array(
372+
'event' => 'workflow.article.guard.publish',
373+
'method' => 'onTransition',
374+
),
375+
), $guardDefinition->getTag('kernel.event_listener'));
376+
$guardsConfiguration = $guardDefinition->getArgument(0);
377+
$this->assertTrue(1 === \count($guardsConfiguration), 'Workflow guard configuration contains one element per transition name');
378+
$transitionGuardExpressions = $guardsConfiguration['workflow.article.guard.publish'];
379+
$this->assertSame('workflow.article.transition.3', (string) $transitionGuardExpressions[0]->getArgument(0));
380+
$this->assertSame('!!true', $transitionGuardExpressions[0]->getArgument(1));
381+
$this->assertSame('workflow.article.transition.4', (string) $transitionGuardExpressions[1]->getArgument(0));
382+
$this->assertSame('!!false', $transitionGuardExpressions[1]->getArgument(1));
313383
}
314384

315385
public function testWorkflowServicesCanBeEnabled()
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Workflow\EventListener;
13+
14+
use Symfony\Component\Workflow\Transition;
15+
16+
class GuardExpression
17+
{
18+
private $transition;
19+
20+
private $expression;
21+
22+
public function getTransition(): Transition
23+
{
24+
return $this->transition;
25+
}
26+
27+
public function getExpression(): string
28+
{
29+
return $this->expression;
30+
}
31+
32+
public function __construct(Transition $transition, string $expression)
33+
{
34+
$this->transition = $transition;
35+
$this->expression = $expression;
36+
}
37+
}

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

Lines changed: 21 additions & 4 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>
@@ -32,7 +33,7 @@ class GuardListener
3233
private $roleHierarchy;
3334
private $validator;
3435

35-
public function __construct($configuration, ExpressionLanguage $expressionLanguage, TokenStorageInterface $tokenStorage, AuthorizationCheckerInterface $authenticationChecker, AuthenticationTrustResolverInterface $trustResolver, RoleHierarchyInterface $roleHierarchy = null, ValidatorInterface $validator = null)
36+
public function __construct(array $configuration, ExpressionLanguage $expressionLanguage, TokenStorageInterface $tokenStorage, AuthorizationCheckerInterface $authenticationChecker, AuthenticationTrustResolverInterface $trustResolver, RoleHierarchyInterface $roleHierarchy = null, ValidatorInterface $validator = null)
3637
{
3738
$this->configuration = $configuration;
3839
$this->expressionLanguage = $expressionLanguage;
@@ -49,13 +50,29 @@ 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+
$eventConfiguration = (array) $this->configuration[$eventName];
54+
foreach ($eventConfiguration as $guard) {
55+
if ($guard instanceof GuardExpression) {
56+
if ($guard->getTransition() !== $event->getTransition()) {
57+
continue;
58+
}
59+
$this->validateGuardExpression($event, $guard->getExpression());
60+
} else {
61+
$this->validateGuardExpression($event, $guard);
62+
}
63+
}
64+
}
65+
66+
private function validateGuardExpression(GuardEvent $event, string $expression)
67+
{
68+
if (!$this->expressionLanguage->evaluate($expression, $this->getVariables($event))) {
69+
$blocker = TransitionBlocker::createBlockedByExpressionGuardListener($expression);
70+
$event->addTransitionBlocker($blocker);
5471
}
5572
}
5673

5774
// code should be sync with Symfony\Component\Security\Core\Authorization\Voter\ExpressionVoter
58-
private function getVariables(GuardEvent $event)
75+
private function getVariables(GuardEvent $event): array
5976
{
6077
$token = $this->tokenStorage->getToken();
6178

0 commit comments

Comments
 (0)
0