Skip to content

Commit 32398a8

Browse files
[Workflow] Allow use of UnitEnum and BackedEnum in workflow places
1 parent a695b30 commit 32398a8

19 files changed

+729
-206
lines changed

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

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,24 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode)
396396
->end()
397397
->arrayNode('initial_marking')
398398
->beforeNormalization()->castToArray()->end()
399-
->defaultValue([])
399+
->beforeNormalization()
400+
->ifTrue(function ($v) {
401+
foreach ($v as $value) {
402+
if (!is_scalar($value) && !$value instanceof \UnitEnum) {
403+
return true;
404+
}
405+
}
406+
407+
return false;
408+
})
409+
->thenInvalid('Initial marking must either be a scalar or an UnitEnum.')
410+
->end()
411+
->beforeNormalization()
412+
->always()
413+
->then(function ($places) {
414+
return array_map(static fn ($element) => $element instanceof \UnitEnum ? \get_class($element).'::'.$element->name : $element, $places);
415+
})
416+
->end()
400417
->prototype('scalar')->end()
401418
->end()
402419
->variableNode('events_to_dispatch')
@@ -431,9 +448,9 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode)
431448
->always()
432449
->then(function ($places) {
433450
// It's an indexed array of shape ['place1', 'place2']
434-
if (isset($places[0]) && \is_string($places[0])) {
435-
return array_map(function (string $place) {
436-
return ['name' => $place];
451+
if (isset($places[0]) && (\is_string($places[0]) || $places[0] instanceof \UnitEnum)) {
452+
return array_map(function (string|\UnitEnum $place) {
453+
return ['name' => $place instanceof \UnitEnum ? \get_class($place).'::'.$place->name : $place];
437454
}, $places);
438455
}
439456

@@ -505,19 +522,49 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode)
505522
->example('is_fully_authenticated() and is_granted(\'ROLE_JOURNALIST\') and subject.getTitle() == \'My first article\'')
506523
->end()
507524
->arrayNode('from')
525+
->beforeNormalization()->castToArray()->end()
526+
->beforeNormalization()
527+
->ifTrue(function ($v) {
528+
foreach ($v as $value) {
529+
if (!is_scalar($value) && !$value instanceof \UnitEnum) {
530+
return true;
531+
}
532+
}
533+
534+
return false;
535+
})
536+
->thenInvalid('"From" places must either be scalars or UnitEnum.')
537+
->end()
508538
->beforeNormalization()
509-
->ifString()
510-
->then(function ($v) { return [$v]; })
539+
->always()
540+
->then(function ($places) {
541+
return array_map(static fn ($element) => $element instanceof \UnitEnum ? \get_class($element).'::'.$element->name : $element, $places);
542+
})
511543
->end()
512544
->requiresAtLeastOneElement()
513545
->prototype('scalar')
514546
->cannotBeEmpty()
515547
->end()
516548
->end()
517549
->arrayNode('to')
550+
->beforeNormalization()->castToArray()->end()
551+
->beforeNormalization()
552+
->ifTrue(function ($v) {
553+
foreach ($v as $value) {
554+
if (!is_scalar($value) && !$value instanceof \UnitEnum) {
555+
return true;
556+
}
557+
}
558+
559+
return false;
560+
})
561+
->thenInvalid('"To" places must either be scalars or UnitEnum.')
562+
->end()
518563
->beforeNormalization()
519-
->ifString()
520-
->then(function ($v) { return [$v]; })
564+
->always()
565+
->then(function ($places) {
566+
return array_map(static fn ($element) => $element instanceof \UnitEnum ? \get_class($element).'::'.$element->name : $element, $places);
567+
})
521568
->end()
522569
->requiresAtLeastOneElement()
523570
->prototype('scalar')

src/Symfony/Component/Workflow/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
6.1
5+
---
6+
7+
* Allow use of UnitEnum and BackedEnum in workflow places
8+
49
6.0
510
---
611

src/Symfony/Component/Workflow/Definition.php

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Component\Workflow\Exception\LogicException;
1515
use Symfony\Component\Workflow\Metadata\InMemoryMetadataStore;
1616
use Symfony\Component\Workflow\Metadata\MetadataStoreInterface;
17+
use Symfony\Component\Workflow\Utils\PlaceEnumerationUtils;
1718

1819
/**
1920
* @author Fabien Potencier <fabien@symfony.com>
@@ -28,11 +29,11 @@ final class Definition
2829
private MetadataStoreInterface $metadataStore;
2930

3031
/**
31-
* @param string[] $places
32-
* @param Transition[] $transitions
33-
* @param string|string[]|null $initialPlaces
32+
* @param string[]|\UnitEnum[] $places
33+
* @param Transition[] $transitions
34+
* @param string|string[]|\UnitEnum|\UnitEnum[]|null $initialPlaces
3435
*/
35-
public function __construct(array $places, array $transitions, string|array $initialPlaces = null, MetadataStoreInterface $metadataStore = null)
36+
public function __construct(array $places, array $transitions, string|\UnitEnum|array $initialPlaces = null, MetadataStoreInterface $metadataStore = null)
3637
{
3738
foreach ($places as $place) {
3839
$this->addPlace($place);
@@ -52,7 +53,7 @@ public function __construct(array $places, array $transitions, string|array $ini
5253
*/
5354
public function getInitialPlaces(): array
5455
{
55-
return $this->initialPlaces;
56+
return array_map(static fn ($element) => PlaceEnumerationUtils::getTypedValue($element), $this->initialPlaces);
5657
}
5758

5859
/**
@@ -76,43 +77,45 @@ public function getMetadataStore(): MetadataStoreInterface
7677
return $this->metadataStore;
7778
}
7879

79-
private function setInitialPlaces(string|array $places = null)
80+
private function setInitialPlaces(string|\UnitEnum|array $places = null)
8081
{
8182
if (!$places) {
8283
return;
8384
}
8485

85-
$places = (array) $places;
86+
$places = array_map(static fn ($element) => PlaceEnumerationUtils::getPlaceKey($element), \is_array($places) ? $places : [$places]);
8687

8788
foreach ($places as $place) {
8889
if (!isset($this->places[$place])) {
89-
throw new LogicException(sprintf('Place "%s" cannot be the initial place as it does not exist.', $place));
90+
throw new LogicException(sprintf('Place "%s" cannot be the initial place as it does not exist.', PlaceEnumerationUtils::getPlaceKey($place)));
9091
}
9192
}
9293

9394
$this->initialPlaces = $places;
9495
}
9596

96-
private function addPlace(string $place)
97+
private function addPlace(string|\UnitEnum $place)
9798
{
9899
if (!\count($this->places)) {
99-
$this->initialPlaces = [$place];
100+
$this->initialPlaces = [PlaceEnumerationUtils::getPlaceKey($place)];
100101
}
101102

102-
$this->places[$place] = $place;
103+
$this->places[PlaceEnumerationUtils::getPlaceKey($place)] = $place;
103104
}
104105

105106
private function addTransition(Transition $transition)
106107
{
107108
$name = $transition->getName();
108109

109110
foreach ($transition->getFroms() as $from) {
111+
$from = PlaceEnumerationUtils::getPlaceKey($from);
110112
if (!isset($this->places[$from])) {
111113
throw new LogicException(sprintf('Place "%s" referenced in transition "%s" does not exist.', $from, $name));
112114
}
113115
}
114116

115117
foreach ($transition->getTos() as $to) {
118+
$to = PlaceEnumerationUtils::getPlaceKey($to);
116119
if (!isset($this->places[$to])) {
117120
throw new LogicException(sprintf('Place "%s" referenced in transition "%s" does not exist.', $to, $name));
118121
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Psr\Log\LoggerInterface;
1515
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
1616
use Symfony\Component\Workflow\Event\Event;
17+
use Symfony\Component\Workflow\Utils\PlaceEnumerationUtils;
1718

1819
/**
1920
* @author Grégoire Pineau <lyrixx@lyrixx.info>
@@ -30,7 +31,7 @@ public function __construct(LoggerInterface $logger)
3031
public function onLeave(Event $event)
3132
{
3233
foreach ($event->getTransition()->getFroms() as $place) {
33-
$this->logger->info(sprintf('Leaving "%s" for subject of class "%s" in workflow "%s".', $place, \get_class($event->getSubject()), $event->getWorkflowName()));
34+
$this->logger->info(sprintf('Leaving "%s" for subject of class "%s" in workflow "%s".', PlaceEnumerationUtils::getPlaceKey($place), \get_class($event->getSubject()), $event->getWorkflowName()));
3435
}
3536
}
3637

@@ -42,7 +43,7 @@ public function onTransition(Event $event)
4243
public function onEnter(Event $event)
4344
{
4445
foreach ($event->getTransition()->getTos() as $place) {
45-
$this->logger->info(sprintf('Entering "%s" for subject of class "%s" in workflow "%s".', $place, \get_class($event->getSubject()), $event->getWorkflowName()));
46+
$this->logger->info(sprintf('Entering "%s" for subject of class "%s" in workflow "%s".', PlaceEnumerationUtils::getPlaceKey($place), \get_class($event->getSubject()), $event->getWorkflowName()));
4647
}
4748
}
4849

src/Symfony/Component/Workflow/Marking.php

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace Symfony\Component\Workflow;
1313

14+
use Symfony\Component\Workflow\Utils\PlaceEnumerationUtils;
15+
1416
/**
1517
* Marking contains the place of every tokens.
1618
*
@@ -22,33 +24,44 @@ class Marking
2224
private ?array $context = null;
2325

2426
/**
25-
* @param int[] $representation Keys are the place name and values should be 1
27+
* @param int[]|\UnitEnum[] $representation Keys are the place name and values should be 1, unless UnitEnums
28+
* are used as workflow places
2629
*/
2730
public function __construct(array $representation = [])
2831
{
29-
foreach ($representation as $place => $nbToken) {
30-
$this->mark($place);
32+
foreach ($representation as $place => $token) {
33+
$this->mark($token instanceof \UnitEnum ? $token : $place);
3134
}
3235
}
3336

34-
public function mark(string $place)
37+
public function mark(string|\UnitEnum $place)
3538
{
36-
$this->places[$place] = 1;
39+
$this->places[PlaceEnumerationUtils::getPlaceKey($place)] = 1;
3740
}
3841

39-
public function unmark(string $place)
42+
public function unmark(string|\UnitEnum $place)
4043
{
41-
unset($this->places[$place]);
44+
unset($this->places[PlaceEnumerationUtils::getPlaceKey($place)]);
4245
}
4346

44-
public function has(string $place)
47+
public function has(string|\UnitEnum $place)
4548
{
46-
return isset($this->places[$place]);
49+
return isset($this->places[PlaceEnumerationUtils::getPlaceKey($place)]);
4750
}
4851

4952
public function getPlaces()
5053
{
51-
return $this->places;
54+
$places = [];
55+
foreach ($this->places as $key => $value) {
56+
$typedKey = PlaceEnumerationUtils::getTypedValue($key);
57+
if ($typedKey instanceof \UnitEnum) {
58+
$places[$key] = $typedKey;
59+
} else {
60+
$places[$typedKey] = 1;
61+
}
62+
}
63+
64+
return $places;
5265
}
5366

5467
/**

src/Symfony/Component/Workflow/MarkingStore/MethodMarkingStore.php

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\Workflow\Exception\LogicException;
1515
use Symfony\Component\Workflow\Marking;
16+
use Symfony\Component\Workflow\Utils\PlaceEnumerationUtils;
1617

1718
/**
1819
* MethodMarkingStore stores the marking with a subject's method.
@@ -61,10 +62,17 @@ public function getMarking(object $subject): Marking
6162
}
6263

6364
if ($this->singleState) {
64-
$marking = [(string) $marking => 1];
65+
$markingRepresentation = [PlaceEnumerationUtils::getPlaceKey($marking) => 1];
66+
} else {
67+
$markingRepresentation = [];
68+
foreach ($marking as $key => $item) {
69+
// When using enumerations, as the enumeration case can't be used as an array key, the value is actually
70+
// stored in the item instead of the key.
71+
$markingRepresentation[PlaceEnumerationUtils::getPlaceKey($item instanceof \UnitEnum ? $item : $key)] = 1;
72+
}
6573
}
6674

67-
return new Marking($marking);
75+
return new Marking($markingRepresentation);
6876
}
6977

7078
/**
@@ -75,7 +83,18 @@ public function setMarking(object $subject, Marking $marking, array $context = [
7583
$marking = $marking->getPlaces();
7684

7785
if ($this->singleState) {
78-
$marking = key($marking);
86+
$markingResult = PlaceEnumerationUtils::getTypedValue(key($marking));
87+
} else {
88+
$markingResult = [];
89+
foreach ($marking as $key => $item) {
90+
$value = PlaceEnumerationUtils::getTypedValue($key);
91+
if ($value instanceof \UnitEnum) {
92+
// UnitEnum can't be used as array key, put it as a simple value without specific index.
93+
$markingResult[] = $value;
94+
} else {
95+
$markingResult[$value] = 1;
96+
}
97+
}
7998
}
8099

81100
$method = 'set'.ucfirst($this->property);
@@ -84,6 +103,6 @@ public function setMarking(object $subject, Marking $marking, array $context = [
84103
throw new LogicException(sprintf('The method "%s::%s()" does not exist.', get_debug_type($subject), $method));
85104
}
86105

87-
$subject->{$method}($marking, $context);
106+
$subject->{$method}($markingResult, $context);
88107
}
89108
}

0 commit comments

Comments
 (0)