Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 13d75a4

Browse files
committed
feature #23499 [Workflow] add guard is_valid() method support (alain-flaus, lyrixx)
This PR was merged into the 3.4 branch. Discussion ---------- [Workflow] add guard is_valid() method support | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | Yes | License | MIT Workflow guard configuration support expression language like **is_fully_authenticated()**, **has_role()** or **is_granted()**, etc... I would like to add the support for a new **is_valid()** expression. Configuration allow to validate subject against specific validation groups to check if a transition can be applied. In the next configuration exemple, my issue must validate "affectable" validation group to apply "affect" transistion: ```yaml framework: workflows: issue: marking_store: type: single_state arguments: - state supports: AppBundle\Entity\Issue initial_place: created places: - created - affected - closed transitions: affect: guard: "is_valid(subject, ['affectable'])" from: created to: affected close: from: completed to: closed ``` Commits ------- 06d8198 [Workflow] Added tests for the is_valid() guard expression 9499bc2 [Workflow] Added guard 'is_valid()' method support
2 parents bfda407 + 06d8198 commit 13d75a4

File tree

9 files changed

+131
-80
lines changed

9 files changed

+131
-80
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/WorkflowGuardListenerPass.php

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
/**
1919
* @author Christian Flothmann <[email protected]>
20+
* @author Grégoire Pineau <[email protected]>
2021
*/
2122
class WorkflowGuardListenerPass implements CompilerPassInterface
2223
{
@@ -31,20 +32,17 @@ public function process(ContainerBuilder $container)
3132

3233
$container->getParameterBag()->remove('workflow.has_guard_listeners');
3334

34-
if (!$container->has('security.token_storage')) {
35-
throw new LogicException('The "security.token_storage" service is needed to be able to use the workflow guard listener.');
36-
}
37-
38-
if (!$container->has('security.authorization_checker')) {
39-
throw new LogicException('The "security.authorization_checker" service is needed to be able to use the workflow guard listener.');
40-
}
41-
42-
if (!$container->has('security.authentication.trust_resolver')) {
43-
throw new LogicException('The "security.authentication.trust_resolver" service is needed to be able to use the workflow guard listener.');
44-
}
45-
46-
if (!$container->has('security.role_hierarchy')) {
47-
throw new LogicException('The "security.role_hierarchy" service is needed to be able to use the workflow guard listener.');
35+
$servicesNeeded = array(
36+
'security.token_storage',
37+
'security.authorization_checker',
38+
'security.authentication.trust_resolver',
39+
'security.role_hierarchy',
40+
);
41+
42+
foreach ($servicesNeeded as $service) {
43+
if (!$container->has($service)) {
44+
throw new LogicException(sprintf('The "%s" service is needed to be able to use the workflow guard listener.', $service));
45+
}
4846
}
4947
}
5048
}

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
use Symfony\Component\Translation\Translator;
8686
use Symfony\Component\Validator\ConstraintValidatorInterface;
8787
use Symfony\Component\Validator\ObjectInitializerInterface;
88+
use Symfony\Component\Validator\Validator\ValidatorInterface;
8889
use Symfony\Component\WebLink\HttpHeaderSerializer;
8990
use Symfony\Component\Workflow;
9091
use Symfony\Component\Yaml\Command\LintCommand as BaseYamlLintCommand;
@@ -752,6 +753,7 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $
752753
new Reference('security.authorization_checker'),
753754
new Reference('security.authentication.trust_resolver'),
754755
new Reference('security.role_hierarchy'),
756+
new Reference('validator', ContainerInterface::NULL_ON_INVALID_REFERENCE),
755757
));
756758

757759
$container->setDefinition(sprintf('%s.listener.guard', $workflowId), $guard);

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/WorkflowGuardListenerPassTest.php

Lines changed: 14 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,11 @@
1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\WorkflowGuardListenerPass;
1616
use Symfony\Component\DependencyInjection\ContainerBuilder;
17-
use Symfony\Component\DependencyInjection\Exception\LogicException;
1817
use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface;
1918
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
2019
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
2120
use Symfony\Component\Security\Core\Role\RoleHierarchy;
22-
use Symfony\Component\Workflow\EventListener\GuardListener;
21+
use Symfony\Component\Validator\Validator\ValidatorInterface;
2322

2423
class WorkflowGuardListenerPassTest extends TestCase
2524
{
@@ -29,53 +28,37 @@ class WorkflowGuardListenerPassTest extends TestCase
2928
protected function setUp()
3029
{
3130
$this->container = new ContainerBuilder();
32-
$this->container->register('foo.listener.guard', GuardListener::class);
33-
$this->container->register('bar.listener.guard', GuardListener::class);
3431
$this->compilerPass = new WorkflowGuardListenerPass();
3532
}
3633

37-
public function testListenersAreNotRemovedIfParameterIsNotSet()
34+
public function testNoExeptionIfParameterIsNotSet()
3835
{
3936
$this->compilerPass->process($this->container);
4037

41-
$this->assertTrue($this->container->hasDefinition('foo.listener.guard'));
42-
$this->assertTrue($this->container->hasDefinition('bar.listener.guard'));
43-
}
44-
45-
public function testParameterIsRemovedWhenThePassIsProcessed()
46-
{
47-
$this->container->setParameter('workflow.has_guard_listeners', array('foo.listener.guard', 'bar.listener.guard'));
48-
49-
try {
50-
$this->compilerPass->process($this->container);
51-
} catch (LogicException $e) {
52-
// Here, we are not interested in the exception handling. This is tested further down.
53-
}
54-
5538
$this->assertFalse($this->container->hasParameter('workflow.has_guard_listeners'));
5639
}
5740

58-
public function testListenersAreNotRemovedIfAllDependenciesArePresent()
41+
public function testNoExeptionIfAllDependenciesArePresent()
5942
{
60-
$this->container->setParameter('workflow.has_guard_listeners', array('foo.listener.guard', 'bar.listener.guard'));
43+
$this->container->setParameter('workflow.has_guard_listeners', true);
6144
$this->container->register('security.token_storage', TokenStorageInterface::class);
6245
$this->container->register('security.authorization_checker', AuthorizationCheckerInterface::class);
6346
$this->container->register('security.authentication.trust_resolver', AuthenticationTrustResolverInterface::class);
6447
$this->container->register('security.role_hierarchy', RoleHierarchy::class);
48+
$this->container->register('validator', ValidatorInterface::class);
6549

6650
$this->compilerPass->process($this->container);
6751

68-
$this->assertTrue($this->container->hasDefinition('foo.listener.guard'));
69-
$this->assertTrue($this->container->hasDefinition('bar.listener.guard'));
52+
$this->assertFalse($this->container->hasParameter('workflow.has_guard_listeners'));
7053
}
7154

7255
/**
7356
* @expectedException \Symfony\Component\DependencyInjection\Exception\LogicException
7457
* @expectedExceptionMessage The "security.token_storage" service is needed to be able to use the workflow guard listener.
7558
*/
76-
public function testListenersAreRemovedIfTheTokenStorageServiceIsNotPresent()
59+
public function testExceptionIfTheTokenStorageServiceIsNotPresent()
7760
{
78-
$this->container->setParameter('workflow.has_guard_listeners', array('foo.listener.guard', 'bar.listener.guard'));
61+
$this->container->setParameter('workflow.has_guard_listeners', true);
7962
$this->container->register('security.authorization_checker', AuthorizationCheckerInterface::class);
8063
$this->container->register('security.authentication.trust_resolver', AuthenticationTrustResolverInterface::class);
8164
$this->container->register('security.role_hierarchy', RoleHierarchy::class);
@@ -87,9 +70,9 @@ public function testListenersAreRemovedIfTheTokenStorageServiceIsNotPresent()
8770
* @expectedException \Symfony\Component\DependencyInjection\Exception\LogicException
8871
* @expectedExceptionMessage The "security.authorization_checker" service is needed to be able to use the workflow guard listener.
8972
*/
90-
public function testListenersAreRemovedIfTheAuthorizationCheckerServiceIsNotPresent()
73+
public function testExceptionIfTheAuthorizationCheckerServiceIsNotPresent()
9174
{
92-
$this->container->setParameter('workflow.has_guard_listeners', array('foo.listener.guard', 'bar.listener.guard'));
75+
$this->container->setParameter('workflow.has_guard_listeners', true);
9376
$this->container->register('security.token_storage', TokenStorageInterface::class);
9477
$this->container->register('security.authentication.trust_resolver', AuthenticationTrustResolverInterface::class);
9578
$this->container->register('security.role_hierarchy', RoleHierarchy::class);
@@ -101,9 +84,9 @@ public function testListenersAreRemovedIfTheAuthorizationCheckerServiceIsNotPres
10184
* @expectedException \Symfony\Component\DependencyInjection\Exception\LogicException
10285
* @expectedExceptionMessage The "security.authentication.trust_resolver" service is needed to be able to use the workflow guard listener.
10386
*/
104-
public function testListenersAreRemovedIfTheAuthenticationTrustResolverServiceIsNotPresent()
87+
public function testExceptionIfTheAuthenticationTrustResolverServiceIsNotPresent()
10588
{
106-
$this->container->setParameter('workflow.has_guard_listeners', array('foo.listener.guard', 'bar.listener.guard'));
89+
$this->container->setParameter('workflow.has_guard_listeners', true);
10790
$this->container->register('security.token_storage', TokenStorageInterface::class);
10891
$this->container->register('security.authorization_checker', AuthorizationCheckerInterface::class);
10992
$this->container->register('security.role_hierarchy', RoleHierarchy::class);
@@ -115,9 +98,9 @@ public function testListenersAreRemovedIfTheAuthenticationTrustResolverServiceIs
11598
* @expectedException \Symfony\Component\DependencyInjection\Exception\LogicException
11699
* @expectedExceptionMessage The "security.role_hierarchy" service is needed to be able to use the workflow guard listener.
117100
*/
118-
public function testListenersAreRemovedIfTheRoleHierarchyServiceIsNotPresent()
101+
public function testExceptionIfTheRoleHierarchyServiceIsNotPresent()
119102
{
120-
$this->container->setParameter('workflow.has_guard_listeners', array('foo.listener.guard', 'bar.listener.guard'));
103+
$this->container->setParameter('workflow.has_guard_listeners', true);
121104
$this->container->register('security.token_storage', TokenStorageInterface::class);
122105
$this->container->register('security.authorization_checker', AuthorizationCheckerInterface::class);
123106
$this->container->register('security.authentication.trust_resolver', AuthenticationTrustResolverInterface::class);

src/Symfony/Component/Workflow/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
3.4.0
55
-----
66

7+
* Added guard `is_valid()` method support.
78
* Added support for `Event::getWorkflowName()` for "announce" events.
89
* Added `workflow.completed` events which are fired after a transition is completed.
910

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
namespace Symfony\Component\Workflow\EventListener;
1313

1414
use Symfony\Component\Security\Core\Authorization\ExpressionLanguage as BaseExpressionLanguage;
15+
use Symfony\Component\Validator\Validator\ValidatorInterface;
16+
use Symfony\Component\Workflow\Exception\RuntimeException;
1517

1618
/**
1719
* Adds some function to the default Symfony Security ExpressionLanguage.
@@ -29,5 +31,17 @@ protected function registerFunctions()
2931
}, function (array $variables, $attributes, $object = null) {
3032
return $variables['auth_checker']->isGranted($attributes, $object);
3133
});
34+
35+
$this->register('is_valid', function ($object = 'null', $groups = 'null') {
36+
return sprintf('0 === count($validator->validate(%s, null, %s))', $object, $groups);
37+
}, function (array $variables, $object = null, $groups = null) {
38+
if (!$variables['validator'] instanceof ValidatorInterface) {
39+
throw new RuntimeException('"is_valid" cannot be used as the Validator component is not installed.');
40+
}
41+
42+
$errors = $variables['validator']->validate($object, null, $groups);
43+
44+
return 0 === count($errors);
45+
});
3246
}
3347
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
1616
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
1717
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
18+
use Symfony\Component\Validator\Validator\ValidatorInterface;
1819
use Symfony\Component\Workflow\Event\GuardEvent;
1920
use Symfony\Component\Workflow\Exception\InvalidTokenConfigurationException;
2021

@@ -29,15 +30,17 @@ class GuardListener
2930
private $authenticationChecker;
3031
private $trustResolver;
3132
private $roleHierarchy;
33+
private $validator;
3234

33-
public function __construct($configuration, ExpressionLanguage $expressionLanguage, TokenStorageInterface $tokenStorage, AuthorizationCheckerInterface $authenticationChecker, AuthenticationTrustResolverInterface $trustResolver, RoleHierarchyInterface $roleHierarchy = null)
35+
public function __construct($configuration, ExpressionLanguage $expressionLanguage, TokenStorageInterface $tokenStorage, AuthorizationCheckerInterface $authenticationChecker, AuthenticationTrustResolverInterface $trustResolver, RoleHierarchyInterface $roleHierarchy = null, ValidatorInterface $validator = null)
3436
{
3537
$this->configuration = $configuration;
3638
$this->expressionLanguage = $expressionLanguage;
3739
$this->tokenStorage = $tokenStorage;
3840
$this->authenticationChecker = $authenticationChecker;
3941
$this->trustResolver = $trustResolver;
4042
$this->roleHierarchy = $roleHierarchy;
43+
$this->validator = $validator;
4144
}
4245

4346
public function onTransition(GuardEvent $event, $eventName)
@@ -77,6 +80,8 @@ private function getVariables(GuardEvent $event)
7780
'auth_checker' => $this->authenticationChecker,
7881
// needed for the is_* expression function
7982
'trust_resolver' => $this->trustResolver,
83+
// needed for the is_valid expression function
84+
'validator' => $this->validator,
8085
);
8186

8287
return $variables;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Exception;
13+
14+
/**
15+
* Base RuntimeException for the Workflow component.
16+
*
17+
* @author Alain Flaus <[email protected]>
18+
*/
19+
class RuntimeException extends \RuntimeException implements ExceptionInterface
20+
{
21+
}

0 commit comments

Comments
 (0)