Problem/Motivation
Local action plugins do not have any way to provide cacheability metadata.
I came to this realization when trying to port a contrib module where the local action link will change based on the module's configuration setting. It's easy to write a local action plugin that varies the route and/or route parameters based on a config setting, but the render cache will continue to hold the wrong version.
Since the D7 version of the module selectively populates a CSRF token in the rendered link depending on the config setting (hence not cached at all in the render cache), this incorrect caching would be critical problem.
This is because the local action plugin manager has no way to accumulate cacheability metadata and we make no provision for local action plugins that implement the CacheableDependencyInterface
This was addressed for local tasks in #2511516: Make local tasks and actions hooks provide cacheability metadata, to make the blocks showing them cacheable but accidentally omitted for local actions.
Proposed resolution
Modify the local action manager to be in line with the local task manager (though this might be an interface change), or see if it's possible to change the internals of the implementation so cachability metadata is added to the render array.
Remaining tasks
write patch, add tests
User interface changes
none
API changes
TBD
Data model changes
none
Comment | File | Size | Author |
---|---|---|---|
#44 | increment.txt | 2.46 KB | pwolanin |
#44 | 2624594-44.patch | 13.77 KB | pwolanin |
#42 | increment.txt | 6.83 KB | pwolanin |
#42 | 2624594-42.patch | 13.17 KB | pwolanin |
#20 | increment.txt | 1.61 KB | pwolanin |
Comments
Comment #2
pwolanin CreditAttribution: pwolanin as a volunteer commentedPossibly we could do something like this? Need to add some tests to verify the behavior.
Need feedback from Wim Leers and laurii or someone else who understands this cacheable metadata system.
Comment #3
pwolanin CreditAttribution: pwolanin as a volunteer commentedOr maybe like this? Since we want to bubble up any cachabilty data from the access check also?
Comment #5
pwolanin CreditAttribution: pwolanin as a volunteer commentedOk, need to add the #cache elements to the unit test data.
Comment #6
dawehnerNote: We have the same issue with contextual links ...
Comment #7
lauriiiWorking on this one for a bit
Comment #8
lauriiiOne of the problems I can figure out right now is that links depend on the route cache context, but its only set on the block. There probably should be additional parameter ::getActionsForRoute that sets the cacheability metadata for the whole link set.
Comment #9
pwolanin CreditAttribution: pwolanin as a volunteer commented@laurii - to avoid changing the interface, we could have LocalActionManager::getActionsForRoute set #cache on the top level of the render array, but I'm a little unclear on some of the details of the render caching in terms where these properties are set.
@dawehner - indeed, contextual links seem to suffer the same, but it's possibly harder to solve since we are not returning a render array there.
I'm also not sure (in terms of BC) if we should assume links that don't implement CacheableDependencyInterface should have max-age of 0 or forever. I guess 0 is the more "conservative" approach in terms of not over-caching, even though if there are any contrib modules with custom plugin classes they'd need to be updated.
Comment #10
dawehnerWhat do we do in other places in core? Views for example doesn't deal with it, see
\Drupal\views\Plugin\views\display\DisplayPluginBase::calculateCacheMetadata
Comment #11
pwolanin CreditAttribution: pwolanin as a volunteer commented@dawehner, for local tasks, if they don't implement that interface they get a max-age of 0, so doing the same in other places would be consistent at least.
Comment #12
pwolanin CreditAttribution: pwolanin as a volunteer commentedAdding route cache context to the render array per laurii. Not sure if the block still needs to have it also?
Comment #13
lauriiiSo right now we have this in the build method of the block:
and that will remove the cache context away from the render array. That logic has to change so that #cache item wont get removed and then we can remove the ::getCacheContexts from the block.
After we've remove the method we should try to investigate whether there is already existing test coverage for this, or if we should add new tests.
Comment #14
lauriiiNot working on the issue right now so unassigning
Comment #15
dawehnerLocal actions are used on admin pages, which aren't cached. I don't think this is a critical issue but rather a major one.
Comment #16
pwolanin CreditAttribution: pwolanin as a volunteer commentedok, fixing the block code, but still need tests.
Comment #17
Wim Leers+1
Comment #18
pwolanin CreditAttribution: pwolanin as a volunteer commentedIn contrib, they are added to node view pages, etc. So this is pretty serious - it's not admin only
Comment #19
pwolanin CreditAttribution: pwolanin as a volunteer commentedHere's first pass at a test that seems to validate the expected behavior
The test-only patch is also the interdiff.
Comment #20
pwolanin CreditAttribution: pwolanin as a volunteer commentedSome small textual fixes.
Comment #22
lauriiiFinally got a chance to review the tests and the patch. Looks good for me!
Comment #23
alexpottWill this change result in things being uncachable that once were?
Comment #24
pwolanin CreditAttribution: pwolanin as a volunteer commentedYes, any e.g. custom contrib local action classes that are not extending LocaActionDefault will be uncached until they add cacheability metadata.
LocalAction Default returns:
Cache::PERMANENT;
so in general there will be no change from current behavior.Comment #25
lauriiiI think this is still RTBC and ready for the commiter feedback again
Comment #26
Wim LeersBut this is not actually tested AFAICT, because of the default of
max-age = 0
.The
menu_test.local_action.cache_check
local action should specifycache_max_age: 1000000
or something like that.Or what am I missing?
Comment #27
pwolanin CreditAttribution: pwolanin as a volunteer commented@Wim Leers - I don't understand your comment. The default is only
0
if the plugin doesn't implementCacheableDependencyInterface.
Since
TestLocalActionWithConfig extends LocalActionDefault
it will have a max-age ofCache::PERMANENT
.Is there another test case you'd want to see added?
Comment #28
XanoComment #29
XanoIs there a way we can prevent adding more array items, and use interfaced objects for this, just like elsewhere in core? #2633878: Finalize cacheability for plugins solves a different problem by also adding cacheability metadata to plugin definitions, but in the form of
CacheableDependencyInterface
like we use elsewhere in core. Since we should convert plugin definitions to objects in the future anyway, this seems like a good place to start. The managers could convert YAML arrays to objects in this case.Comment #30
XanoAfter talking to @pwolanin on IRC I now understand the exact reason for these additional keys. It would not make sense to use a generic
cache
key just like in the other definition. Maybe we can userender_cache_*
to emphasize those values are for render array cacheability only?Comment #31
Wim LeersThe answer to this is:
… clearly I was missing something :) I misread. Sorry.
Comment #32
XanoSince we should allow plugin definitions to expose their own cacheability metadata (in another issue), can we prefix the definition keys here so it's absolutely clear those influence render array cacheability and not something else?
Comment #33
pwolanin CreditAttribution: pwolanin as a volunteer commented@Xano - based on our discussion in IRC, possibly we should not put the cache info in the YAML at all, but rather require it to be defined in the PHP?
The implementation here just mirrors what is already in core for local tasks. We should probably make them work the same way, and I'm guessing changing local tasks now would be considered a BC break. That's the best argument, I think, for keeping the patch as-is. If you can convince xjm that changing the YAML keys or the whole scheme for local tasks makes sense, we can do it.
Comment #34
XanoI don't think we should in this case, because it would undo the benefits of YAML discovery (not having to write your own class). It's more suitable when used with annotated class discovery.
I checked the code again, and see that
LocalTaskDefault
does the same thing already. We should probably use the same approach forLocalActionDefault
and not prefix the definition keys for consistency with existing code.Comment #35
pwolanin CreditAttribution: pwolanin as a volunteer commented@Xano - yes, I think at this point consistency is the easiest way forward, but it will rarely be the case that you need some non-default redner cache metadata unless you are also implementing a custom class.
Comment #36
alexpottI'm committing this to 8.1.x first. I'm not sure this should go into 8.0.x as it is adding an interface and public methods to a class. Committed 1a71d3b and pushed to 8.1.x. Thanks!
Fixed up a few coding standards on commit.
Comment #38
pwolanin CreditAttribution: pwolanin as a volunteer commented@alexpott - I think this is really important to go to 8.0.x. This is essential for making contrib modules work correctly with the render cache.
Comment #39
Wim LeersI think the disruption here is super minimal: a default plugin manager implementation now implements one additional interface. No interfaces are changed. So I don't see why this couldn't go into 8.0. Even existing subclasses of this plugin manager would continue to work just fine.
This is a straight bugfix, with no BC impact, so can be safely committed to 8.0.x.
Comment #40
pwolanin CreditAttribution: pwolanin as a volunteer commentedFrom discussion with xjm and alexpott in IRC - will try to re-roll the patch for 8.0.x without the changes to the default class, but with enough changes to the manager and block that a custom plugin class could bubble up cache tags.
Comment #41
Wim LeersAdjusting status for #40.
Comment #42
pwolanin CreditAttribution: pwolanin as a volunteer commentedOk, new patch for 8.0.x that follows the plan from IRC as I understood it.
Comment #44
pwolanin CreditAttribution: pwolanin as a volunteer commentedNeeded to make a change to the unit test for the BC behavior.
Also - looking again at the fact we were not currently adding cacheability metadata for the access object, this fix does have some possible security implications for contrib.
Comment #45
dawehnerCould we provide a class in core which allows you to define cache metadata as part of the yml file, which itself extends from the default class
Comment #46
pwolanin CreditAttribution: pwolanin as a volunteer commented@dawehner - well, that's basically what's in 8.1.x? Or you are thinking as a shim for 8.0.x?
I think this is still enough of an edge case that it's not really needed. For example, the implementation in the test class here would work perfectly fine in 8.1.x, and in general defining the cacheability in the class code may be a better pattern than using the yaml file.
Comment #47
dawehnerWell yeah I was thinking about making it at least easy by providing that class, so people can specify it in the yml file.
Comment #48
pwolanin CreditAttribution: pwolanin as a volunteer commented@dawhener - I don't see a lot of value to such a helper given that adding cache tags or contexts is an uncommon need. But, I can certainly add one if alexpott wants it.
Comment #49
dawehnerI think it would be nice because it also tells people that they should be thinking about it.
Comment #50
dawehnerYeah potentially this is not that much needed in reality.
Comment #51
alexpottThanks @pwolanin for working on a backportable version for 8.0.x. Committed 99158a9 and pushed to 8.0.x. Thanks!