Updated: Comment #69

Problem/Motivation

Right now, access checks get their values by doing something like $foo = $request->attributes->get('foo');
This is not ideal, as it doesn't allow for typehinting, it's harder to grep for, and it is basically just abusing a data structure dumping ground.

Proposed resolution

Similar to controllers and forms, match the named parameters of an access checker using reflection. Specifically, what the patch does is:

  • Adds an access_arguments_resolver service, and injects it into the access_manager and access_check.custom services. The use of this resolver adds a small performance cost to invoking each access checker (which needs to happen when rendering each menu link), but that was deemed not a blocker for this issue (see #61-#64).
  • Removes the access() method from Drupal\Core\Routing\Access\AccessInterface. Adds a @todo for whether to remove the interface entirely, but that would break existing contrib access checkers, requiring them to change their class declaration as implementing Drupal\Core\Access\AccessInterface instead.
  • Changes each access checker's access() method to define a per-use-case signature and docs, since it's no longer interface-defined.
  • Removes 2 dead access checkers from Shortcut module (nothing in HEAD uses them).
  • Since access() is no longer interface-defined, changes RegisterAccessChecksPass to allow the service tag to override the method name, but there's no good use case in core to do so.

Remaining tasks

Review.

User interface changes

None

API changes

  • For contrib access checkers, no BC breaks: they would still work with their prior signatures. However, for IDE friendliness, they should change their access() method documentation from {@inheritdoc} to something specific.
  • If there is any contrib code that is calling ->access() on a specific core access checker, it needs to adjust to its new signature.
  • If there is any contrib code that is calling ->access() on a generic instance of Drupal\Core\Routing\Access\AccessInterface, it must use the access arguments resolver to get the correct set of arguments to pass in.
CommentFileSizeAuthor
#95 interdiff.txt2.18 KBtim.plunkett
#95 access-checker-2222719-95.patch101.57 KBtim.plunkett
#84 interdiff.txt5.5 KBeffulgentsia
#84 access-checkers-2222719-84.patch93.34 KBeffulgentsia
#83 interdiff-75-83.txt31.48 KBxjm
#83 access-checkers-2222719-83.patch91.85 KBxjm
#75 interdiff.txt801 byteseffulgentsia
#75 access-checker-2222719-75.patch90.3 KBeffulgentsia
#71 interdiff.txt904 byteseffulgentsia
#71 access-checker-2222719-71.patch90.11 KBeffulgentsia
#69 interdiff.txt1.51 KBeffulgentsia
#69 access-checker-2222719-69.patch89.66 KBeffulgentsia
#66 interdiff.txt30.87 KBeffulgentsia
#66 access-checker-2222719-66.patch89.98 KBeffulgentsia
#60 interdiff-58-60.txt2.97 KBeffulgentsia
#60 access-checker-2222719-60.patch81.75 KBeffulgentsia
#59 interdiff.txt6.32 KBeffulgentsia
#59 access-checker-2222719-59.patch83.06 KBeffulgentsia
#58 access-checker-2222719-58.patch81.67 KBeffulgentsia
#57 interdiff.txt6.43 KBeffulgentsia
#57 access-checker-2222719-57.patch81.65 KBeffulgentsia
#56 interdiff.txt2.63 KBeffulgentsia
#56 access-checker-2222719-56.patch85.76 KBeffulgentsia
#51 interdiff.txt614 byteseffulgentsia
#51 access-checker-2222719-51.patch84.9 KBeffulgentsia
#50 interdiff.txt6.31 KBeffulgentsia
#50 access-checker-2222719-50.patch84.9 KBeffulgentsia
#48 access-checker-2222719-48.patch79.52 KBeffulgentsia
#45 access-checker-2222719-45.patch79.68 KBeffulgentsia
#41 interdiff.txt4.68 KBtim.plunkett
#41 access-checker-2222719-41.patch80.72 KBtim.plunkett
#38 access-checker-2222719-38.patch82.65 KBtim.plunkett
#38 interdiff.txt8.84 KBtim.plunkett
#36 interdiff.txt1.62 KBeffulgentsia
#36 access-2222719-36.patch76.44 KBeffulgentsia
#35 access-2222719-35.patch75.89 KBeffulgentsia
#30 interdiff.txt40.71 KBeffulgentsia
#30 access-2222719-30.patch77.28 KBeffulgentsia
#29 interdiff.txt9.64 KBeffulgentsia
#29 access-2222719-29.patch85.24 KBeffulgentsia
#27 interdiff.txt2.36 KBeffulgentsia
#27 access-2222719-27.patch75.25 KBeffulgentsia
#23 interdiff.txt628 byteseffulgentsia
#23 access-2222719-23.patch75.26 KBeffulgentsia
#20 interdiff.txt21.95 KBeffulgentsia
#20 access-2222719-20.patch74.55 KBeffulgentsia
#19 access-2222719-19.patch60.77 KBdawehner
#19 interdiff.txt25.6 KBdawehner
#16 access-2222719-16.patch55.68 KBtim.plunkett
#16 interdiff.txt6.35 KBtim.plunkett
#15 access-checker-2222719-15.patch49.33 KBeffulgentsia
#13 access-checker-2222719-13.patch40.37 KBeffulgentsia
#10 access-checker-2222719-10-do-not-test.patch9.23 KBeffulgentsia
#1 access-checker-2222719-1.patch24.5 KBtim.plunkett
#92 access-checker-2222719-92-PASS.patch100.81 KBtim.plunkett
#92 access-checker-2222719-92-FAIL.patch100.73 KBtim.plunkett
#92 interdiff.txt8.72 KBtim.plunkett

Comments

tim.plunkett’s picture

Status: Active » Needs review
StatusFileSize
new24.5 KB
tim.plunkett’s picture

Note that this is still just a proposal and only makes a few conversions.

Crell’s picture

Why the extra method? Why not just map the arguments directly to the access method? That would mean the interface is less useful on it, but that would be true of arguments() as well so it's a wash. But splitting the methods makes it inconsistent with other places we use the callable resolver style.

I'm open to this change if we can make it cleanly.

effulgentsia’s picture

One issue with using the access() method is that it also receives $route and $account, which aren't on the request attributes. I started #2223473: Simplify AccessManager methods to act on either route or request, but not both in order to remove the $route at least, since that actually is on the request attributes (just not named 'route').

dawehner’s picture

Why the extra method? Why not just map the arguments directly to the access method? That would mean the interface is less useful on it, but that would be true of arguments() as well so it's a wash. But splitting the methods makes it inconsistent with other places we use the callable resolver style.

It is the old disussion. Either you have an interface, in which case you need default values, or you have a separate setter or whatever.
To be honest I think the DX would be worse than before if you would have to write an extra method, which by some magic is executed before the main access method.

We could leverage the request info to get some of the parameters or just accept that the request object is part of the api. If you look at the proposed patch it will be one parameter so an extra method really feels like an overhead.

Crell’s picture

The attributes() method is also setting stateful data, which therefore disqualifies the request object as a service. So I'm -1 to that.

So we either pass the request (as now) or declare access() to be an attributes-derived method and figure out how to document that in the interface/API spec.

effulgentsia’s picture

or declare access() to be an attributes-derived method

That's the option I'd prefer: change it from being interface-defined to a non-interface-defined callable that receives arguments via reflection only. But we still need to figure out how to get $route and $account in there for implementations that need it. I think the use case for $route is minimal, and the few implementations that need it can use RequestInfo::getRouteObject($request) instead. Not sure how to best handle $account though. I kind of think we should go back to an _account attribute on $request, but I don't think I'm ready to reopen that battle up quite yet. In lieu of that, perhaps this means we need a separate AccessCallableResolver service rather than reusing ControllerResolver? Or are there any other ideas?

Crell’s picture

I've felt for a while that ControllerResolver needs to be split up; one service that does just the callable resolution from an arbitrary string, which ControllerResolver then leverages. Then ControllerResolver can be responsible for the argument mapping, while TitleResolver, AccessResolver, or whatever can do their own argument mapping with a different set of magic values.

That should be done at least partially upstream in Symfony; that doesn't block this issue though, as we could have an AccessResolver already. Actually we should probably continue to insist that it's an "access" method on objects of that interface and just do the attribute resolution with $account as the magic case.

dawehner’s picture

Moving to non-interface method would be interesting, especially if a module provides multiple access checkers. This though
would call issues with the applies method.

I've felt for a while that ControllerResolver needs to be split up; one service that does just the callable resolution from an arbitrary string, which ControllerResolver then leverages. Then ControllerResolver can be responsible for the argument mapping, while TitleResolver, AccessResolver, or whatever can do their own argument mapping with a different set of magic values.

Just to be clear, there is an issue: #2165475: Provide a generic class resolver
If we would move that upstream we would have to override it anyway 100%, but sure let's upon up on, and don't rely on it being there.

effulgentsia’s picture

Work in progress. Provide feedback if you like, or you can wait for a more complete patch.

dawehner’s picture

just a quick note
I would argue that you want to have the account required on there, as it is an edge case not take the current user into account, if you are honest.

catch’s picture

This will need profiling where there's lots of access checks on the same page (i..e menu links/tabs). We're caching menu link blocks by default now, so can take a bit of hit there, although primary/secondary menu variables, breadcrumbs, tabs are still not at all.

effulgentsia’s picture

StatusFileSize
new40.37 KB

Far enough along for bot to test, but does not yet implement #11.

effulgentsia’s picture

Status: Needs work » Needs review
StatusFileSize
new49.33 KB

Simplified the access() signatures for the easy checkers. There are still some tricky ones left.

tim.plunkett’s picture

StatusFileSize
new6.35 KB
new55.68 KB

Was taking a look locally and decided to help out with the unit test failures.
There are still two broken, AccessManagerTest::testCheckException() and CustomAccessCheckTest::testAccess

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new25.6 KB
new60.77 KB

Fixed the other bits of the tests.

  1. +++ b/core/lib/Drupal/Core/Access/DefaultAccessCheck.php
    @@ -7,20 +7,20 @@
     class DefaultAccessCheck implements RoutingAccessInterface {
    +  use DefaultAccessCallbackTrait;
     
    

    I really hate that you have to use both the interface as well as the interface ... Having the interface here does not help as a lot. What about making the interface optional and just used in the really edge-cases like the custom one?

  2. +++ b/core/modules/book/lib/Drupal/book/Access/BookNodeIsRemovableAccessCheck.php
    @@ -9,14 +9,14 @@
     class BookNodeIsRemovableAccessCheck implements AccessInterface {
    +  use DefaultAccessCallbackTrait;
    

    I wonder whether we can let the DefaultAccessCallbackTrait implement the AccessInterface. Having to implement both is REALLY odd

effulgentsia’s picture

StatusFileSize
new74.55 KB
new21.95 KB

This converts all the remaining access checks to get route parameters via their signature rather than via $request->attributes. $request->attributes is still being used for internal attributes that aren't route parameters, because removing those is outside the scope of this issue (see #2124749: [meta] Stop using $request->attributes->get(MAGIC_KEY) as a public API for that). And Edit module has access checks that still do funky things with $request, but I updated the @todo to link to #1837388: Provide a ParamConverter than can upcast any entity. for fixing that.

effulgentsia’s picture

I would argue that you want to have the account required on there, as it is an edge case not take the current user into account

We have 7 access checks that don't depend on $account: CsrfAccessCheck, DefaultAccessCheck, ThemeAccessCheck, BookNodeIsRemovableAccessCheck, FormatDisableCheck, CronAccessCheck, UpdateManagerAccessCheck, and those all seem fairly legit to me, so I wouldn't call that an edge case.

This will need profiling where there's lots of access checks on the same page

Yeah, that would be good. The current patch does the reflection for every invocation, but we could add static caching (in the AccessArgumentsResolver service) of the reflection results per callable, so a menu with the same access checker running lots of times (e.g., a menu with a lot of node links) wouldn't need to do all that reflection for every link.

I wonder whether we can let the DefaultAccessCallbackTrait implement the AccessInterface.

Sadly, PHP traits can't implement interfaces; they can only implement the methods, but the using class must be the one to specify whether it implements the interface or not.

What about making the interface optional and just used in the really edge-cases like the custom one?

I thought about doing that by making AccessManager::performCheck() check to see if the interface is implemented, and if not, then fall back to the default of array($this->checks[$service_id], 'access'). Then we wouldn't need DefaultAccessCallbackTrait either. However, that felt wrong because it creates an implied interface (that the access() method exists and means what it means) rather than an explicit one. Also, the interface is what contains the constants (ALLOW, DENY, KILL), and it's nice for the access checkers to be able to refer to them via static::.

effulgentsia’s picture

Status: Needs work » Needs review
StatusFileSize
new75.26 KB
new628 bytes
Crell’s picture

Here's a thought: If it's too wonky to do for the checker services due to the interface, what about focusing on the custom access callbacks? They're intended for the one-off cases. They may be more common in practice so more relevant for the typical developer anyway. (Note: I haven't actually checked to see if that's the case, I'm just wondering if it is.)

dawehner’s picture

I agree that having the constants available is a huge win.
Couldn't we also write down the used methods into the service definition, so the interface would be just the constants?

effulgentsia’s picture

Status: Needs work » Needs review
StatusFileSize
new75.25 KB
new2.36 KB

I like #26. Will work on that after getting this green.

effulgentsia’s picture

Status: Needs work » Needs review
StatusFileSize
new85.24 KB
new9.64 KB
effulgentsia’s picture

StatusFileSize
new77.28 KB
new40.71 KB

For simplicity, just forcing the method name to access(). That's no more constricting than what's in HEAD. The flexibility of #26 might be a good follow up to work on, but I'd rather not hold this up on it.

I think all that's left now is profiling and optimizing the arguments resolver, unless there's additional feedback.

effulgentsia’s picture

Here's a scenario I tested for profiling: standard install profile, give anon users permission to "Administer site configuration", "Use the administration pages and help", and "Use the administration toolbar". Then, as anon, visit the home page, and see the URL for the toolbar JSONP request (/toolbar/subtrees/INSERT_HASH_HERE/en). Then ab -c1 -n100 that URL. This results in a few dozen menu links being rendered and not much else happening. On my machine with xdebug disabled:

HEAD: 105ms
Patch: 108ms

With patch, AccessArgumentsResolver::getArguments() is called 48 times, and I'm guessing that that's where the bulk of the time difference is, in which case that's ~60us per invocation, and I suspect can be optimized per #22.2.

effulgentsia’s picture

Priority: Normal » Critical
Issue tags: +beta blocker

Based on discussion with webchick and xjm, raising this to beta blocker, since it's a child of #2124749: [meta] Stop using $request->attributes->get(MAGIC_KEY) as a public API. We'll need to file more children of that issue, and not every one necessarily needs to block beta, but access checkers is one of the more common module-developer-facing ones.

dawehner’s picture

It is great to get rid of the trait madness, though I don't really get why we don't do it right in the beginning and put the callable onto the service

neclimdul’s picture

does not apply after #2235347: Field instance operations are out of order

Noticed this though.

+++ b/core/tests/Drupal/Tests/Core/Route/RoleAccessCheckTest.php
@@ -161,14 +161,14 @@ public function testRoleAccess($path, $grant_accounts, $deny_accounts) {
       $subrequest = Request::create($path, 'GET');
       $message = sprintf('Access granted for user with the roles %s on path: %s', implode(', ', $account->getRoles()), $path);
-      $this->assertSame(AccessCheckInterface::ALLOW, $role_access_check->access($collection->get($path), $subrequest, $account), $message);
+      $this->assertSame(AccessCheckInterface::ALLOW, $role_access_check->access($collection->get($path), $account), $message);
...
       $subrequest = Request::create($path, 'GET');
       $message = sprintf('Access denied for user %s with the roles %s on path: %s', $account->id(), implode(', ', $account->getRoles()), $path);
-      $has_access = $role_access_check->access($collection->get($path), $subrequest, $account);
+      $has_access = $role_access_check->access($collection->get($path), $account);

we get rid of the usage of subrequest but not its creation?

effulgentsia’s picture

StatusFileSize
new75.89 KB

Just a reroll. Doesn't yet address #33 or #34.

effulgentsia’s picture

StatusFileSize
new76.44 KB
new1.62 KB

Fixes #34.

Re #33: I think doing so would require some nontrivial changes to AccessManager that would be better reviewed independently in a separate issue. If I'm wrong about that, and it's actually more straightforward than I assume, can you post the patch for it?

tim.plunkett’s picture

Assigned: Unassigned » tim.plunkett

I really don't feel comfortable with calling that access method without any checks.
If it's named anything else (or missing), this is the resulting error:

Recoverable fatal error: Argument 1 passed to Drupal\Core\Access\AccessArgumentsResolver::getArguments() must be callable, array given, called in /Users/tim/www/d8/core/lib/Drupal/Core/Access/AccessManager.php on line 350 and defined in Drupal\Core\Access\AccessArgumentsResolver->getArguments() (line 22 of core/lib/Drupal/Core/Access/AccessArgumentsResolver.php).

That's not very helpful.

After trying to add something to the service definition to specify the method name, it completely didn't work, but I have another idea after discussing with @msonnabaum.
I have to think it through a bit more, and I also have to leave the sprint soon. I will post my thoughts and code examples next time I'm at my laptop.

tim.plunkett’s picture

StatusFileSize
new8.84 KB
new82.65 KB

I think that in all but a very few cases, access checks as tagged services is overkill. And in all cases except maybe _permission, it is immensely confusing. Moving the applies_to to the service definition helped only minimally.

 277     _permission
 244     _access
  66     _entity_access
   8     _entity_create_access
   8     _csrf_token

Almost every other checker is used 4 or less times, most 1-2 times.

When using _custom_access, the DX is improved because you know *exactly* where the class is, you know and can control the method used, and it matches the pattern used for controllers, title checkers, etc.

@msonnabaum wanted me to split the value of _custom_access into an array, with a callable and whatever other values needed.
I did not do that here, I just added them to the defaults for now. This can be changed later if we want...

I've converted the two node checkers for now. My brain is a little fuzzy, so if I'm not being clear, please inform me of that :)

tim.plunkett’s picture

Status: Needs work » Needs review

Marking needs review to keep discussion moving here, I filed #2239307: Remove obsolete _node_revision_access to help fix that fail.

tim.plunkett’s picture

StatusFileSize
new80.72 KB
new4.68 KB

Fixes for node revision access.

dawehner’s picture

HAHA, yeah i really think it is a bad idea to switch all stuff to custom access checkers, obviously. It is good that we got get rid of that.

Almost every other checker is used 4 or less times, most 1-2 times.

Even if it is hard to accept, people will write modules outside of /core which will depend on our existing stuff. We should not make their lives increadible hard for the sake of purity.

Crell’s picture

I don't follow the implications of #38. Are you suggesting we move more things to custom checkers and just make custom checkers do the reflection magic, or something else...?

tim.plunkett’s picture

Assigned: tim.plunkett » effulgentsia

Custom checkers already support reflection magic, but they cannot get the route object in HEAD, that's the only functional change here for them.

I think that switching to them everywhere is just as reusable as a magic string, you're still going to have to copy/paste it, and at least you can more readily figure out what you're copy/pasting.

As far as improving the non-_custom_access checks, I see three options:

  1. Use the = NULL hack and work around the interface (matching the pain from FormInterface)
  2. Use a different method for the arguments (patch in #1)
  3. Remove the method from the interface and just hardcode calls to it (patch in #36)

@dawehner seems opposed to prefer custom access checks, but we haven't heard from @effulgentsia since I posted that, assigning to him for some feedback.

effulgentsia’s picture

StatusFileSize
new79.68 KB

Straight reroll. Will answer #44 in following comment.

effulgentsia’s picture

I support converting most of the module-specific access check services currently tagged access_check into custom access checkers. For example, I see absolutely no benefit in BookNodeIsRemovableAccessCheck or ContactPageAccess being tagged services. To me, those are examples of ones that should clearly fall into the "custom" category.

For the two that Tim chose to convert in #38 though: NodeAddAccessCheck and NodeRevisionAccessCheck, I'm a little less sure. I actually wonder if we want to abstract those into generic entity access checkers that other entity types could leverage for per-bundle or cross-bundle creation checking and operation-on-revision checking.

Because I think each specific access check conversion to either "make custom" or "make more generic" or "leave alone" should get its own review, I'd prefer to defer all such conversions to a separate issue from this one (not even a follow up, as that work can be done independently of this issue).

The way those conversions got lumped into this issue is the comment in #37:

I really don't feel comfortable with calling that access method without any checks. If it's named anything else (or missing), [the resulting error is] not very helpful.

Would it be sufficient for resolving this issue to make that error more helpful? For example, in AccessManager::performCheck(), throw a friendly AccessException if !is_callable($callable)?

Per #36, I still think we can have a follow up for making it possible to specify the method name as another attribute on the access_check tag, to help with the DX of whatever checks we decide to keep as tagged services. I'd just like that to be a follow up, since I think we'll need to do a little refactoring of AccessManager's addCheckService() and friends, which I don't think will be that big a deal, but just not something I want cluttering this patch. If you insist on it being done in this issue though, I'll go along with it.

effulgentsia’s picture

Status: Needs work » Needs review
StatusFileSize
new79.52 KB

Happily rerolled for shortcut's conversion to PSR-4.

tim.plunkett’s picture

#46, NodeAddAccessCheck is just a special case of EntityCreateAccessCheck, but includes the fallback for for /node/add in addition to node/add/{node_type}. That can and should be refactored anyway...

I still think we can have a follow up for making it possible to specify the method name as another attribute on the access_check tag, to help with the DX of whatever checks we decide to keep as tagged services.

I tried doing this, I couldn't get it to work, since only ContainerBuilder has the ability to check the tags, and it is *really* messy to do this on compile-time, and within the AccessManager we only have a Container.
If addCheckService() was the only entry point, we'd be fine, but AccessManager::setChecks() runs on a completely different code path, and cannot introspect the tags of the service.

Not sure about the !is_callable, but I guess that would be a way forward.

effulgentsia’s picture

StatusFileSize
new84.9 KB
new6.31 KB

it is *really* messy to do this on compile-time

Like this? What's messy about it?

effulgentsia’s picture

StatusFileSize
new84.9 KB
new614 bytes
tim.plunkett’s picture

Messy, because as I said in #49:

If addCheckService() was the only entry point, we'd be fine, but AccessManager::setChecks() runs on a completely different code path, and cannot introspect the tags of the service.

That's why your patch failed, and that's what I don't know how to fix.

tim.plunkett’s picture

Or maybe I have no idea what is going on anymore. But at NYCCamp I wrote a patch very similar to #51, and it failed miserably.

effulgentsia’s picture

Status: Needs work » Needs review
StatusFileSize
new85.76 KB
new2.63 KB
effulgentsia’s picture

StatusFileSize
new81.65 KB
new6.43 KB

Given #42 and #46, reverting the conversions of Node module access checkers to custom ones. Let's have a separate issue to refactor them if desired.

effulgentsia’s picture

StatusFileSize
new81.67 KB

Reroll.

Am working on optimization per #31 next.

effulgentsia’s picture

Issue tags: +needs profiling
StatusFileSize
new83.06 KB
new6.32 KB

This is the (rather ugly) optimization I mentioned in #22.2. It doesn't seem to improve the ab numbers of the #31 scenario by much though. So both this and #58 need to be profiled for real, to see what's causing the difference.

effulgentsia’s picture

StatusFileSize
new81.75 KB
new2.97 KB

So I tried xhprof'ing on my machine, and it appeared that #59's approach of trying to statically cache and reuse reflection results didn't improve anything because reflection isn't what's slow. Actually, it appeared that parameter bag's get(), has(), and array_key_exists() were some of the slowest contributors, so here's a different micro-optimization. It discards #59, so interdiff is relative to #58.

However, I'm not posting xhprof results because I'm having issues with my machine setup, where I'm using PHP 5.5, and I can't get xhprof to work without segfaulting unless I also enable the xdebug.so extension, but doing that makes wall time numbers meaningless.

Also, when I test locally, I get as much noise between individual runs on the same codebase as the signal between HEAD and patch.

Hoping someone else can profile and post cleaner results. The goal is to profile a request that checks access on many menu links (#31 is one way to do that, and doesn't need to be done as anonymous, that was just for the benefit of running ab). The biggest culprit should be the inclusive time of AccessArgumentsResolver::getArguments(). Other differences are likely to be noise.

star-szr’s picture

I did some quick profiling here using xhprof-kit and the scenario outlined in #31. This is roughly an average result. I had wt results as low as +1.3% and as high as +2.3%, but the function calls were always consistent.

=== 8.x..8.x compared (5361b94b74ee9..5361b9bbb4efb):

ct  : 30,919|30,919|0|0.0%
wt  : 209,958|209,738|-220|-0.1%
cpu : 196,764|196,517|-247|-0.1%
mu  : 23,989,528|23,989,528|0|0.0%
pmu : 24,019,136|24,019,136|0|0.0%

http://www.lionsad.de/xhprof-kit/xhprof/xhprof_html/?run1=5361b94b74ee9&...

=== 8.x..access-2222719-60 compared (5361b94b74ee9..5361b9d05fc0c):

ct  : 30,919|32,068|1,149|3.7%
wt  : 209,958|213,657|3,699|1.8%
cpu : 196,764|200,472|3,708|1.9%
mu  : 23,989,528|24,038,480|48,952|0.2%
pmu : 24,019,136|24,068,152|49,016|0.2%

http://www.lionsad.de/xhprof-kit/xhprof/xhprof_html/?run1=5361b94b74ee9&...

(edited to add missing link for the actual comparison).

effulgentsia’s picture

Assigned: effulgentsia » catch
Issue tags: -needs profiling +Needs committer feedback

Thanks, Cottser! That does look like a clean result.

I think the best way to analyze this is to sort by function calls diff: http://www.lionsad.de/xhprof-kit/xhprof/xhprof_html/?sort=ct&run1=5361b9....

Adding up the exclusive wall diff of the top 13 lines (calls diff >= 47) is 3.3ms of the 3.7ms total. Remaining differences include things like the load of AccessArgumentsResolver.php (0.145ms), plus noise (note that #61's comparison of 2 different runs of HEAD showed 0.2ms of noise).

So, if we ignore noise and the loading of a PHP file, and just look at what would scale linearly with number of access checks performed, 3.3ms is 1.6% of the total time of a request whose only job is to render 40ish menu links (the toolbar JSONP request). @catch: is that acceptable?

This patch hasn't been RTBC'd yet, but assigning to catch for feedback on performance cost.

effulgentsia’s picture

One possibility, if we're concerned about the performance impact, is that we can create a new interface (e.g., UnreflectedAccessInterface) with an access() method that's hardcoded to the same signature as in HEAD ($route, $request, $account), and we make AccessManager::performCheck() check if the service implements that interface first, and only if it doesn't, do the whole AccessArgumentsResolver lookup.

In core, we could then at a minimum make PermissionAccessCheck and EntityAccessCheck use that interface, and I think that would take care of the vast majority of menu links, so that we'd only have a relatively small number of AccessArgumentsResolver lookups happening for any given request.

But then, each contrib access checker would need to make a decision as to whether to implement UnreflectedAccessInterface or allow reflection to be used. Is the optimization worth adding the extra decision and code branch?

Alternatively, we could do what tim.plunkett suggested, and only implement reflection for custom access checks, and move many of our core ones to that. That would remove a decision point for contrib: they either implement a 'access_check' service or a custom callback, just as in HEAD, and based on that one decision, they know whether to implement an interface-defined access() signature or a reflection-resolved one. However, then we'd be coupling the DX of the signature to whatever other factors are involved in deciding between tagged service and custom callback, and is that making things easier or harder on contrib?

Note that the entire purpose of this issue's attempt to support reflection-based signature is to eliminate $request->attributes->get('foo') as a common thing for contrib to deal with. If something like $route_match->getArgument('foo') (from #2238217: Introduce a RouteMatch class) within access() methods would be seen as acceptable DX, we could alternatively "won't fix" this.

catch’s picture

One possibility, if we're concerned about the performance impact, is that we can create a new interface (e.g., UnreflectedAccessInterface) with an access() method that's hardcoded to the same signature as in HEAD ($route, $request, $account), and we make AccessManager::performCheck() check if the service implements that interface first, and only if it doesn't, do the whole AccessArgumentsResolver lookup.

This sounds like something we could do as a follow-up including after release?

Assuming the performance regression here is correct, I think that's acceptable given we have critical issues to cache all menu output by default - including the access checks.

That won't apply to local tasks etc. but there's less of them .

effulgentsia’s picture

Assigned: catch » Unassigned
Issue tags: -Needs committer feedback

Yay! Ok, anything left to do on the patch then before RTBC?

effulgentsia’s picture

StatusFileSize
new89.98 KB
new30.87 KB

Removed even the commented out access() method from the interface, and inlined access() docs into each using class. RTBC, anyone?

dawehner’s picture

+++ b/core/lib/Drupal/Core/Access/AccessArgumentsResolver.php
@@ -0,0 +1,126 @@
+      // Even if there's no type hint, use the upcasted value if available;
+      // otherwise, fall back to the raw value.
+      if (isset($upcasted_route_arguments[$parameter_name])) {
+        return $upcasted_route_arguments[$parameter_name];
+      }
+      if (isset($raw_route_arguments[$parameter_name]) || array_key_exists($parameter_name, $raw_route_arguments)) {
+        return $raw_route_arguments[$parameter_name];
+      }
+    }

This opens up the problem as we basically solved in #1906810: Require type hints for automatic entity upcasting already.

pwolanin’s picture

Status: Needs review » Needs work
Issue tags: +Needs issue summary update

Agreed, this seems wrong - if you don't type hint, you should only get the raw value:

// Even if there's no type hint, use the upcasted value if available;

Also, an updated summary would help to review.

effulgentsia’s picture

Status: Needs work » Needs review
StatusFileSize
new89.66 KB
new1.51 KB

Fixes #67/#68. Working on updating the summary.

effulgentsia’s picture

Issue summary: View changes
effulgentsia’s picture

Issue tags: -Needs issue summary update
StatusFileSize
new90.11 KB
new904 bytes

Updated the summary. And here's a small improvement I discovered while writing it.

effulgentsia’s picture

Issue summary: View changes

The last submitted patch, 69: access-checker-2222719-69.patch, failed testing.

effulgentsia’s picture

Status: Needs work » Needs review
StatusFileSize
new90.3 KB
new801 bytes

Sigh. Found a landmine. Sidestepping it here, but will open a separate issue if there isn't one already.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community
+++ b/core/lib/Drupal/Core/Access/DefaultAccessCheck.php
@@ -18,9 +16,14 @@
+   *
+   * @return One of the constants from \Drupal\Core\Access\AccessInterface.

Do we have a proper way to do that? Everybody uses their own way to document it.

xjm’s picture

Assigned: Unassigned » catch

Assigning to catch to review the profiling.

sun’s picture

+ * @return One of the constants from \Drupal\Core\Access\AccessInterface.

Do we have a proper way to do that? Everybody uses their own way to document it.

No need to invent something. @return documents the data type, not the origin. The description can point to the origin.

 * @return string
 *   A \Drupal\Core\Access\AccessInterface constant value.
catch’s picture

Assigned: catch » Unassigned
Status: Reviewed & tested by the community » Needs work

I don't have much to add on the profiling beyond #64 - it's a noticeable regression but we have caching issues in progress, and the interface idea if the uncached performance turns out to be a problem.

Let's fix those docs. I haven't done a final review of the patch yet, but that doesn't have to be me either.

xjm’s picture

Assigned: Unassigned » xjm

I'll clean up the docs; I think @sun's suggestion is spot on.

xjm’s picture

Did a quick read for any other docs gate issues or other docs cleanups while I'm at it; few notes...

  1. +++ b/core/lib/Drupal/Core/Access/AccessArgumentsResolver.php
    @@ -0,0 +1,123 @@
    +   *   The value of the requested parameter value.
    +   * @throws \RuntimeException
    

    blank line

  2. +++ b/core/lib/Drupal/Core/Access/AccessArgumentsResolver.php
    @@ -0,0 +1,123 @@
    +    // @todo Remove this once AccessManager::checkNamedRoute() is fixed to not
    +    //   leak _raw_variables from the request being duplicated.
    

    Issue for this?

  3. +++ b/core/lib/Drupal/Core/Access/AccessArgumentsResolver.php
    @@ -0,0 +1,123 @@
    +   * @param callable $callable
    ...
    +   * @return \ReflectionFunctionAbstract
    

    Missing descriptions

  4. +++ b/core/lib/Drupal/Core/Access/AccessArgumentsResolver.php
    @@ -0,0 +1,123 @@
    +   * Called by getArgument() if an argument value can't be resolved.
    ...
    +  protected function handleUnresolvedArgument(\ReflectionParameter $parameter) {
    

    Needs a one-line summary about what it actually does. Also missing parameter documentation.

  5. +++ b/core/lib/Drupal/Core/Access/AccessManager.php
    @@ -46,6 +45,13 @@ class AccessManager implements ContainerAwareInterface {
    +   * Array of access check method names keyed by service id.
    

    s/id/ID/g

  6. +++ b/core/lib/Drupal/Core/Access/AccessManager.php
    @@ -328,10 +339,46 @@ protected function checkAny(array $checks, $route, $request, AccountInterface $a
    +   * @return string
    +   *   Either the AccessInterface::ALLOW, AccessInterface::DENY, or
    +   *   AccessInterface::KILL constant.
    

    This is documented differently from the other one.

  7. +++ b/core/lib/Drupal/Core/Access/CsrfAccessCheck.php
    @@ -39,9 +38,16 @@ function __construct(CsrfTokenGenerator $csrf_token) {
    +   * Checks access.
    

    (and elsewhere) This is sort of a vague description.

  8. +++ b/core/lib/Drupal/Core/Access/CsrfAccessCheck.php
    @@ -39,9 +38,16 @@ function __construct(CsrfTokenGenerator $csrf_token) {
    +   * @return One of the constants from \Drupal\Core\Access\AccessInterface.
    
    +++ b/core/lib/Drupal/Core/Access/CustomAccessCheck.php
    @@ -33,25 +33,41 @@ class CustomAccessCheck implements RoutingAccessInterface {
    +   * @return One of the constants from \Drupal\Core\Access\AccessInterface.
    
    +++ b/core/lib/Drupal/Core/Access/DefaultAccessCheck.php
    @@ -18,9 +16,14 @@
    +   * @return One of the constants from \Drupal\Core\Access\AccessInterface.
    
    +++ b/core/lib/Drupal/Core/Entity/EntityAccessCheck.php
    @@ -29,6 +29,15 @@ class EntityAccessCheck implements AccessInterface {
    +   * @return One of the constants from \Drupal\Core\Access\AccessInterface.
    
    +++ b/core/lib/Drupal/Core/Entity/EntityCreateAccessCheck.php
    @@ -42,7 +42,16 @@ public function __construct(EntityManagerInterface $entity_manager) {
    +   * @return One of the constants from \Drupal\Core\Access\AccessInterface.
    
    +++ b/core/lib/Drupal/Core/Theme/ThemeAccessCheck.php
    @@ -18,10 +15,15 @@
    +   * @return One of the constants from \Drupal\Core\Access\AccessInterface.
    
    +++ b/core/modules/book/lib/Drupal/book/Access/BookNodeIsRemovableAccessCheck.php
    @@ -36,14 +34,15 @@ public function __construct(BookManagerInterface $book_manager) {
    +   * @return One of the constants from \Drupal\Core\Access\AccessInterface.
    
    +++ b/core/modules/config_translation/lib/Drupal/config_translation/Access/ConfigTranslationOverviewAccess.php
    @@ -43,7 +43,16 @@ public function __construct(ConfigMapperManagerInterface $config_mapper_manager)
    +   * @return One of the constants from \Drupal\Core\Access\AccessInterface.
    
    +++ b/core/modules/contact/lib/Drupal/contact/Access/ContactPageAccess.php
    @@ -47,10 +46,17 @@ public function __construct(ConfigFactoryInterface $config_factory, UserDataInte
    +   * @return One of the constants from \Drupal\Core\Access\AccessInterface.
    
    +++ b/core/modules/content_translation/lib/Drupal/content_translation/Access/ContentTranslationManageAccessCheck.php
    @@ -37,14 +37,29 @@ public function __construct(EntityManagerInterface $manager) {
    +   * @return One of the constants from \Drupal\Core\Access\AccessInterface.
    
    +++ b/core/modules/content_translation/lib/Drupal/content_translation/Access/ContentTranslationOverviewAccess.php
    @@ -36,9 +35,16 @@ public function __construct(EntityManagerInterface $manager) {
    +   * @return One of the constants from \Drupal\Core\Access\AccessInterface.
    
    +++ b/core/modules/field_ui/lib/Drupal/field_ui/Access/FormModeAccessCheck.php
    @@ -36,22 +36,33 @@ public function __construct(EntityManagerInterface $entity_manager) {
    +   * @return One of the constants from \Drupal\Core\Access\AccessInterface.
    
    +++ b/core/modules/field_ui/lib/Drupal/field_ui/Access/ViewModeAccessCheck.php
    @@ -36,22 +36,33 @@ public function __construct(EntityManagerInterface $entity_manager) {
    +   * @return One of the constants from \Drupal\Core\Access\AccessInterface.
    
    +++ b/core/modules/node/lib/Drupal/node/Access/NodeAddAccessCheck.php
    @@ -36,17 +35,24 @@ public function __construct(EntityManagerInterface $entity_manager) {
    +   * @return One of the constants from \Drupal\Core\Access\AccessInterface.
    
    +++ b/core/modules/node/lib/Drupal/node/Access/NodeRevisionAccessCheck.php
    @@ -63,21 +62,25 @@ public function __construct(EntityManagerInterface $entity_manager, Connection $
    +   * @return One of the constants from \Drupal\Core\Access\AccessInterface.
    
    +++ b/core/modules/quickedit/lib/Drupal/quickedit/Access/EditEntityAccessCheck.php
    @@ -38,12 +36,19 @@ public function __construct(EntityManagerInterface $entity_manager) {
    +   * @return One of the constants from \Drupal\Core\Access\AccessInterface.
    
    +++ b/core/modules/quickedit/lib/Drupal/quickedit/Access/EditEntityFieldAccessCheck.php
    @@ -37,17 +36,28 @@ public function __construct(EntityManagerInterface $entity_manager) {
    +   * @return One of the constants from \Drupal\Core\Access\AccessInterface.
    
    +++ b/core/modules/rest/lib/Drupal/rest/Access/CSRFAccessCheck.php
    @@ -41,9 +41,16 @@ public function applies(Route $route) {
    +   * @return One of the constants from \Drupal\Core\Access\AccessInterface.
    
    +++ b/core/modules/shortcut/src/Access/ShortcutSetSwitchAccessCheck.php
    @@ -9,18 +9,24 @@
    +   * @return One of the constants from \Drupal\Core\Access\AccessInterface.
    
    +++ b/core/modules/system/lib/Drupal/system/Access/CronAccessCheck.php
    @@ -18,10 +15,14 @@
    +   * @return One of the constants from \Drupal\Core\Access\AccessInterface.
    
    +++ b/core/modules/system/tests/modules/router_test_directory/lib/Drupal/router_test/Access/DefinedTestAccessCheck.php
    @@ -18,9 +16,14 @@
    +   * @return One of the constants from \Drupal\Core\Access\AccessInterface.
    
    +++ b/core/modules/system/tests/modules/router_test_directory/lib/Drupal/router_test/Access/TestAccessCheck.php
    @@ -18,9 +15,11 @@
    +   * @return One of the constants from \Drupal\Core\Access\AccessInterface.
    
    +++ b/core/modules/tracker/lib/Drupal/tracker/Access/ViewOwnTrackerAccessCheck.php
    @@ -18,12 +17,16 @@
    +   * @return One of the constants from \Drupal\Core\Access\AccessInterface.
    
    +++ b/core/modules/update/lib/Drupal/update/Access/UpdateManagerAccessCheck.php
    @@ -36,9 +33,11 @@ public function __construct(Settings $settings) {
    +   * @return One of the constants from \Drupal\Core\Access\AccessInterface.
    
    +++ b/core/modules/user/lib/Drupal/user/Access/LoginStatusCheck.php
    @@ -18,9 +17,16 @@
    +   * @return One of the constants from \Drupal\Core\Access\AccessInterface.
    
    +++ b/core/modules/user/lib/Drupal/user/Access/PermissionAccessCheck.php
    @@ -18,11 +17,17 @@
    +   * @return One of the constants from \Drupal\Core\Access\AccessInterface.
    
    +++ b/core/modules/user/lib/Drupal/user/Access/RegisterAccessCheck.php
    @@ -18,9 +17,16 @@
    +   * @return One of the constants from \Drupal\Core\Access\AccessInterface.
    
    +++ b/core/modules/user/lib/Drupal/user/Access/RoleAccessCheck.php
    @@ -22,9 +21,16 @@
    +   * @return One of the constants from \Drupal\Core\Access\AccessInterface.
    
    +++ b/core/modules/views/lib/Drupal/views/ViewsAccessCheck.php
    @@ -27,12 +26,15 @@ public function applies(Route $route) {
    +   * @return One of the constants from \Drupal\Core\Access\AccessInterface.
    

    These are the ones to change. Wow but we repeat that a lot... feels like a code smell... I'll ask effulgentsia to clarify why this is.

  9. +++ b/core/lib/Drupal/Core/Routing/Access/AccessInterface.php
    @@ -8,28 +8,12 @@
    +  // @todo Remove this interface since it no longer defines any methods?
    

    So we're now using the interface as a container for three constants. Is there an issue to explore this?

  10. +++ b/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php
    @@ -633,11 +659,18 @@ protected static function convertAccessCheckInterfaceToString($constant) {
    + * An interface with a defined access() method for mocking.
    

    Defines. :)

effulgentsia’s picture

Re #81.2: #2265939: [sechole] AccessManager::checkNamedRoute() leaks attributes from current request. I still need to file issues for the other two @todos that are missing issue links.

xjm’s picture

Status: Needs work » Needs review
StatusFileSize
new91.85 KB
new31.48 KB

Here we go. I addressed everything in #81. Couple unrelated things I noted during the process.

  • I noted that FormModeAccessCheck and ViewModeAccessCheck have incorrect class docblocks. Will file a followup issue for that.
  • I also noticed that \Drupal\quickedit\Access\EditEntityAccessCheck and EditEntityFieldAccessCheck should probably be renamed to indicate they're specifically for quickedit now that the module has been renamed (as maybe should their relevant methods possibly other classes in the namespace); I was confused at first since there's a different checker for entity CRUD.

I also noticed that there are some places where we're not properly documenting optional parameters (comparing the @param docs to the signatures). The docblock should indicate when the parameter is optional and what happens in the default case when it is not provided. E.g.:

+++ b/core/modules/node/lib/Drupal/node/Access/NodeRevisionAccessCheck.php
@@ -63,21 +62,25 @@ public function __construct(EntityManagerInterface $entity_manager, Connection $
+   * Checks access.
+   *
+   * @param \Symfony\Component\Routing\Route $route
+   *   The route to check against.
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The currently logged in account.
+   * @param \Drupal\node\NodeInterface $node
+   *   The node.
+   * @param string $node_revision
+   *   The node revision.
...
+  public function access(Route $route, AccountInterface $account, NodeInterface $node = NULL, $node_revision = NULL) {

Should $node really be optional here, and is the revision really a string? Also, the parameters should be marked as optional in the docblock if they are.

And the rest:

+++ b/core/modules/field_ui/lib/Drupal/field_ui/Access/FormModeAccessCheck.php
@@ -36,22 +36,33 @@ public function __construct(EntityManagerInterface $entity_manager) {
+   * @param string $form_mode_name
+   *   The form mode.
+   * @param string $bundle
+   *   The bundle.
...
+  public function access(Route $route, Request $request, AccountInterface $account, $form_mode_name = 'default', $bundle = NULL) {

+++ b/core/modules/field_ui/lib/Drupal/field_ui/Access/ViewModeAccessCheck.php
@@ -36,22 +36,33 @@ public function __construct(EntityManagerInterface $entity_manager) {
+   * @param string $view_mode_name
+   *   The view mode.
+   * @param string $bundle
+   *   The bundle.
...
+  public function access(Route $route, Request $request, AccountInterface $account, $view_mode_name = 'default', $bundle = NULL) {

+++ b/core/modules/node/lib/Drupal/node/Access/NodeAddAccessCheck.php
@@ -36,17 +35,24 @@ public function __construct(EntityManagerInterface $entity_manager) {
+   * @param \Drupal\node\NodeTypeInterface $node_type
+   *   The node type.
...
+  public function access(AccountInterface $account, NodeTypeInterface $node_type = NULL) {
effulgentsia’s picture

StatusFileSize
new93.34 KB
new5.5 KB

Filed the remaining todos that were missing issue links: #2266817: Deprecate empty AccessInterface and remove usages and #2266809: Make QuickEditEntityFieldAccessCheck::access() use the $account that's passed in, and fixed #83, but did not file the two issues mentioned at the top of #83.

xjm’s picture

+++ b/core/modules/node/lib/Drupal/node/Access/NodeRevisionAccessCheck.php
@@ -68,15 +68,19 @@
    *   The route to check against.
    * @param \Drupal\Core\Session\AccountInterface $account
    *   The currently logged in account.
+   * @param int $node_revision
+   *   (optional) The node revision ID. If not specified, but $node is, access
+   *   is checked for that object's revision.
    * @param \Drupal\node\NodeInterface $node
-   *   The node.
-   * @param string $node_revision
-   *   The node revision.
+   *   (optional) A node object. Used for checking access to a node's default
+   *   revision when $node_revision is unspecified. Ignored when $node_revision
+   *   is specified. If neither $node_revision nor $node are specified, then
+   *   access is denied.
    *
    * @return string
    *   A \Drupal\Core\Access\AccessInterface constant value.
    */
-  public function access(Route $route, AccountInterface $account, NodeInterface $node = NULL, $node_revision = NULL) {
+  public function access(Route $route, AccountInterface $account, $node_revision = NULL, NodeInterface $node = NULL) {
     if ($node_revision) {
       $node = $this->nodeStorage->loadRevision($node_revision);

The order of the arguments switched around here; does that not matter/impact calling code because of upcasting? (I can see how this is a more logical optional order based on the docs.)

xjm’s picture

Assigned: xjm » Unassigned

Talked to @effulgentsia about #85 and the answer is "yes that's the point". :) So AFAIK all the followups and docs improvements are taken care of now.

tim.plunkett’s picture

Status: Needs review » Reviewed & tested by the community

This has long diverged from my work, so I feel okay RTBCing.

My only comment is that I wish we had unit tests for AccessArgumentsResolver, but I think that can be a follow-up.

Thanks for persisting on this @effulgentsia, I like where we ended up.

xjm’s picture

Yay, thanks @tim.plunkett!

Filed #2268537: Add unit tests for AccessArgumentsResolver since as @tim.plunkett points out there's already integration test coverage, but unit tests for the new class would be good.

tim.plunkett’s picture

Assigned: Unassigned » tim.plunkett
Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs tests

Actually, now that I work on these tests, I think I found something odd. Give me a bit to work through this. Also, I felt bad about trying to circumvent the testing gate.

tim.plunkett’s picture

Assigned: tim.plunkett » Unassigned
Status: Needs work » Needs review
Issue tags: -Needs tests
StatusFileSize
new8.72 KB
new100.73 KB
new100.81 KB

Okay, here is a patch with the test and my fix, a patch without the fix, and the interdiff of the test and fix.

If these changes are fine with @effulgentsia, I think he can re-RTBC.

The last submitted patch, 92: access-checker-2222719-92-FAIL.patch, failed testing.

effulgentsia’s picture

Thanks for the tests. The 2 tiny non-test changes look great. For the tests:

  1. +++ b/core/tests/Drupal/Tests/Core/Access/AccessArgumentsResolverTest.php
    @@ -0,0 +1,238 @@
    +    // Test an optional parameter with no default.
    +    $data[] = array(
    +      function($foo = 'foo') {}, new Request(), array('foo'),
    +    );
    

    s/default/provided value/?

  2. +++ b/core/tests/Drupal/Tests/Core/Access/AccessArgumentsResolverTest.php
    @@ -0,0 +1,238 @@
    +  /**
    +   * Tests getArgument() with a 'route' parameter with no typehint and a value.
    +   */
    +  public function testGetArgumentRouteNoTypehintAndValue() {
    +    $callable = function($route) {};
    +    $request = new Request();
    +    $request->attributes->set('route', 'foo');
    +
    +    $arguments = (new AccessArgumentsResolver())->getArguments($callable, $this->route, $request, $this->account);
    +    $this->assertSame(array('foo'), $arguments);
    +  }
    +
    +  /**
    +   * Tests getArgument() when upcasting is bypassed.
    +   */
    +  public function testGetArgumentBypassUpcasting() {
    +    $callable = function(Route $route = NULL) {};
    +
    +    $request = new Request();
    +    $request->attributes->set('route', NULL);
    +
    +    $arguments = (new AccessArgumentsResolver())->getArguments($callable, $this->route, $request, $this->account);
    +    $this->assertSame(array(NULL), $arguments);
    +  }
    

    I don't think it's clear what the purpose of these two tests are, especially the last one. Are we trying to test a generic parameter name and the ability for it to be in ->attributes as NULL, even if the callable typehints? Or are we specifically testing the functionality of 'route' as a named attribute overriding $this->route? Related, do we also want a test for in that latter case, to still be able to receive $this->route into the callable via a Route $pick_any_name_you_like parameter?

tim.plunkett’s picture

StatusFileSize
new101.57 KB
new2.18 KB

testGetArgumentRouteNoTypehintAndValue() is to prove that just naming something $route will not cause any conflict with the Route object.

testGetArgumentBypassUpcasting() is to cover this case:

    // If the route argument exists and is NULL, return it, regardless of
    // parameter type hint.
    if (!isset($upcasted_route_arguments[$parameter_name]) && array_key_exists($parameter_name, $upcasted_route_arguments)) {
      return NULL;
    }

I added some comments.

Also, I added testGetArgumentRouteCustomName() to test Route $custom_name, as suggested.

effulgentsia’s picture

Status: Needs review » Reviewed & tested by the community

Thanks.

dries’s picture

Status: Reviewed & tested by the community » Fixed

This look like a good DX win and it helps us make progress on #2124749: [meta] Stop using $request->attributes->get(MAGIC_KEY) as a public API. Given that catch feels comfortable with the performance issue, I decided to commit it to 8.x. Thanks everyone!

  • Commit 809b361 on 8.x by Dries:
    Issue #2222719 by effulgentsia, tim.plunkett, xjm, dawehner: Use...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

dawehner’s picture

We certainly should improve our horrible documentation now, see #2611272: Add documentation to \Drupal\Core\Routing\Access\AccessInterface how to implement it