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

Merged
fabpot merged 2 commits intosymfony:masterfromlyrixx:workflow-blocker
Mar 20, 2018

Conversation

@lyrixx
Copy link
Member

QA
Branch?master
Bug fix?no
New feature?yes
BC breaks?no
Deprecations?no
Tests pass?yes
Fixed tickets#24745#24501
LicenseMIT

* {@inheritdoc}
*/
publicfunctioncan($subject,$transitionName)
publicfunctioncan($subject,$transitionName,bool$throwException =false)
Copy link
Contributor

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.

Copy link
MemberAuthor

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.

Copy link
Contributor

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 s‭hould 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.

malutanpetronel reacted with thumbs up emoji
Copy link
MemberAuthor

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 s‭hould 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 ?

Copy link
MemberAuthor

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 s‭hould 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

Copy link
Contributor

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)

Copy link
MemberAuthor

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 )

Copy link
Contributor

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

Copy link
MemberAuthor

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
Copy link
Contributor

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

Copy link
MemberAuthor

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

@nicolas-grekasnicolas-grekas added this to the4.1 milestoneFeb 11, 2018
@lyrixxlyrixxforce-pushed theworkflow-blocker branch 2 times, most recently fromfca2db5 to199032aCompareFebruary 13, 2018 13:09
* @throwsBlockedTransitionException
*/
publicfunctioncan($subject,$transitionName);
publicfunctioncan($subject,$transitionName,bool$throwException =false);
Copy link
Member

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

Copy link
MemberAuthor

@lyrixxlyrixxFeb 15, 2018
edited
Loading

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

Copy link
Member

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
Copy link
MemberAuthor

This PR is ready

@lyrixxlyrixxforce-pushed theworkflow-blocker branch 2 times, most recently from5d364bd toc6a786dCompareFebruary 16, 2018 09:48
@lyrixx
Copy link
MemberAuthor

I would like to merge this PR today (because it's open for a week now), so I'm calling for a review :) Thanks
(cc /@stof@nicolas-grekas@Nyholm@xabbuh )

*/
finalclass TransitionBlocker
{
constBLOCKED_BY_MARKING ='19beefc8-6b1e-4716-9d07-a39bd6d16e34';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

What are these?

Copy link
MemberAuthor

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.

Copy link
Member

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

HeahDude reacted with thumbs up emoji
@d-ph
Copy link

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:getEnabledTransitionsByNameOrTransitionBlockerList()) didn't have.

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 forbuildTransitionBlockerList() and second forapply(). This is not only not ideal but also potentially seriously time consuming. Imagine one of the guards are doing an API call to a 3rd party system for some reason. The API call would need to be done twice because of the way the Workflow code is written.

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

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
Copy link
Contributor

I agree with@d-ph, but i would keepbuildTransitionBlockerList because some users (like me :)) want to show the user what needs to be done in advance and not after he tried to transit into a new place.

@lyrixx
Copy link
MemberAuthor

I can put back the exception in the apply method.

d-ph reacted with thumbs up emoji

@lyrixx
Copy link
MemberAuthor

Actually, I the more I think to this issue, the more I think is not the right way to go.
Exception should remain exceptionnel and should not be used to control the flow.

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.

atrauzzi reacted with thumbs down emoji

@d-ph
Copy link

I'm sorry, but I fail to see what the problem here is.

Firstly, theWorkflow::apply() method, as it stands right now, is already throwing an exception (LogicException), when a transition isn't applied in the::apply() method for whatever reason. My solution just makes sure that the reason is clearly stated -- whether the transition wasn't applied because it's missing or whether it wasn't applied because someGuards said "No". The same execution model and "principle", just more informative.

Secondly, throwing specific exceptions, as a way to knowing what the TransitionBlockers are, is an alternative to the solution of doing it with theif can(), then apply(). If devs want less performant solution but more ideology-compliant, then they can go for theif can(), then apply(). Other devs can catch exceptions from theapply() method. Everyone's happy.

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:

  1. Returning a valid value or an error code from a routine.
  2. Returning a valid value orfalse and offering a stateful getter for the last error in that routine (e.g.json_last_error())
  3. Throwing an exception.

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 ofWorkflow::apply().

I have a feeling that the actual problem is in the fact, that in order to get::apply() throw specific exceptions, my ugly soundinggetEnabledTransitionsByNameOrTransitionBlockerList() would need to be brought over from my PR and for some reason this is being tried to be avoided at all cost. I admit the name of this method is ugly, and that it returns two different things depending on circumstances. However, it's just a private method, so there should be no issues with the fact that it's a highly specialised and optimised method to achieve a specific goal (i.e. not to run theGuards twice).

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.

lyrixx, atrauzzi, and malutanpetronel reacted with thumbs up emoji

@andrewtch
Copy link
Contributor

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
Copy link
MemberAuthor

@andrewtch Sure. It will be fixed with#26092

@lyrixxlyrixxforce-pushed theworkflow-blocker branch 3 times, most recently from9219144 to59c2f8bCompareMarch 20, 2018 14:52

publicfunction__construct(string$transitionName,string$workflowName,TransitionBlockerList$transitionBlockerList)
{
parent::__construct(sprintf('The transition "%s" is not enabled for the workflow "%s".',$transitionName,$workflowName));
Copy link
Member

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));
Copy link
Member

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
Copy link
Member

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
Copy link
Member

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
Copy link
Member

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.
Copy link
Member

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
Copy link
Member

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())
Copy link
Member

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.
Copy link
Member

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;
Copy link
Member

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
Copy link
MemberAuthor

@fabpot Thanks for the review. I have addressed your comments.

Copy link
Member

@fabpotfabpot left a 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Could be removed

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

done 👍

@fabpot
Copy link
Member

Thank you@lyrixx.

@fabpotfabpot merged commit2b8faff intosymfony:masterMar 20, 2018
fabpot added a commit that referenced this pull requestMar 20, 2018
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
@lyrixxlyrixx deleted the workflow-blocker branchMarch 20, 2018 21:12
@lyrixx
Copy link
MemberAuthor

And thanks@d-ph for the initial work ;)

d-ph reacted with heart emoji

@lyrixxlyrixx mentioned this pull requestMar 21, 2018
fabpot added a commit that referenced this pull requestMar 21, 2018
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
Copy link
Contributor

This looks like#23863? \o/

@fabpotfabpot mentioned this pull requestMay 7, 2018
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@stofstofstof requested changes

@NyholmNyholmNyholm left review comments

@xabbuhxabbuhxabbuh left review comments

@fabpotfabpotfabpot approved these changes

@nicolas-grekasnicolas-grekasnicolas-grekas approved these changes

+1 more reviewer

@backbone87backbone87backbone87 requested changes

Reviewers whose approvals may not affect merge requirements

Assignees

No one assigned

Projects

None yet

Milestone

4.1

Development

Successfully merging this pull request may close these issues.

11 participants

@lyrixx@d-ph@backbone87@andrewtch@fabpot@dkarlovi@nicolas-grekas@stof@Nyholm@xabbuh@carsonbot

[8]ページ先頭

©2009-2025 Movatter.jp