-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[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 our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
lyrixx
commented
Feb 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 |
d3d84df
to
e8b44a5
Compare
@@ -79,25 +81,28 @@ public function getMarking($subject) | ||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function can($subject, $transitionName) | ||
public function can($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 method isTransitionDefined
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 with checkTransition
(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 added isTransitionDefined
. 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 ;)
@@ -185,32 +190,41 @@ public function getMarkingStore() | ||
return $this->markingStore; | ||
} | ||
|
||
private function doCan($subject, Marking $marking, Transition $transition) | ||
private function isTransitionEnabled($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 to determineTransitionAvailablity
or computeTransitionState
clarifies the intention: $this->computeTransitionState(...)->isEnabled()
. Another helper method isAvailable
could be added to TransitionBlockerList, that returns true, either if isEnabled()
returns true or if all blockers on the list are of type BLOCKED_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 the throwException
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
e8b44a5
to
47d712b
Compare
fca2db5
to
199032a
Compare
*/ | ||
public function can($subject, $transitionName); | ||
public function can($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
199032a
to
3f16c5b
Compare
This PR is ready |
5d364bd
to
c6a786d
Compare
I would like to merge this PR today (because it's open for a week now), so I'm calling for a review :) Thanks |
*/ | ||
final class TransitionBlocker | ||
{ | ||
const BLOCKED_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 them public
explicitly
Hello, Sorry to muddle the PR even further, but there's a functionality regression in this PR that the original 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? |
I agree with @d-ph, but i would keep |
I can put back the exception in the apply method. |
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. |
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. |
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). |
@andrewtch Sure. It will be fixed with #26092 |
9219144
to
59c2f8b
Compare
|
||
public function __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".
?
{ | ||
public function __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; | ||
|
||
public function __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.
*/ | ||
public function can($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 | ||
*/ | ||
public function buildTransitionBlockerList($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
59c2f8b
to
4867194
Compare
@fabpot Thanks for the review. I have addressed your comments. |
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 👍
4867194
to
b8cfbfc
Compare
b8cfbfc
to
2b8faff
Compare
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 | MIT Commits ------- 2b8faff [Workflow] Cleaned the transition blocker implementations 4d10e10 [Workflow] Add transition blockers
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
This looks like #23863? \o/ |