Problem/Motivation
This problem was pointed out by @Berdir:
content_moderation_entity_access() adds the $account
object as a cacheable dependency, which is an uncacheable AccountProxy
object.
Additionally, it also adds each \Drupal\workflows\Transition
returned by \Drupal\content_moderation\StateTransitionValidation::getValidTransitions()
object as a cacheable dependency, which are uncacheable objects as well.
The initial purpose for adding those dependencies was based on @Wim Leers's feedback from #2725533-64: Add experimental content_moderation module (point 10), but they were implemented wrongly by me :/
Proposed resolution
Since \Drupal\content_moderation\StateTransitionValidation::getValidTransitions()
returns a list of transitions based on user permissions, that's the only cacheable dependency we need to use.
Remaining tasks
Review.
User interface changes
Nope.
API changes
Nope.
Data model changes
Nope.
Comment | File | Size | Author |
---|---|---|---|
#32 | 2985253-32-30-interdiff.txt | 787 bytes | mbovan |
#32 | 2985253-32.patch | 4.49 KB | mbovan |
#30 | 2985253-30-26-interdiff.txt | 922 bytes | mbovan |
#30 | 2985253-30.patch | 4.53 KB | mbovan |
#26 | 2985253-26-23-interdiff.txt | 9.1 KB | mbovan |
Comments
Comment #2
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedThis should do it.
Comment #3
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedComment #5
BerdirThe tricky part is that someone could override that service and actually *do* something that varies on more than just permissions.
So to handle it correctly, we would need to allow that service to return cacheability metadata I guess..
Comment #6
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedRight you are :)
Comment #7
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedWhile we're here, I'm not sure what this check is meant to reflect, but no issues with that being a follow-up:
Some comments/concerns on #6:
$moderation_info->isModeratedEntity($entity)
is FALSE, we return the NULL case, however wouldn't we also require the cacheability metadata of::isModeratedEntity
to be returned with that NULL/neutral case? It would need to be invalidated if the conditions ofisModeratedEntity
changed right? Ie, the cacheability of all 'update' entity access results for revisionable entities should depend on theworkflow_list
tag. I'm not sure if this makes sense, but something feels off.CacheableDependencyInterface
isn't that useful if the cacheability depends on the arguments passed togetValidTransitions
. You could argue, since our default implementation is what uses$entity
,$workflow
anduser.permissions
to make those decisions, all of those should be added in the body of that method, and optional for other implementations.This cacheability stuff is far from a strong area for me, so happy to be corrected on the finer details here.
Comment #8
jibranComment #9
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedAlso, not sure why the version was changed with my comment. Guessing this was meant for 8.6.x anyway.
Comment #11
jibranLet's add some tests.
Comment #14
mbovan CreditAttribution: mbovan at MD Systems GmbH commentedRerolled #6 and added the test coverage as suggested in #11.
Comment #15
BerdirLets add a check that $transition_validation implements CacheableDependencyInterface?
Or we on purpose don't do that, because then services that don't override it would be uncacheable? Not sure, but then we should probably document that somehow?
Comment #16
BerdirAnd I'm not sure if just unit tests are enough here. What I'd like to to see is a functional test that makes sure that a page isn't marked as uncacheable due to this.
Comment #17
mbovan CreditAttribution: mbovan at MD Systems GmbH commentedI added a comment for #15 and a functional test (#16) that creates a node and asserts dynamic cache headers for the created node.
In my manual testing with content moderation and node entities of
content_moderation_entity_access()
(without the patchmax-age: 0
) I always ended up withmax-age: -1
due to its result being merged withnode_node_access()
access result which defaults to permanent max-age. That said, I could not upload a failing Test-Only patch.I am not sure if the current test is enough or whether we need another one (with non-node entities?) that would make a page uncacheable?
Comment #19
BerdirI see, this is a bit trickier than I remembered, because the problematic code is "only" in the $op = update case. That means we need to have something that checks that. For example the local tasks block. But that is a block and is then auto-placeholdered.
Comment #20
BerdirLets go with a kernel test then, then we can assert the exact cacheability and make sure it's cacheable.
Comment #22
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedI've reviewed this again, I'm still not quite sure about the service implementing
CacheableDependencyInterface
directly. I think primarily my issue is, it doesn't actually provide all the appropriate cacheability information about decisions made within the service, the correct cacheability is a combination of:Code in the service:
and dependencies added in the access hook:
I think that's a bit misleading and leads me to believe that
getValidTransitions
should probably just optionally accept aRefinableCacheableDependencyInterface
parameter.Comment #23
BerdirYeah, that's a fair point, I suppose it would be possible although pretty weird to build up an internal cacheablity metadata and return it.
I also thought about returning a value object as ArrayObject, but BC is impossible, because what we specifically do here in this case is if ($valid_transitions) and you can't simulate that.
It's much tricker with BC though because unfortunately, we have an interface. I'm a bit concerned on whether that will delay getting it committed. Attached is a patch that does that, but didn't update documentation BC stuff yet.
Comment #24
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedI think the quickest path to fixing the bug would be to keep the current approach and update
content_moderation_entity_access
with the correct metadata and file a follow up for creating an implementation where thecontent_moderation.state_transition_validation
service can specify the metadata.NW given the issues you already identified in #23. I'm not actually sure how to add methods to an interface with BC.
Comment #25
BerdirSo basically going back to #2 with the test coverage? Fine with me :)
Comment #26
mbovan CreditAttribution: mbovan at MD Systems GmbH commentedCreated a combined patch of #2 and functional tests from #20.
Comment #27
mbovan CreditAttribution: mbovan at MD Systems GmbH commentedCreated #3095685: Allow content_moderation.state_transition_validation service to specify the cacheability metadata as a followup as per #24.
Comment #29
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedExtra space at the start of this line. Can we perhaps describe the service instead of refreshing the fully qualified method name, something like "The transition validation service returns ... ".
Beyond that, fix and test looks good to me.
Comment #30
mbovan CreditAttribution: mbovan at MD Systems GmbH commentedAddressed #29.
Comment #31
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedThe test coverage for this looks good to me as well. Just a small nitpick:
Workflow
is a config entity type, so we don't need to do this.Comment #32
mbovan CreditAttribution: mbovan at MD Systems GmbH commentedFixed #31.
Comment #33
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedThanks, @mbovan! I think this is ready to go now :)
Comment #34
alexpottCommitted and pushed 84b9809a0b to 9.0.x and 56719db926 to 8.9.x. Thanks!
Is there an actual bug here apart from this not being cacheable? Is there any advantage to having this fixed in 8.8.0?
Comment #37
BerdirI guess the answer to that is, it depends. For our project, this was an important improvement. Depending on how/where access is checked, the max-age 0 can "bleed" into the main content and make that the whole page uncacheable for dynamic page cache. And also in the local tasks block, which is then converted to a placeholder, that can bubble up into the internal/anonymous page cache if you use https://www.drupal.org/project/cache_control_override or a similar custom solution.
Also, a placeholdered local tasks block can result in it being inserted later on into the page and moving things around with big_pipe.
On the other side, the current implementation might be hiding incorrect custom access checks that don't consider cacheability correctly and would get cached incorrectly once this is fixed.
I'm OK with it being in 8.9 only, happy it is now committed :)
Comment #38
alexpottYeah this is why I'm tempted to let this stew in 8.9.x and not backport to 8.8.x. That gives us 6 months for any of this to come out of the woodwork.