Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork9.7k
[Workflow] Add transition blockers#26076
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
Uh oh!
There was an error while loading.Please reload this page.
Conversation
lyrixx commentedFeb 7, 2018
| Q | A |
|---|---|
| Branch? | master |
| Bug fix? | no |
| New feature? | yes |
| BC breaks? | no |
| Deprecations? | no |
| Tests pass? | yes |
| Fixed tickets | #24745#24501 |
| License | MIT |
| * {@inheritdoc} | ||
| */ | ||
| publicfunctioncan($subject,$transitionName) | ||
| publicfunctioncan($subject,$transitionName,bool$throwException =false) |
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.
Imho $throwException should be default true on next major. A deprecation should be added for calling ->can with $throwException = false. keeping this bool around for longer than necessary is bloating the API imho.
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 disagree with that ;) IMHO knowing exactly why a transition is blocked is not so common.
more over, in twig it will not work well.
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.
Hm. Imho Every user driven workflow wants to know exactly why a transition isnt enabled. And also what transitions can be enabled in a given state (transition availibility). For highly automated workflows i can See your point.
To Clarify my desired behavior:can should always Return a bool except when a transition isnt even defined in the workflow. A second method should always return a blocker list (except for undefined transitions). A third methodisTransitionDefined That always returns bool and Never throws.
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.
To Clarify my desired behavior: can should always Return a bool except when a transition isnt even defined in the workflow. A second method should always return a blocker list (except for undefined transitions). A third method isTransitionDefined That always returns bool and Never throws.
Why not ;) What do other think ?
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.
But I have two concerns with your proposal:
: can should always Return a bool except when a transition isnt even defined in the workflow
This is a BC break
A second method should always return a blocker list (except for undefined transitions)
What would be the name
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.
A) keep the throwException param with default false, But deprecate Passing false
B) Workflow::whyNot ::checkTransition ::testTransition ::determineTransitionAvailibility (Probably the 2nd is my favorite)
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.
Ok, I will do that withcheckTransition (I'm not really fan of this name though )
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.
If you think another name is better then go for it. I myself am not convinced 100% by checkTransition
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 did not addedisTransitionDefined. IMHO it's useless. If you really think it's useful please open an issue and let's discuss it there.
I would like to merge this PR ASAP ;)
| } | ||
| privatefunctiondoCan($subject,Marking$marking,Transition$transition) | ||
| privatefunctionisTransitionEnabled($subject,Marking$marking,Transition$transition,$throwException =false):bool |
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.
exceptions are not necessary imho. always returing a (possibly empty) TransitionBlockerList ist much more straight forward. in the calling place you can do$this->isTransitionEnabled(...)->isEnabled().isEnabled is then a new helper method on TransitionBlockerList. maybe renaming the method todetermineTransitionAvailablity orcomputeTransitionState clarifies the intention:$this->computeTransitionState(...)->isEnabled(). Another helper methodisAvailable could be added to TransitionBlockerList, that returns true, either ifisEnabled() returns true or if all blockers on the list are of typeBLOCKED_BY_MARKING
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.
You are right. I will remove thethrowException parameter.
But I would like to avoid the word "state" as it could be confusing (place / state / marking)
About isAvailable is don't think is really useful. We will see
fca2db5 to199032aCompare| * @throwsBlockedTransitionException | ||
| */ | ||
| publicfunctioncan($subject,$transitionName); | ||
| publicfunctioncan($subject,$transitionName,bool$throwException =false); |
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.
Adding a new argument in the interface is a BC break, and cannot be done before 5.0
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.
The interface is not released yet. So there are no BC break ;)
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.
then OK.
It thought it was already there in 4.0
lyrixx commentedFeb 15, 2018
This PR is ready |
5d364bd toc6a786dComparelyrixx commentedFeb 16, 2018
I would like to merge this PR today (because it's open for a week now), so I'm calling for a review :) Thanks |
| */ | ||
| finalclass TransitionBlocker | ||
| { | ||
| constBLOCKED_BY_MARKING ='19beefc8-6b1e-4716-9d07-a39bd6d16e34'; |
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.
What are these?
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.
It was in the initial PR.
It's used to identify a blocker by a "machine". It's the same approach as in the Validator component.
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 would make thempublic explicitly
Uh oh!
There was an error while loading.Please reload this page.
d-ph commentedFeb 18, 2018
Hello, Sorry to muddle the PR even further, but there's a functionality regression in this PR that theoriginal PR (although including an ugly sounding private method name: Namely, my PR was optimised for not having to run the guards twice and still being able to tell what the transition blockers were, if they occurred. For example, imagine the following code: try {$transitionBlockerList =$workflow->buildTransitionBlockerList($subject,'foo-transition');if (!$transitionBlockerList->isEmtpy()) {// handle this exceptional case by e.g. showing an error in the UI.return; }$workflow->apply($subject,'foo-transition');}catch (UndefinedTransitionException$exception) {// handle the exception and/or rethrow.throw$exception;} In the code above, the transition guards are run twice -- first for As I mentioned, the original PR optimised for this scenario by allowing devs to do the following: try {$workflow->apply($subject,'foo-transition');}catch (BlockedTransitionException$exception) {// handle this exceptional case by e.g. showing an error in the UI.return;}catch (UndefinedTransitionException$exception) {// handle the exception and/or rethrow.throw$exception;} In the code above, devs just call the 'apply()' method and let if fail, if the transition is blocked. If the devs are interested in the exact reason why, then they can catch the BlockedTransitionException exception and retrieve the TransitionBlockers that blocked the transition. Most importantly though, the guards are not called twice, which was one of the two reasons I created the original PR in the first place (the first reason was to introduce the TransitionBlockers). Maybe it'd be a good idea to further require this behaviour by writing a unit test for it ( Although I appreciate the work put in this PR, the fact that guards are called twice doesn't make me very happy as a consumer of the Workflow component. Could something be done in this PR to make sure the guards are not called twice in the case scenario I brought forth here, please? |
backbone87 commentedFeb 19, 2018
I agree with@d-ph, but i would keep |
lyrixx commentedFeb 19, 2018
I can put back the exception in the apply method. |
lyrixx commentedFeb 22, 2018
Actually, I the more I think to this issue, the more I think is not the right way to go. If you have some perf issue with guard, The best way is to add a cache layer in your application. I should be really easy to implement, since we have a very nice cache component. |
d-ph commentedFeb 24, 2018
I'm sorry, but I fail to see what the problem here is. Firstly, the Secondly, throwing specific exceptions, as a way to knowing what the TransitionBlockers are, is an alternative to the solution of doing it with the Thirdly, as the old adage has it: "The two most difficult things in software development are: naming things and invalidating the caches". Sorry to sound harsh, but what you essentially said was: "I have personal/ideological issues with using the language's standard means to communicate errors in routines, but that's alright because you can fix the performance problems by using and maintaining caches, which is one of the two of the most annoying problems in software industry". Communicating errors in routines is not a new problem and we've all seen all the possible solutions out there already:
None of these are pretty, but all of them solve the problem. I'd say it's highly contextual, as to which of these methods should be used when. And I'd say option 3. has the best "getting things done effectively"/ugliness factor in the case of I have a feeling that the actual problem is in the fact, that in order to get I understand that this is your code and your component, so eventually whichever approach is chosen is down to you. I just wanted to express my humble dev feedback. Thanks for taking it into your consideration and consciously deciding what's best for this component. |
andrewtch commentedMar 19, 2018
As for#26581, the exception class should contain transition / workflow name instead merging them into string (that needs to be parsed to determine particular exception source when caught in somehow top-level exception handler). |
lyrixx commentedMar 19, 2018
@andrewtch Sure. It will be fixed with#26092 |
9219144 to59c2f8bCompare| publicfunction__construct(string$transitionName,string$workflowName,TransitionBlockerList$transitionBlockerList) | ||
| { | ||
| parent::__construct(sprintf('The transition "%s" is not enabled for the workflow "%s".',$transitionName,$workflowName)); |
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.
Transition "%s" is not enabled for workflow "%s".?
| { | ||
| publicfunction__construct(string$transitionName,string$workflowName) | ||
| { | ||
| parent::__construct(sprintf('The transition "%s" is not defined for the workflow "%s".',$transitionName,$workflowName)); |
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.
Transition ... for workflow ...
| private$code; | ||
| /** | ||
| * @var array This is useful if you would like to pass around the condition values, that |
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 would move this doc to the constructor phpdoc instead as this var is private.
| } | ||
| /** | ||
| * Create a blocker, that says the transition cannot be made because it is |
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.
Creates a blocker that says ...
| } | ||
| /** | ||
| * Create a blocker, that says the transition cannot be made because it has |
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.
same here (missing s, no comma)
| /** | ||
| * Create a blocker, that says the transition cannot be made because it has | ||
| * blocked by the expression guard listener. |
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.
it has been blocked
| } | ||
| /** | ||
| * Create a blocker, that says the transition cannot be made because of |
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.
same here as well
| */ | ||
| private$parameters; | ||
| publicfunction__construct(string$message,string$code,array$parameters =array()) |
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.
We should probably document what code is and the different possibilities.
| publicfunctioncan($subject,$transitionName); | ||
| /** | ||
| * Build a TransitionBlockerList to know why a transition is blocked. |
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.
Builds
| * @param object $subject A subject | ||
| * @param string $transitionName A transition | ||
| */ | ||
| publicfunctionbuildTransitionBlockerList($subject,$transitionName):TransitionBlockerList; |
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.
As this is a new method, you can use type hints here at least for$transitionName
lyrixx commentedMar 20, 2018
@fabpot Thanks for the review. I have addressed your comments. |
fabpot left a comment
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.
with a minor comment
| * Builds a TransitionBlockerList to know why a transition is blocked. | ||
| * | ||
| * @param object $subject A subject | ||
| * @param string $transitionName A transition |
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.
Could be removed
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.
done 👍
fabpot commentedMar 20, 2018
Thank you@lyrixx. |
This PR was merged into the 4.1-dev branch.Discussion----------[Workflow] Add transition blockers| Q | A| ------------- | ---| Branch? | master| Bug fix? | no| New feature? | yes| BC breaks? | no| Deprecations? | no| Tests pass? | yes| Fixed tickets |#24745#24501| License | MITCommits-------2b8faff [Workflow] Cleaned the transition blocker implementations4d10e10 [Workflow] Add transition blockers
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
lyrixx commentedMar 21, 2018
And thanks@d-ph for the initial work ;) |
This PR was merged into the 4.1-dev branch.Discussion----------[Workflow] removed bc break| Q | A| ------------- | ---| Branch? | master| Bug fix? | no| New feature? | no| BC breaks? | no| Deprecations? || Tests pass? | yes| Fixed tickets |#26076| License | MIT| Doc PR |Commits-------685695d [Workflow] removed bc break
dkarlovi commentedMar 21, 2018
This looks like#23863? \o/ |