Problem/Motivation
When layout builder constructs the render array for a block component, it calls the build()
method on the block plugin instance and checks if it's empty (using Element::isEmpty()
). If it is empty, then it returns early without returning any render data to the block component. This results in the loss of any render data that was returned from the block's build method.
Note that there are two ways a block plugin can expose render data. Since block plugins implement CacheableDependencyInterface
, they can declare their cache metadata via dedicated methods on the class. Layout builder correctly handles this metadata, even if the block is empty.
The other way cache metadata can be set on the returned render array from the block's build method. This is what layout builder is ignoring.
Steps to Reproduce
- Check all caching is turned on
- Create tiny module that has a block plugin that varies based on the existence of a query param. If the param is not present, the block should output nothing but the cache metadata. See below for code.
- Add that block to layout of a page in layout builder
- Visit the page as a logged out user. Block is not output (correct behavior)
- Now visit the page and include the query string parameter that should force the block to output text. Observe that the block is still not output
Note that this affects the render cache as well as page/dynamic cache. Render cache is involved here because the rendered output of entity view displays is cached.
The code for the simple block:
namespace Drupal\simple_block\Plugin\Block;
use Drupal\Core\Block\BlockBase;
<?php
/**
* @Block(
* id = "simple_block",
* admin_label = @Translation("Simple Block"),
* category = @Translation("Lists")
* )
*/
class SimpleBlock extends BlockBase {
public function build() {
$build = [
'#cache' => [
'contexts' => ['url.query_args'],
],
];
if (\Drupal::request()->query->has('test')) {
$build['#markup'] = 'Test param found!';
}
return $build;
}
}
This is a simple example, but node that ViewsBlock plugin behaves the same way. It outputs cache metadata in the render array.
I originally encountered this when I discovered that when Search API was indexing my nodes using the "rendered entity" field indexer, it was corrupting the render cache. Search API indexer ran on cron where there was no URL for my block to pull from, so the block returned empty render data. Layout builder didn't use the cache tags, so the cache was corrupted. Now when a visitor visits that page, the bad cache data is used and the block is empty.
Comment | File | Size | Author |
---|---|---|---|
#19 | 3088077-19.patch | 3.35 KB | Sam152 |
#19 | interdiff.txt | 1.22 KB | Sam152 |
#16 | 3088077-16-TEST-ONLY.patch | 1.17 KB | bkosborne |
#16 | 3088077-16.patch | 2.49 KB | bkosborne |
#11 | 3088077-11.patch | 2.3 KB | Sam152 |
Comments
Comment #2
bkosborneComment #3
bkosborneComment #4
bkosborneThis fixes it. I guess this needs tests too so leaving at needs work.
Comment #5
bkosborneComment #6
bkosborneComment #7
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedI reported the same thing in #3088198: Layout builder discards cacheability metadata from blocks when they are rendered without content, but the patch included a "use" for CacheableMetadata. NR to see if that's needed here.
Comment #9
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedUploading the green patch, will try find some time to write a test.
Comment #10
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedComment #11
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedThere was an existing test case that was really easy to extend.
Comment #13
bkosborneAh yea, missed the use statement. This looks good to me, but since it's essentially the same patch as mine I don't think I can make this RTBC
Comment #14
tim.plunkettWhy only do this when the block is empty? Why not move it out of the if() and right after $content is defined?
Comment #15
bkosborneI guess we could, but in that case the cache metadata will bubble up anyway from the rendering of the block content. But, I see that higher up in this code we're already adding the cache metadata from the block to the event via it's cacheable interface, so I agree it makes sense to just do it always. The cache metadata will be duplicated once the block is rendered but that shouldn't matter.
Comment #16
bkosborneThis bubbles the metadata from block render array even if block has render elements. Test is identical to #11
Comment #17
tim.plunkettI think we need one test case that captures the difference between #16 and #11, and then this is good to go.
Comment #19
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedThe following addition to the non-empty test case demonstrates the difference between the two approaches, the metadata is repeated on the event-build and within the 'content' component.
Comment #20
rensingh99 CreditAttribution: rensingh99 at Red Crackle commentedComment #21
rensingh99 CreditAttribution: rensingh99 at Red Crackle commentedHi
I have reviewed the patch #19 and it worked as designed.
Below is my review:
I have created custom content type and enabled layout builder for it and then i have created custom block with the same code as given above and added this block using layout builder for this content type.
When i visited the page with query string parameter then block didn't show until I do clear cache.
After applying the patch it's showing without clearing cache.
Below is screenshot output with and without the query string
Screenshot 1 :
Screenshot 2 :
Thanks,
Ren
Comment #22
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedI think given @tim.plunkett requested specific changes to this patch, he should RTBC.
Comment #23
tim.plunkettOoof, I missed the unRTBC. This is fine, thanks all!
Comment #24
alexpottCommitted and pushed 8e37bfb03f to 9.0.x and d88b83dfc8 to 8.9.x and 99ffe9a084 to 8.8.x. Thanks!
Backported to 8.8.x as a low risk but helpful major bugfix.
Comment #28
Wim LeersComment #30
bkosborneThis introduced an issue with placeholdering. Created a follow up here: #3244755: Not possible to use render placeholdering with Layout Builder blocks