Problem/Motivation
As we start doing more things asynchronously we'll have more batched work that can succeed and fail. In those kinds of scenario's an exception would cancel out the entire batch, even though it's often desirable to have a single item in a batch file while the rest of the batch continues.
Other (functional) languages solve this by providing a Result type that can be Ok or Error. By making clever use of the type-system this allows communicating that something can fail and forces the calling code to either deal with the success and error case (making sure not to forget one) or to propagate the result (optionally transforming the Ok or Error case).
PHP does not have a type like this itself so some custom code is needed. It's a capability that we will want to use in Drupal Core (e.g. for asynchronously processing BigPipe or rendering tasks where item-errors don't stop the entire process) and where contrib will want to standardise on a shared type for interoperability.
Steps to reproduce
Proposed resolution
Implement a Drupal/Core/Result (or should it be Drupal/Component/Result?) type that uses PHPStan types to implement the same logic as is available in functional programming languages (on Error you'll get the Error Type and on Ok you get the Ok Type).
An example implementation exists in https://phpstan.org/r/e9c7213e-b7ad-47f8-93b4-ec19e479b688 which is waiting for a small bit of PHPStan support and not quite ready for implementation in Drupal.
Remaining tasks
Agree on the namespace of the new classFix the existing PHPStan error (which is detected at Level 3 and above)Create MRAdd PHPUnit tests for the methods on the Result object, these PHPUnit tests should help PHPStan demonstrate the types are correct and working- Write release notes snippet
User interface changes
API changes
Drupal introduces a new Result type which can be used to communicate that the result of an action can be successful or failure and that such a failure is not an exceptional circumstance (i.e. does not warrant an Exception). The Result class is typed in such a way that calling code can get type-hints of the types produced for either case.
Data model changes
Release notes snippet
| Comment | File | Size | Author |
|---|---|---|---|
| #5 | 3425201-nr-bot.txt | 1.84 KB | needs-review-queue-bot |
Issue fork drupal-3425201
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #3
kingdutchAdded a merge request to show the proposed implementation. However this "Needs Work" because we can't quite tell PHPStan what we want to do :)
However, this allows async issues to start using the type and demonstrate its usefulness.
Comment #4
kingdutchI've asked for some help in the PHPStan discussions on GitHub and Ondrej was very kind in providing more insights: https://github.com/phpstan/phpstan/discussions/10667. It turns out I was running into a known issue https://github.com/phpstan/phpstan/issues/6732.
I've now pushed a workaround using a type annotation :)
Comment #5
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #6
kingdutchThis was failing on some coding standards (parts of which is PHPCS not yet understanding more complex PHPStan types). These were fixed in a commit amend.
Comment #7
kingdutchI missed Catch's feedback on the missing
boolreturn types for isOk/isError which I've now added :DComment #8
andypostIt looks straightforward so not sure a test needed.
But it needs CR with example of use to announce it to module developers and maybe follow-up to apply monadic result to some core places
Comment #9
kingdutchI wasn’t quite sure how to test it. I suppose we could test that the isOk and isError types do what they say.
What I’ve previously done is write a few function calls around it with PHPStan at level 9 to make sure all the type annotations had the right behavior. We don’t really have infrastructure for that though and that should be solidified when we adopt it :)
See the linked PHPStan sandbox for that bit of code
I’ll write a CR proposal in the coming days unless someone is faster
Comment #10
kingdutchI've applied the suggested changes to the documentation and removed the sentence about the list example, instead showing it as code which I hope helps it more easily click with developers.
Comment #11
smustgrave commentedNice!
Shouldn't we have test coverage though to make sure this does what is expected?
Comment #12
alexander allen commentedYes, the tests don't have to be large or complex for just a few types. Like you say, it's just to test the types do what's advertised, and also to provide the coverage. They can always be expanded for edge cases as needed.
I'll crank out a test this week, and will make sure PHPStan returns accurate results (part of the tests).
Comment #13
alexander allen commentedHere's my initial contribution, without any unit tests: https://phpstan.org/r/3964689e-925b-4b7f-bdc5-eca6d4eb7cf5
OkTandIdentityValueokanderrormethods have to define their own local method-level genericsDrupal\Core\Result<bool, int>, which means the type hint of the genericIdentityValueis preseved.OkTgeneric is locked down to a bool both at the class template and the class property, all compilation warnings have been resolvedThere is some funny business with having to specify:
But that is what was required to resolve the
Method Drupal\Core\Result::ok() should return Drupal\Core\Result<T, never> but returns Drupal\Core\Result<T, T>compile error in https://phpstan.org/r/e9c7213e-b7ad-47f8-93b4-ec19e479b688Might want to ask Ondrej or one of the smarter guys about that one.
Now about those unit tests...where to place them...
Comment #14
kingdutchSorry Alexander Allen! Looks like you started off from an older starting point. From a types perspective the MR is already ready to go.
I've updated the issue summary's remaining tasks to better reflect that.
Comment #15
alexander allen commentedHaha @Kingdutch, I played myself there! I'm still laughing over here.
I rebased 11.x so it's nice and up to date and I'm going to take a crack at writing a basic test that checks for the class instance, does some basic returns, etc. just too see if I can help a bit. Cheers!
Comment #16
alexander allen commentedComment #17
smustgrave commentedRan the test-only feature to verify/show the test coverage is there https://github.com/phpstan/phpstan/issues/6732 which it is!
Reading the CR, believe all the info is there with examples. Added the default branch of 11.x
Appears all feedback/threads have been addressed.
Code review not entirely sure don't see other examples in core of using things like
@template Tor@param T $valueI'm a +1 but going to leave in review for a better code review.
Comment #18
smustgrave commentedGoing to take a leap of faith and mark it
Comment #19
quietone commentedI read the MR and made comments there. This needs work to make sure that the result on a.d.o is readable. This is using tags that a.d.o does not understand and the output is not legible.
Comment #20
catchMoving to needs work for #19.
Comment #21
alexander allen commentedI am not sure I can address all the review issues by myself, but I will start by looking into the readability issues created by using generic tags.
Thank you for the review @quietone.
Comment #22
quietone commented@alexander allen, the short answer is that I don't know much about how api.drupal.org works. I figure out it by testing on my local version. The API project page has a link to instructions for setting it up locally.
Having said that, if we use the tags that are documented in the Coding Standards for comments, it will work much better. I was only able to make the suggestions I did after testing locally.
Comment #23
kingdutchThanks for the additions and questions AlexanderAllen and quietone.
I've updated the comments throughout the file to address the feedback, after setting up api.drupal.org locally (that was more difficult than I expected). I wasn't familiar with the tooling so I appreciate the link! There were a few hurdles (such as the nested multi-line comment) but I've done my best to adjust the docblock so that it renders nicely on api.drupal.org and also added a link to explain the generics. I've ensured there's a newline between template definitions so that they at least don't bundle into a single paragraph but remain readable.
I realize that Drupal hasn't really used generics yet, but they're a very powerful feature to help developers deal with "container classes". In this case as AlexanderAllen explained, they're indeed not optional, so removing them to improve the documentation rendering would be a downgrade for anyone using a static analysis tool.
As an example the PHPStan types will make the following type detection possible and help you handle both the success and error path:
It's a really powerful feature and I hope we can improve the API Doc support for it in the future. Especially with applications such as #3491574: Static analysis for EntityStorage coming up to better document entity storage classes (and remove a bunch of logic that's currently in mglaman/phpstan-drupal).
I've done my best to change the way it's documented in the class to ensure that it remains readable within the current capabilities of api.drupal.org.
Comment #24
catchI think if the phpstan template usage is additional and not replacing other docs, and genuinely helps with phpstan, as is the case here, then it should be fine to add it.
There was another issue recently, which I can't find right now but will link when/if I see it again, which was using templates more as a shorthand to prevent copypasta, and that seems like a net-loss for those of us who don't use IDEs.
Comment #25
quietone commentedI made some more comments and others still need to be resolved.
Comment #26
kingdutchAddressed the feedback where possible, provided explanations for why the code looks the way it does in other places.
Regarding the use of
@templatein this issue. The usage in the Result class is purely to add type information for static type analysis, not to DRY up code. The removal of the@templateannotations would mean that the type information would be replaced withmixedwhich means that type information is lost whenResultis used. The proper application of generics ensures that the type information is preserved.For users who do not use static analysis tools or IDEs the result is no different than having a
mixedtype declaration in terms of visual load, except that they too can see that they're not two unrelated unknown types, but that there is a connection between certain inputs and certain outputs.Comment #27
borisson_I think this looks good, the documentation here looks great and the code itself looks clear.
I think we should add a followup issue for api.d.o to handle `@template` in a good way.
Additionally, we don't usually add new functionality without implementing it somewhere, and we can't really add this to any existing api's without breaking backwards compatibility. I wonder where/how we're going to implement this, is there a followup already created where we know where can implement this?
Comment #28
akalam commentedAFAIK the implementation is to be handled at #3425212: [PP-1] Migrate BigPipe and Renderer fiber implementation to use Revolt Event Loop
Comment #29
el7cosmosIs there a compelling reason to use a Drupal-specific implementation rather than adopting an existing solution? I’ve noticed some Composer packages that offer a similar goal.
Comment #30
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.