Problem/Motivation

Returning an empty render array from a formatter does not produce the expected Layout Builder placeholder fallback.

Steps to reproduce

  1. Create a formatter that returns an empty render array:
    /**
     * A blank field formatter.
     */
    #[FieldFormatter(
      id: 'blank',
      label: new TranslatableMarkup('Blank'),
      field_types: [
        'string',
      ],
    )]
    final class BlankFormatter extends FormatterBase {
    
      /**
       * {@inheritdoc}
       */
      public function viewElements(FieldItemListInterface $items, $langcode): array {
        return [];
      }
    
    }
    
  2. Create a content type using Layout Builder.
  3. Create a text field, and place it in the default layout using the above formatter.
  4. Observe that an empty block is rendered in preview mode instead of the placeholder fallback.

Proposed resolution

Update the field block plugin provided by the Layout Builder module to check Element::isEmpty() when building the block.

Issue fork drupal-3617719

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

clayfreeman created an issue. See original summary.

clayfreeman’s picture

Assigned: clayfreeman » Unassigned
Status: Active » Needs review

Test should demonstrate the failure scenario.

Ready for review.

scontzen’s picture

Status: Needs review » Reviewed & tested by the community

Thanks @clayfreeman for the clear issue summary and for including a test that demonstrates the failure.

Element::isEmpty() is the same check that BlockComponentRenderArray uses to decide whether a block is empty, so both places now use the same definition of empty. I also checked the cacheability handling: keeping the metadata of the empty $view means the empty result gets invalidated correctly once the field has a value again, and the non-empty and exception paths behave as before.

I ran the new test locally on PHP 8.5: it passes with the fix and fails without it, the same result as the test-only job in the pipeline. I also reproduced the bug manually with the Blank test formatter: without the fix the block renders as an empty area in the preview, with the fix the placeholder appears.

One small thing that can be fixed on commit: the docblock of the test formatter reads "An blank field formatter" instead of "A blank field formatter".

The fix also changes the live page: the block no longer renders as an empty wrapper there. Maybe worth a second assertion in the test?

clayfreeman’s picture

Addressed the typo highlighted in #4, also adjusted the label of the field created during the test ("field block" vs "block field").

clayfreeman’s picture

Issue summary: View changes
clayfreeman’s picture

The fix also changes the live page: the block no longer renders as an empty wrapper there. Maybe worth a second assertion in the test?

I'd argue against it; this is just a consequence of how block plugins work, and it's already being tested elsewhere (\Drupal\Tests\block\Kernel\BlockViewBuilderTest::testEmptyRender()).

scontzen’s picture

Thanks, that makes sense. The section rendering path is covered as well: BlockComponentRenderArrayTest has testOnBuildRenderEmptyBuild() and testOnBuildRenderEmptyBuildWithCacheTags(), both for the non-preview case. They assert that an empty block build results in an empty component build and that the cache metadata is kept. So the live page doesn't need an extra assertion here.

I also compared 7b0dfac8 with the state I reviewed in #4. The changes only touch the test (the docblock typo and the field label), the fix itself is unchanged, and the pipeline is green. The RTBC from #4 still stands.