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] [WIP] Add transition blockers.#24745

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

Closed
d-ph wants to merge13 commits intosymfony:masterfromd-ph:ticket_24501_rebased

Conversation

@d-ph
Copy link

@d-phd-ph commentedOct 29, 2017
edited
Loading

QA
Branch?4.1
Bug fix?no
New feature?yes
BC breaks?no
Deprecations?no
Tests pass?yes
Fixed tickets#24501
LicenseMIT
Doc PRsymfony/symfony-docs#...
  • gather feedback for my changes
  • update docs

Add feature mentioned in#24501.

Please let me know, whether this code is good to merge and I will update the docs for the new feature.

A bit of my commentary: I find supportingDefinitions with duplicated transition names as a fair maintenance burden for no real benefit. I really can't see any use case why someone might want to have transitions in a workflow, that have duplicated names. In my opinion it violates the principle of least surprise. That is all.

@d-ph
Copy link
Author

The build is failing for unrelated reason (timezone config on the build server?)

/**
* Thrown by Workflow when an undefined transition is applied on a subject.
*/
class UndefinedTransitionExceptionextends SubjectTransitionException
Copy link
Author

Choose a reason for hiding this comment

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

I created a dedicated exception for this case, so devs can do the following:

try {$workflow->apply($subject,$transitionName);}catch (UndefinedTransitionException$exception) {// throw hard}catch (SubjectTransitionException$exception) {// return pretty response with a list of transition blockers.}

Copy link
Author

Choose a reason for hiding this comment

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

Ideally, I'd just throw aLogicException from all methods, that accept$transitionName as a parameter instead of introducing this Exception, but this would break the BC.

Copy link
Author

Choose a reason for hiding this comment

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

On second thought, thistry..catch is annoying, because it would force devs to always catch that exception just to rethrow it. Let me fix that.

*
* @return Transition[]
*/
privatefunctiongetTransitionsByName(string$transitionName)
Copy link
Author

Choose a reason for hiding this comment

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

This isprivate, because I find the fact, that this method returns a collection instead of aTransition|null too confusing to make this methodpublic,

* @return Transition[]|TransitionBlockerList All enabled transitions or a blocker list
* if no enabled transitions can be found
*/
privatefunctiongetEnabledTransitionsByNameOrTransitionBlockerList($subject,string$transitionName)
Copy link
Author

Choose a reason for hiding this comment

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

Very long name, I agree. This method achieves the same as:

$transitions =$this->getEnabledTransitions();if (!$transitions) {$transitionBlockerList =$this->whyCannot();}

but the benefit is, that this method doesn't run the guards twice in case there are no enabled transitions.

This method wasn't needed until I found out, that unit tests, that test duplicated transition names cases, fail. I.e. this method is an example of the maintenance burden I mentioned in the PR.

Copy link
Member

Choose a reason for hiding this comment

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

I really don't like this method either. Could you get ride of it ?

Copy link
Author

Choose a reason for hiding this comment

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

I would like to get rid of it, but I can't because the only other solution is to have the guards be run twice in the pessimistic case scenario (i.e. when there are no enabled transitions). And I'd rather have an ugly but a very dedicated private method than runtime performance degradation.

I guess this is one of those cases, when one looks at other dev's code and thinks "I would've done it better", but when they try to rewrite it to make it better, they realize, there's no better solution.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would throw an exception that carries the list in case the transition is not enabled. this would be better instead of having multple return types

Copy link
Member

Choose a reason for hiding this comment

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

I agree, But anyway, I'm currently rewrite the Workflow class ATM.
It has become too complex to understand. The very first version was very simple and now it too complex too me.
I will open a new PR today.

$transitions =array();

// this is needed to silence static analysis in phpstorm
$transitionBlockerList =newTransitionBlockerList();
Copy link
Author

Choose a reason for hiding this comment

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

Latest phpstorm can't understand, that at this point there must be at least 1$eligibleTransitions. And I don't like static inspector warnings in source files. If you'd like to remove this line, then I'm fine with that.


/**
* @param string $message
* @param string$message
Copy link
Contributor

Choose a reason for hiding this comment

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

You can remove the phpdoc


/**
* @param string $message
* @param TransitionBlockerList $transitionBlockerList
Copy link
Contributor

Choose a reason for hiding this comment

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

phpdoc seems useless

d-ph reacted with thumbs up emoji
@nicolas-grekasnicolas-grekas added this to the4.1 milestoneOct 30, 2017
@nicolas-grekas
Copy link
Member

This should target 4.1 (4.0/3.4 is in feat freeze), which means you could/should add scalar type hints.

@d-ph
Copy link
Author

d-ph commentedOct 31, 2017
edited
Loading

@Simperfit
I removed redundant docblocks.

@nicolas-grekas
I added scalar and return type hints to the new code. You didn't mention return type hints, but since symfony 4.1. requires php ^7.1.3., I figured that's alright. I can remove the return type hints, if they shouldn't be used. Also, only the new code inWorkflow.php uses return typehints now, which means that that file has a mixture of methods that define return typehints and that don't define them. I can set the return type hints for the currentWorkflow.php::* methods as well, if you'd like.

Also, you said, that this PR should target 4.1, but there's no 4.1 git branch. I just changed the value in the table above to mentionBranch? to be 4.1. Fyi.

I don't know why Travis build failed.

An error occurred while generating the build script.

Copy link
Member

@lyrixxlyrixx left a comment

Choose a reason for hiding this comment

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

Hi.

thanks for this PR. This is a very good start.

I made many comments, I can not review it fully for now. It will be easier after an update.

4.1.0
-----

* Added TransitionBlockers as a way to convey why exactly transitions can't be made (#24501).
Copy link
Member

Choose a reason for hiding this comment

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

We usually don't put the reference to the github ticket in the CHANGELOG

d-ph reacted with thumbs up emoji
* Thrown by Workflow when a transition is applied on a subject that is
* not possible to be made.
*/
class SubjectTransitionExceptionextends LogicException
Copy link
Member

Choose a reason for hiding this comment

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

This not is not really good. We need to read the code to understand what it does.
What aboutBlockedTransitionException

Copy link
Author

Choose a reason for hiding this comment

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

You're right. I went with your name.

*/
class TransitionBlocker
{
constREASON_CODE_TRANSITION_NOT_DEFINED ='com.symfony.www.workflow.transition_blocker.not_defined';
Copy link
Member

Choose a reason for hiding this comment

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

What about using UUID like it's done 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 also removeCODE_ from all theses constant name.

Copy link
Author

@d-phd-phNov 11, 2017
edited
Loading

Choose a reason for hiding this comment

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

Ok. I usually use machine codes, that are also human readable, which is helpful, when tracking down problems. But I'm fine to use uuids.

publicfunction__construct($message,$code =null,array$parameters =array())
{
$this->message =$message;
$this->parameters =$parameters;
Copy link
Member

Choose a reason for hiding this comment

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

$parameter should be after$code, to have the same order between attr declaration, arguments, and binding

d-ph reacted with thumbs up emoji
/**
* Creates a new transition blocker.
*
* @param string $message The blocker message
Copy link
Member

Choose a reason for hiding this comment

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

Please, removeall useless PHPDoc.
For exemple, here, everything is useless.
More over we prefer type hint over PHPDoc
This comment applies to all this PR.

Copy link
Author

@d-phd-phNov 11, 2017
edited
Loading

Choose a reason for hiding this comment

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

Ok. I follow the same philosophy in my own code anyway.

*
* @return TransitionBlockerList
*/
privatefunctiondoCan($subject,Marking$marking,Transition$transition)
Copy link
Member

Choose a reason for hiding this comment

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

Typical exemple of useless PHPDoc ;)
As this method is private, you can safely add a return type hint

d-ph reacted with thumbs up emoji
foreach ($transition->getFroms()as$place) {
if (!$marking->has($place)) {
returnfalse;
returnnewTransitionBlockerList(array(TransitionBlocker::createNotApplicable($transition->getName())));
Copy link
Member

Choose a reason for hiding this comment

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

I have an issue this this new feature.
If the a transition is not enabled because of a missing place in a marking, the end user will only know thefirst reason. It's the same for the guard, they will not be triggered.

Anyway, it's a very bad idea to trigger the guard listener if the marking is not valid (performance issue).

What do you think about that?

Copy link
Author

Choose a reason for hiding this comment

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

You're right in saying, that the end user won't know all the blocker reasons, if the the subject isn't in the required state to start with (i.e. the marking is missing a place). I'd say: this is not a bug, but a feature.

I certainly don't need to know the rest of the reasons, if the subject fails to be in the correct state to begin with. And, as you correctly mentioned, it'd be a (massive) performance issue to run the guards in this case, so that's another reason for not doing it.

So what I think is this:

  1. Ain't nobody gonna need this (yagni).
  2. It's a performance issue.
  3. If someone needs this, then he needs to be explained, that this would be a performance issue and therefore it won't be implemented.

*/
privatefunctiongetTransitionsByName(string$transitionName):array
{
$transitions =array_filter(
Copy link
Member

Choose a reason for hiding this comment

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

you can return directly instead of using a var

Copy link
Author

Choose a reason for hiding this comment

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

Yeah, I know. I just have this habit of not immediately returning something, that I might need to xdebug, so I can set a breakpoint on the local variable and see the result of the complex instruction easier.

I changed the code, as you asked.

* @return Transition[]|TransitionBlockerList All enabled transitions or a blocker list
* if no enabled transitions can be found
*/
privatefunctiongetEnabledTransitionsByNameOrTransitionBlockerList($subject,string$transitionName)
Copy link
Member

Choose a reason for hiding this comment

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

I really don't like this method either. Could you get ride of it ?

*
* @param TransitionBlocker[] $blockers The transition blockers to add to the list
*/
publicfunction__construct(array$blockers =array())
Copy link
Member

Choose a reason for hiding this comment

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

You never use the array feature here. You always have to writenew TransitionBlockerList([$b]);
You can change this to:

publicfunction__construct(TransitionBlocker$blocker =null){if ($blocker) {$this->add($blocker);    }}

Copy link
Author

Choose a reason for hiding this comment

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

This constructor expects an array, becauseTransitionBlockerList is an array ofTransitionBlockers, but with a dedicated::findByCode() method. This method is literally the only reason why I created this class, because otherwise I would just be passing aroundTransitionBlocker[] (i.e. native arrays).

This is also, why there'sConstraintViolation andConstraintViolationList in theValidator component.

I would be very surprised, if I discovered, that an array-like class can't be constructed with a native array (principle of least surprise).

Also, by accepting an array, I can construct this class with a collection ofTransitionBlockers or just a singleTransitionBlocker. Bynot accepting an array, I would need to construct this class with anull and then manually run theTransitionBlockerList::add() in a foreach loop in my own code, if I wanted to construct the class with a collection ofTransitionBlockers. This would be extremely annoying (poor DX).

I know, that in the Workflow code I never construct this list with more than 1 item. But taking into account the above, may I leave this constructor with an array argument, please? Having to wrap a singleTransitionBlocker in an array, in order to construct this class, is really not an issue.

@lyrixx
Copy link
Member

@d-ph

A bit of my commentary: I find supporting Definitions with duplicated transition names as a fair maintenance burden for no real benefit.

I totally agree with you about the maintenance burden. I was against 1+ year ago. But I have changed my mind.

But actually it solves many case with easy. Lets say you have N places: A..N. And you want on each a transition "Go back to A". you can name this transition "Go Back to A". So in you templates / API it's really easier to display "Go back to A" than "Go back to A from B", "Go back to A from C" etc...

And it makes support of StateMachine.

d-ph reacted with thumbs up emoji

{
$message =sprintf('Transition "%s" is not defined in workflow "%s".',$transitionName,$workflowName);

returnnewstatic($message,self::REASON_TRANSITION_NOT_DEFINED);
Copy link
Contributor

Choose a reason for hiding this comment

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

workflow and transition name could be additionally passed as parameters.

*/
publicstaticfunctioncreateNotApplicable(string$transitionName):self
{
$message =sprintf('Transition "%s" cannot be made, because the subject is not in the required place.',$transitionName);
Copy link
Contributor

Choose a reason for hiding this comment

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

transition name could be additionally passed as parameter.

*/
publicstaticfunctioncreateUnknownReason(string$transitionName):self
{
$message =sprintf('Transition "%s" cannot be made, because of unknown reason.',$transitionName);
Copy link
Contributor

Choose a reason for hiding this comment

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

transition name could be additionally passed as parameter.

Copy link
Author

Choose a reason for hiding this comment

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

You're right. I guess I didn't eat my own dog food here. I've done what you mentioned in all three places.

}

publicfunctionisBlocked()
publicfunctiongetTransitionBlockerList():TransitionBlockerList
Copy link
Member

Choose a reason for hiding this comment

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

Changing the order of method make the diff harder to read and to review. Could you keep the same method order ?

Thanks

Copy link
Author

Choose a reason for hiding this comment

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

The order of methods didn't change. Github's diff tool just struggles to find the similarity, due to the amount of changes in this file. Screenshot of PhpStorm's diff tool:

image

Let me know, if I missed something.

lyrixx reacted with thumbs up emoji
useSymfony\Component\Workflow\TransitionBlockerList;

/**
* Thrown by Workflow when a transition is applied on a subject that is
Copy link
Member

Choose a reason for hiding this comment

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

Thrown by the workflow when a transition is not enabled.

(I'm not a native English speaker, but it seems easier to understand)

Copy link
Author

Choose a reason for hiding this comment

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

I agree. I just wanted to use the vocabulary set out by theWorkflow component.

I'll use the sentence you suggested.

* in a workflow.
*
* @param string $transitionName
* @param string $workflowName
Copy link
Member

Choose a reason for hiding this comment

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

this PHP Doc is useless. Please remove it (same for other occurrence)

Copy link
Author

Choose a reason for hiding this comment

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

This specific one -- yes, it's useless.

I don't find the remaining two useless, though.

  1. createNotApplicable() alone doesn't include the fact, that it's supposed to be created only, when a transition isn't applicable due to the subject's being in wrong status. For any other reason, why transition isn't applicable, there should be a different transition blocker used (i.e. a different blocker code). I can rename the method tocreateNotApplicableDueToWrongSubjectPlace() to make it more clear, if you'd like me to.
  2. createUnknownReason(), the docblock for this one emphasizes, that this transition blocker is used for preserving backwards compatibility and, by extension, shouldn't be used in new code.

I kept the docblock for::createNotDefined() to keep it consistent with other methods' having docblocks in this file.

If after this explanation you still would like me to remove these docblocks, then I'll remove them.

Copy link
Member

Choose a reason for hiding this comment

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

Sorry, I was talking only about the parameter.
(Note I'm going to finish your PR ;)

*/
publicstaticfunctioncreateNotDefined(string$transitionName,string$workflowName):self
{
$message =sprintf('Transition "%s" is not defined in 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.

Since PHP 7.0, you can use:$message = "Transition '$transitionName' is not defined in workflow '$workflowName'."

It's easier to read IMHO

Copy link
Contributor

Choose a reason for hiding this comment

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

sprintf is used throughout symfony, as well as double quotes for quoting values in messages

Copy link
Author

Choose a reason for hiding this comment

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

Yeah, I usedsprintf() because it's used everywhere else. If I recall correctly, I used string interpolation at first, but then was told to change it tosprintf() by fabbot.io.

/**
* A list of transition blockers.
*/
class TransitionBlockerListimplements \IteratorAggregate, \Countable, \ArrayAccess
Copy link
Member

Choose a reason for hiding this comment

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

Do we really need the ArrayAccess ? I don't think so

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree ArrayAccess should be dropped and maybe even an interface introduced that only grants read access

Copy link
Member

Choose a reason for hiding this comment

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

👎 for the interface.

Copy link
Author

@d-phd-phJan 28, 2018
edited
Loading

Choose a reason for hiding this comment

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

I'll remove theArrayAccess, but I found that useful to think of the instances of this class as "smarter arrays".

I'll also remove the::remove() and::set() methods, since I didn't find them useful in any use case from the very beginning.

I disagree, that the thing should be completely immutable, though. The common pattern in Transition Guards is to start with an emptyTransitionBlockerList and then::add() blockers asif statements evaluate, that the transition isn't possible.

{
$transitions =$this->definition->getTransitions();
$marking =$this->getMarking($subject);
return0 ===count($this->buildTransitionBlockerList($subject,$transitionName));
Copy link
Contributor

Choose a reason for hiding this comment

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

Since you use this logic in quite a few places, it would be nice to have a TransitionBlockerList::isEmpty

* @return Transition[]|TransitionBlockerList All enabled transitions or a blocker list
* if no enabled transitions can be found
*/
privatefunctiongetEnabledTransitionsByNameOrTransitionBlockerList($subject,string$transitionName)
Copy link
Contributor

Choose a reason for hiding this comment

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

I would throw an exception that carries the list in case the transition is not enabled. this would be better instead of having multple return types

*
* @return TransitionBlockerList Empty if the transition is possible
*/
publicfunctionbuildTransitionBlockerList($subject,string$transitionName):TransitionBlockerList;
Copy link
Contributor

Choose a reason for hiding this comment

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

isnt this a BC break? AFAIK you are only allowed to add new interface methods in major versions

Copy link
Member

Choose a reason for hiding this comment

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

No, because it's only in SF 4.1 which has not been released.

@lyrixx
Copy link
Member

lyrixx commentedFeb 7, 2018
edited
Loading

closed in favor of#26076

d-ph reacted with thumbs up emoji

@lyrixxlyrixx closed thisFeb 7, 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
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@lyrixxlyrixxlyrixx requested changes

+2 more reviewers

@SimperfitSimperfitSimperfit left review comments

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

6 participants

@d-ph@nicolas-grekas@lyrixx@backbone87@Simperfit@carsonbot

[8]ページ先頭

©2009-2025 Movatter.jp