Problem/Motivation
UserInterface
defines the hasRole()
method, but the Drupal::currentUser()
returns an object implementing AccountInterface
, which does not have this method unless the current user is authenticated, in which case a UserInterface
object is returned.
Because of this, hasRole()
cannot be used without first checking the object type.
Proposed resolution
Since UserInterface
extends AccountInterface
, move the hasRole
method there.
Quoting @catch from #28:
We do allow adding methods to interfaces that aren't explicitly tagged with @api, see https://www.drupal.org/about/core/policies/core-change-policies/drupal-8...
However in practice, we'd want to avoid breakage in a minor for this change, so I think we should grep contrib.
If we can't change the interface in a Drupal 9 minor, we could still add the implementations of the method, and document that the additional method will be on the interface in Drupal 10 etc. just without changing the interface itself.
And @xjm:
So, I think we should do what @catch says in the second part of his comment in #28.
To summarize:
In this task, we add hasRole()
implementations to AccountInterface
implementations: AccountProxy
and UserSession
.
In the follow-up task, we add a method to AccountInterface
in Drupal 10 - #3228209: Move hasRole() method from UserInterface to AccountInterface.
Remaining tasks
Review https://git.drupalcode.org/project/drupal/-/merge_requests/1059;
Address comment #42;
Commit;
API changes
Drupal\Core\Session\AccountProxy
and Drupal\Core\Session\UserSession
now implement hasRole()
method. See the change record: https://www.drupal.org/node/3002289
Comment | File | Size | Author |
---|---|---|---|
#60 | reroll_diff_58-70.txt | 2.16 KB | Medha Kumari |
#60 | 2991232-10.1.x-70.patch | 4.61 KB | Medha Kumari |
| |||
#58 | interdiff-2991232-58.txt | 672 bytes | dagmar |
#58 | 2991232-10.1.x-58.patch | 4.61 KB | dagmar |
Issue fork drupal-2991232
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 #2
jhedstromComment #4
jhedstromThe last patch missed the
AccountProxy
class.Comment #6
jhedstromOops, the patches above mistakenly removed the
hasRole
method from the user class, not just the interface.Comment #7
wturrell CreditAttribution: wturrell as a volunteer commentedThis is convenient and logical - if AccountProxy gives you hasPermission(), it's reasonable to assume hasRole() will also work, more so given getRoles() exists.
(Is there a good reason we don't have a getPermissions() method for users, only for roles?)
Partial review (not RTBC-ing mostly because I assume we need tests - tagged):
- I too noticed the problem, patch applies cleanly to 8.6.x, works and actually using it
- Diff makes sense (to me) on my read through
- In scope (should we explain the new UserSession method in issue summary?)
- Code style/comments look ok
- No UI / CSS / JS / RTL changes
- No browser testing needed
- No translatable text changes
Comment #8
jhedstrom@wturrell I think the tests for this are that all the existing tests pass :) Adding an explicit test to ensure a method is on an interface and implementation isn't needed since PHP enforces that itself.
Comment #9
wturrell CreditAttribution: wturrell as a volunteer commentedOK, noted :) Removing tag and RTBCing.
Comment #10
alexpottWe should have a test for the implementation.
This should be tested and I think we should always do strict type checking on in_array() calls. Mind you that's not what we do in \Drupal\user\Entity\User::hasRole() so let's open a follow-up to add this to both.
Comment #11
alexpottWe also need a change record.
Comment #12
jhedstromThis adds tests for the 2 new implementations.
Comment #13
jhedstromI've added a change record too.
Comment #14
longwaveTests and change record look great to me.
Comment #15
BerdirIsn't this an API change? Unlike UserInterface, AccountInterface does not have a 1:1 class that others can be expected to implement. So if someone has a custom implementation of this, it would break in 8.7.
Comment #16
longwaveSo what can we do here? Add a new interface that includes hasRole(), and deprecate AccountInterface?
Comment #18
jhedstromSince this is a parent interface, if I'm reading Symfony's policy correctly, moving a method there is allowed (guessing we follow the same logic as they do).
Comment #19
BerdirBut the parent interface is an existing interface as well.
And that means it's also "Add method" for AccountInterface and that is not allowed according to exactly those rules you posted :) That said, we have our own rules I think, for example the interface+default class where adding methods is allowed in most cases. But AccountInterface doesn't have default class, which is exactly the problem.
I also think that "Move to parent" means Move to a *new* parent interface :)
Comment #22
jhedstromThis sort of stalled out. Are we able to make the change in Drupal 9 if not in 8?
Comment #23
BerdirNot really, the same rules apply to 9.0, if anything it's stricter as we try to keep it same as 8.9 except deprecation removals and dependency changes. And with 9.1, the same rules apply again.
Comment #25
LpSolit CreditAttribution: LpSolit as a volunteer commentedIf this won't be accepted for 9.1 either, so when will this issue have a chance to be fixed? In 10.0 only??
Comment #27
MatroskeenCan we make it in Drupal 10? 😇
Comment #28
catchWe do allow adding methods to interfaces that aren't explicitly tagged with @api, see https://www.drupal.org/about/core/policies/core-change-policies/drupal-8...
However in practice we'd want to avoid breakage in a minor for this change, so I think we should grep contrib.
The question is:
1. Are any contrib modules extending AccountInterface and not implementing ::hasRole() ?
2. Are any contrib modules extending AccountProxy or UserSession (in which case they'd get the method, but that might highlight whether there are custom/theoretical use cases for #1.
If we can't change the interface in a Drupal 9 minor, we could still add the implementations of the method, and document that the additional method will be on the interface in Drupal 10 etc. just without changing the interface itself.
Comment #29
longwaveContrib search for "implements AccountInterface" looks fairly promising:
http://grep.xnddx.ru/search?text=implements+AccountInterface&filename=
Most of these are false positives; they are custom Account entities with a corresponding interface. Only testtools module looks problematic, and that is a one line fix: https://git.drupalcode.org/project/testtools/-/blob/8.x-2.x/tests/src/Un...
Two modules extend AccountProxy: http://grep.xnddx.ru/search?text=extends+AccountProxy&filename=
Three modules extend UserSession: http://grep.xnddx.ru/search?text=extends+UserSession&filename=
None of these look like adding hasRole() would be an issue.
Comment #30
andypostnew method should use type-hints for argument and result
Comment #32
MatroskeenUpdating the issue summary.
Comment #33
MatroskeenComment #37
xjmSince there is not an abstract base class with a 1:1 relationship with
AccountInterface
, I think according to the API and BC policy:Also, according to the deprecation policy, we do provide a best-effort BC for internal APIs and we have a path to do so here.
So, I think we should do what @catch says in the second part of his comment in #28:
Leaving the RM review tag on because we will want to review any RTBC proposal before it is committed.
Comment #38
xjmRemoving credit for @keboca's accidental MR.
Comment #39
longwaveSo to move this forward, I think this needs work to remove
AccountInterface::hasRole()
from the patch and move the docs to the implementations for the time being, plus a @todo for Drupal 10, and a followup issue to addAccountInterface::hasRole()
in D10.Comment #40
apadernoComment #41
apadernoI agree it makes more sense for
hasRole()
to be inAccountInterface
, since the same interface includes thegetRoles()
method, which implies objects implementingAccountInterface
have access to the roles assigned to an account.On the other side, code could also only rely on
getRoles()
. Checking the$user
account has the$role
role just requires code similar to the following one.That is essentially the code used from
User::hasRole()
.In code used from contributed modules, checking a user account has a role is less common than checking which permission that user account has.
Comment #42
xjmForgot to mention in my comment, another thing that we should do is raise a deprecation in core callers if an implementation of the interface is lacking an implementation of the method.
Comment #44
MatroskeenThanks everyone for the input! 🙇♂️
I turned the latest patch into a merge request and applied the following changes:
hasRole()
back fromAccountInterface
toUserInterface
;I also updated the issue summary and change record.
The remaining item is a deprecation notice mentioned in #42, but I would need some guidance here.
Comment #47
MatroskeenMR #1058 did not trigger tests for some reason.
I created a new branch and new MR: https://git.drupalcode.org/project/drupal/-/merge_requests/1059
Also, hiding a patch to make it less confusing.
Comment #48
MatroskeenI noticed a related task, which is also has a potential change to
AccountInterface
- #2123883: Add API for "upcasting" from AccountInterface to a full User entity?.It looks like a good DX improvement but has less activity in the task comments.
I'll be happy to post a patch there as well, but it would probably need a sign-off from one of the release managers.
Comment #49
dww+1 to adding hasRole() in a BC-friendly way. Thanks for already opening #3228209: Move hasRole() method from UserInterface to AccountInterface to handle the @todo-ness. Seems like this is ready for re-review, although I don't have time to actually review it right now.
Thanks everyone,
-Derek
Comment #50
MatroskeenThis is probably already late to be added to 9.3.x... but if not, I would be happy to apply any additional changes during the weekend.
The merge request was rebased against 9.3.x and looking forward to review 🙏
Comment #51
andypostOTOH it fits into 10.x which soon opening
Comment #54
dwwFrom @xjm in today's #d10readiness meeting:
Comment #55
longwaveSymfony DebugClassLoader supports an @method annotation on interfaces which implies that a new method will be added in the next major version, maybe we should just adopt this? When the debug class loader is used this raises a notice to users if the method is not implemented, without it explicitly being required on the interface.
See how the withOptions method was added to HttpClientInterface: https://github.com/symfony/http-client-contracts/compare/2.5...3.0#diff-...
Comment #57
nod_well now that we are in RC, seems like it's not going to happen for 9.5 so only relevant for 10.1.x
In any case, MR needs updating
Comment #58
dagmarComment #60
Medha KumariRerolled the patch #58 in Drupal 10.1.x.
Comment #61
nod_Patch applies.
What needs to be sorted out is how we can add a method to an interface, since the code is working setting RTBC. There is already a follow-up to handle that in #3228209: Move hasRole() method from UserInterface to AccountInterface. And that other issue is blocked on this one.
Comment #62
alexpottCommitted 419b492 and pushed to 10.1.x. Thanks!
Comment #65
catchThe re-roll in #60 was posted less then 24 hours after the patch in #58 and doesn't change anything from that patch. I'm removing issue credit.