Problem/Motivation
Cache tags are brilliant when they work well, and can be obtuse and difficult to use when they don't.
In the case of preprocessing nodes, it's quite difficult to find a way to make sure that node cache tags bubble correctly. Consider this use case (seemingly pretty common):
- "Parent" nodes have one "Child" node associated with it.
- The Parent node should render the Parent node, then render the Child node in 'Parent teaser' view mode.
- Themer decides to use a preprocess function to preprocess the Parent node, then get the render array using something like
$view_builder->viewMultiple($child_node, 'parent_teaser'), then the Twig template prints the teaser. - Content admins complain that when they save updates to the "Child" node, the "Parent" doesn't reflect changes until the entire site's cache is cleared.
Apparently Views' viewRenderable() method sets all the cache contexts/tags correctly—we need something similar for getting the render arrays for nodes in general...
Proposed resolution
Either document how to add the appropriate cache contexts/tags when rendering a node via preprocess functions, or add functionality to make this easier to do.
Remaining tasks
TBD
User interface changes
N/A
API changes
TBD
Data model changes
N/A
Comments
Comment #2
wim leersRendering things correctly in preprocess is always difficult. It's why it's actively discouraged to do very complex things in preprocess functions.
Anything we would do "to make this easier to do" would just be fighting symptoms. The root cause is using the preprocess layer for things it's not designed to do.
Comment #3
geerlingguy commentedBe that as it may, it seems the majority of FE developers tend to stick to preprocess functions and templates. What alternative route would you recommend? I haven't seen any simpler way of rendering one node inside another yet in D8 :-/
Comment #4
sarahjean commentedYou don't have to be doing preprocessing, you can see this issue if you simply use twig and don't print {{content}}, like the example here: https://www.previousnext.com.au/blog/ensuring-drupal-8-block-cache-tags-...
Comment #5
geerlingguy commentedComment #6
geerlingguy commentedRelated? #2660002: Allow explicit bubbling of cacheability metadata inside Twig template (when accessing data from instead of rendering render arrays)
Comment #7
berdirIn which direction is the reference, child to parent?
can you share the full relevant snippets of how you get the child, prepare it and then display it in twig? There are complicated cases, but an actually viewed entity should just work as far as I see, also in preprocess.
I would recommend doing things in hook_node_view() and to use extra fields, so you can control if things are done/processed per view mode, but preprocess should still work.
Comment #8
geerlingguy commented@Berdir - here's a high-level overview of what we're doing, in code:
Then, inside
node--product.html.twig, we have:Not exactly the code we're using, but you get the gist.
Comment #9
berdirAnd what kind of changes are not being updated? Anything?
The only thing I can think of is adding new children, that makes sense, you need to add the node_list or a more specific cache tag, or invalidate the correspending parent cache tag when saving a children, but that's by design and not related to preprocess.
Rendering those things in the template should result in adding the necessary cache tags, I don't see why that wouldn't work.
Comment #10
geerlingguy commentedThinking about it with my backend dev hat on, this does make sense. But it's (a) not obvious why it doesn't work out of the box (like it did in Drupal < 8, when all the page cache was cleared when you saved a new node...), and (b) difficult to find an example of how to clear the parent when the child is saved/updated.
I mean, in one sense this is just a documentation issue. But it's not the first time I've run into a theme-related cache tag issue where google searches are fruitless and the examples in the Cache/Render API documentation are not specific enough to quickly find a solution. I can only imagine someone who's more of a front end developer/site builder and doesn't dig deeper into the question will end up disabling the dynamic page cache entirely because of these kinds of issues...
For the site in question, we'll likely find and clear the parent node's cache tag manually, but it took too much time for us to go from 'why isn't this working like we thought it would?' to 'we may have a working solution'. I want to reduce that time for others in our situation in the future!
Comment #11
geerlingguy commentedIn the interest of assisting others who will likely hit this issue in the future, here's how we fixed it on our site (so we can use the method in #2848158-8: Bubbling cache tag metadata when rendering nodes in preprocess functions is difficult to print a teaser display of a child content type in a parent content type page that the child references:
This seems to work okay in our instance. Any way to make it more efficient? It would be nice if there were a way in core to just say "invalidate cache tags for this node" and pass it an ID...
Comment #13
mxh commentedThis is a more common problem, isn't it? I had to embed a block inside another block and did it via preprocessing, the embedded block had further cache contexts defined. The result was that the renderer created the generated cache entry for the "parent" block both with and without the additional cache context by the "child" block. Even setting explicitly $variables['#cache'] didn't solve it.
It's pretty frustrating that a themer can't rely on the caching system the way it's described. There's always a caveat somewhere which costs time to work around it, and can only be solved outside of a themer's scope.
Comment #14
wim leers@mxh
This sounds like that first cache entry is a "redirecting" cache entry. Does it start with something like
a:2:{s:15:"#cache_redirect";[…]?Comment #15
mxh commentedYes, it stores it as a cache redirect, and thus leads to the wrong cache hit for the parent block on pages where an other cache context should be present.
Comment #16
mxh commentedThe root of problem in #13 was a custom cache context implementation, which always calculated the same value as the key (example: [customcontext:abc]=abc). It obviously caused wrong cache redirections. Thanks @Wim Leers for pointing me to the right track to identify this.
Comment #23
cilefen commentedI am triaging old support requests. This one hasn't had a comment in four years. It it still a "thing"?
Comment #24
cilefen commentedComment #25
joachim commentedFor future reference: in a preprocess hook, you can do: