Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
Proposed commit message:
Issue #2543332 by Wim Leers, Fabianx, effulgentsia, Crell, dawehner, borisson_: Auto-placeholdering for #lazy_builder without bubbling
Problem/Motivation
See #2499157: [meta] Auto-placeholdering.
Proposed resolution
Elements that have:
#lazy_builder
- poor cacheability (low max-age, high-cardinality cache contexts or high-invalidation frequency cache tags (concrete examples: see below)
Remaining tasks
Review.
User interface changes
None.
API changes
None.
API addition: auto_placeholder_conditions
: the conditions can be set for any of the cacheability metadata properties:
- max-age: if at or below a specified max-age, it qualifies for auto-placeholdering, comes with a sane default:
0
— but some sites may want to set this to e.g.60
(to placeholder anything that can only be cached for a minute) - contexts: any contexts that are specified make it qualify for auto-placeholdering (recommended: only high cardinality cache contexts, comes with a sane default:
['session', 'user']
— but some sites may want to exclude e.g.'user'
because they have a very limited number of users) - tags: any cache tags that are specified make it qualify for auto-placeholdering (recommended: only cache tags that have a very high invalidation frequency, comes with a sane default:
[]
— but some sites may want to exclude e.g. a cache tag likecurrent-temperature
for sites showing the current temperature)
Data model changes
None.
Comment | File | Size | Author |
---|---|---|---|
#30 | auto-placeholdering-step1-2543332-30.patch | 29.57 KB | Wim Leers |
Comments
Comment #1
Wim LeersComment #2
Wim LeersNotes to aid reviewers:
Renderer
!renderer.config.high_cardinality_cache_contexts
todefault.services.yml
, the default value is:['session', 'user']
, i.e. when those are encountered, placeholdering will happen, because letting those cache contexts bubble and affect the supertree/response results in poor cacheability\Drupal\Tests\Core\Render\RendererPlaceholdersTest::providerPlaceholders()
, bringing it to 8 tested permutations (that themselves go through multiple scenarios):#lazy_builder
+#cache[max-age] = 0
+ NO#cache[keys]
#lazy_builder
+#cache[max-age] = 0
+#cache[keys]
#lazy_builder
+#cache[contexts] = [user]
+ NO#cache[keys]
#lazy_builder
+#cache[contexts] = [user]
+#cache[keys]
Comment #3
Wim LeersLOL, forgot to attach the patch.
Comment #4
Wim LeersIf people are +1 to that, we could also already get rid of one manual
#create_placeholder
call in Drupal 8: that for the comment form. It can automatically be placeholdered easily now: we only need to provide the cache contexts it varies by.I'm happy to remove this from the patch if it feels too distracting. But in any case it provides a clear (and easy to test) example that shows the benefits.
This makes
CommentDefaultFormatter
significantly simpler.Comment #6
Fabianx CreditAttribution: Fabianx as a volunteer commentedOverall looks really truly fantastic, I agree to include one conversion here.
Just had one quick remark:
Either this default needs to live in core.services.yml as well OR
in the __construct() we do something like:
$this->config = $config + [
'required_cache_contexts' => [ '...' ],
'high_card_...' => ['...'],
]
Comment #7
Wim Leers#6: Yep: that bit being missing from
core.services.yml
also caused a kernel test failure. (I very much disagree with all this duplication, but that's a subject for another issue — opened #2543514: Confusing duplication of container parameters in core.services.yml and default.services.yml for that.)Comment #8
effulgentsia CreditAttribution: effulgentsia at Acquia commentedPatch looks great. Just some minor nits and questions:
I don't think we need that. We don't do it for other container parameters.
Why? I realize this is already the case in HEAD, but I think a comment explaining why comment forms are personalized would be helpful. Is it just the CSRF token or something else? If it's the CSRF token, why doesn't FAPI set the 'user' cache context on that token element? No need to refactor that here, but a comment, and if applicable, a @todo linking to an issue, would help.
Is this because of the CommentDefaultFormatter change or due to something else? If due to the CommentDefaultFormatter change, then a) why does that affect the translation UI, and b) how was HEAD working prior to this patch, given that the comment form was personalized there as well?
Comment #9
Wim Leersuser
cache context is necessary.CommentDefaultFormatter
: rather than hardcoding its#create_placeholder = TRUE
, it now just sets the cache contexts that it varies by (user
, if the current user is authenticated) and lets auto-placeholdering take care of that. Which means thatuser
cache context still applies to the resulting final page that has the placeholder for the comment form replaced. And sinceuser
impliesuser.permission
anduser.roles
, these changes to the expectations are necessary.Comment #10
Crell CreditAttribution: Crell at Palantir.net commentedWhy is this 2 separate conditionals? Seems like it's just one long set of them...
Since I can't keep up with all the moving parts of render API, am I reading this correctly that the lazy builder can/should be a service name and method? (The correct answer is yes.)
I'm confused. Why do we need to add the user context only if the user is authenticated? All anonymous users should get the same form, presumably, and all have the same user ID, so why would adding the user context in all cases be a problem?
This is a file that people will be customizing early in their Drupal experience. To someone who hasn't already written large portions of the render API, this paragraph is completely useless if not counter-productive with how much it assumes. Even I only barely follow it, and I am reading this patch. :-)
Alternative text:
Drupal allows portions of the page to be automatically deferred when rendering to improve cache performance. That is especially helpful for cache contexts that vary widely, such as the active user. On some sites those may be different, however, such as sites with only a handful of users. If you know what the high-cardinality cache contexts are for your site, specify those here. If you're not sure, the defaults are fairly safe in general.
And then maybe a @see to something that explains about #lazy_builder (which I've not actually heard about until just now, to show how obscure that reference is).
Comment #11
Wim Leers#8 Fixed point 1, and fixed point 2 by improving the code comments.
#10:
#lazy_builder
supports that too.user
cache context. But I think this is a bit conceptually clearer, and is more in line with the original logic that this patch now simplifies.If you feel strongly about this though, I'm happy to change it to just
'user'
:)I'm glad you said that, because that statement is apparently wrong — I was under the same false impression. See #2543514: Confusing duplication of container parameters in core.services.yml and default.services.yml. At least I was not alone :)
RE: not being able to follow it — thanks for the suggested text, much better indeed. Used it verbatim :) Also added that link you asked for, but not with an
@see
, but in the same language that is used in thetwig.config.debug
documentation.Comment #12
Crell CreditAttribution: Crell at Palantir.net commentedRe #11.3: On the list of things I have a strong feeling about it's fairly low on the list :-), but it does strike me as something that is going to confuse a lot of people who stare at it and go "what's the subtle reason we have to only add it conditionally, what am I missing?" which is exactly what I did. If the answer is "meh, no particular reason", then let's skip the confusing part and just always add it.
If there is some subtle reason for it, the that needs to be better documented.
Comment #13
Wim LeersWell, we only conditionally vary per user (i.e. if the user is authenticated). So, we use the same conditionality to associate the
'user'
cache context. That's the entire reason. I think #11 documented that very clearly, didn't it?Comment #14
effulgentsia CreditAttribution: effulgentsia at Acquia commentedI share the same confusion as Crell. In HEAD, there's an if/else for anonymous/authenticated, because one uses #lazy_builder and the other doesn't. But the patch unifies to always use #lazy_builder. So, given that, I'm confused how:
has any functional difference to:
So all I see is 8 lines of code performing what could be done with 1.
Huh? All anonymous users have uid=0, so what does it mean to "only conditionally vary per user if the user is authenticated"?
Comment #15
effulgentsia CreditAttribution: effulgentsia at Acquia commentedOn the other hand:
Does it make sense to disconnect this context assignment from the "Your name" field? Shouldn't we do this within CommentForm::form() instead, at the point that we actually perform the personalization? Is doing it here just a temporary artifact of this issue's title ending with "without bubbling"? If so, then maybe leaving it as a separate conditional makes sense, if we add a @todo to the "with bubbling" issue?
Comment #16
Wim Leers#14: There indeed is no functional difference, only a semantical difference.
#15: Correct, this is just a temporary artifact until #2543334: Auto-placeholdering for #lazy_builder with bubbling of contexts and tags! I should've added such a
@todo
from the very beginning, that'd have made this clearer. My bad. Done.Comment #17
effulgentsia CreditAttribution: effulgentsia at Acquia commentedRe #16, how about this?
Comment #18
dawehnerI'm curious, could this username display be a placeholder for itself so most of the form could be cached? Is there an issue for that?
Comment #19
Wim Leers#17 looks great to me. I wanted to minimize change, but this is clearly better :)
#18: I've considered that too, and even tried it a long time ago (>1 year ago). It doesn't work because Form API tracks which fields exist in a given built form, for validation/security purposes. We cannot just add a form field at a later time.
Comment #20
borisson_Prevent the renderable array? Or the placeholder?
I think the readability would be better if you split this if up into 2 different if statements.
Comment #21
dawehnerResponded as a new issue #2544560: Try to make the comment form cacheable. I think its solveable.
Comment #22
Wim LeersComment #23
Crell CreditAttribution: Crell at Palantir.net commentedI'm comfortable with where this is ending up. Nice work, all!
Comment #24
Wim Leerscatch reviewed this (see IRC log below), and wants to see two changes that Fabianx & I agree with:
renderer.config.high_cardinality_cache_contexts
container parameter should be renamed torenderer.config.placeholder_cache_contexts
(rationale: see below)hasPoorCacheability()
helper method should be renamed toshouldAutomaticallyPlaceholder()
(rationale: see below)Comment #25
Wim LeersDiscussed it more with catch & Fabianx. Refined #24 more.
Updated patch attached, including expanded test coverage, IS updated too.
Comment #26
Fabianx CreditAttribution: Fabianx as a volunteer commentedShould add:
Disable by setting to max-age: -1
and for the other two:
Disable by specifying [].
Overall looks great! :)
Comment #27
Wim LeersDone.
Comment #28
Fabianx CreditAttribution: Fabianx for Acquia commentedBack to RTBC. Patch looks great!
Comment #29
Wim LeersOops, @effulgentsia just pointed out I lost his #17 interdiff. Fixing that.
Comment #30
Wim LeersThe #17 interdiff applies to this reroll. Simply brought that back.
Comment #31
Crell CreditAttribution: Crell at Palantir.net commentedStill +1 to RTBC. Nice tweakage.
Comment #32
Fabianx CreditAttribution: Fabianx for Acquia commentedAdded a proposed commit message, patch indeed is still RTBC.
Proposed commit message:
Comment #33
catchCommitted/pushed to 8.0.x, thanks!
Comment #35
Wim LeersHurray! See you all in the next step: #2543334: Auto-placeholdering for #lazy_builder with bubbling of contexts and tags.
Comment #36
davidhernandezThis appears to have caused some weirdness with messages. See #2547127: [regression] Empty messages container appearing when a Notice occurs
I tracked it to this commit.
Comment #37
szeidler CreditAttribution: szeidler as a volunteer commentedAfter performing
drush cr
with the current HEAD i get an Undefined index once.Notice: Undefined index: auto_placeholder_conditions in Drupal\Core\Render\Renderer->shouldAutomaticallyPlaceholder() (line 658 of core/lib/Drupal/Core/Render/Renderer.php).
I didn't saw any side-effects along with this notice.
Comment #38
Wim LeersCR created to help people upgrade existing sites due to the updated container parameter: https://www.drupal.org/node/2547351. For details on why this is necessary, see #2547127-14: [regression] Empty messages container appearing when a Notice occurs.
#37: instead of doing that, you need to do what the CR says. #37 introduces a bug.
Comment #39
Wim Leers#36:
Comment #40
szeidler CreditAttribution: szeidler as a volunteer commented