diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 5b6c8b447e..9ec02f0722 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -602,15 +602,44 @@ class FrameworkExtension extends Extension @trigger_error(sprintf('The "type" option of the "framework.workflows.%s" configuration entry must be defined since Symfony 3.3. The default value will be "state_machine" in Symfony 4.0.', $name), E_USER_DEPRECATED); } $type = $workflow['type']; + $workflowId = sprintf('%s.%s', $type, $name); + // Create transitions $transitions = array(); + $guardsConfiguration = array(); + // Global transition counter per workflow + $transitionCounter = 0; foreach ($workflow['transitions'] as $transition) { if ('workflow' === $type) { - $transitions[] = new Definition(Workflow\Transition::class, array($transition['name'], $transition['from'], $transition['to'])); + $transitionDefinition = new Definition(Workflow\Transition::class, array($transition['name'], $transition['from'], $transition['to'])); + $transitionDefinition->setPublic(false); + $transitionId = sprintf('%s.transition.%s', $workflowId, $transitionCounter++); + $container->setDefinition($transitionId, $transitionDefinition); + $transitions[] = new Reference($transitionId); + if (isset($transition['guard'])) { + $configuration = new Definition(Workflow\EventListener\GuardExpression::class); + $configuration->addArgument(new Reference($transitionId)); + $configuration->addArgument($transition['guard']); + $configuration->setPublic(false); + $eventName = sprintf('workflow.%s.guard.%s', $name, $transition['name']); + $guardsConfiguration[$eventName][] = $configuration; + } } elseif ('state_machine' === $type) { foreach ($transition['from'] as $from) { foreach ($transition['to'] as $to) { - $transitions[] = new Definition(Workflow\Transition::class, array($transition['name'], $from, $to)); + $transitionDefinition = new Definition(Workflow\Transition::class, array($transition['name'], $from, $to)); + $transitionDefinition->setPublic(false); + $transitionId = sprintf('%s.transition.%s', $workflowId, $transitionCounter++); + $container->setDefinition($transitionId, $transitionDefinition); + $transitions[] = new Reference($transitionId); + if (isset($transition['guard'])) { + $configuration = new Definition(Workflow\EventListener\GuardExpression::class); + $configuration->addArgument(new Reference($transitionId)); + $configuration->addArgument($transition['guard']); + $configuration->setPublic(false); + $eventName = sprintf('workflow.%s.guard.%s', $name, $transition['name']); + $guardsConfiguration[$eventName][] = $configuration; + } } } } @@ -641,7 +670,6 @@ class FrameworkExtension extends Extension } // Create Workflow - $workflowId = sprintf('%s.%s', $type, $name); $workflowDefinition = new ChildDefinition(sprintf('%s.abstract', $type)); $workflowDefinition->replaceArgument(0, new Reference(sprintf('%s.definition', $workflowId))); if (isset($markingStoreDefinition)) { @@ -677,16 +705,7 @@ class FrameworkExtension extends Extension } // Add Guard Listener - $guard = new Definition(Workflow\EventListener\GuardListener::class); - $guard->setPrivate(true); - $configuration = array(); - foreach ($workflow['transitions'] as $config) { - $transitionName = $config['name']; - - if (!isset($config['guard'])) { - continue; - } - + if ($guardsConfiguration) { if (!class_exists(ExpressionLanguage::class)) { throw new LogicException('Cannot guard workflows as the ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".'); } @@ -695,13 +714,11 @@ class FrameworkExtension extends Extension throw new LogicException('Cannot guard workflows as the Security component is not installed. Try running "composer require symfony/security".'); } - $eventName = sprintf('workflow.%s.guard.%s', $name, $transitionName); - $guard->addTag('kernel.event_listener', array('event' => $eventName, 'method' => 'onTransition')); - $configuration[$eventName] = $config['guard']; - } - if ($configuration) { + $guard = new Definition(Workflow\EventListener\GuardListener::class); + $guard->setPrivate(true); + $guard->setArguments(array( - $configuration, + $guardsConfiguration, new Reference('workflow.security.expression_language'), new Reference('security.token_storage'), new Reference('security.authorization_checker'), @@ -709,6 +726,9 @@ class FrameworkExtension extends Extension new Reference('security.role_hierarchy'), new Reference('validator', ContainerInterface::NULL_ON_INVALID_REFERENCE), )); + foreach ($guardsConfiguration as $eventName => $config) { + $guard->addTag('kernel.event_listener', array('event' => $eventName, 'method' => 'onTransition')); + } $container->setDefinition(sprintf('%s.listener.guard', $workflowId), $guard); $container->setParameter('workflow.has_guard_listeners', true); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/workflow_with_guard_expression.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/workflow_with_guard_expression.xml index cf129e45c3..a5124d1fe7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/workflow_with_guard_expression.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/workflow_with_guard_expression.xml @@ -13,12 +13,12 @@ a Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTest - - - - - - + draft + wait_for_journalist + approved_by_journalist + wait_for_spellchecker + approved_by_spellchecker + published draft wait_for_journalist diff --git a/src/Symfony/Component/Workflow/EventListener/GuardExpression.php b/src/Symfony/Component/Workflow/EventListener/GuardExpression.php index cf3b8c7e18..09ab15086b 100644 --- a/src/Symfony/Component/Workflow/EventListener/GuardExpression.php +++ b/src/Symfony/Component/Workflow/EventListener/GuardExpression.php @@ -19,19 +19,22 @@ class GuardExpression private $expression; - public function getTransition(): Transition - { - return $this->transition; - } - - public function getExpression(): string - { - return $this->expression; - } - - public function __construct(Transition $transition, string $expression) + /** + * @param string $expression + */ + public function __construct(Transition $transition, $expression) { $this->transition = $transition; $this->expression = $expression; } + + public function getTransition() + { + return $this->transition; + } + + public function getExpression() + { + return $this->expression; + } } diff --git a/src/Symfony/Component/Workflow/EventListener/GuardListener.php b/src/Symfony/Component/Workflow/EventListener/GuardListener.php index 4f1c229e51..4d3cfac57e 100644 --- a/src/Symfony/Component/Workflow/EventListener/GuardListener.php +++ b/src/Symfony/Component/Workflow/EventListener/GuardListener.php @@ -18,7 +18,6 @@ use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\Workflow\Event\GuardEvent; use Symfony\Component\Workflow\Exception\InvalidTokenConfigurationException; -use Symfony\Component\Workflow\TransitionBlocker; /** * @author Grégoire Pineau @@ -63,16 +62,15 @@ class GuardListener } } - private function validateGuardExpression(GuardEvent $event, string $expression) + private function validateGuardExpression(GuardEvent $event, $expression) { if (!$this->expressionLanguage->evaluate($expression, $this->getVariables($event))) { - $blocker = TransitionBlocker::createBlockedByExpressionGuardListener($expression); - $event->addTransitionBlocker($blocker); + $event->setBlocked(true); } } // code should be sync with Symfony\Component\Security\Core\Authorization\Voter\ExpressionVoter - private function getVariables(GuardEvent $event): array + private function getVariables(GuardEvent $event) { $token = $this->tokenStorage->getToken(); diff --git a/src/Symfony/Component/Workflow/Tests/EventListener/GuardListenerTest.php b/src/Symfony/Component/Workflow/Tests/EventListener/GuardListenerTest.php index f4c646ade3..8686d74cf6 100644 --- a/src/Symfony/Component/Workflow/Tests/EventListener/GuardListenerTest.php +++ b/src/Symfony/Component/Workflow/Tests/EventListener/GuardListenerTest.php @@ -21,16 +21,16 @@ class GuardListenerTest extends TestCase private $authenticationChecker; private $validator; private $listener; - private $transition; + private $configuration; protected function setUp() { - $configuration = array( + $this->configuration = array( 'test_is_granted' => 'is_granted("something")', 'test_is_valid' => 'is_valid(subject)', 'test_expression' => array( - new GuardExpression($this->getTransition(true), '!is_valid(subject)'), - new GuardExpression($this->getTransition(true), 'is_valid(subject)'), + new GuardExpression(new Transition('name', 'from', 'to'), '!is_valid(subject)'), + new GuardExpression(new Transition('name', 'from', 'to'), 'is_valid(subject)'), ), ); $expressionLanguage = new ExpressionLanguage(); @@ -41,7 +41,7 @@ class GuardListenerTest extends TestCase $this->authenticationChecker = $this->getMockBuilder(AuthorizationCheckerInterface::class)->getMock(); $trustResolver = $this->getMockBuilder(AuthenticationTrustResolverInterface::class)->getMock(); $this->validator = $this->getMockBuilder(ValidatorInterface::class)->getMock(); - $this->listener = new GuardListener($configuration, $expressionLanguage, $tokenStorage, $this->authenticationChecker, $trustResolver, null, $this->validator); + $this->listener = new GuardListener($this->configuration, $expressionLanguage, $tokenStorage, $this->authenticationChecker, $trustResolver, null, $this->validator); } protected function tearDown() @@ -104,8 +104,8 @@ class GuardListenerTest extends TestCase public function testWithGuardExpressionWithNotSupportedTransition() { - $event = $this->createEvent(true); - $this->configureValidator(false, false); + $event = $this->createEvent(); + $this->configureValidator(false); $this->listener->onTransition($event, 'test_expression'); $this->assertFalse($event->isBlocked()); @@ -113,7 +113,7 @@ class GuardListenerTest extends TestCase public function testWithGuardExpressionWithSupportedTransition() { - $event = $this->createEvent(); + $event = $this->createEvent($this->configuration['test_expression'][1]->getTransition()); $this->configureValidator(true, true); $this->listener->onTransition($event, 'test_expression'); @@ -122,18 +122,18 @@ class GuardListenerTest extends TestCase public function testGuardExpressionBlocks() { - $event = $this->createEvent(); + $event = $this->createEvent($this->configuration['test_expression'][1]->getTransition()); $this->configureValidator(true, false); $this->listener->onTransition($event, 'test_expression'); $this->assertTrue($event->isBlocked()); } - private function createEvent($newTransition = false) + private function createEvent(Transition $transition = null) { $subject = new \stdClass(); $subject->marking = new Marking(); - $transition = $this->getTransition($newTransition); + $transition = $transition ?: new Transition('name', 'from', 'to'); return new GuardEvent($subject, $subject->marking, $transition); } @@ -173,13 +173,4 @@ class GuardListenerTest extends TestCase ->willReturn($valid ? array() : array('a violation')) ; } - - private function getTransition($new = false) - { - if ($new || !$this->transition) { - $this->transition = new Transition('name', 'from', 'to'); - } - - return $this->transition; - } }