From 06e8b233127eb9ee7428442ee201293da36a5303 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Wed, 24 Nov 2021 13:57:39 +0100 Subject: [PATCH] [Workflow] Allow use of UnitEnum and BackedEnum in workflow places --- .../DependencyInjection/Configuration.php | 63 ++- src/Symfony/Component/Workflow/CHANGELOG.md | 5 + src/Symfony/Component/Workflow/Definition.php | 25 +- .../EventListener/AuditTrailListener.php | 5 +- src/Symfony/Component/Workflow/Marking.php | 33 +- .../MarkingStore/MethodMarkingStore.php | 27 +- .../Workflow/Tests/DefinitionTest.php | 98 ++++- .../EventListener/AuditTrailListenerTest.php | 29 +- .../MarkingStore/MethodMarkingStoreTest.php | 64 +++- .../Component/Workflow/Tests/MarkingTest.php | 33 +- .../Workflow/Tests/TransitionTest.php | 15 +- .../Tests/Utils/PlaceEnumerationUtilsTest.php | 28 ++ .../Workflow/Tests/WorkflowBuilderTrait.php | 67 +++- .../Component/Workflow/Tests/WorkflowTest.php | 360 ++++++++++++------ .../Tests/fixtures/AlphabeticalEnum.php | 14 + .../Workflow/Tests/fixtures/FooEnum.php | 10 + src/Symfony/Component/Workflow/Transition.php | 6 +- .../Workflow/Utils/PlaceEnumerationUtils.php | 43 +++ src/Symfony/Component/Workflow/Workflow.php | 7 +- 19 files changed, 726 insertions(+), 206 deletions(-) create mode 100644 src/Symfony/Component/Workflow/Tests/Utils/PlaceEnumerationUtilsTest.php create mode 100644 src/Symfony/Component/Workflow/Tests/fixtures/AlphabeticalEnum.php create mode 100644 src/Symfony/Component/Workflow/Tests/fixtures/FooEnum.php create mode 100644 src/Symfony/Component/Workflow/Utils/PlaceEnumerationUtils.php diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 18115870fa87e..ee47e9a4a8966 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -396,7 +396,24 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode) ->end() ->arrayNode('initial_marking') ->beforeNormalization()->castToArray()->end() - ->defaultValue([]) + ->beforeNormalization() + ->ifTrue(function ($v) { + foreach ($v as $value) { + if (!is_scalar($value) && !$value instanceof \UnitEnum) { + return true; + } + } + + return false; + }) + ->thenInvalid('Initial marking must either be a scalar or an UnitEnum.') + ->end() + ->beforeNormalization() + ->always() + ->then(function ($places) { + return array_map(static fn ($element) => $element instanceof \UnitEnum ? \get_class($element).'::'.$element->name : $element, $places); + }) + ->end() ->prototype('scalar')->end() ->end() ->variableNode('events_to_dispatch') @@ -431,9 +448,9 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode) ->always() ->then(function ($places) { // It's an indexed array of shape ['place1', 'place2'] - if (isset($places[0]) && \is_string($places[0])) { - return array_map(function (string $place) { - return ['name' => $place]; + if (isset($places[0]) && (\is_string($places[0]) || $places[0] instanceof \UnitEnum)) { + return array_map(function (string|\UnitEnum $place) { + return ['name' => $place instanceof \UnitEnum ? \get_class($place).'::'.$place->name : $place]; }, $places); } @@ -505,9 +522,24 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode) ->example('is_fully_authenticated() and is_granted(\'ROLE_JOURNALIST\') and subject.getTitle() == \'My first article\'') ->end() ->arrayNode('from') + ->beforeNormalization()->castToArray()->end() + ->beforeNormalization() + ->ifTrue(function ($v) { + foreach ($v as $value) { + if (!is_scalar($value) && !$value instanceof \UnitEnum) { + return true; + } + } + + return false; + }) + ->thenInvalid('"From" places must either be scalars or UnitEnum.') + ->end() ->beforeNormalization() - ->ifString() - ->then(function ($v) { return [$v]; }) + ->always() + ->then(function ($places) { + return array_map(static fn ($element) => $element instanceof \UnitEnum ? \get_class($element).'::'.$element->name : $element, $places); + }) ->end() ->requiresAtLeastOneElement() ->prototype('scalar') @@ -515,9 +547,24 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode) ->end() ->end() ->arrayNode('to') + ->beforeNormalization()->castToArray()->end() + ->beforeNormalization() + ->ifTrue(function ($v) { + foreach ($v as $value) { + if (!is_scalar($value) && !$value instanceof \UnitEnum) { + return true; + } + } + + return false; + }) + ->thenInvalid('"To" places must either be scalars or UnitEnum.') + ->end() ->beforeNormalization() - ->ifString() - ->then(function ($v) { return [$v]; }) + ->always() + ->then(function ($places) { + return array_map(static fn ($element) => $element instanceof \UnitEnum ? \get_class($element).'::'.$element->name : $element, $places); + }) ->end() ->requiresAtLeastOneElement() ->prototype('scalar') diff --git a/src/Symfony/Component/Workflow/CHANGELOG.md b/src/Symfony/Component/Workflow/CHANGELOG.md index b533107a8f9b8..6ad8b4641101f 100644 --- a/src/Symfony/Component/Workflow/CHANGELOG.md +++ b/src/Symfony/Component/Workflow/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.1 +--- + + * Allow use of UnitEnum and BackedEnum in workflow places + 6.0 --- diff --git a/src/Symfony/Component/Workflow/Definition.php b/src/Symfony/Component/Workflow/Definition.php index 45de623d5a386..eafa720aebe71 100644 --- a/src/Symfony/Component/Workflow/Definition.php +++ b/src/Symfony/Component/Workflow/Definition.php @@ -14,6 +14,7 @@ use Symfony\Component\Workflow\Exception\LogicException; use Symfony\Component\Workflow\Metadata\InMemoryMetadataStore; use Symfony\Component\Workflow\Metadata\MetadataStoreInterface; +use Symfony\Component\Workflow\Utils\PlaceEnumerationUtils; /** * @author Fabien Potencier @@ -28,11 +29,11 @@ final class Definition private MetadataStoreInterface $metadataStore; /** - * @param string[] $places - * @param Transition[] $transitions - * @param string|string[]|null $initialPlaces + * @param string[]|\UnitEnum[] $places + * @param Transition[] $transitions + * @param string|string[]|\UnitEnum|\UnitEnum[]|null $initialPlaces */ - public function __construct(array $places, array $transitions, string|array $initialPlaces = null, MetadataStoreInterface $metadataStore = null) + public function __construct(array $places, array $transitions, string|\UnitEnum|array $initialPlaces = null, MetadataStoreInterface $metadataStore = null) { foreach ($places as $place) { $this->addPlace($place); @@ -52,7 +53,7 @@ public function __construct(array $places, array $transitions, string|array $ini */ public function getInitialPlaces(): array { - return $this->initialPlaces; + return array_map(static fn ($element) => PlaceEnumerationUtils::getTypedValue($element), $this->initialPlaces); } /** @@ -76,30 +77,30 @@ public function getMetadataStore(): MetadataStoreInterface return $this->metadataStore; } - private function setInitialPlaces(string|array $places = null) + private function setInitialPlaces(string|\UnitEnum|array $places = null) { if (!$places) { return; } - $places = (array) $places; + $places = array_map(static fn ($element) => PlaceEnumerationUtils::getPlaceKey($element), \is_array($places) ? $places : [$places]); foreach ($places as $place) { if (!isset($this->places[$place])) { - throw new LogicException(sprintf('Place "%s" cannot be the initial place as it does not exist.', $place)); + throw new LogicException(sprintf('Place "%s" cannot be the initial place as it does not exist.', PlaceEnumerationUtils::getPlaceKey($place))); } } $this->initialPlaces = $places; } - private function addPlace(string $place) + private function addPlace(string|\UnitEnum $place) { if (!\count($this->places)) { - $this->initialPlaces = [$place]; + $this->initialPlaces = [PlaceEnumerationUtils::getPlaceKey($place)]; } - $this->places[$place] = $place; + $this->places[PlaceEnumerationUtils::getPlaceKey($place)] = $place; } private function addTransition(Transition $transition) @@ -107,12 +108,14 @@ private function addTransition(Transition $transition) $name = $transition->getName(); foreach ($transition->getFroms() as $from) { + $from = PlaceEnumerationUtils::getPlaceKey($from); if (!isset($this->places[$from])) { throw new LogicException(sprintf('Place "%s" referenced in transition "%s" does not exist.', $from, $name)); } } foreach ($transition->getTos() as $to) { + $to = PlaceEnumerationUtils::getPlaceKey($to); if (!isset($this->places[$to])) { throw new LogicException(sprintf('Place "%s" referenced in transition "%s" does not exist.', $to, $name)); } diff --git a/src/Symfony/Component/Workflow/EventListener/AuditTrailListener.php b/src/Symfony/Component/Workflow/EventListener/AuditTrailListener.php index 8a7ea374c90aa..7706d0a3dc732 100644 --- a/src/Symfony/Component/Workflow/EventListener/AuditTrailListener.php +++ b/src/Symfony/Component/Workflow/EventListener/AuditTrailListener.php @@ -14,6 +14,7 @@ use Psr\Log\LoggerInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Workflow\Event\Event; +use Symfony\Component\Workflow\Utils\PlaceEnumerationUtils; /** * @author Grégoire Pineau @@ -30,7 +31,7 @@ public function __construct(LoggerInterface $logger) public function onLeave(Event $event) { foreach ($event->getTransition()->getFroms() as $place) { - $this->logger->info(sprintf('Leaving "%s" for subject of class "%s" in workflow "%s".', $place, \get_class($event->getSubject()), $event->getWorkflowName())); + $this->logger->info(sprintf('Leaving "%s" for subject of class "%s" in workflow "%s".', PlaceEnumerationUtils::getPlaceKey($place), \get_class($event->getSubject()), $event->getWorkflowName())); } } @@ -42,7 +43,7 @@ public function onTransition(Event $event) public function onEnter(Event $event) { foreach ($event->getTransition()->getTos() as $place) { - $this->logger->info(sprintf('Entering "%s" for subject of class "%s" in workflow "%s".', $place, \get_class($event->getSubject()), $event->getWorkflowName())); + $this->logger->info(sprintf('Entering "%s" for subject of class "%s" in workflow "%s".', PlaceEnumerationUtils::getPlaceKey($place), \get_class($event->getSubject()), $event->getWorkflowName())); } } diff --git a/src/Symfony/Component/Workflow/Marking.php b/src/Symfony/Component/Workflow/Marking.php index 36deb2d4086c6..893696bfabe56 100644 --- a/src/Symfony/Component/Workflow/Marking.php +++ b/src/Symfony/Component/Workflow/Marking.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Workflow; +use Symfony\Component\Workflow\Utils\PlaceEnumerationUtils; + /** * Marking contains the place of every tokens. * @@ -22,33 +24,44 @@ class Marking private ?array $context = null; /** - * @param int[] $representation Keys are the place name and values should be 1 + * @param int[]|\UnitEnum[] $representation Keys are the place name and values should be 1, unless UnitEnums + * are used as workflow places */ public function __construct(array $representation = []) { - foreach ($representation as $place => $nbToken) { - $this->mark($place); + foreach ($representation as $place => $token) { + $this->mark($token instanceof \UnitEnum ? $token : $place); } } - public function mark(string $place) + public function mark(string|\UnitEnum $place) { - $this->places[$place] = 1; + $this->places[PlaceEnumerationUtils::getPlaceKey($place)] = 1; } - public function unmark(string $place) + public function unmark(string|\UnitEnum $place) { - unset($this->places[$place]); + unset($this->places[PlaceEnumerationUtils::getPlaceKey($place)]); } - public function has(string $place) + public function has(string|\UnitEnum $place) { - return isset($this->places[$place]); + return isset($this->places[PlaceEnumerationUtils::getPlaceKey($place)]); } public function getPlaces() { - return $this->places; + $places = []; + foreach ($this->places as $key => $value) { + $typedKey = PlaceEnumerationUtils::getTypedValue($key); + if ($typedKey instanceof \UnitEnum) { + $places[$key] = $typedKey; + } else { + $places[$typedKey] = 1; + } + } + + return $places; } /** diff --git a/src/Symfony/Component/Workflow/MarkingStore/MethodMarkingStore.php b/src/Symfony/Component/Workflow/MarkingStore/MethodMarkingStore.php index 93c5302032c4b..f70b10904f287 100644 --- a/src/Symfony/Component/Workflow/MarkingStore/MethodMarkingStore.php +++ b/src/Symfony/Component/Workflow/MarkingStore/MethodMarkingStore.php @@ -13,6 +13,7 @@ use Symfony\Component\Workflow\Exception\LogicException; use Symfony\Component\Workflow\Marking; +use Symfony\Component\Workflow\Utils\PlaceEnumerationUtils; /** * MethodMarkingStore stores the marking with a subject's method. @@ -61,10 +62,17 @@ public function getMarking(object $subject): Marking } if ($this->singleState) { - $marking = [(string) $marking => 1]; + $markingRepresentation = [PlaceEnumerationUtils::getPlaceKey($marking) => 1]; + } else { + $markingRepresentation = []; + foreach ($marking as $key => $item) { + // When using enumerations, as the enumeration case can't be used as an array key, the value is actually + // stored in the item instead of the key. + $markingRepresentation[PlaceEnumerationUtils::getPlaceKey($item instanceof \UnitEnum ? $item : $key)] = 1; + } } - return new Marking($marking); + return new Marking($markingRepresentation); } /** @@ -75,7 +83,18 @@ public function setMarking(object $subject, Marking $marking, array $context = [ $marking = $marking->getPlaces(); if ($this->singleState) { - $marking = key($marking); + $markingResult = PlaceEnumerationUtils::getTypedValue(key($marking)); + } else { + $markingResult = []; + foreach ($marking as $key => $item) { + $value = PlaceEnumerationUtils::getTypedValue($key); + if ($value instanceof \UnitEnum) { + // UnitEnum can't be used as array key, put it as a simple value without specific index. + $markingResult[] = $value; + } else { + $markingResult[$value] = 1; + } + } } $method = 'set'.ucfirst($this->property); @@ -84,6 +103,6 @@ public function setMarking(object $subject, Marking $marking, array $context = [ throw new LogicException(sprintf('The method "%s::%s()" does not exist.', get_debug_type($subject), $method)); } - $subject->{$method}($marking, $context); + $subject->{$method}($markingResult, $context); } } diff --git a/src/Symfony/Component/Workflow/Tests/DefinitionTest.php b/src/Symfony/Component/Workflow/Tests/DefinitionTest.php index ed6e7d38ba8d0..6ba5441c98605 100644 --- a/src/Symfony/Component/Workflow/Tests/DefinitionTest.php +++ b/src/Symfony/Component/Workflow/Tests/DefinitionTest.php @@ -5,11 +5,12 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Workflow\Definition; use Symfony\Component\Workflow\Exception\LogicException; +use Symfony\Component\Workflow\Tests\fixtures\FooEnum; use Symfony\Component\Workflow\Transition; class DefinitionTest extends TestCase { - public function testAddPlaces() + public function testAddPlacesAsString() { $places = range('a', 'e'); $definition = new Definition($places, []); @@ -19,7 +20,20 @@ public function testAddPlaces() $this->assertEquals(['a'], $definition->getInitialPlaces()); } - public function testSetInitialPlace() + /** + * @requires PHP 8.1 + */ + public function testAddPlacesAsEnum() + { + $places = FooEnum::cases(); + $definition = new Definition($places, []); + + $this->assertCount(\count(FooEnum::cases()), $definition->getPlaces()); + + $this->assertEquals([FooEnum::Bar], $definition->getInitialPlaces()); + } + + public function testSetInitialPlaceAsString() { $places = range('a', 'e'); $definition = new Definition($places, [], $places[3]); @@ -27,7 +41,18 @@ public function testSetInitialPlace() $this->assertEquals([$places[3]], $definition->getInitialPlaces()); } - public function testSetInitialPlaces() + /** + * @requires PHP 8.1 + */ + public function testSetInitialPlaceAsEnum() + { + $places = FooEnum::cases(); + $definition = new Definition($places, [], FooEnum::Baz); + + $this->assertEquals([FooEnum::Baz], $definition->getInitialPlaces()); + } + + public function testSetInitialPlacesAsString() { $places = range('a', 'e'); $definition = new Definition($places, [], ['a', 'e']); @@ -35,14 +60,35 @@ public function testSetInitialPlaces() $this->assertEquals(['a', 'e'], $definition->getInitialPlaces()); } - public function testSetInitialPlaceAndPlaceIsNotDefined() + /** + * @requires PHP 8.1 + */ + public function testSetInitialPlacesAsEnum() + { + $places = FooEnum::cases(); + $definition = new Definition($places, [], [FooEnum::Bar, FooEnum::Qux]); + + $this->assertEquals([FooEnum::Bar, FooEnum::Qux], $definition->getInitialPlaces()); + } + + public function testSetInitialPlaceAsStringAndPlaceIsNotDefined() { $this->expectException(LogicException::class); $this->expectExceptionMessage('Place "d" cannot be the initial place as it does not exist.'); new Definition([], [], 'd'); } - public function testAddTransition() + /** + * @requires PHP 8.1 + */ + public function testSetInitialPlaceAsEnumAndPlaceIsNotDefined() + { + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Place "Symfony\Component\Workflow\Tests\fixtures\FooEnum::Bar" cannot be the initial place as it does not exist.'); + new Definition([], [], FooEnum::Bar); + } + + public function testAddTransitionWithStringPlaces() { $places = range('a', 'b'); @@ -53,7 +99,21 @@ public function testAddTransition() $this->assertSame($transition, $definition->getTransitions()[0]); } - public function testAddTransitionAndFromPlaceIsNotDefined() + /** + * @requires PHP 8.1 + */ + public function testAddTransitionWithEnumPlaces() + { + $places = FooEnum::cases(); + + $transition = new Transition('name', $places[0], $places[1]); + $definition = new Definition($places, [$transition]); + + $this->assertCount(1, $definition->getTransitions()); + $this->assertSame($transition, $definition->getTransitions()[0]); + } + + public function testAddTransitionAndFromPlaceAsStringIsNotDefined() { $this->expectException(LogicException::class); $this->expectExceptionMessage('Place "c" referenced in transition "name" does not exist.'); @@ -62,7 +122,19 @@ public function testAddTransitionAndFromPlaceIsNotDefined() new Definition($places, [new Transition('name', 'c', $places[1])]); } - public function testAddTransitionAndToPlaceIsNotDefined() + /** + * @requires PHP 8.1 + */ + public function testAddTransitionAndFromPlaceAsEnumIsNotDefined() + { + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Place "Symfony\Component\Workflow\Tests\fixtures\FooEnum::Qux" referenced in transition "name" does not exist.'); + $places = [FooEnum::Bar, FooEnum::Baz]; + + new Definition($places, [new Transition('name', FooEnum::Qux, $places[1])]); + } + + public function testAddTransitionAndToPlaceAsStringIsNotDefined() { $this->expectException(LogicException::class); $this->expectExceptionMessage('Place "c" referenced in transition "name" does not exist.'); @@ -70,4 +142,16 @@ public function testAddTransitionAndToPlaceIsNotDefined() new Definition($places, [new Transition('name', $places[0], 'c')]); } + + /** + * @requires PHP 8.1 + */ + public function testAddTransitionAndToPlaceAsEnumIsNotDefined() + { + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Place "Symfony\Component\Workflow\Tests\fixtures\FooEnum::Qux" referenced in transition "name" does not exist.'); + $places = [FooEnum::Bar, FooEnum::Baz]; + + new Definition($places, [new Transition('name', $places[0], FooEnum::Qux)]); + } } diff --git a/src/Symfony/Component/Workflow/Tests/EventListener/AuditTrailListenerTest.php b/src/Symfony/Component/Workflow/Tests/EventListener/AuditTrailListenerTest.php index 0416e7a9db83c..ee5d0230789e9 100644 --- a/src/Symfony/Component/Workflow/Tests/EventListener/AuditTrailListenerTest.php +++ b/src/Symfony/Component/Workflow/Tests/EventListener/AuditTrailListenerTest.php @@ -15,7 +15,7 @@ class AuditTrailListenerTest extends TestCase { use WorkflowBuilderTrait; - public function testItWorks() + public function testItWorksWithStrings() { $definition = $this->createSimpleWorkflowDefinition(); @@ -38,6 +38,33 @@ public function testItWorks() $this->assertSame($expected, $logger->logs); } + + /** + * @requires PHP 8.1 + */ + public function testItWorksWithEnumerations() + { + $definition = $this->createSimpleWorkflowDefinition(true); + + $object = new Subject(); + + $logger = new Logger(); + + $ed = new EventDispatcher(); + $ed->addSubscriber(new AuditTrailListener($logger)); + + $workflow = new Workflow($definition, new MethodMarkingStore(), $ed); + + $workflow->apply($object, 't1'); + + $expected = [ + 'Leaving "Symfony\Component\Workflow\Tests\fixtures\AlphabeticalEnum::A" for subject of class "Symfony\Component\Workflow\Tests\Subject" in workflow "unnamed".', + 'Transition "t1" for subject of class "Symfony\Component\Workflow\Tests\Subject" in workflow "unnamed".', + 'Entering "Symfony\Component\Workflow\Tests\fixtures\AlphabeticalEnum::B" for subject of class "Symfony\Component\Workflow\Tests\Subject" in workflow "unnamed".', + ]; + + $this->assertSame($expected, $logger->logs); + } } class Logger extends AbstractLogger diff --git a/src/Symfony/Component/Workflow/Tests/MarkingStore/MethodMarkingStoreTest.php b/src/Symfony/Component/Workflow/Tests/MarkingStore/MethodMarkingStoreTest.php index 96244577e18d8..6ed0214564bdf 100644 --- a/src/Symfony/Component/Workflow/Tests/MarkingStore/MethodMarkingStoreTest.php +++ b/src/Symfony/Component/Workflow/Tests/MarkingStore/MethodMarkingStoreTest.php @@ -5,11 +5,16 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Workflow\Marking; use Symfony\Component\Workflow\MarkingStore\MethodMarkingStore; +use Symfony\Component\Workflow\Tests\fixtures\FooEnum; use Symfony\Component\Workflow\Tests\Subject; +use Symfony\Component\Workflow\Utils\PlaceEnumerationUtils; class MethodMarkingStoreTest extends TestCase { - public function testGetSetMarkingWithMultipleState() + /** + * @dataProvider providePlaceAndExpectedResultForMultipleState + */ + public function testGetSetMarkingWithMultipleState(string|\UnitEnum $place, array $expectedResult) { $subject = new Subject(); @@ -20,18 +25,30 @@ public function testGetSetMarkingWithMultipleState() $this->assertInstanceOf(Marking::class, $marking); $this->assertCount(0, $marking->getPlaces()); - $marking->mark('first_place'); + $marking->mark($place); $markingStore->setMarking($subject, $marking); - $this->assertSame(['first_place' => 1], $subject->getMarking()); + $this->assertSame($expectedResult, $subject->getMarking()); $marking2 = $markingStore->getMarking($subject); $this->assertEquals($marking, $marking2); } - public function testGetSetMarkingWithSingleState() + public function providePlaceAndExpectedResultForMultipleState(): \Generator + { + yield ['first_place', ['first_place' => 1]]; + + if (\PHP_VERSION_ID >= 80100) { + yield [FooEnum::Bar, [FooEnum::Bar]]; + } + } + + /** + * @dataProvider providePlaceAndExpectedResultForSingleState + */ + public function testGetSetMarkingWithSingleState(string|\UnitEnum $place) { $subject = new Subject(); @@ -42,17 +59,26 @@ public function testGetSetMarkingWithSingleState() $this->assertInstanceOf(Marking::class, $marking); $this->assertCount(0, $marking->getPlaces()); - $marking->mark('first_place'); + $marking->mark($place); $markingStore->setMarking($subject, $marking); - $this->assertSame('first_place', $subject->getMarking()); + $this->assertSame($place, $subject->getMarking()); $marking2 = $markingStore->getMarking($subject); $this->assertEquals($marking, $marking2); } + public function providePlaceAndExpectedResultForSingleState(): \Generator + { + yield ['first_place']; + + if (\PHP_VERSION_ID >= 80100) { + yield [FooEnum::Bar]; + } + } + public function testGetSetMarkingWithSingleStateAndAlmostEmptyPlaceName() { $subject = new Subject(0); @@ -65,9 +91,12 @@ public function testGetSetMarkingWithSingleStateAndAlmostEmptyPlaceName() $this->assertCount(1, $marking->getPlaces()); } - public function testGetMarkingWithValueObject() + /** + * @dataProvider provideValueObjectMarkingValue + */ + public function testGetMarkingWithValueObject(string|\UnitEnum $value) { - $subject = new Subject($this->createValueObject('first_place')); + $subject = new Subject($this->createValueObject($value)); $markingStore = new MethodMarkingStore(true); @@ -75,23 +104,32 @@ public function testGetMarkingWithValueObject() $this->assertInstanceOf(Marking::class, $marking); $this->assertCount(1, $marking->getPlaces()); - $this->assertSame('first_place', (string) $subject->getMarking()); + $this->assertSame(PlaceEnumerationUtils::getPlaceKey($value), (string) $subject->getMarking()); + } + + public function provideValueObjectMarkingValue(): \Generator + { + yield ['first_place']; + + if (\PHP_VERSION_ID >= 80100) { + yield [FooEnum::Bar]; + } } - private function createValueObject(string $markingValue) + private function createValueObject(string|\UnitEnum $markingValue) { return new class($markingValue) { - /** @var string */ + /** @var string|\UnitEnum */ private $markingValue; - public function __construct(string $markingValue) + public function __construct(string|\UnitEnum $markingValue) { $this->markingValue = $markingValue; } public function __toString(): string { - return $this->markingValue; + return PlaceEnumerationUtils::getPlaceKey($this->markingValue); } }; } diff --git a/src/Symfony/Component/Workflow/Tests/MarkingTest.php b/src/Symfony/Component/Workflow/Tests/MarkingTest.php index 9ed6df041c3b7..ba8df1b8f62a2 100644 --- a/src/Symfony/Component/Workflow/Tests/MarkingTest.php +++ b/src/Symfony/Component/Workflow/Tests/MarkingTest.php @@ -4,10 +4,11 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Workflow\Marking; +use Symfony\Component\Workflow\Tests\fixtures\FooEnum; class MarkingTest extends TestCase { - public function testMarking() + public function testMarkingWithPlacesAsString() { $marking = new Marking(['a' => 1]); @@ -33,4 +34,34 @@ public function testMarking() $this->assertFalse($marking->has('b')); $this->assertSame([], $marking->getPlaces()); } + + /** + * @requires PHP 8.1 + */ + public function testMarkingWithPlacesAsEnumerations() + { + $marking = new Marking([FooEnum::Bar]); + + $this->assertTrue($marking->has(FooEnum::Bar)); + $this->assertFalse($marking->has(FooEnum::Baz)); + $this->assertSame(['Symfony\Component\Workflow\Tests\fixtures\FooEnum::Bar' => FooEnum::Bar], $marking->getPlaces()); + + $marking->mark(FooEnum::Baz); + + $this->assertTrue($marking->has(FooEnum::Bar)); + $this->assertTrue($marking->has(FooEnum::Baz)); + $this->assertSame(['Symfony\Component\Workflow\Tests\fixtures\FooEnum::Bar' => FooEnum::Bar, 'Symfony\Component\Workflow\Tests\fixtures\FooEnum::Baz' => FooEnum::Baz], $marking->getPlaces()); + + $marking->unmark(FooEnum::Bar); + + $this->assertFalse($marking->has(FooEnum::Bar)); + $this->assertTrue($marking->has(FooEnum::Baz)); + $this->assertSame(['Symfony\Component\Workflow\Tests\fixtures\FooEnum::Baz' => FooEnum::Baz], $marking->getPlaces()); + + $marking->unmark(FooEnum::Baz); + + $this->assertFalse($marking->has(FooEnum::Bar)); + $this->assertFalse($marking->has(FooEnum::Baz)); + $this->assertSame([], $marking->getPlaces()); + } } diff --git a/src/Symfony/Component/Workflow/Tests/TransitionTest.php b/src/Symfony/Component/Workflow/Tests/TransitionTest.php index 14a646d509927..504dc305fce2d 100644 --- a/src/Symfony/Component/Workflow/Tests/TransitionTest.php +++ b/src/Symfony/Component/Workflow/Tests/TransitionTest.php @@ -3,11 +3,12 @@ namespace Symfony\Component\Workflow\Tests; use PHPUnit\Framework\TestCase; +use Symfony\Component\Workflow\Tests\fixtures\FooEnum; use Symfony\Component\Workflow\Transition; class TransitionTest extends TestCase { - public function testConstructor() + public function testConstructorWithStrings() { $transition = new Transition('name', 'a', 'b'); @@ -15,4 +16,16 @@ public function testConstructor() $this->assertSame(['a'], $transition->getFroms()); $this->assertSame(['b'], $transition->getTos()); } + + /** + * @requires PHP 8.1 + */ + public function testConstructorWithEnumerations() + { + $transition = new Transition('name', FooEnum::Bar, FooEnum::Baz); + + $this->assertSame('name', $transition->getName()); + $this->assertSame([FooEnum::Bar], $transition->getFroms()); + $this->assertSame([FooEnum::Baz], $transition->getTos()); + } } diff --git a/src/Symfony/Component/Workflow/Tests/Utils/PlaceEnumerationUtilsTest.php b/src/Symfony/Component/Workflow/Tests/Utils/PlaceEnumerationUtilsTest.php new file mode 100644 index 0000000000000..86938fdf74ca1 --- /dev/null +++ b/src/Symfony/Component/Workflow/Tests/Utils/PlaceEnumerationUtilsTest.php @@ -0,0 +1,28 @@ +assertSame('my_place', PlaceEnumerationUtils::getPlaceKey('my_place')); + + if (\PHP_VERSION_ID >= 80100) { + $this->assertSame('Symfony\Component\Workflow\Tests\fixtures\FooEnum::Bar', PlaceEnumerationUtils::getPlaceKey(FooEnum::Bar)); + } + } + + public function testTypedValue() + { + $this->assertSame('my_place', PlaceEnumerationUtils::getTypedValue('my_place')); + + if (\PHP_VERSION_ID >= 80100) { + $this->assertSame(FooEnum::Bar, PlaceEnumerationUtils::getTypedValue('Symfony\Component\Workflow\Tests\fixtures\FooEnum::Bar')); + } + } +} diff --git a/src/Symfony/Component/Workflow/Tests/WorkflowBuilderTrait.php b/src/Symfony/Component/Workflow/Tests/WorkflowBuilderTrait.php index ae48d52d07ee5..6889aab44e4f0 100644 --- a/src/Symfony/Component/Workflow/Tests/WorkflowBuilderTrait.php +++ b/src/Symfony/Component/Workflow/Tests/WorkflowBuilderTrait.php @@ -4,22 +4,29 @@ use Symfony\Component\Workflow\Definition; use Symfony\Component\Workflow\Metadata\InMemoryMetadataStore; +use Symfony\Component\Workflow\Tests\fixtures\AlphabeticalEnum; use Symfony\Component\Workflow\Transition; trait WorkflowBuilderTrait { - private function createComplexWorkflowDefinition() + private function createComplexWorkflowDefinition(bool $useEnumerations = false) { - $places = range('a', 'g'); + $places = $useEnumerations ? AlphabeticalEnum::cases() : range('a', 'g'); $transitions = []; - $transitions[] = new Transition('t1', 'a', ['b', 'c']); - $transitions[] = new Transition('t2', ['b', 'c'], 'd'); - $transitionWithMetadataDumpStyle = new Transition('t3', 'd', 'e'); + $transitions[] = new Transition('t1', $this->getTypedPlaceValue('a', $useEnumerations), [ + $this->getTypedPlaceValue('b', $useEnumerations), + $this->getTypedPlaceValue('c', $useEnumerations), + ]); + $transitions[] = new Transition('t2', [ + $this->getTypedPlaceValue('b', $useEnumerations), + $this->getTypedPlaceValue('c', $useEnumerations), + ], $this->getTypedPlaceValue('d', $useEnumerations)); + $transitionWithMetadataDumpStyle = new Transition('t3', $this->getTypedPlaceValue('d', $useEnumerations), $this->getTypedPlaceValue('e', $useEnumerations)); $transitions[] = $transitionWithMetadataDumpStyle; - $transitions[] = new Transition('t4', 'd', 'f'); - $transitions[] = new Transition('t5', 'e', 'g'); - $transitions[] = new Transition('t6', 'f', 'g'); + $transitions[] = new Transition('t4', $this->getTypedPlaceValue('d', $useEnumerations), $this->getTypedPlaceValue('f', $useEnumerations)); + $transitions[] = new Transition('t5', $this->getTypedPlaceValue('e', $useEnumerations), $this->getTypedPlaceValue('g', $useEnumerations)); + $transitions[] = new Transition('t6', $this->getTypedPlaceValue('f', $useEnumerations), $this->getTypedPlaceValue('g', $useEnumerations)); $transitionsMetadata = new \SplObjectStorage(); $transitionsMetadata[$transitionWithMetadataDumpStyle] = [ @@ -43,18 +50,18 @@ private function createComplexWorkflowDefinition() // +----+ +----+ +----+ +----+ } - private function createSimpleWorkflowDefinition() + private function createSimpleWorkflowDefinition(bool $useEnumerations = false) { - $places = range('a', 'c'); + $places = $useEnumerations ? AlphabeticalEnum::cases() : range('a', 'c'); $transitions = []; - $transitionWithMetadataDumpStyle = new Transition('t1', 'a', 'b'); + $transitionWithMetadataDumpStyle = new Transition('t1', $this->getTypedPlaceValue('a', $useEnumerations), $this->getTypedPlaceValue('b', $useEnumerations)); $transitions[] = $transitionWithMetadataDumpStyle; - $transitionWithMetadataArrowColorPink = new Transition('t2', 'b', 'c'); + $transitionWithMetadataArrowColorPink = new Transition('t2', $this->getTypedPlaceValue('b', $useEnumerations), $this->getTypedPlaceValue('c', $useEnumerations)); $transitions[] = $transitionWithMetadataArrowColorPink; $placesMetadata = []; - $placesMetadata['c'] = [ + $placesMetadata[$this->getPlaceKey($useEnumerations ? AlphabeticalEnum::C : 'c')] = [ 'bg_color' => 'DeepSkyBlue', 'description' => 'My custom place description', ]; @@ -78,15 +85,18 @@ private function createSimpleWorkflowDefinition() // +---+ +----+ +---+ +----+ +---+ } - private function createWorkflowWithSameNameTransition() + private function createWorkflowWithSameNameTransition(bool $useEnumerations = false) { - $places = range('a', 'c'); + $places = $useEnumerations ? AlphabeticalEnum::cases() : range('a', 'c'); $transitions = []; - $transitions[] = new Transition('a_to_bc', 'a', ['b', 'c']); - $transitions[] = new Transition('b_to_c', 'b', 'c'); - $transitions[] = new Transition('to_a', 'b', 'a'); - $transitions[] = new Transition('to_a', 'c', 'a'); + $transitions[] = new Transition('a_to_bc', $this->getTypedPlaceValue('a', $useEnumerations), [ + $this->getTypedPlaceValue('b', $useEnumerations), + $this->getTypedPlaceValue('c', $useEnumerations), + ]); + $transitions[] = new Transition('b_to_c', $this->getTypedPlaceValue('b', $useEnumerations), $this->getTypedPlaceValue('c', $useEnumerations)); + $transitions[] = new Transition('to_a', $this->getTypedPlaceValue('b', $useEnumerations), $this->getTypedPlaceValue('a', $useEnumerations)); + $transitions[] = new Transition('to_a', $this->getTypedPlaceValue('c', $useEnumerations), $this->getTypedPlaceValue('a', $useEnumerations)); return new Definition($places, $transitions); @@ -144,4 +154,23 @@ private function createComplexStateMachineDefinition() // | d | -------------+ // +-----+ } + + private function getTypedPlaceValue(string $value, bool $useEnumeration = false): string|AlphabeticalEnum + { + if ($useEnumeration) { + $value = AlphabeticalEnum::tryFrom($value) ?? $value; + } + + return $value; + } + + private function getPlaceKey(string|\UnitEnum $value): string + { + return $value instanceof \UnitEnum ? \get_class($value).'::'.$value->name : $value; + } + + private function getPlaceEventSuffix(string $value, bool $useEnumerations): string + { + return $useEnumerations ? $this->getPlaceKey(AlphabeticalEnum::tryFrom($value)) : $value; + } } diff --git a/src/Symfony/Component/Workflow/Tests/WorkflowTest.php b/src/Symfony/Component/Workflow/Tests/WorkflowTest.php index 75885c0bb9e85..08c13c4342aec 100644 --- a/src/Symfony/Component/Workflow/Tests/WorkflowTest.php +++ b/src/Symfony/Component/Workflow/Tests/WorkflowTest.php @@ -13,6 +13,7 @@ use Symfony\Component\Workflow\Exception\UndefinedTransitionException; use Symfony\Component\Workflow\Marking; use Symfony\Component\Workflow\MarkingStore\MethodMarkingStore; +use Symfony\Component\Workflow\Tests\fixtures\AlphabeticalEnum; use Symfony\Component\Workflow\Transition; use Symfony\Component\Workflow\TransitionBlocker; use Symfony\Component\Workflow\Workflow; @@ -43,31 +44,39 @@ public function testGetMarkingWithImpossiblePlace() $workflow->getMarking($subject); } - public function testGetMarkingWithEmptyInitialMarking() + /** + * @dataProvider provideUseEnumerations + */ + public function testGetMarkingWithEmptyInitialMarking(bool $useEnumerations) { - $definition = $this->createComplexWorkflowDefinition(); + $definition = $this->createComplexWorkflowDefinition($useEnumerations); $subject = new Subject(); $workflow = new Workflow($definition, new MethodMarkingStore()); $marking = $workflow->getMarking($subject); $this->assertInstanceOf(Marking::class, $marking); - $this->assertTrue($marking->has('a')); - $this->assertSame(['a' => 1], $subject->getMarking()); + $this->assertTrue($marking->has($this->getTypedPlaceValue('a', $useEnumerations))); + $this->assertSame($useEnumerations ? [AlphabeticalEnum::A] : ['a' => 1], $subject->getMarking()); } - public function testGetMarkingWithExistingMarking() + /** + * @dataProvider provideUseEnumerations + */ + public function testGetMarkingWithExistingMarking(bool $useEnumerations) { - $definition = $this->createComplexWorkflowDefinition(); + $definition = $this->createComplexWorkflowDefinition($useEnumerations); $subject = new Subject(); - $subject->setMarking(['b' => 1, 'c' => 1]); + $subject->setMarking($useEnumerations ? [AlphabeticalEnum::B, AlphabeticalEnum::C] : ['b' => 1, 'c' => 1]); + $workflow = new Workflow($definition, new MethodMarkingStore()); $marking = $workflow->getMarking($subject); $this->assertInstanceOf(Marking::class, $marking); - $this->assertTrue($marking->has('b')); - $this->assertTrue($marking->has('c')); + + $this->assertTrue($marking->has($this->getTypedPlaceValue('b', $useEnumerations))); + $this->assertTrue($marking->has($this->getTypedPlaceValue('c', $useEnumerations))); } public function testCanWithUnexistingTransition() @@ -79,36 +88,42 @@ public function testCanWithUnexistingTransition() $this->assertFalse($workflow->can($subject, 'foobar')); } - public function testCan() + /** + * @dataProvider provideUseEnumerations + */ + public function testCan(bool $useEnumerations) { - $definition = $this->createComplexWorkflowDefinition(); + $definition = $this->createComplexWorkflowDefinition($useEnumerations); $subject = new Subject(); $workflow = new Workflow($definition, new MethodMarkingStore()); $this->assertTrue($workflow->can($subject, 't1')); $this->assertFalse($workflow->can($subject, 't2')); - $subject->setMarking(['b' => 1]); + $subject->setMarking($useEnumerations ? [AlphabeticalEnum::B] : ['b' => 1]); $this->assertFalse($workflow->can($subject, 't1')); // In a workflow net, all "from" places should contain a token to enable // the transition. $this->assertFalse($workflow->can($subject, 't2')); - $subject->setMarking(['b' => 1, 'c' => 1]); + $subject->setMarking($useEnumerations ? [AlphabeticalEnum::B, AlphabeticalEnum::C] : ['b' => 1, 'c' => 1]); $this->assertFalse($workflow->can($subject, 't1')); $this->assertTrue($workflow->can($subject, 't2')); - $subject->setMarking(['f' => 1]); + $subject->setMarking($useEnumerations ? [AlphabeticalEnum::F] : ['f' => 1]); $this->assertFalse($workflow->can($subject, 't5')); $this->assertTrue($workflow->can($subject, 't6')); } - public function testCanWithGuard() + /** + * @dataProvider provideUseEnumerations + */ + public function testCanWithGuard(bool $useEnumerations) { - $definition = $this->createComplexWorkflowDefinition(); + $definition = $this->createComplexWorkflowDefinition($useEnumerations); $subject = new Subject(); $eventDispatcher = new EventDispatcher(); $eventDispatcher->addListener('workflow.workflow_name.guard.t1', function (GuardEvent $event) { @@ -119,9 +134,12 @@ public function testCanWithGuard() $this->assertFalse($workflow->can($subject, 't1')); } - public function testCanDoesNotTriggerGuardEventsForNotEnabledTransitions() + /** + * @dataProvider provideUseEnumerations + */ + public function testCanDoesNotTriggerGuardEventsForNotEnabledTransitions(bool $useEnumerations) { - $definition = $this->createComplexWorkflowDefinition(); + $definition = $this->createComplexWorkflowDefinition($useEnumerations); $subject = new Subject(); $dispatchedEvents = []; @@ -143,9 +161,12 @@ public function testCanDoesNotTriggerGuardEventsForNotEnabledTransitions() $this->assertSame(['workflow_name.guard.t3'], $dispatchedEvents); } - public function testCanWithSameNameTransition() + /** + * @dataProvider provideUseEnumerations + */ + public function testCanWithSameNameTransition(bool $useEnumerations) { - $definition = $this->createWorkflowWithSameNameTransition(); + $definition = $this->createWorkflowWithSameNameTransition($useEnumerations); $workflow = new Workflow($definition, new MethodMarkingStore()); $subject = new Subject(); @@ -153,7 +174,7 @@ public function testCanWithSameNameTransition() $this->assertFalse($workflow->can($subject, 'b_to_c')); $this->assertFalse($workflow->can($subject, 'to_a')); - $subject->setMarking(['b' => 1]); + $subject->setMarking($useEnumerations ? [AlphabeticalEnum::B] : ['b' => 1]); $this->assertFalse($workflow->can($subject, 'a_to_bc')); $this->assertTrue($workflow->can($subject, 'b_to_c')); $this->assertTrue($workflow->can($subject, 'to_a')); @@ -170,34 +191,40 @@ public function testBuildTransitionBlockerListReturnsUndefinedTransition() $workflow->buildTransitionBlockerList($subject, '404 Not Found'); } - public function testBuildTransitionBlockerList() + /** + * @dataProvider provideUseEnumerations + */ + public function testBuildTransitionBlockerList(bool $useEnumerations) { - $definition = $this->createComplexWorkflowDefinition(); + $definition = $this->createComplexWorkflowDefinition($useEnumerations); $subject = new Subject(); $workflow = new Workflow($definition, new MethodMarkingStore()); $this->assertTrue($workflow->buildTransitionBlockerList($subject, 't1')->isEmpty()); $this->assertFalse($workflow->buildTransitionBlockerList($subject, 't2')->isEmpty()); - $subject->setMarking(['b' => 1]); + $subject->setMarking($useEnumerations ? [AlphabeticalEnum::B] : ['b' => 1]); $this->assertFalse($workflow->buildTransitionBlockerList($subject, 't1')->isEmpty()); $this->assertFalse($workflow->buildTransitionBlockerList($subject, 't2')->isEmpty()); - $subject->setMarking(['b' => 1, 'c' => 1]); + $subject->setMarking($useEnumerations ? [AlphabeticalEnum::B, AlphabeticalEnum::C] : ['b' => 1, 'c' => 1]); $this->assertFalse($workflow->buildTransitionBlockerList($subject, 't1')->isEmpty()); $this->assertTrue($workflow->buildTransitionBlockerList($subject, 't2')->isEmpty()); - $subject->setMarking(['f' => 1]); + $subject->setMarking($useEnumerations ? [AlphabeticalEnum::F] : ['f' => 1]); $this->assertFalse($workflow->buildTransitionBlockerList($subject, 't5')->isEmpty()); $this->assertTrue($workflow->buildTransitionBlockerList($subject, 't6')->isEmpty()); } - public function testBuildTransitionBlockerListReturnsReasonsProvidedByMarking() + /** + * @dataProvider provideUseEnumerations + */ + public function testBuildTransitionBlockerListReturnsReasonsProvidedByMarking(bool $useEnumerations) { - $definition = $this->createComplexWorkflowDefinition(); + $definition = $this->createComplexWorkflowDefinition($useEnumerations); $subject = new Subject(); $workflow = new Workflow($definition, new MethodMarkingStore()); @@ -208,9 +235,12 @@ public function testBuildTransitionBlockerListReturnsReasonsProvidedByMarking() $this->assertSame('19beefc8-6b1e-4716-9d07-a39bd6d16e34', $blockers[0]->getCode()); } - public function testBuildTransitionBlockerListReturnsReasonsProvidedInGuards() + /** + * @dataProvider provideUseEnumerations + */ + public function testBuildTransitionBlockerListReturnsReasonsProvidedInGuards(bool $useEnumerations) { - $definition = $this->createSimpleWorkflowDefinition(); + $definition = $this->createSimpleWorkflowDefinition($useEnumerations); $subject = new Subject(); $dispatcher = new EventDispatcher(); $workflow = new Workflow($definition, new MethodMarkingStore(), $dispatcher); @@ -263,9 +293,12 @@ public function testApplyWithNotExisingTransition() } } - public function testApplyWithNotEnabledTransition() + /** + * @dataProvider provideUseEnumerations + */ + public function testApplyWithNotEnabledTransition(bool $useEnumerations) { - $definition = $this->createComplexWorkflowDefinition(); + $definition = $this->createComplexWorkflowDefinition($useEnumerations); $subject = new Subject(); $workflow = new Workflow($definition, new MethodMarkingStore()); $context = [ @@ -288,94 +321,119 @@ public function testApplyWithNotEnabledTransition() } } - public function testApply() + /** + * @dataProvider provideUseEnumerations + */ + public function testApply(bool $useEnumerations) { - $definition = $this->createComplexWorkflowDefinition(); + $definition = $this->createComplexWorkflowDefinition($useEnumerations); $subject = new Subject(); $workflow = new Workflow($definition, new MethodMarkingStore()); $marking = $workflow->apply($subject, 't1'); $this->assertInstanceOf(Marking::class, $marking); - $this->assertFalse($marking->has('a')); - $this->assertTrue($marking->has('b')); - $this->assertTrue($marking->has('c')); + $this->assertFalse($marking->has($this->getTypedPlaceValue('a', $useEnumerations))); + $this->assertTrue($marking->has($this->getTypedPlaceValue('b', $useEnumerations))); + $this->assertTrue($marking->has($this->getTypedPlaceValue('c', $useEnumerations))); } - public function testApplyWithSameNameTransition() + /** + * @dataProvider provideUseEnumerations + */ + public function testApplyWithSameNameTransition(bool $useEnumerations) { $subject = new Subject(); - $definition = $this->createWorkflowWithSameNameTransition(); + $definition = $this->createWorkflowWithSameNameTransition($useEnumerations); $workflow = new Workflow($definition, new MethodMarkingStore()); $marking = $workflow->apply($subject, 'a_to_bc'); - $this->assertFalse($marking->has('a')); - $this->assertTrue($marking->has('b')); - $this->assertTrue($marking->has('c')); + $this->assertFalse($marking->has($this->getTypedPlaceValue('a', $useEnumerations))); + $this->assertTrue($marking->has($this->getTypedPlaceValue('b', $useEnumerations))); + $this->assertTrue($marking->has($this->getTypedPlaceValue('c', $useEnumerations))); $marking = $workflow->apply($subject, 'to_a'); - $this->assertTrue($marking->has('a')); - $this->assertFalse($marking->has('b')); - $this->assertFalse($marking->has('c')); + $this->assertTrue($marking->has($this->getTypedPlaceValue('a', $useEnumerations))); + $this->assertFalse($marking->has($this->getTypedPlaceValue('b', $useEnumerations))); + $this->assertFalse($marking->has($this->getTypedPlaceValue('c', $useEnumerations))); $workflow->apply($subject, 'a_to_bc'); $marking = $workflow->apply($subject, 'b_to_c'); - $this->assertFalse($marking->has('a')); - $this->assertFalse($marking->has('b')); - $this->assertTrue($marking->has('c')); + $this->assertFalse($marking->has($this->getTypedPlaceValue('a', $useEnumerations))); + $this->assertFalse($marking->has($this->getTypedPlaceValue('b', $useEnumerations))); + $this->assertTrue($marking->has($this->getTypedPlaceValue('c', $useEnumerations))); $marking = $workflow->apply($subject, 'to_a'); - $this->assertTrue($marking->has('a')); - $this->assertFalse($marking->has('b')); - $this->assertFalse($marking->has('c')); + $this->assertTrue($marking->has($this->getTypedPlaceValue('a', $useEnumerations))); + $this->assertFalse($marking->has($this->getTypedPlaceValue('b', $useEnumerations))); + $this->assertFalse($marking->has($this->getTypedPlaceValue('c', $useEnumerations))); } - public function testApplyWithSameNameTransition2() + /** + * @dataProvider provideUseEnumerations + */ + public function testApplyWithSameNameTransition2(bool $useEnumerations) { $subject = new Subject(); - $subject->setMarking(['a' => 1, 'b' => 1]); + $subject->setMarking($useEnumerations ? [AlphabeticalEnum::A, AlphabeticalEnum::B] : ['a' => 1, 'b' => 1]); - $places = range('a', 'd'); + $places = $useEnumerations ? [ + AlphabeticalEnum::A, + AlphabeticalEnum::B, + AlphabeticalEnum::C, + AlphabeticalEnum::D, + ] : range('a', 'd'); $transitions = []; - $transitions[] = new Transition('t', 'a', 'c'); - $transitions[] = new Transition('t', 'b', 'd'); + $transitions[] = new Transition('t', $this->getTypedPlaceValue('a', $useEnumerations), $this->getTypedPlaceValue('c', $useEnumerations)); + $transitions[] = new Transition('t', $this->getTypedPlaceValue('b', $useEnumerations), $this->getTypedPlaceValue('d', $useEnumerations)); $definition = new Definition($places, $transitions); $workflow = new Workflow($definition, new MethodMarkingStore()); $marking = $workflow->apply($subject, 't'); - $this->assertFalse($marking->has('a')); - $this->assertFalse($marking->has('b')); - $this->assertTrue($marking->has('c')); - $this->assertTrue($marking->has('d')); + $this->assertFalse($marking->has($this->getTypedPlaceValue('a', $useEnumerations))); + $this->assertFalse($marking->has($this->getTypedPlaceValue('b', $useEnumerations))); + $this->assertTrue($marking->has($this->getTypedPlaceValue('c', $useEnumerations))); + $this->assertTrue($marking->has($this->getTypedPlaceValue('d', $useEnumerations))); } - public function testApplyWithSameNameTransition3() + /** + * @dataProvider provideUseEnumerations + */ + public function testApplyWithSameNameTransition3(bool $useEnumerations) { $subject = new Subject(); - $subject->setMarking(['a' => 1]); + $subject->setMarking($useEnumerations ? [AlphabeticalEnum::A] : ['a' => 1]); - $places = range('a', 'd'); + $places = $useEnumerations ? [ + AlphabeticalEnum::A, + AlphabeticalEnum::B, + AlphabeticalEnum::C, + AlphabeticalEnum::D, + ] : range('a', 'd'); $transitions = []; - $transitions[] = new Transition('t', 'a', 'b'); - $transitions[] = new Transition('t', 'b', 'c'); - $transitions[] = new Transition('t', 'c', 'd'); + $transitions[] = new Transition('t', $this->getTypedPlaceValue('a', $useEnumerations), $this->getTypedPlaceValue('b', $useEnumerations)); + $transitions[] = new Transition('t', $this->getTypedPlaceValue('b', $useEnumerations), $this->getTypedPlaceValue('c', $useEnumerations)); + $transitions[] = new Transition('t', $this->getTypedPlaceValue('c', $useEnumerations), $this->getTypedPlaceValue('d', $useEnumerations)); $definition = new Definition($places, $transitions); $workflow = new Workflow($definition, new MethodMarkingStore()); $marking = $workflow->apply($subject, 't'); // We want to make sure we do not end up in "d" - $this->assertTrue($marking->has('b')); - $this->assertFalse($marking->has('d')); + $this->assertTrue($marking->has($this->getTypedPlaceValue('b', $useEnumerations))); + $this->assertFalse($marking->has($this->getTypedPlaceValue('d', $useEnumerations))); } - public function testApplyWithEventDispatcher() + /** + * @dataProvider provideUseEnumerations + */ + public function testApplyWithEventDispatcher(bool $useEnumerations) { - $definition = $this->createComplexWorkflowDefinition(); + $definition = $this->createComplexWorkflowDefinition($useEnumerations); $subject = new Subject(); $eventDispatcher = new EventDispatcherMock(); $workflow = new Workflow($definition, new MethodMarkingStore(), $eventDispatcher, 'workflow_name'); @@ -383,24 +441,24 @@ public function testApplyWithEventDispatcher() $eventNameExpected = [ 'workflow.entered', 'workflow.workflow_name.entered', - 'workflow.workflow_name.entered.a', + 'workflow.workflow_name.entered.'.$this->getPlaceEventSuffix('a', $useEnumerations), 'workflow.guard', 'workflow.workflow_name.guard', 'workflow.workflow_name.guard.t1', 'workflow.leave', 'workflow.workflow_name.leave', - 'workflow.workflow_name.leave.a', + 'workflow.workflow_name.leave.'.$this->getPlaceEventSuffix('a', $useEnumerations), 'workflow.transition', 'workflow.workflow_name.transition', 'workflow.workflow_name.transition.t1', 'workflow.enter', 'workflow.workflow_name.enter', - 'workflow.workflow_name.enter.b', - 'workflow.workflow_name.enter.c', + 'workflow.workflow_name.enter.'.$this->getPlaceEventSuffix('b', $useEnumerations), + 'workflow.workflow_name.enter.'.$this->getPlaceEventSuffix('c', $useEnumerations), 'workflow.entered', 'workflow.workflow_name.entered', - 'workflow.workflow_name.entered.b', - 'workflow.workflow_name.entered.c', + 'workflow.workflow_name.entered.'.$this->getPlaceEventSuffix('b', $useEnumerations), + 'workflow.workflow_name.entered.'.$this->getPlaceEventSuffix('c', $useEnumerations), 'workflow.completed', 'workflow.workflow_name.completed', 'workflow.workflow_name.completed.t1', @@ -442,11 +500,14 @@ public function testApplyWithEventDispatcherForAnnounce(bool $fired, array $cont } } - public function testApplyDispatchesWithDisableEventInContext() + /** + * @dataProvider provideUseEnumerations + */ + public function testApplyDispatchesWithDisableEventInContext(bool $useEnumerations) { - $transitions[] = new Transition('a-b', 'a', 'b'); - $transitions[] = new Transition('a-c', 'a', 'c'); - $definition = new Definition(['a', 'b', 'c'], $transitions); + $transitions[] = new Transition('a-b', $this->getTypedPlaceValue('a', $useEnumerations), $this->getTypedPlaceValue('b', $useEnumerations)); + $transitions[] = new Transition('a-c', $this->getTypedPlaceValue('a', $useEnumerations), $this->getTypedPlaceValue('c', $useEnumerations)); + $definition = new Definition($useEnumerations ? AlphabeticalEnum::cases() : ['a', 'b', 'c'], $transitions); $subject = new Subject(); $eventDispatcher = new EventDispatcherMock(); @@ -472,11 +533,14 @@ public function testApplyDispatchesWithDisableEventInContext() $this->assertSame($eventNameExpected, $eventDispatcher->dispatchedEvents); } - public function testApplyDispatchesNoEventsWhenSpecifiedByDefinition() + /** + * @dataProvider provideUseEnumerations + */ + public function testApplyDispatchesNoEventsWhenSpecifiedByDefinition(bool $useEnumerations) { - $transitions[] = new Transition('a-b', 'a', 'b'); - $transitions[] = new Transition('a-c', 'a', 'c'); - $definition = new Definition(['a', 'b', 'c'], $transitions); + $transitions[] = new Transition('a-b', $this->getTypedPlaceValue('a', $useEnumerations), $this->getTypedPlaceValue('b', $useEnumerations)); + $transitions[] = new Transition('a-c', $this->getTypedPlaceValue('a', $useEnumerations), $this->getTypedPlaceValue('c', $useEnumerations)); + $definition = new Definition($useEnumerations ? AlphabeticalEnum::cases() : ['a', 'b', 'c'], $transitions); $subject = new Subject(); $eventDispatcher = new EventDispatcherMock(); @@ -493,11 +557,14 @@ public function testApplyDispatchesNoEventsWhenSpecifiedByDefinition() $this->assertSame($eventNameExpected, $eventDispatcher->dispatchedEvents); } - public function testApplyOnlyDispatchesEventsThatHaveBeenSpecifiedByDefinition() + /** + * @dataProvider provideUseEnumerations + */ + public function testApplyOnlyDispatchesEventsThatHaveBeenSpecifiedByDefinition(bool $useEnumerations) { - $transitions[] = new Transition('a-b', 'a', 'b'); - $transitions[] = new Transition('a-c', 'a', 'c'); - $definition = new Definition(['a', 'b', 'c'], $transitions); + $transitions[] = new Transition('a-b', $this->getTypedPlaceValue('a', $useEnumerations), $this->getTypedPlaceValue('b', $useEnumerations)); + $transitions[] = new Transition('a-c', $this->getTypedPlaceValue('a', $useEnumerations), $this->getTypedPlaceValue('c', $useEnumerations)); + $definition = new Definition($useEnumerations ? AlphabeticalEnum::cases() : ['a', 'b', 'c'], $transitions); $subject = new Subject(); $eventDispatcher = new EventDispatcherMock(); @@ -517,11 +584,14 @@ public function testApplyOnlyDispatchesEventsThatHaveBeenSpecifiedByDefinition() $this->assertSame($eventNameExpected, $eventDispatcher->dispatchedEvents); } - public function testApplyDoesNotTriggerExtraGuardWithEventDispatcher() + /** + * @dataProvider provideUseEnumerations + */ + public function testApplyDoesNotTriggerExtraGuardWithEventDispatcher(bool $useEnumerations) { - $transitions[] = new Transition('a-b', 'a', 'b'); - $transitions[] = new Transition('a-c', 'a', 'c'); - $definition = new Definition(['a', 'b', 'c'], $transitions); + $transitions[] = new Transition('a-b', $this->getTypedPlaceValue('a', $useEnumerations), $this->getTypedPlaceValue('b', $useEnumerations)); + $transitions[] = new Transition('a-c', $this->getTypedPlaceValue('a', $useEnumerations), $this->getTypedPlaceValue('c', $useEnumerations)); + $definition = new Definition($useEnumerations ? AlphabeticalEnum::cases() : ['a', 'b', 'c'], $transitions); $subject = new Subject(); $eventDispatcher = new EventDispatcherMock(); @@ -530,22 +600,22 @@ public function testApplyDoesNotTriggerExtraGuardWithEventDispatcher() $eventNameExpected = [ 'workflow.entered', 'workflow.workflow_name.entered', - 'workflow.workflow_name.entered.a', + 'workflow.workflow_name.entered.'.$this->getPlaceEventSuffix('a', $useEnumerations), 'workflow.guard', 'workflow.workflow_name.guard', 'workflow.workflow_name.guard.a-b', 'workflow.leave', 'workflow.workflow_name.leave', - 'workflow.workflow_name.leave.a', + 'workflow.workflow_name.leave.'.$this->getPlaceEventSuffix('a', $useEnumerations), 'workflow.transition', 'workflow.workflow_name.transition', 'workflow.workflow_name.transition.a-b', 'workflow.enter', 'workflow.workflow_name.enter', - 'workflow.workflow_name.enter.b', + 'workflow.workflow_name.enter.'.$this->getPlaceEventSuffix('b', $useEnumerations), 'workflow.entered', 'workflow.workflow_name.entered', - 'workflow.workflow_name.entered.b', + 'workflow.workflow_name.entered.'.$this->getPlaceEventSuffix('b', $useEnumerations), 'workflow.completed', 'workflow.workflow_name.completed', 'workflow.workflow_name.completed.a-b', @@ -650,9 +720,12 @@ public function testEventContextUpdated() $this->assertSame(['foo' => 'bar'], $marking->getContext()); } - public function testEventDefaultInitialContext() + /** + * @dataProvider provideUseEnumerations + */ + public function testEventDefaultInitialContext(bool $useEnumerations) { - $definition = $this->createComplexWorkflowDefinition(); + $definition = $this->createComplexWorkflowDefinition($useEnumerations); $subject = new Subject(); $dispatcher = new EventDispatcher(); $name = 'workflow_name'; @@ -664,7 +737,7 @@ public function testEventDefaultInitialContext() }; $eventNames = [ - 'workflow.workflow_name.entered.a', + 'workflow.workflow_name.entered.'.$this->getPlaceEventSuffix('a', $useEnumerations), ]; foreach ($eventNames as $eventName) { @@ -674,19 +747,42 @@ public function testEventDefaultInitialContext() $workflow->apply($subject, 't1'); } - public function testMarkingStateOnApplyWithEventDispatcher() - { - $definition = new Definition(range('a', 'f'), [new Transition('t', range('a', 'c'), range('d', 'f'))]); - - $subject = new Subject(); - $subject->setMarking(['a' => 1, 'b' => 1, 'c' => 1]); + /** + * @dataProvider provideUseEnumerations + */ + public function testMarkingStateOnApplyWithEventDispatcher(bool $useEnumerations) + { + $definition = new Definition($useEnumerations ? AlphabeticalEnum::cases() : range('a', 'f'), + [ + new Transition('t', + $useEnumerations ? + [ + AlphabeticalEnum::A, + AlphabeticalEnum::B, + AlphabeticalEnum::C, + ] : range('a', 'c'), + $useEnumerations ? + [ + AlphabeticalEnum::D, + AlphabeticalEnum::E, + AlphabeticalEnum::F, + ] : range('d', 'f'), + ), + ]); + + $subject = new Subject(); + $subject->setMarking($useEnumerations ? [ + AlphabeticalEnum::A, + AlphabeticalEnum::B, + AlphabeticalEnum::C, + ] : ['a' => 1, 'b' => 1, 'c' => 1]); $dispatcher = new EventDispatcher(); $workflow = new Workflow($definition, new MethodMarkingStore(), $dispatcher, 'test'); - $assertInitialState = function (Event $event) { - $this->assertEquals(new Marking(['a' => 1, 'b' => 1, 'c' => 1]), $event->getMarking()); + $assertInitialState = function (Event $event) use ($useEnumerations) { + $this->assertEquals(new Marking($useEnumerations ? [AlphabeticalEnum::A, AlphabeticalEnum::B, AlphabeticalEnum::C] : ['a' => 1, 'b' => 1, 'c' => 1]), $event->getMarking()); }; $assertTransitionState = function (Event $event) { $this->assertEquals(new Marking([]), $event->getMarking()); @@ -694,24 +790,27 @@ public function testMarkingStateOnApplyWithEventDispatcher() $dispatcher->addListener('workflow.leave', $assertInitialState); $dispatcher->addListener('workflow.test.leave', $assertInitialState); - $dispatcher->addListener('workflow.test.leave.a', $assertInitialState); - $dispatcher->addListener('workflow.test.leave.b', $assertInitialState); - $dispatcher->addListener('workflow.test.leave.c', $assertInitialState); + $dispatcher->addListener('workflow.test.leave.'.$this->getPlaceEventSuffix('a', $useEnumerations), $assertInitialState); + $dispatcher->addListener('workflow.test.leave.'.$this->getPlaceEventSuffix('b', $useEnumerations), $assertInitialState); + $dispatcher->addListener('workflow.test.leave.'.$this->getPlaceEventSuffix('c', $useEnumerations), $assertInitialState); $dispatcher->addListener('workflow.transition', $assertTransitionState); $dispatcher->addListener('workflow.test.transition', $assertTransitionState); $dispatcher->addListener('workflow.test.transition.t', $assertTransitionState); $dispatcher->addListener('workflow.enter', $assertTransitionState); $dispatcher->addListener('workflow.test.enter', $assertTransitionState); - $dispatcher->addListener('workflow.test.enter.d', $assertTransitionState); - $dispatcher->addListener('workflow.test.enter.e', $assertTransitionState); - $dispatcher->addListener('workflow.test.enter.f', $assertTransitionState); + $dispatcher->addListener('workflow.test.enter.'.$this->getPlaceEventSuffix('d', $useEnumerations), $assertTransitionState); + $dispatcher->addListener('workflow.test.enter.'.$this->getPlaceEventSuffix('e', $useEnumerations), $assertTransitionState); + $dispatcher->addListener('workflow.test.enter.'.$this->getPlaceEventSuffix('f', $useEnumerations), $assertTransitionState); $workflow->apply($subject, 't'); } - public function testGetEnabledTransitions() + /** + * @dataProvider provideUseEnumerations + */ + public function testGetEnabledTransitions(bool $useEnumerations) { - $definition = $this->createComplexWorkflowDefinition(); + $definition = $this->createComplexWorkflowDefinition($useEnumerations); $subject = new Subject(); $eventDispatcher = new EventDispatcher(); $eventDispatcher->addListener('workflow.workflow_name.guard.t1', function (GuardEvent $event) { @@ -721,25 +820,28 @@ public function testGetEnabledTransitions() $this->assertEmpty($workflow->getEnabledTransitions($subject)); - $subject->setMarking(['d' => 1]); + $subject->setMarking($useEnumerations ? [AlphabeticalEnum::D] : ['d' => 1]); $transitions = $workflow->getEnabledTransitions($subject); $this->assertCount(2, $transitions); $this->assertSame('t3', $transitions[0]->getName()); $this->assertSame('t4', $transitions[1]->getName()); - $subject->setMarking(['c' => 1, 'e' => 1]); + $subject->setMarking($useEnumerations ? [AlphabeticalEnum::C, AlphabeticalEnum::E] : ['c' => 1, 'e' => 1]); $transitions = $workflow->getEnabledTransitions($subject); $this->assertCount(1, $transitions); $this->assertSame('t5', $transitions[0]->getName()); } - public function testGetEnabledTransition() + /** + * @dataProvider provideUseEnumerations + */ + public function testGetEnabledTransition(bool $useEnumerations) { - $definition = $this->createComplexWorkflowDefinition(); + $definition = $this->createComplexWorkflowDefinition($useEnumerations); $subject = new Subject(); $workflow = new Workflow($definition, new MethodMarkingStore()); - $subject->setMarking(['d' => 1]); + $subject->setMarking($useEnumerations ? [AlphabeticalEnum::D] : ['d' => 1]); $transition = $workflow->getEnabledTransition($subject, 't3'); $this->assertInstanceOf(Transition::class, $transition); $this->assertSame('t3', $transition->getName()); @@ -748,9 +850,12 @@ public function testGetEnabledTransition() $this->assertNull($transition); } - public function testGetEnabledTransitionsWithSameNameTransition() + /** + * @dataProvider provideUseEnumerations + */ + public function testGetEnabledTransitionsWithSameNameTransition(bool $useEnumerations) { - $definition = $this->createWorkflowWithSameNameTransition(); + $definition = $this->createWorkflowWithSameNameTransition($useEnumerations); $subject = new Subject(); $workflow = new Workflow($definition, new MethodMarkingStore()); @@ -758,13 +863,22 @@ public function testGetEnabledTransitionsWithSameNameTransition() $this->assertCount(1, $transitions); $this->assertSame('a_to_bc', $transitions[0]->getName()); - $subject->setMarking(['b' => 1, 'c' => 1]); + $subject->setMarking($useEnumerations ? [AlphabeticalEnum::B, AlphabeticalEnum::C] : ['b' => 1, 'c' => 1]); $transitions = $workflow->getEnabledTransitions($subject); $this->assertCount(3, $transitions); $this->assertSame('b_to_c', $transitions[0]->getName()); $this->assertSame('to_a', $transitions[1]->getName()); $this->assertSame('to_a', $transitions[2]->getName()); } + + public function provideUseEnumerations(): \Generator + { + yield [false]; + + if (\PHP_VERSION_ID >= 80100) { + yield [true]; + } + } } class EventDispatcherMock implements \Symfony\Contracts\EventDispatcher\EventDispatcherInterface diff --git a/src/Symfony/Component/Workflow/Tests/fixtures/AlphabeticalEnum.php b/src/Symfony/Component/Workflow/Tests/fixtures/AlphabeticalEnum.php new file mode 100644 index 0000000000000..4b894752a77e1 --- /dev/null +++ b/src/Symfony/Component/Workflow/Tests/fixtures/AlphabeticalEnum.php @@ -0,0 +1,14 @@ +name = $name; - $this->froms = (array) $froms; - $this->tos = (array) $tos; + $this->froms = \is_array($froms) ? $froms : [$froms]; + $this->tos = \is_array($tos) ? $tos : [$tos]; } public function getName(): string diff --git a/src/Symfony/Component/Workflow/Utils/PlaceEnumerationUtils.php b/src/Symfony/Component/Workflow/Utils/PlaceEnumerationUtils.php new file mode 100644 index 0000000000000..d18deb2f06cfa --- /dev/null +++ b/src/Symfony/Component/Workflow/Utils/PlaceEnumerationUtils.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\Utils; + +/** + * @author Alexandre Daubois + */ +final class PlaceEnumerationUtils +{ + public static function getPlaceKey(string|\UnitEnum $place): string + { + if ($place instanceof \UnitEnum) { + return \get_class($place).'::'.$place->name; + } + + return $place; + } + + public static function getTypedValue(string $place): string|\UnitEnum + { + try { + $value = \constant($place); + if ($value instanceof \UnitEnum) { + // Assure we actually retrieved an enumeration case and not a constant with a name + // that looks like an enumeration. + return $value; + } + + return $place; + } catch (\Throwable) { + return $place; + } + } +} diff --git a/src/Symfony/Component/Workflow/Workflow.php b/src/Symfony/Component/Workflow/Workflow.php index 86820151e2407..079e24d83034f 100644 --- a/src/Symfony/Component/Workflow/Workflow.php +++ b/src/Symfony/Component/Workflow/Workflow.php @@ -24,6 +24,7 @@ use Symfony\Component\Workflow\MarkingStore\MarkingStoreInterface; use Symfony\Component\Workflow\MarkingStore\MethodMarkingStore; use Symfony\Component\Workflow\Metadata\MetadataStoreInterface; +use Symfony\Component\Workflow\Utils\PlaceEnumerationUtils; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** @@ -367,7 +368,7 @@ private function leave(object $subject, Transition $transition, Marking $marking $this->dispatcher->dispatch($event, sprintf('workflow.%s.leave', $this->name)); foreach ($places as $place) { - $this->dispatcher->dispatch($event, sprintf('workflow.%s.leave.%s', $this->name, $place)); + $this->dispatcher->dispatch($event, sprintf('workflow.%s.leave.%s', $this->name, PlaceEnumerationUtils::getPlaceKey($place))); } } @@ -402,7 +403,7 @@ private function enter(object $subject, Transition $transition, Marking $marking $this->dispatcher->dispatch($event, sprintf('workflow.%s.enter', $this->name)); foreach ($places as $place) { - $this->dispatcher->dispatch($event, sprintf('workflow.%s.enter.%s', $this->name, $place)); + $this->dispatcher->dispatch($event, sprintf('workflow.%s.enter.%s', $this->name, PlaceEnumerationUtils::getPlaceKey($place))); } } @@ -423,7 +424,7 @@ private function entered(object $subject, ?Transition $transition, Marking $mark $this->dispatcher->dispatch($event, sprintf('workflow.%s.entered', $this->name)); foreach ($marking->getPlaces() as $placeName => $nbToken) { - $this->dispatcher->dispatch($event, sprintf('workflow.%s.entered.%s', $this->name, $placeName)); + $this->dispatcher->dispatch($event, sprintf('workflow.%s.entered.%s', $this->name, PlaceEnumerationUtils::getPlaceKey($placeName))); } }