Skip to content

Commit 5ac6862

Browse files
Merge branch '3.4'
* 3.4: Clarify the exceptions are going to be rendered just after [DI] Remove colon from env placeholders fixed CS [Yaml] initialize inline line numbers [Workflow] Added tests for the is_valid() guard expression [Workflow] Added guard 'is_valid()' method support Add & use OptionResolverIntrospector Add debug:form type option
2 parents 504f265 + 9be01bc commit 5ac6862

File tree

6 files changed

+103
-35
lines changed

6 files changed

+103
-35
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ CHANGELOG
99
3.4.0
1010
-----
1111

12+
* Added guard `is_valid()` method support.
1213
* Added support for `Event::getWorkflowName()` for "announce" events.
1314
* Added `workflow.completed` events which are fired after a transition is completed.
1415

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
}

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): array
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;

Exception/RuntimeException.php

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 <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\Exception;
13+
14+
/**
15+
* Base RuntimeException for the Workflow component.
16+
*
17+
* @author Alain Flaus <alain.flaus@gmail.com>
18+
*/
19+
class RuntimeException extends \RuntimeException implements ExceptionInterface
20+
{
21+
}

Tests/EventListener/GuardListenerTest.php

Lines changed: 59 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
99
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
1010
use Symfony\Component\Security\Core\Role\Role;
11+
use Symfony\Component\Validator\Validator\ValidatorInterface;
1112
use Symfony\Component\Workflow\EventListener\ExpressionLanguage;
1213
use Symfony\Component\Workflow\EventListener\GuardListener;
1314
use Symfony\Component\Workflow\Event\GuardEvent;
@@ -16,69 +17,83 @@
1617

1718
class GuardListenerTest extends TestCase
1819
{
19-
private $tokenStorage;
20+
private $authenticationChecker;
21+
private $validator;
2022
private $listener;
2123

2224
protected function setUp()
2325
{
2426
$configuration = array(
25-
'event_name_a' => 'true',
26-
'event_name_b' => 'false',
27+
'test_is_granted' => 'is_granted("something")',
28+
'test_is_valid' => 'is_valid(subject)',
2729
);
28-
2930
$expressionLanguage = new ExpressionLanguage();
30-
$this->tokenStorage = $this->getMockBuilder(TokenStorageInterface::class)->getMock();
31-
$authenticationChecker = $this->getMockBuilder(AuthorizationCheckerInterface::class)->getMock();
31+
$token = $this->getMockBuilder(TokenInterface::class)->getMock();
32+
$token->expects($this->any())->method('getRoles')->willReturn(array(new Role('ROLE_USER')));
33+
$tokenStorage = $this->getMockBuilder(TokenStorageInterface::class)->getMock();
34+
$tokenStorage->expects($this->any())->method('getToken')->willReturn($token);
35+
$this->authenticationChecker = $this->getMockBuilder(AuthorizationCheckerInterface::class)->getMock();
3236
$trustResolver = $this->getMockBuilder(AuthenticationTrustResolverInterface::class)->getMock();
33-
34-
$this->listener = new GuardListener($configuration, $expressionLanguage, $this->tokenStorage, $authenticationChecker, $trustResolver);
37+
$this->validator = $this->getMockBuilder(ValidatorInterface::class)->getMock();
38+
$this->listener = new GuardListener($configuration, $expressionLanguage, $tokenStorage, $this->authenticationChecker, $trustResolver, null, $this->validator);
3539
}
3640

3741
protected function tearDown()
3842
{
43+
$this->authenticationChecker = null;
44+
$this->validator = null;
3945
$this->listener = null;
4046
}
4147

4248
public function testWithNotSupportedEvent()
4349
{
4450
$event = $this->createEvent();
45-
$this->configureTokenStorage(false);
51+
$this->configureAuthenticationChecker(false);
52+
$this->configureValidator(false);
4653

4754
$this->listener->onTransition($event, 'not supported');
4855

4956
$this->assertFalse($event->isBlocked());
5057
}
5158

52-
public function testWithSupportedEventAndReject()
59+
public function testWithSecuritySupportedEventAndReject()
5360
{
5461
$event = $this->createEvent();
55-
$this->configureTokenStorage(true);
62+
$this->configureAuthenticationChecker(true, false);
5663

57-
$this->listener->onTransition($event, 'event_name_a');
64+
$this->listener->onTransition($event, 'test_is_granted');
65+
66+
$this->assertTrue($event->isBlocked());
67+
}
68+
69+
public function testWithSecuritySupportedEventAndAccept()
70+
{
71+
$event = $this->createEvent();
72+
$this->configureAuthenticationChecker(true, true);
73+
74+
$this->listener->onTransition($event, 'test_is_granted');
5875

5976
$this->assertFalse($event->isBlocked());
6077
}
6178

62-
public function testWithSupportedEventAndAccept()
79+
public function testWithValidatorSupportedEventAndReject()
6380
{
6481
$event = $this->createEvent();
65-
$this->configureTokenStorage(true);
82+
$this->configureValidator(true, false);
6683

67-
$this->listener->onTransition($event, 'event_name_b');
84+
$this->listener->onTransition($event, 'test_is_valid');
6885

6986
$this->assertTrue($event->isBlocked());
7087
}
7188

72-
/**
73-
* @expectedException \Symfony\Component\Workflow\Exception\InvalidTokenConfigurationException
74-
* @expectedExceptionMessage There are no tokens available for workflow unnamed.
75-
*/
76-
public function testWithNoTokensInTokenStorage()
89+
public function testWithValidatorSupportedEventAndAccept()
7790
{
7891
$event = $this->createEvent();
79-
$this->tokenStorage->setToken(null);
92+
$this->configureValidator(true, true);
93+
94+
$this->listener->onTransition($event, 'test_is_valid');
8095

81-
$this->listener->onTransition($event, 'event_name_a');
96+
$this->assertFalse($event->isBlocked());
8297
}
8398

8499
private function createEvent()
@@ -90,28 +105,39 @@ private function createEvent()
90105
return new GuardEvent($subject, $subject->marking, $transition);
91106
}
92107

93-
private function configureTokenStorage($hasUser)
108+
private function configureAuthenticationChecker($isUsed, $granted = true)
94109
{
95-
if (!$hasUser) {
96-
$this->tokenStorage
110+
if (!$isUsed) {
111+
$this->authenticationChecker
97112
->expects($this->never())
98-
->method('getToken')
113+
->method('isGranted')
99114
;
100115

101116
return;
102117
}
103118

104-
$token = $this->getMockBuilder(TokenInterface::class)->getMock();
105-
$token
119+
$this->authenticationChecker
106120
->expects($this->once())
107-
->method('getRoles')
108-
->willReturn(array(new Role('ROLE_ADMIN')))
121+
->method('isGranted')
122+
->willReturn($granted)
109123
;
124+
}
125+
126+
private function configureValidator($isUsed, $valid = true)
127+
{
128+
if (!$isUsed) {
129+
$this->validator
130+
->expects($this->never())
131+
->method('validate')
132+
;
133+
134+
return;
135+
}
110136

111-
$this->tokenStorage
137+
$this->validator
112138
->expects($this->once())
113-
->method('getToken')
114-
->willReturn($token)
139+
->method('validate')
140+
->willReturn($valid ? array() : array('a violation'))
115141
;
116142
}
117143
}

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@
2828
"symfony/dependency-injection": "~3.4|~4.0",
2929
"symfony/event-dispatcher": "~3.4|~4.0",
3030
"symfony/expression-language": "~3.4|~4.0",
31-
"symfony/security-core": "~3.4|~4.0"
31+
"symfony/security-core": "~3.4|~4.0",
32+
"symfony/validator": "~3.4|~4.0"
3233
},
3334
"autoload": {
3435
"psr-4": { "Symfony\\Component\\Workflow\\": "" }

0 commit comments

Comments
 (0)