Skip to content

Commit 7b8b394

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

19 files changed

+723
-204
lines changed

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

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
use Symfony\Component\Uid\Factory\UuidFactory;
4040
use Symfony\Component\Validator\Validation;
4141
use Symfony\Component\WebLink\HttpHeaderSerializer;
42+
use Symfony\Component\Workflow\Utils\PlaceEnumerationUtils;
4243
use Symfony\Component\Workflow\WorkflowEvents;
4344

4445
/**
@@ -396,7 +397,24 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode)
396397
->end()
397398
->arrayNode('initial_marking')
398399
->beforeNormalization()->castToArray()->end()
399-
->defaultValue([])
400+
->beforeNormalization()
401+
->ifTrue(function ($v) {
402+
foreach ($v as $value) {
403+
if (!is_scalar($value) && !$value instanceof \UnitEnum) {
404+
return true;
405+
}
406+
}
407+
408+
return false;
409+
})
410+
->thenInvalid('Initial marking must either be a scalar or an UnitEnum.')
411+
->end()
412+
->beforeNormalization()
413+
->always()
414+
->then(function ($places) {
415+
return array_map(static fn ($element) => PlaceEnumerationUtils::getPlaceKey($element), $places);
416+
})
417+
->end()
400418
->prototype('scalar')->end()
401419
->end()
402420
->variableNode('events_to_dispatch')
@@ -431,9 +449,9 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode)
431449
->always()
432450
->then(function ($places) {
433451
// 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];
452+
if (isset($places[0]) && (\is_string($places[0]) || $places[0] instanceof \UnitEnum)) {
453+
return array_map(function (string|\UnitEnum $place) {
454+
return ['name' => PlaceEnumerationUtils::getPlaceKey($place)];
437455
}, $places);
438456
}
439457

@@ -505,19 +523,49 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode)
505523
->example('is_fully_authenticated() and is_granted(\'ROLE_JOURNALIST\') and subject.getTitle() == \'My first article\'')
506524
->end()
507525
->arrayNode('from')
526+
->beforeNormalization()->castToArray()->end()
527+
->beforeNormalization()
528+
->ifTrue(function ($v) {
529+
foreach ($v as $value) {
530+
if (!is_scalar($value) && !$value instanceof \UnitEnum) {
531+
return true;
532+
}
533+
}
534+
535+
return false;
536+
})
537+
->thenInvalid('"From" places must either be scalars or UnitEnum.')
538+
->end()
508539
->beforeNormalization()
509-
->ifString()
510-
->then(function ($v) { return [$v]; })
540+
->always()
541+
->then(function ($places) {
542+
return array_map(static fn ($element) => PlaceEnumerationUtils::getPlaceKey($element), $places);
543+
})
511544
->end()
512545
->requiresAtLeastOneElement()
513546
->prototype('scalar')
514547
->cannotBeEmpty()
515548
->end()
516549
->end()
517550
->arrayNode('to')
551+
->beforeNormalization()->castToArray()->end()
552+
->beforeNormalization()
553+
->ifTrue(function ($v) {
554+
foreach ($v as $value) {
555+
if (!is_scalar($value) && !$value instanceof \UnitEnum) {
556+
return true;
557+
}
558+
}
559+
560+
return false;
561+
})
562+
->thenInvalid('"To" places must either be scalars or UnitEnum.')
563+
->end()
518564
->beforeNormalization()
519-
->ifString()
520-
->then(function ($v) { return [$v]; })
565+
->always()
566+
->then(function ($places) {
567+
return array_map(static fn ($element) => PlaceEnumerationUtils::getPlaceKey($element), $places);
568+
})
521569
->end()
522570
->requiresAtLeastOneElement()
523571
->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
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace Symfony\Component\Workflow\Tests;
4+
5+
enum AlphabeticalEnum: string
6+
{
7+
case A = 'a';
8+
case B = 'b';
9+
case C = 'c';
10+
case D = 'd';
11+
case E = 'e';
12+
case F = 'f';
13+
case G = 'g';
14+
}

0 commit comments

Comments
 (0)