Skip to content

Commit 54af6fa

Browse files
committed
[Workflow] Add support for Backed Enum in MethodMarkingStore
| Q | A | ------------- | --- | Branch? | 7.3 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | Kind of relates to #44211 | License | MIT Supporting Enums in Workflow (and overall in Symfony) is a highly debated topic. While using Enums is not always a good choice, I personally found that Backed Enums are really suited for status and places because they ensure type safety in your model and naturally validate the set of allowed values. This is why I think supporting them in Workflow could be nice if not too complicated. While trying to implementing this in my current project using custom code, I figured we could go with a really narrow scope to support them out-of-the-box (but only for Single State Machine): in such case only `MethodMarkingStore` has to support Enums. In my mind, the way the workflow uses strings internally in the `Marking` is an implementation detail and what really matters to users is being able to control the values stored in their objects. Also, I don’t believe supporting enums for transitions is necessary as they are mostly configuration and validating transitions is already part of the component’s job (and in such case I would recommend using constants anyway). Finally, supporting Enum values in the configuration is not a target at the moment either because it can already be done as one can use `!php/enum` if they are using yaml files or use PHP config and directly use the enums. And improving this experience can be done later on if deemed necessary. Supporting this use case currently requires some boilerplate in the user project (with a dedicated getter for instance). Or completely reimplementing a marking store. Therefore I’m sharing what is in my mind a small patch that can greatly improve the situation for users as it allows them to use Backed Enums in their model right away with their getters (ideally we would not need this patch but as PHP will not allow Enums to implement `\Stringable` soon we need to do it ourself). The complicated part could be the setters but now that PHP supports union types we can only document how users can support them: ```php class MyObject { // ... private ObjectStatus $status = ObjectStatus::Draft; public function getStatus(): ObjectStatus { return $this->status; } public function setStatus(ObjectStatus|string $status): static { if (\is_string($status)) { $status = ObjectStatus::from($status); } $this->status = $status; return $this; } } ```
1 parent 1a8b82e commit 54af6fa

File tree

5 files changed

+50
-2
lines changed

5 files changed

+50
-2
lines changed

src/Symfony/Component/Workflow/CHANGELOG.md

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

4+
7.3
5+
---
6+
* Add support for Backed Enum in `MethodMarkingStore`
7+
48
7.1
59
---
610

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ public function getMarking(object $subject): Marking
6464
}
6565

6666
if ($this->singleState) {
67+
if ($marking instanceof \BackedEnum) {
68+
$marking = $marking->value;
69+
}
70+
6771
$marking = [(string) $marking => 1];
6872
} elseif (!\is_array($marking)) {
6973
throw new LogicException(\sprintf('The marking stored in "%s::$%s" is not an array and the Workflow\'s Marking store is instantiated with $singleState=false.', get_debug_type($subject), $this->property));

src/Symfony/Component/Workflow/Tests/MarkingStore/MethodMarkingStoreTest.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\Workflow\MarkingStore\MethodMarkingStore;
1616
use Symfony\Component\Workflow\Tests\Subject;
17+
use Symfony\Component\Workflow\Tests\TestEnum;
1718

1819
class MethodMarkingStoreTest extends TestCase
1920
{
@@ -84,6 +85,24 @@ public function testGetMarkingWithValueObject()
8485
$this->assertSame('first_place', (string) $subject->getMarking());
8586
}
8687

88+
public function testGetMarkingWithBackedEnum()
89+
{
90+
$subject = new Subject(TestEnum::Foo);
91+
92+
$markingStore = new MethodMarkingStore(true);
93+
94+
$marking = $markingStore->getMarking($subject);
95+
96+
$this->assertCount(1, $marking->getPlaces());
97+
$this->assertSame(['foo' => 1], $marking->getPlaces());
98+
99+
$marking->mark('bar');
100+
$marking->unmark('foo');
101+
$markingStore->setMarking($subject, $marking);
102+
103+
$this->assertSame(TestEnum::Bar, $subject->getMarking());
104+
}
105+
87106
public function testGetMarkingWithUninitializedProperty()
88107
{
89108
$subject = new SubjectWithType();

src/Symfony/Component/Workflow/Tests/Subject.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,24 @@
1313

1414
final class Subject
1515
{
16-
private string|array|null $marking;
16+
private string|array|\BackedEnum|null $marking;
1717
private array $context = [];
1818

1919
public function __construct($marking = null)
2020
{
2121
$this->marking = $marking;
2222
}
2323

24-
public function getMarking(): string|array|null
24+
public function getMarking(): string|array|\BackedEnum|null
2525
{
2626
return $this->marking;
2727
}
2828

2929
public function setMarking($marking, array $context = []): void
3030
{
31+
if (\is_string($marking) && $newMarking = TestEnum::tryFrom($marking)) {
32+
$marking = $newMarking;
33+
}
3134
$this->marking = $marking;
3235
$this->context = $context;
3336
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Workflow\Tests;
13+
14+
enum TestEnum: string
15+
{
16+
case Foo = 'foo';
17+
case Bar = 'bar';
18+
}

0 commit comments

Comments
 (0)