- Issue created by @kristiaanvandeneynde
- π§πͺBelgium wim leers Ghent π§πͺπͺπΊ
π€ Interesting. It's been too long for me to remember the details on this, but two immediate thoughts:
- something can only be cached individually if and only if it has
#cache[keys]
set:
// Try to fetch the prerendered element from cache, replace any placeholders // and return the final markup. if (isset($elements['#cache']['keys'])) {
- β¦ which I guess maybe (I do not recall!) implies that auto-placeholdering is impossible? But the detailed validation logic to ensure a reasonable DX does not check for that:
// First validate the usage of #lazy_builder; both of the next if-statements // use it if available. if (isset($elements['#lazy_builder'])) { assert(is_array($elements['#lazy_builder']), 'The #lazy_builder property must have an array as a value.'); assert(count($elements['#lazy_builder']) === 2, 'The #lazy_builder property must have an array as a value, containing two values: the callback, and the arguments for the callback.'); assert(is_array($elements['#lazy_builder'][1]), 'The #lazy_builder argument for callback must have an array as a value.'); assert(count($elements['#lazy_builder'][1]) === count(array_filter($elements['#lazy_builder'][1], function ($v) { return is_null($v) || is_scalar($v); })), "A #lazy_builder callback's context may only contain scalar values or NULL."); assert(!Element::children($elements), sprintf('When a #lazy_builder callback is specified, no children can exist; all children must be generated by the #lazy_builder callback. You specified the following children: %s.', implode(', ', Element::children($elements)))); $supported_keys = [ '#lazy_builder', '#cache', '#create_placeholder', '#lazy_builder_preview', '#preview', // The keys below are not actually supported, but these are added // automatically by the Renderer. Adding them as though they are // supported allows us to avoid throwing an exception 100% of the time. '#weight', '#printed', ]; assert(empty(array_diff(array_keys($elements), $supported_keys)), sprintf('When a #lazy_builder callback is specified, no properties can exist; all properties must be generated by the #lazy_builder callback. You specified the following properties: %s.', implode(', ', array_diff(array_keys($elements), $supported_keys)))); } // Determine whether to do auto-placeholdering. if ($this->placeholderGenerator->canCreatePlaceholder($elements) && $this->placeholderGenerator->shouldAutomaticallyPlaceholder($elements)) { $elements['#create_placeholder'] = TRUE; }
There's no way to do the bubbling of contexts and update the cache redirect if there isn't any containing cacheable parent. I bet that that is the reason?
So AFAICT you're right, and this is a bug. At minimum this is poor DX.
- something can only be cached individually if and only if it has
- π§πͺBelgium kristiaanvandeneynde Antwerp, Belgium
Yeah, in a nutshell: Late placeholdering (i.e. lazy builder added a highly variable cache context) happens in the cache, but if the thing that specified the lazy builder has no cache keys, then the late placeholdering can never take place because it never gets to the cache.
- π§πͺBelgium kristiaanvandeneynde Antwerp, Belgium
Gave this some more thought and I think it might actually kill page cacheability.
- We specify no 'user' cache context so we do not immediately placeholder.
- We call the lazy builder and now the render array has the user cache context.
- We have no cache keys, so we do not go to the render cache and therefore do not late placeholder.
- Now our render array is merged into its parent array, along with the user cache context.
- When we finally get to an ancestor that does have cache keys, we probably do not have a lazy_builder there so we cache by user
- DPC gets this content with the user cache context and cannot cache the page
For a minute I thought that blocks might save us because they do have lazy_builder callbacks, but then I remembered that the main content and title block do not.
Perhaps a test is needed here to confirm my findings?