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

  1. Check all caching is turned on
  2. 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.
  3. Add that block to layout of a page in layout builder
  4. Visit the page as a logged out user. Block is not output (correct behavior)
  5. 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.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

bkosborne created an issue. See original summary.

bkosborne’s picture

Title: Layout builder does not bubble up cache metadata for empty blocks » Layout builder does not correctly bubble up cache metadata for empty blocks
bkosborne’s picture

Issue summary: View changes
bkosborne’s picture

Status: Active » Needs work
Issue tags: +Needs tests
FileSize
902 bytes

This fixes it. I guess this needs tests too so leaving at needs work.

bkosborne’s picture

Issue summary: View changes
bkosborne’s picture

Issue summary: View changes
Sam152’s picture

Status: Needs work » Needs review

I 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.

Status: Needs review » Needs work

The last submitted patch, 4: 3088077-layout-builder-block-cache.patch, failed testing. View results

Sam152’s picture

Uploading the green patch, will try find some time to write a test.

Sam152’s picture

Status: Needs work » Needs review
Sam152’s picture

There was an existing test case that was really easy to extend.

The last submitted patch, 11: 3088077-11-TEST-ONLY.patch, failed testing. View results

bkosborne’s picture

Ah 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

tim.plunkett’s picture

+++ b/core/modules/layout_builder/src/EventSubscriber/BlockComponentRenderArray.php
@@ -106,6 +107,7 @@ public function onBuildRender(SectionComponentBuildRenderArrayEvent $event) {
       if ($is_content_empty && !$is_placeholder_ready) {
+        $event->addCacheableDependency(CacheableMetadata::createFromRenderArray($content));
         return;
       }

Why only do this when the block is empty? Why not move it out of the if() and right after $content is defined?

bkosborne’s picture

I 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.

bkosborne’s picture

This bubbles the metadata from block render array even if block has render elements. Test is identical to #11

tim.plunkett’s picture

I think we need one test case that captures the difference between #16 and #11, and then this is good to go.

Status: Needs review » Needs work

The last submitted patch, 16: 3088077-16-TEST-ONLY.patch, failed testing. View results

Sam152’s picture

Status: Needs work » Needs review
FileSize
1.22 KB
3.35 KB

The 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.

rensingh99’s picture

Assigned: Unassigned » rensingh99
rensingh99’s picture

Assigned: rensingh99 » Unassigned
Status: Needs review » Reviewed & tested by the community
FileSize
26.05 KB
25.78 KB
21.2 KB

Hi

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

Sam152’s picture

Status: Reviewed & tested by the community » Needs review

I think given @tim.plunkett requested specific changes to this patch, he should RTBC.

tim.plunkett’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: +Blocks-Layouts

Ooof, I missed the unRTBC. This is fine, thanks all!

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed 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.

  • alexpott committed 8e37bfb on 9.0.x
    Issue #3088077 by Sam152, bkosborne, rensingh99, tim.plunkett: Layout...

  • alexpott committed d88b83d on 8.9.x
    Issue #3088077 by Sam152, bkosborne, rensingh99, tim.plunkett: Layout...

  • alexpott committed 99ffe9a on 8.8.x
    Issue #3088077 by Sam152, bkosborne, rensingh99, tim.plunkett: Layout...
Wim Leers’s picture

Issue tags: +D8 cacheability

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

bkosborne’s picture