-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Workflow] Add support for \BackedEnum
in MethodMarkingStore
#60114
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 7.3
Are you sure you want to change the base?
Conversation
| Q | A | ------------- | --- | Branch? | 7.3 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | Kind of relates to symfony#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; } } ```
8e589e4
to
54af6fa
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am surprised the implementation is that short. But that's good thing!
This was my very first try at this feature, and it was really challenging to bring full support (I mean support enums in workflows everywhere, especially in the Framework configuration).
I also shared a backed enum marking store, I'm sharing it here just for reference 🙂 #54582 (comment)
So, if I get this right, you propose to bring reduced scope. But does it make sense to improve the marking store without bringing support to the Framework config as well? From my experience, devs mostly declare their workflows in framework.yaml
(and other formats of course). And this last point was a pain to implement right. Maybe things changed since?
Co-authored-by: Alexandre Daubois <2144837+alexandre-daubois@users.noreply.github.com>
Thank you for the questions @alexandre-daubois
Yes, because in my mind the fact that the Workflow uses strings internally is ok. If PHP was allowing to cast Backed Enums to string it would actually “just” work. So in my opinion we don’t need to deal with Enum internally, only at the component boundaries and where it makes the more sense: places.
Yes! I’ve seen it and it is interesting. But I tried to find a solution that does not require my team to implement a custom service for each workflow (our project requires a tons of them and it would make onboarding new developers on the project more easy).
Good question! I didn’t had a look initially because the project I’m working on is using YAML and I’m not sure we can do much more than what is currently possible using framework:
workflows:
my_workflow:
# …
places:
- !php/enum App\Enumeration\ObjectStatus::Draft->value
- !php/enum App\Enumeration\ObjectStatus::Received->value
# … But I was convinced we could do something in FrameworkBundle for the PHP format so I quickly tried something this morning working with the normalization process and managed to make it work to support the following snippet: // config/packages/workflow.php
return static function (FrameworkConfig $framework): void {
$missionWorkflow = $framework->workflows()->workflows('mission');
$missionWorkflow
->type('state_machine')
->supports(\App\Entity\MyObject::class)
->initialMarking([\App\Enumeration\ObjectStatus::Draft]);
// …
$missionWorkflow->transition()
->name('complete')
->from([\App\Enumeration\ObjectStatus::Draft])
->to([\App\Enumeration\ObjectStatus::Received])
}; and it also seems to allow removing the framework:
workflows:
my_workflow:
# …
places:
- !php/enum App\Enumeration\ObjectStatus::Draft
- !php/enum App\Enumeration\ObjectStatus::Received
# … So if this current PR is accepted I can work on improving the support in FrameworkBundle for 7.4. |
Doesn't it defeat the purpose of enumerations? The consensus was that places are finite and precisely defined in the configuration, making them already "strongly typed" (as trying to move to an undefined place throws an exception), which makes workflow places actually enum of their kind. This was the biggest reason for not going further with this idea, thus supporting enumeration didn't bring anything actually. But anyway I get what you mean. Although discussions around enum support have concluded in the past that it wasn't worth the effort, perhaps opinions have changed in the last few years. I'd be happy to hear other people's opinion on this, and don't get me wrong: I think I'd be glad to see this feature implemented given the number of times it's been requested by the community. 🙂 I just want to clarify the points that led to the subject being dropped the last few times, so that we can move in the right direction and eliminate show stoppers. Thank you for digging the subject! |
using a type of If you access the backing value each time you use your enum, you just use a more verbose syntax for string constants, with no additional type safety compared to string constants. |
MethodMarkingStore
\BackedEnum
in MethodMarkingStore
This makes completely sense to me. |
I'm fine with this. And I like @nicolas-grekas comment. BUT, this could be hard to support all cases. |
Would work for me - making simple cases simple. Another idea (draft, for another PR): places: !php/enum FOO This would make the workflow still use strings for places, but use enums for the marking store. |
hi folks Sorry for the delay in the response (busy week here)
I would agree if the Workflow was the only place where one could set those values. But when using the Please note I don't apply this reasoning to transitions as those are only usable via the Worfklow system.
In my view, this is the responsibility of the configuration (which is not part of this PR), and one can easily do it when using PHP for configuration: foreach (\App\Enumeration\ObjectStatus::cases() as $case) {
$myWorkflow->place()->name($case->value);
} And IF someone is willing to add to the syntactic sugar later (like @nicolas-grekas idea) on or globally improve Enums support in configuration, they are free to do it.
Is it not the opposite of what @alexandre-daubois is saying? ("The consensus was that places are finite and precisely defined in the configuration, making them already "strongly typed"")
Maybe I'm not seeing what you refer to: I don't think I'm constantly accessing the backing value here. Am I missing something?
Yes indeed, but this looked like a good middle ground to me as it does not involve too much implementation on Symfony's side. Plus, it makes it kind of BC and provides an upgrade path.
As @lyrixx said, it seemed a bit complicated to do "quickly" and in a performant way. |
Perhaps I misspoke, so to clarify: when I said "finite values", I meant that if we use workflow transitions, we're constrained to the values of a defined set. But actually, values can be non-finite in the sense that any string can be used to define a workflow place. Maybe this explains the feeling of "two opposite ideas" we seemed to give |
Actually, this is super easy :) I'll open a PR soon (except for XML 🥺) edit: here we go #60204 |
Why supporting Backed Enums in Workflow can be useful
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.
Reducing the supported scope to `MethodMarkingStore`
While trying to implementing BackedEnum support 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 theMarking
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 BackedEnum currently requires some boilerplate in the user project (with a dedicated getter for instance) or a complete implementation of 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 setter but as PHP supports union types we can document how users can support them:WDYT?