Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[Workflow] Add support for\BackedEnum inMethodMarkingStore#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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
tucksaun wants to merge2 commits intosymfony:7.4
base:7.4
Choose a base branch
Loading
fromtucksaun:feat/workflow-backed-enum

Conversation

tucksaun
Copy link
Contributor

QA
Branch?7.3
Bug fix?no
New feature?yes
Deprecations?no
IssuesKind of relates to#44211
LicenseMIT
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 onlyMethodMarkingStore 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:

class MyObject {// ...privateObjectStatus$status = ObjectStatus::Draft;publicfunctiongetStatus():ObjectStatus    {return$this->status;    }publicfunctionsetStatus(ObjectStatus|string$status):static    {if (\is_string($status)) {$status = ObjectStatus::from($status);        }$this->status =$status;return$this;    }}

WDYT?

mtarld, Spomky, 29Hido, Valmonzo, and chalasr reacted with heart emoji
| Q             | A| ------------- | ---| Branch?       | 7.3| Bug fix?      | no| New feature?  | yes| Deprecations? | no| Issues        | Kind of relates tosymfony#44211| License       | MITSupporting 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:```phpclass 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;    }}```
@tucksauntucksaunforce-pushed thefeat/workflow-backed-enum branch from8e589e4 to54af6faCompareApril 2, 2025 07:01
Copy link
Member

@alexandre-dauboisalexandre-daubois left a comment
edited
Loading

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 wasmy 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 inframework.yaml (and other formats of course). And this last point was apain to implement right. Maybe things changed since?

Co-authored-by: Alexandre Daubois <2144837+alexandre-daubois@users.noreply.github.com>
@tucksaun
Copy link
ContributorAuthor

Thank you for the questions@alexandre-daubois

So, if I get this right, you propose to bring reduced scope.

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.

I also shared a backed enum marking store, I'm sharing it here just for reference 🙂#54582 (comment)

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).

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 inframework.yaml (and other formats of course). And this last point was apain to implement right. Maybe things changed since?

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!php:enum:

framework:workflows:my_workflow:#places:                -!php/enumApp\Enumeration\ObjectStatus::Draft->value                -!php/enumApp\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.phpreturnstaticfunction (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->value in YAML (and it should be backward compatible):

framework:workflows:my_workflow:#places:                -!php/enumApp\Enumeration\ObjectStatus::Draft                -!php/enumApp\Enumeration\ObjectStatus::Received#

So if this current PR is accepted I can work on improving the support in FrameworkBundle for 7.4.

Spomky and smnandre reacted with thumbs up emoji

@alexandre-daubois
Copy link
Member

alexandre-daubois commentedApr 2, 2025
edited
Loading

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!php:enum:

framework:workflows:my_workflow:#places:                -!php/enumApp\Enumeration\ObjectStatus::Draft->value                -!php/enumApp\Enumeration\ObjectStatus::Received->value#

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!

@stof
Copy link
Member

stof commentedApr 2, 2025

using a type ofBackedEnum in the Workflow component does not provide any type safety guarantee. Enums give extra type safety when your type is the specific enum you intend to support. But the workflow component allows configuring the list of places and transitions, and so cannot define an enum for the parameter type.
As far as the workflow component is concerned, the list of places and transitions is not a closed list (and so is a bad fit for an enum).

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.

@OskarStarkOskarStark changed the title[Workflow] Add support for Backed Enum inMethodMarkingStore[Workflow] Add support for\BackedEnum inMethodMarkingStoreApr 2, 2025
@nicolas-grekas
Copy link
Member

This makes completely sense to me.
I'm just not a fan of the union on the setter that this requires.
What about checking in the compiler pass which enum class setters use and give that to an augmented marking store so that it knows it should cast to enums before calling the setter?

@lyrixx
Copy link
Member

lyrixx commentedApr 10, 2025
edited
Loading

I'm fine with this.

And I like@nicolas-grekas comment.BUT, this could be hard to support all cases.
We could at least use thesupport option configuration. If one uses support_strategy, we could not.

@nicolas-grekas
Copy link
Member

We could at least use the support option configuration. If one uses support_strategy, we could not.

Would work for me - making simple cases simple.

Another idea (draft, for another PR):
support defining places via an enum (a backed one):

places:!php/enum FOO

This would make the workflow still use strings for places, but use enums for the marking store.

@tucksaun
Copy link
ContributorAuthor

hi folks

Sorry for the delay in the response (busy week here)

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.

I would agree if the Workflow was the only place where one could set those values. But when using theMethodMarkingStore, it calls public methods, so nothing is preventing a call tosetStatus with an inappropriate value or an external database alteration. One can do it manually but Enums can help here (preventing the use of values not allowed in the set). This is not entirely error-proof and can still let business logic errors happen or inconsistent states, but in some contexts (eg. government projects) you are asked to use every possible protection measure 🤪

Please note I don't apply this reasoning to transitions as those are only usable via the Worfklow system.

But the workflow component allows configuring the list of places and transitions, and so cannot define an enum for the parameter type.

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.

As far as the workflow component is concerned, the list of places and transitions is not a closed list (and so is a bad fit for an enum).

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"")

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.

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?

I'm just not a fan of the union on the setter that this requires.

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.

What about checking in the compiler pass which enum class setters use and give that to an augmented marking store so that it knows it should cast to enums before calling the setter?

As@lyrixx said, it seemed a bit complicated to do "quickly" and in a performant way.
But usingsupports and a compiler pass might make it easier indeed.
However, this will probably require some refactoring as theMethodMarkingStore is quite closed for now.
I'm okay to work on such a feature if we are willing to go this way (I already have something locally that I tried using reflection to know which Enum type to instantiate)

smnandre and lyrixx reacted with thumbs up emoji

@alexandre-daubois
Copy link
Member

alexandre-daubois commentedApr 10, 2025
edited
Loading

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

@lyrixx
Copy link
Member

lyrixx commentedApr 12, 2025
edited
Loading

Another idea (draft, for another PR): support defining places via an enum (a backed one):

places:!php/enum FOO

Actually, this is super easy :) I'll open a PR soon (except for XML 🥺)

edit: here we go#60204

@lyrixx
Copy link
Member

@tucksaun I think you can leverage#60204 to configure the marking store. Now we can be 100% sure the places are BackedEnum.

tucksaun and Valmonzo reacted with thumbs up emoji

@fabpotfabpot modified the milestones:7.3,7.4May 26, 2025
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@alexandre-dauboisalexandre-dauboisalexandre-daubois left review comments

@lyrixxlyrixxAwaiting requested review from lyrixxlyrixx is a code owner

Assignees
No one assigned
Projects
None yet
Milestone
7.4
Development

Successfully merging this pull request may close these issues.

7 participants
@tucksaun@alexandre-daubois@stof@nicolas-grekas@lyrixx@fabpot@carsonbot

[8]ページ先頭

©2009-2025 Movatter.jp