Problem/Motivation
Sometimes, interfaces are wrong #3352916: Fix PHPStan L1 errors "Call to method getDefinitions()/getSortedDefinitions() on an unknown class Drupal\Core\Plugin\CategorizingPluginManagerTrait.". Sometimes, you may want to add parameters to existing methods. Sometimes, you may want to add or change typehints to arguments or return values.
How to do this in Drupal given its BC policy that does not allow this even in majors?
Cases to cover:
- adding a method to the interface -> already covered
- removing a method from the interface -> already covered
- adding parameter(s) to existing method --> covered by Adding arguments to interface methods
- removing parameter(s) from existing method -> already covered
- adding typehint(s) to method parameter(s) -> see #19
- changing typehint(s) to method parameter(s) -> see #19
Proposed resolution
Changes arguments or typehints of interface methods
Leveraging on Symfony's DebugClassLoader, it's now possible to add arguments to and change signatures of methods in interfaces in a major release.
Since it's normally a BC break to introduce new arguments (because any implementing class that does not comply with the changed signature would fail at loading), this is a two steps process.
This pattern is applicable on all argument changes. For example, changing the argument order of a method.
Step 1 - prepare the new signature
During the current release cycle, introduce the argument change(s) in an inline comment, for example:
- public function foo($bar);
+ public function foo($bar /* , BazInterface $baz */);
Or change the typehint of an argument, for example:
- public function foo(string $bar);
+ public function foo(/* string|Stringable $bar */);
Document the changes in the docblock of the method, inside a phpcs:disable block to prevent PHPCS from firing an error when parsing the signature of the method; in the comments include a @see to a follow-up issue that will take care of actually doing the change in the next major release:
* phpcs:disable Drupal.Commenting
* @todo Uncomment new method parameters before drupal:11.0.0.
* @see https://www.drupal.org/project/drupal/issues/3354672
*
* @param BazInterface $baz
* Documentation for parameter $baz.
* phpcs:enable
Tag the follow-up issue with 'Major version only'.
This will allow the testing framework to trigger deprecation errors for the implementing classes that do not have the new signature in place yet, but because PHP allows methods to add additional non-interface arguments without errors, classes can immediately update. In order to prevent tests fail because of that, add an ignore line in .deprecation-ignore.txt, like e.g.
# Drupal 11.
%Foo::foo\(\).* will require a new "BazInterface \$baz" argument in the next major version of its interface%
%Bar::bar\(\).* will require a new "BazInterface \$baz" argument in the next major version of its interface%
Step 2 - implement the new signature
Once the branch opens for issues for new major release issues:
- Remove the inline comment from the interface, exposing the new full signature:
-
- public function foo($bar /* , BazInterface $baz */); + public function foo($bar, BazInterface $baz);or
- public function foo(/* string|Stringable $bar */); + public function foo(string|Stringable $bar); - remove the PHPCS ignore and
@todofrom the docblock, - implement the new signature in the concrete classes
- remove the ignore line from the
.deprecation-ignore.txtfile.
Remaining tasks
User interface changes
API changes
Data model changes
Release notes snippet
| Comment | File | Size | Author |
|---|---|---|---|
| #19 | core-deprecation-policy-new-paramtype.txt | 41 KB | bbrala |
| #19 | core-deprecation-policy-paramtype.diff | 1.63 KB | bbrala |
| #16 | 3354524-nr-bot.txt | 97 bytes | needs-review-queue-bot |
| #13 | core-deprecation-policy-new.txt | 37.32 KB | bbrala |
| #13 | core-deprecation-policy.txt | 34.06 KB | bbrala |
Comments
Comment #2
cilefen commentedhttps://www.drupal.org/about/core/policies/core-change-policies/bc-polic...
Comment #3
mondrake#2 yes, but in the related issue the problem is with adding arguments to existing methods.
There's a Slack conversation open atm, https://drupal.slack.com/archives/C1BMUQ9U6/p1681721410656849
Comment #4
cilefen commentedYou mentioned adding methods in the issue summary, which is why I commented with that.
Comment #5
catchIf we can do #3354595: Remove the debug classloader deprecation silencing for Drupal interfaces then I think the next step would be to add docs to https://www.drupal.org/about/core/policies/core-change-policies/bc-policy on how to use it to add parameters to interface methods.
For adding/changing type hints we could probably do something like removing a parameter then adding it back again, but should be separate issues probably.
Comment #6
mondrakeAdd to IS a list of cases to cover. Anything else?
Comment #7
mondrakeIn my view, #2 covers case 1 from the IS, #5 covers case 5.
Would be good to have a position for all the cases.
Comment #8
catchThese are already covered in the existing bc policy, IMO we should not bundle things into one issue but handle one at a time where there are deficiencies.
Comment #9
catchthis is also already covered by the existing bc policy.
Comment #11
mondrakeComment #12
catch#3355839: Prepare FormBuilder for variadic functions did exactly what we need to document here, so I think the next step is adding that as an example to the bc policy at https://www.drupal.org/about/core/policies/core-change-policies/drupal-d...
Comment #13
bbralaOk, to get this rolling i attacked a proposed addition to the core page on deprecations.
Comment #14
bbralaAnd readable text:
Adding arguments to interface methods
Leveraging on Symfony's DebugClassLoader, it's now possible to add arguments to methods in interfaces in next major release.
Since it's normally a BC break to introduce new arguments (because any implementing class that does not comply with the changed signature would fail at loading), this is a two steps process.
Step 1 - prepare the new signature
During the current release cycle, introduce the new argument(s) in an inline comment, for example:
Document the new argument(s) in the docblock of the method, inside an PHPCS ignore block to prevent PHPCS to fire an error when parsing the signature of the method; in the comments include a
@seeto a follow-up issue that will take care of actually doing the change in the next major release:Tag the follow-up issue with 'Major version only'.
This will let the testing framework trigger deprecation errors for the implementing classes that do not have, yet, the to-be signature in place. In order to prevent tests fail because of that, add an ignore line in
.deprecation-ignore.txt, like e.g.Step 2 - implement the new signature
Once the branch opens for issues for new major release issues:
@todofrom the docblock,.deprecation-ignore.txtfile.Comment #15
bbralaUpdated covered bullet points as pointed out by catch
Comment #16
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".
This does not mean that the patch 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 #17
bbralaSuggestion by @catch is to use something like:
For changing interface typehints. Was trying to write this, but need to check it with some real code. Also not sure if if this would need to be an example as above of perhaps:
That perhaps feels a little more realistic in context of Drupal code.
Comment #18
bbralaComment #19
bbralaOk, lets see, this seems like an good idea (tm). Added example for changing method signature:
Adding arguments or changing typehints of interface methods
Leveraging on Symfony's DebugClassLoader, it's now possible to add arguments to and change signatures of methods in interfaces in next major release.
Since it's normally a BC break to introduce new arguments (because any implementing class that does not comply with the changed signature would fail at loading), this is a two steps process.
Step 1 - prepare the new signature
During the current release cycle, introduce the new argument(s) in an inline comment, for example:
Or change the typehint of an argument, for example:
Document the changes in the docblock of the method, inside an PHPCS ignore block to prevent PHPCS to fire an error when parsing the signature of the method; in the comments include a
@seeto a follow-up issue that will take care of actually doing the change in the next major release:Tag the follow-up issue with 'Major version only'.
This will let the testing framework trigger deprecation errors for the implementing classes that do not have, yet, the to-be signature in place. In order to prevent tests fail because of that, add an ignore line in
.deprecation-ignore.txt, like e.g.Step 2 - implement the new signature
Once the branch opens for issues for new major release issues:
or
@todofrom the docblock,.deprecation-ignore.txtfile.Comment #20
bbralaComment #21
bbralaComment #22
bbralaUpdated IS with proposed solution
Comment #23
catchMinor wording changes.
Comment #24
bbralaUpdate propopsed solution to only used fake classes/interfaces/methods/arguments
Comment #25
catchTalked with @bbrala about this in slack, I think the proposed wording is really clear, and covers as many cases as we could think of, so ready to go for me.
RTBC and tagging for documentation updates.
Comment #26
bbralaDocumentation has been updated.
Comment #27
catchDocs changes look good but I wonder if we should move it directly under https://www.drupal.org/about/core/policies/core-change-policies/drupal-d... since it's dealing with parameters?
Comment #28
bbralaHmm, both could be right, the placement right now is classes -> interfaces.
I don't mind either way, both make sense in my mind.
Comment #29
joachim commented> %FormBuilder::getForm\(\).* will require a new "mixed \.\.\. \$args" argument in the next major version of its interface%
Is the idea that implementations/subclasses can add the parameters as optional right away, then then they're compatible with both D10 (without the param) and D11 (with the param)?
I'm assuming this message isn't under our control -- but it would be nice if it said that or if we documented that somewhere.
Comment #30
bbralaThink you might wanna look at the is, that's close to what was actually posted. Does the doc page still show that form example? Thought I generalised it.
Comment #31
joachim commentedAFAICT, the IS doesn't explain how implementors should react to the deprecation.
And the IS says how to ignore the deprecation warning in a test, but it doesn't say where the warning is generated.
Comment #32
catchThe warning is generated by Symfony's debug classloader (and this is why we have no control over the contents).
Implementors should add the extra parameter or type hint to their current codebase - PHP won't complain and it'll be forwards compatible for when the interface changes in the next major release.
Comment #34
quietone commentedI think that #27 and #28, about the placement of the new section on the page, has been resolved. xjm moved it to it's own heading, added the links to the tag and other tidying up. Adding credit for xjm for that work.
The new addition is at Interface method signature changes.
Thanks everyone!
Comment #36
xjmApologies in advance for the issue necromancy, but I noticed in reviewing an issue this past week that we're disabling the entire commenting sniff on methods where we use this approach. That seems a bit dangerous. @mondrake and I are exploring a more targeted approach in #3520730: Fix PHPStan arguments.count error related to interface argument additions in the next major.
Comment #37
quietone commentedYes, adding the specific sniffs to ignore is an improvement. Instead of re-opening this lets do that in a new issue. I think that will help to make it easier to see the steps/process used to develop the documentation.
The new issue is #3531685: Ignore specifc sniffs when adding additional parameters to interface methods