Problem/Motivation
Today when adding a trusted callback I realised that this could be done a bit more cleanly in PHP 8 with method attributes.
Currently you have to implement TrustedCallbackInterface and a method such as
use Drupal\Core\Security\TrustedCallbackInterface;
class SomeClass implements TrustedCallbackInterface {
public static function someCallback(...) {
...
}
/**
* {@inheritdoc}
*/
public static function trustedCallbacks() {
return ['someCallback'];
}
}
Steps to reproduce
Proposed resolution
The interface is no longer required and you can now mark the callback directly with a PHP attribute:
use Drupal\Core\Security\Attribute\TrustedCallback;
class SomeClass {
#[TrustedCallback]
public static function someCallback(...) {
...
}
}
Remaining tasks
Followup to deprecate TrustedCallbackInterface? Or maybe this is needed if there are dynamic callbacks somewhere?
Followup to convert existing TrustedCallbackInterface implementations to use the attribute.
User interface changes
None
API changes
New TrustedCallback attribute
Data model changes
None
Release notes snippet
Comments
Comment #2
longwaveComment #4
smustgrave commentedThink this needs an issue summary with the proposed solution.
Also with the new TrustedCallback will it require a change record?
Comment #6
jungleComment #7
jungleA patch from the MR. Trying to review it, but the target branch of the MR is 10.0x, and I do not have permission to edit the MR. Maybe, @longwave can do it.
Comment #8
joachim commentedI've opened an issue to decide on where to put annotation classes, so all annotation issues can work to a common standard: #3344303: [policy, no patch] Decide on a namespace for PHP Attribute classes.
Comment #9
alexpott<3
Comment #10
alexpottGiven we're doing this a runtime I wonder what the performance impact is. That said render cache exists.
Comment #11
smustgrave commentedFor the reviewer would be helpful for the proposed solution to be added to the issue summary.
Also I previously tagged for change record is that needed?
Comment #12
longwave@smustgrave yes, all API additions or changes need a change record
Comment #13
longwaveComment #14
longwaveComment #15
andypostI think a static cache can negate performance impact on repeatable callbacks
Comment #16
smustgrave commentedLeaning on others here for this one. But the change record has been added and makes it clear what's being added to me.
Comment #17
jungleWhat's the future plan here?
If it's option #2, should we deprecate the TrustedCallbackInterface here?
EDIT: Just saw #2 being mentioned in the Remaining tasks of IS, sorry.
Comment #18
jungleRemoving myself from draft credit
(EDIT again. Sorry, it seems I can not do it. The committer does it, please.)
Comment #19
andypostAt least the loop can be removed, so fix concerns in #10, ref https://www.php.net/manual/en/reflectionfunctionabstract.getattributes.php
I think we can deprecate interface but still needs to profile somehow reflection vs interface check + method call
Comment #20
longwave+1 to the changes in #19, thanks @andypost.
Comment #21
longwaveI used PHPBench to generate some benchmarks for 100,000 calls. A patch for reproducing this locally is attached. Sample run:
Checking the interface is slightly faster than using reflection on the attribute (note that in the reflection case it checks the interface first anyway), and a static string callback is almost exactly the same as the static array version with the attribute. Therefore I'm not worried about performance here at all.
Comment #22
andypostInteresting that callback as array vs string is ~50% faster
Comment #23
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 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 #24
nod_Comment #25
andypostI find it ready but as my patch is the last, I can't RTBC
Comment #26
joachim commentedPatch 19 LGTM.
Comment #27
alexpottWe need some documentation updates.
Comment #28
longwaveUpdated the MR with @andypost's changes from #19 and the additional docs requested in #27.
I disagree with adding a release note: release notes are for end users, we don't usually add release notes for developer-facing features. Although this is a nice developer feature it's not particularly notable. If we want developers to start using this in preference to the interface, then we should open followups to convert existing uses of the interface to attributes instead, and consider deprecating the interface.
Comment #29
alexpottI ummed and ahhed on the release note - fwiw if "release notes are for end users, we don't usually add release notes for developer-facing features" is true then we should not do release notes for dev dependency updates either - the fact that we do is why I decided to add the tag as this is the first use of an attribute in core. Happy to remove the tag as I'm not a release manager.
Comment #30
longwaveRelease notes are also for "this might get in your way when you upgrade", and tbh I don't really think many people rely on our dev dependencies, but it's hard to know for sure. This change doesn't affect anybody directly, but I get your point that it would be nice to show off. Maybe we could tag this with "10.1 release highlights" if you think it is interesting enough, but again the highlights blog post is for end users really; maybe we need a similar blog post but aimed at developers, to highlight dev-friendly features in new minors - I really like the "A Week of Symfony" posts but we don't do anywhere near as much for developers.
Comment #31
andypostLooks docs are improved
Comment #32
alexpottCommitted 2420d38 and pushed to 10.1.x. Thanks!
I'm happy to leave this small thing out of release notes etc.. blocks and routes as attributes are more important and things I think we should highlight.
Comment #34
wim leersNice to see us adopting new language features! :D
I think #28 through #32 are probably why the change record is not yet published?
Comment #35
alexpott@Wim Leers nah I just forgot to publish it :)
We could file a follow-up to convert core's usages of TrustedCallbackInterface and deprecate it.
Comment #36
andypostAs #21 shows - no reason to replace all as reflection x3 slower then interface but some places could use it
Filed follow-up #3354584: Deprecate TrustedCallbackInterface in favour of TrustedCallback attribute
Comment #37
longwaveTagging this for highlights, we may not do anything about it but this will remind us.
Comment #38
joachim commented> As #21 shows - no reason to replace all as reflection x3 slower then interface but some places could use it
This should maybe have been documented on the attribute class so developers know not to use it in performance-critical pathways.
Comment #39
alexpott@joachim I think the performance concerns are overblown. It's looks like it might be x2 as slow but we're dealing less than 1 microsecond here and I won't be surprised if there was variation. When I run this locally I get;
(reducing the table to compare things that should be compared as maybe @andypost has compared apples with oranges). I think from this we can say that using an attribute will cost about 0.2μs per attribute. Given these are used for render callbacks this is likely to be an incredibly small cost even if you have 100 methods using this attribute and all 100 are being used to render a page. And even then non of these will be called if the render is cached in some way (page cache, dynamic page cache).
Comment #40
alexpottAdditionally the attribute test is not correct because the object that is being used also has the TrustedCallbackInterface so it has to do more work that it should. If you are using attributes you should not be using the interface. Plus if we didn't use the interface then we wouldn't need to do the is_subclass_of() so that'd save more time. If we change the logic around in \Drupal\Core\Security\DoTrustedCallbackTrait::doTrustedCallback() to prioritise attribute checking over TrustedCallbackInterface checking and we also test an object only using attributes then we can see something like:
So yeah I think we should deprecate the interface in preference for the attribute.
FWIW here's the code for I had in the relevant part of \Drupal\Core\Security\DoTrustedCallbackTrait::doTrustedCallback()
Comment #41
andypostThank you for good news, then we can deprecate interface in #3354584: Deprecate TrustedCallbackInterface in favour of TrustedCallback attribute