diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 0ba78c5345a0..0da222cc2876 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -34,6 +34,7 @@ use Symfony\Component\Translation\Translator; use Symfony\Component\Validator\Validation; use Symfony\Component\WebLink\HttpHeaderSerializer; +use Symfony\Component\Workflow\WorkflowEvents; /** * FrameworkExtension configuration structure. @@ -339,6 +340,7 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode) ->fixXmlConfig('support') ->fixXmlConfig('place') ->fixXmlConfig('transition') + ->fixXmlConfig('event_to_dispatch', 'events_to_dispatch') ->children() ->arrayNode('audit_trail') ->canBeEnabled() @@ -381,6 +383,33 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode) ->defaultValue([]) ->prototype('scalar')->end() ->end() + ->variableNode('events_to_dispatch') + ->defaultValue(null) + ->validate() + ->ifTrue(function ($v) { + if (null === $v) { + return false; + } + if (!\is_array($v)) { + return true; + } + + foreach ($v as $value) { + if (!\is_string($value)) { + return true; + } + if (class_exists(WorkflowEvents::class) && !\in_array($value, WorkflowEvents::ALIASES)) { + return true; + } + } + + return false; + }) + ->thenInvalid('The value must be "null" or an array of workflow events (like ["workflow.enter"]).') + ->end() + ->info('Select which Transition events should be dispatched for this Workflow') + ->example(['workflow.enter', 'workflow.transition']) + ->end() ->arrayNode('places') ->beforeNormalization() ->always() @@ -509,6 +538,18 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode) }) ->thenInvalid('"supports" or "support_strategy" should be configured.') ->end() + ->beforeNormalization() + ->always() + ->then(function ($values) { + // Special case to deal with XML when the user wants an empty array + if (\array_key_exists('event_to_dispatch', $values) && null === $values['event_to_dispatch']) { + $values['events_to_dispatch'] = []; + unset($values['event_to_dispatch']); + } + + return $values; + }) + ->end() ->end() ->end() ->end() diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 40f5e96e133a..1c04a0ca7fac 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -772,6 +772,7 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $ $workflowDefinition->replaceArgument(1, $markingStoreDefinition); } $workflowDefinition->replaceArgument(3, $name); + $workflowDefinition->replaceArgument(4, $workflow['events_to_dispatch']); // Store to container $container->setDefinition($workflowId, $workflowDefinition); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index 526cf1970e08..41208143a7fa 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -288,6 +288,7 @@ + @@ -345,6 +346,19 @@ + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/workflow.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/workflow.php index 134bb6d33487..6eae2b16c411 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/workflow.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/workflow.php @@ -25,6 +25,7 @@ abstract_arg('marking store'), service('event_dispatcher')->ignoreOnInvalid(), abstract_arg('workflow name'), + abstract_arg('events to dispatch'), ]) ->abstract() ->public() @@ -34,6 +35,7 @@ abstract_arg('marking store'), service('event_dispatcher')->ignoreOnInvalid(), abstract_arg('workflow name'), + abstract_arg('events to dispatch'), ]) ->abstract() ->public() diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/workflow_with_no_events_to_dispatch.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/workflow_with_no_events_to_dispatch.php new file mode 100644 index 000000000000..0ae6ac69ee7c --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/workflow_with_no_events_to_dispatch.php @@ -0,0 +1,42 @@ +loadFromExtension('framework', [ + 'workflows' => [ + 'my_workflow' => [ + 'type' => 'state_machine', + 'marking_store' => [ + 'type' => 'method', + 'property' => 'state' + ], + 'supports' => [ + FrameworkExtensionTest::class, + ], + 'events_to_dispatch' => [], + 'places' => [ + 'one', + 'two', + 'three', + ], + 'transitions' => [ + 'count_to_two' => [ + 'from' => [ + 'one', + ], + 'to' => [ + 'two', + ], + ], + 'count_to_three' => [ + 'from' => [ + 'two', + ], + 'to' => [ + 'three' + ] + ] + ], + ], + ], +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/workflow_with_specified_events_to_dispatch.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/workflow_with_specified_events_to_dispatch.php new file mode 100644 index 000000000000..259ee5087ff2 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/workflow_with_specified_events_to_dispatch.php @@ -0,0 +1,45 @@ +loadFromExtension('framework', [ + 'workflows' => [ + 'my_workflow' => [ + 'type' => 'state_machine', + 'marking_store' => [ + 'type' => 'method', + 'property' => 'state' + ], + 'supports' => [ + FrameworkExtensionTest::class, + ], + 'events_to_dispatch' => [ + 'workflow.leave', + 'workflow.completed', + ], + 'places' => [ + 'one', + 'two', + 'three', + ], + 'transitions' => [ + 'count_to_two' => [ + 'from' => [ + 'one', + ], + 'to' => [ + 'two', + ], + ], + 'count_to_three' => [ + 'from' => [ + 'two', + ], + 'to' => [ + 'three' + ] + ] + ], + ], + ], +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/workflow_with_no_events_to_dispatch.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/workflow_with_no_events_to_dispatch.xml new file mode 100644 index 000000000000..2f563da4bf96 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/workflow_with_no_events_to_dispatch.xml @@ -0,0 +1,28 @@ + + + + + + + one + + Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTest + + + + + + one + two + + + two + three + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/workflow_with_specified_events_to_dispatch.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/workflow_with_specified_events_to_dispatch.xml new file mode 100644 index 000000000000..d442828f8cfb --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/workflow_with_specified_events_to_dispatch.xml @@ -0,0 +1,29 @@ + + + + + + + one + + Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTest + workflow.leave + workflow.completed + + + + + one + two + + + two + three + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/workflow_with_no_events_to_dispatch.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/workflow_with_no_events_to_dispatch.yml new file mode 100644 index 000000000000..e0a281f27db4 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/workflow_with_no_events_to_dispatch.yml @@ -0,0 +1,22 @@ +framework: + workflows: + my_workflow: + type: state_machine + initial_marking: one + events_to_dispatch: [] + marking_store: + type: method + property: state + supports: + - Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTest + places: + - one + - two + - three + transitions: + count_to_two: + from: one + to: two + count_to_three: + from: two + to: three diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/workflow_with_specified_events_to_dispatch.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/workflow_with_specified_events_to_dispatch.yml new file mode 100644 index 000000000000..d5ff3d5e5fe0 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/workflow_with_specified_events_to_dispatch.yml @@ -0,0 +1,22 @@ +framework: + workflows: + my_workflow: + type: state_machine + initial_marking: one + events_to_dispatch: ['workflow.leave', 'workflow.completed'] + marking_store: + type: method + property: state + supports: + - Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTest + places: + - one + - two + - three + transitions: + count_to_two: + from: one + to: two + count_to_three: + from: two + to: three diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index cfe9f555e77b..7818d3c9c798 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -55,6 +55,7 @@ use Symfony\Component\Validator\DependencyInjection\AddConstraintValidatorsPass; use Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader; use Symfony\Component\Workflow; +use Symfony\Component\Workflow\WorkflowEvents; abstract class FrameworkExtensionTest extends TestCase { @@ -216,6 +217,8 @@ public function testWorkflows() $this->assertTrue($container->hasDefinition('workflow.article'), 'Workflow is registered as a service'); $this->assertSame('workflow.abstract', $container->getDefinition('workflow.article')->getParent()); + $this->assertNull($container->getDefinition('workflow.article')->getArgument('index_4'), 'Workflows has eventsToDispatch=null'); + $this->assertTrue($container->hasDefinition('workflow.article.definition'), 'Workflow definition is registered as a service'); $workflowDefinition = $container->getDefinition('workflow.article.definition'); @@ -423,6 +426,24 @@ public function testWorkflowsNamedExplicitlyEnabled() $this->assertTrue($container->hasDefinition('workflow.workflows.definition')); } + public function testWorkflowsWithNoDispatchedEvents() + { + $container = $this->createContainerFromFile('workflow_with_no_events_to_dispatch'); + + $eventsToDispatch = $container->getDefinition('state_machine.my_workflow')->getArgument('index_4'); + + $this->assertSame([], $eventsToDispatch); + } + + public function testWorkflowsWithSpecifiedDispatchedEvents() + { + $container = $this->createContainerFromFile('workflow_with_specified_events_to_dispatch'); + + $eventsToDispatch = $container->getDefinition('state_machine.my_workflow')->getArgument('index_4'); + + $this->assertSame([WorkflowEvents::LEAVE, WorkflowEvents::COMPLETED], $eventsToDispatch); + } + public function testEnabledPhpErrorsConfig() { $container = $this->createContainerFromFile('php_errors_enabled'); diff --git a/src/Symfony/Component/Workflow/CHANGELOG.md b/src/Symfony/Component/Workflow/CHANGELOG.md index 263f2db5e803..3d44a3b6e83f 100644 --- a/src/Symfony/Component/Workflow/CHANGELOG.md +++ b/src/Symfony/Component/Workflow/CHANGELOG.md @@ -8,6 +8,7 @@ CHANGELOG * Added context to the event dispatched * Dispatch an event when the subject enters in the workflow for the very first time * Added a default context to the previous event + * Added support for specifying which events should be dispatched when calling `workflow->apply()` 5.1.0 ----- diff --git a/src/Symfony/Component/Workflow/Tests/WorkflowTest.php b/src/Symfony/Component/Workflow/Tests/WorkflowTest.php index 85543f928686..1c6a3ebd0327 100644 --- a/src/Symfony/Component/Workflow/Tests/WorkflowTest.php +++ b/src/Symfony/Component/Workflow/Tests/WorkflowTest.php @@ -16,6 +16,7 @@ use Symfony\Component\Workflow\Transition; use Symfony\Component\Workflow\TransitionBlocker; use Symfony\Component\Workflow\Workflow; +use Symfony\Component\Workflow\WorkflowEvents; class WorkflowTest extends TestCase { @@ -451,6 +452,81 @@ public function testApplyWithEventDispatcherForAnnounce(bool $fired, array $cont } } + public function testApplyDispatchesWithDisableEventInContext() + { + $transitions[] = new Transition('a-b', 'a', 'b'); + $transitions[] = new Transition('a-c', 'a', 'c'); + $definition = new Definition(['a', 'b', 'c'], $transitions); + + $subject = new Subject(); + $eventDispatcher = new EventDispatcherMock(); + $workflow = new Workflow($definition, new MethodMarkingStore(), $eventDispatcher, 'workflow_name'); + + $eventNameExpected = [ + 'workflow.guard', + 'workflow.workflow_name.guard', + 'workflow.workflow_name.guard.a-b', + 'workflow.transition', + 'workflow.workflow_name.transition', + 'workflow.workflow_name.transition.a-b', + ]; + + $workflow->apply($subject, 'a-b', [ + Workflow::DISABLE_LEAVE_EVENT => true, + Workflow::DISABLE_ENTER_EVENT => true, + Workflow::DISABLE_ENTERED_EVENT => true, + Workflow::DISABLE_COMPLETED_EVENT => true, + Workflow::DISABLE_ANNOUNCE_EVENT => true, + ]); + + $this->assertSame($eventNameExpected, $eventDispatcher->dispatchedEvents); + } + + public function testApplyDispatchesNoEventsWhenSpecifiedByDefinition() + { + $transitions[] = new Transition('a-b', 'a', 'b'); + $transitions[] = new Transition('a-c', 'a', 'c'); + $definition = new Definition(['a', 'b', 'c'], $transitions); + + $subject = new Subject(); + $eventDispatcher = new EventDispatcherMock(); + $workflow = new Workflow($definition, new MethodMarkingStore(), $eventDispatcher, 'workflow_name', []); + + $eventNameExpected = [ + 'workflow.guard', + 'workflow.workflow_name.guard', + 'workflow.workflow_name.guard.a-b', + ]; + + $workflow->apply($subject, 'a-b'); + + $this->assertSame($eventNameExpected, $eventDispatcher->dispatchedEvents); + } + + public function testApplyOnlyDispatchesEventsThatHaveBeenSpecifiedByDefinition() + { + $transitions[] = new Transition('a-b', 'a', 'b'); + $transitions[] = new Transition('a-c', 'a', 'c'); + $definition = new Definition(['a', 'b', 'c'], $transitions); + + $subject = new Subject(); + $eventDispatcher = new EventDispatcherMock(); + $workflow = new Workflow($definition, new MethodMarkingStore(), $eventDispatcher, 'workflow_name', [WorkflowEvents::COMPLETED]); + + $eventNameExpected = [ + 'workflow.guard', + 'workflow.workflow_name.guard', + 'workflow.workflow_name.guard.a-b', + 'workflow.completed', + 'workflow.workflow_name.completed', + 'workflow.workflow_name.completed.a-b', + ]; + + $workflow->apply($subject, 'a-b'); + + $this->assertSame($eventNameExpected, $eventDispatcher->dispatchedEvents); + } + public function testApplyDoesNotTriggerExtraGuardWithEventDispatcher() { $transitions[] = new Transition('a-b', 'a', 'b'); diff --git a/src/Symfony/Component/Workflow/Workflow.php b/src/Symfony/Component/Workflow/Workflow.php index 4220c4323391..00abecd4f153 100644 --- a/src/Symfony/Component/Workflow/Workflow.php +++ b/src/Symfony/Component/Workflow/Workflow.php @@ -34,20 +34,46 @@ */ class Workflow implements WorkflowInterface { + public const DISABLE_LEAVE_EVENT = 'workflow_disable_leave_event'; + public const DISABLE_TRANSITION_EVENT = 'workflow_disable_transition_event'; + public const DISABLE_ENTER_EVENT = 'workflow_disable_enter_event'; + public const DISABLE_ENTERED_EVENT = 'workflow_disable_entered_event'; + public const DISABLE_COMPLETED_EVENT = 'workflow_disable_completed_event'; public const DISABLE_ANNOUNCE_EVENT = 'workflow_disable_announce_event'; + public const DEFAULT_INITIAL_CONTEXT = ['initial' => true]; + private const DISABLE_EVENTS_MAPPING = [ + WorkflowEvents::LEAVE => self::DISABLE_LEAVE_EVENT, + WorkflowEvents::TRANSITION => self::DISABLE_TRANSITION_EVENT, + WorkflowEvents::ENTER => self::DISABLE_ENTER_EVENT, + WorkflowEvents::ENTERED => self::DISABLE_ENTERED_EVENT, + WorkflowEvents::COMPLETED => self::DISABLE_COMPLETED_EVENT, + WorkflowEvents::ANNOUNCE => self::DISABLE_ANNOUNCE_EVENT, + ]; + private $definition; private $markingStore; private $dispatcher; private $name; - public function __construct(Definition $definition, MarkingStoreInterface $markingStore = null, EventDispatcherInterface $dispatcher = null, string $name = 'unnamed') + /** + * When `null` fire all events (the default behaviour). + * Setting this to an empty array `[]` means no events are dispatched (except the Guard Event). + * Passing an array with WorkflowEvents will allow only those events to be dispatched plus + * the Guard Event. + * + * @var array|string[]|null + */ + private $eventsToDispatch = null; + + public function __construct(Definition $definition, MarkingStoreInterface $markingStore = null, EventDispatcherInterface $dispatcher = null, string $name = 'unnamed', array $eventsToDispatch = null) { $this->definition = $definition; $this->markingStore = $markingStore ?: new MethodMarkingStore(); $this->dispatcher = $dispatcher; $this->name = $name; + $this->eventsToDispatch = $eventsToDispatch; } /** @@ -215,9 +241,7 @@ public function apply(object $subject, string $transitionName, array $context = $this->completed($subject, $transition, $marking, $context); - if (!($context[self::DISABLE_ANNOUNCE_EVENT] ?? false)) { - $this->announce($subject, $transition, $marking, $context); - } + $this->announce($subject, $transition, $marking, $context); } return $marking; @@ -334,7 +358,7 @@ private function leave(object $subject, Transition $transition, Marking $marking { $places = $transition->getFroms(); - if (null !== $this->dispatcher) { + if ($this->shouldDispatchEvent(WorkflowEvents::LEAVE, $context)) { $event = new LeaveEvent($subject, $marking, $transition, $this, $context); $this->dispatcher->dispatch($event, WorkflowEvents::LEAVE); @@ -352,7 +376,7 @@ private function leave(object $subject, Transition $transition, Marking $marking private function transition(object $subject, Transition $transition, Marking $marking, array $context): array { - if (null === $this->dispatcher) { + if (!$this->shouldDispatchEvent(WorkflowEvents::TRANSITION, $context)) { return $context; } @@ -369,7 +393,7 @@ private function enter(object $subject, Transition $transition, Marking $marking { $places = $transition->getTos(); - if (null !== $this->dispatcher) { + if ($this->shouldDispatchEvent(WorkflowEvents::ENTER, $context)) { $event = new EnterEvent($subject, $marking, $transition, $this, $context); $this->dispatcher->dispatch($event, WorkflowEvents::ENTER); @@ -387,7 +411,7 @@ private function enter(object $subject, Transition $transition, Marking $marking private function entered(object $subject, ?Transition $transition, Marking $marking, array $context): void { - if (null === $this->dispatcher) { + if (!$this->shouldDispatchEvent(WorkflowEvents::ENTERED, $context)) { return; } @@ -403,7 +427,7 @@ private function entered(object $subject, ?Transition $transition, Marking $mark private function completed(object $subject, Transition $transition, Marking $marking, array $context): void { - if (null === $this->dispatcher) { + if (!$this->shouldDispatchEvent(WorkflowEvents::COMPLETED, $context)) { return; } @@ -416,7 +440,7 @@ private function completed(object $subject, Transition $transition, Marking $mar private function announce(object $subject, Transition $initialTransition, Marking $marking, array $context): void { - if (null === $this->dispatcher) { + if (!$this->shouldDispatchEvent(WorkflowEvents::ANNOUNCE, $context)) { return; } @@ -429,4 +453,25 @@ private function announce(object $subject, Transition $initialTransition, Markin $this->dispatcher->dispatch($event, sprintf('workflow.%s.announce.%s', $this->name, $transition->getName())); } } + + private function shouldDispatchEvent(string $eventName, array $context): bool + { + if (null === $this->dispatcher) { + return false; + } + + if ($context[self::DISABLE_EVENTS_MAPPING[$eventName]] ?? false) { + return false; + } + + if (null === $this->eventsToDispatch) { + return true; + } + + if ([] === $this->eventsToDispatch) { + return false; + } + + return \in_array($eventName, $this->eventsToDispatch, true); + } } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy