Problem/Motivation

There is a huge issue in Drupal about evaluating a render array as true or false from a Twig template: #953034: [meta] Themes improperly check renderable arrays when determining visibility
Very annoying but, after 13 years, still no fix.

UI Patterns users are sometimes confronted to this issue: #3381590: Drupal entities containing optional or multiple fields do not render patterns correctly
It looks very hard to fix at a Drupal level, but we are lucky it may be doable at UI Patterns level.

Steps to reproduce

Let's do some tests on Drupal 10.1.1 with this slot:

{% if description %}<p class="fr-tile__desc">{{ description }}</p>{% endif %}

From https://git.drupalcode.org/project/ui_suite_dsfr/-/blob/1.0.x/templates/...

We expect description to be false.

Test 1: empty array

$variables['description'] = [];

OK: description is false, because in PHP an empty array is false

Test 2: non renderable properties

$variables['description'] = [
  "#cache" => [
    "contexts" => [
      "user.permissions"
    ],
    "tags" => [],
    "max-age" => -1
  ]
]

KO: description is true

Test 3: non renderable properties wrapped in a sequence

$variables['description'] = [
 [
  "#cache" => [
    "contexts" => [
      "user.permissions"
    ],
    "tags" => [],
    "max-age" => -1
  ]
]
]

KO: description is true

Test 4: non renderable properties wrapped in a mapping

Layout Builder is sometimes wrapping the render array in a mapping where the key is the block UUID:

$variables['description'] = [
"cf124739-3274-41cc-829d-c3465ee4eaa5" => [
  "#cache" => [
    "contexts" => [
      "user.permissions"
    ],
    "tags" => [],
    "max-age" => -1
  ]
]
]

KO: description is true.

Test 5: empty mapping

$variables['description'] = [
    "whatever" => []
];

KO: description is true.

Proposed resolution

So, it may be fixable at UI Patterns level because:

  • we know which Twig variables are expecting a render array : fields ("slots") with an array
  • we know where to put the bubbled properties: the component render array

So, in Pattern::processFields() for each component slot:

  1. if the array is not a render array, do a recursive loop on items
  2. if the array is a render array where all the children are empty AND where all properties are non-printable or empty:
    • move the non-printable properties (#cache, #attached... what else?) to the component render element if they are bubbable
    • remove the non-printable properties (#weight... what else?) if they are not bubbable
  3. unset the keys of empty children & empty properties

Other tasks

Once fixed, share the solution with #953034: [meta] Themes improperly check renderable arrays when determining visibility

Later, we will need to port this solution to UI Patterns 2. Hopefully, SDC allows ComponentElement to be overridden.

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

pdureau created an issue. See original summary.

pdureau’s picture

Issue summary: View changes
tim-diels’s picture

StatusFileSize
new1.01 KB

Not sure if this is the right approach, but we fixed it in our setup with the attached patch.
We would love to discuss this if there are other ways to fix this.

pdureau’s picture

Hi Tim,

Your proposal is interesting. It works only for layouts but maybe it is enough and we don't need a more generic solution. For exeample, this issue is about layouts: #3381590: Drupal entities containing optional or multiple fields do not render patterns correctly

However, you are dealing only with empty #cache and #weight values:

['#cache' => [], '#weight' => NULL];

We need to deal also with non empty #cache values, and bubble them.keys

fboulanger’s picture

Not sure of the issue, but have you try simply using the |render filter, like this :

{% if description|render %}<p class="fr-tile__desc">{{ description }}</p>{% endif %}

ravi kant’s picture

{% if description.0 is not empty %}<p class="fr-tile__desc">{{ description }}</p>{% endif %}

ravi kant’s picture

{% if description|render|trim %}<p class="fr-tile__desc">{{ description }}</p>{% endif %}

tim-diels’s picture

Hey Pierre,

I did not have the full context there on what could all be possible, so just made something so our team had something working.
Happy to discuss this approach and see if we get there with this approach or if it does something different.

I understand what you say with none empty #cache and maybe non empty #weight values.
You have an idea on how to bubble the cache keys?

@fb-multimedia and @ravi kant Those are not solutions as you can read in the original D.O. core issue.

fboulanger’s picture

@Tim-Diels
You're right, sorry.

pdureau’s picture

Title: Empty field values when not renderable » [2.1.x] Empty field values when not renderable
Version: 8.x-1.x-dev » 2.0.x-dev
Status: Active » Postponed

Let's move this to 2.x now the development rbanch is open and active.

However, not in the scope of 2.0.0, so let's target 2.1.x

pdureau’s picture

If the issue is found only in Layout Builder, we can do the fix in:

pdureau’s picture

We also have this weird behaviour with empty markup:

  $variables['test'] =  [
    "#markup" => "",
  ];

{{ test ? "true" : "false" }}

Result:
true

Maybe we have to create an issue for Drupal Core.

smustgrave’s picture

Following

pdureau’s picture

Title: [2.1.x] Empty field values when not renderable » Empty field values when not renderable
Version: 2.0.x-dev » 8.x-1.x-dev
Status: Postponed » Active

Target: 1.9

For 2.x, it will be done at SDC level #3425818: SDC: Make empty render arrays evaluate to false in component templates (hopefully)

pdureau’s picture

Issue summary: View changes
pdureau’s picture

Title: Empty field values when not renderable » [2.0.0-beta5] Empty field values when not renderable

Let's do it in 2.x finally

pdureau’s picture

Version: 8.x-1.x-dev » 2.0.x-dev
christian.wiedemann’s picture

Assigned: Unassigned » christian.wiedemann
christian.wiedemann’s picture

christian.wiedemann’s picture

This is actually a SDC Issue. We are doing an ugly workaround here.

The main problem is still there is no real good method to check if the array is empty. The way described in the issue only works in a list of cases but there will be cases where which it doesn't work.

Maybe we switch the scope of this issue and only checking slots as fields in layout builder and with field formatter and nothing more. Because this array guessing if it is maybe empty works only sometimes.

pdureau’s picture

Yes, it is a SDC issue, and Core team has the same worries as you: #3425818: SDC: Make empty render arrays evaluate to false in component templates

But it is also doable at a UIP2 level IMHO, and we can propose to move the fix to Core later

christian.wiedemann’s picture

Status: Active » Needs review

I implemented a empty check for render arrays for 1 slots.

christian.wiedemann’s picture

pdureau’s picture

pdureau’s picture

Assigned: pdureau » christian.wiedemann
Status: Needs review » Needs work

I have added a few coding related comments, but my main concern is the logic being located in ComponentElementBuilder instead of ComponentElementAlter.

Because those 2 methods are only called from ComponentElementAlter for now, and we want to keep the calls here so all renderables will benefit from the logic instead of only the renderables built from ComponentElementBuilder .

Am I missing something?

christian.wiedemann’s picture

Status: Needs work » Needs review

I moved the stuff to ComponentElementAlter and made them protected. My initial idea was that maybe other consumers using this method before they provide the slot. For example if layout builder surround the slots with an container. But it is better to make it first protected and if there is a need we introduce it as an API.

christian.wiedemann’s picture

pdureau’s picture

That looks great. I will add a few comments for the future us, and I will merge.

pdureau’s picture

Assigned: pdureau » christian.wiedemann
Status: Needs review » Needs work

I am doing a big rebase because #3449653: [2.0.0-beta5] Replace component() function by native include() function was merged before.

With this rebase, I may lose this MR comment from @duaelfr:

suggestion (perfs): merge metadata without using the renderer service by directly calling BubbleableMetadata::createFromRenderArray, BubbleableMetadata::merge and BubbleableMetadata::applyTo methods

Christian, have you seen this comment?

christian.wiedemann’s picture

I checked BubbleableMetadata right now. There is only BubbleableMetadata::createFromRenderArray static. All other methods are not static. Do I miss somtehing?

duaelfr made their first commit to this issue’s fork.

duaelfr’s picture

@christian.wiedemann Sorry my comment was unclear.
The idea was to replicate the feature of \Drupal\Core\Render\Renderer::mergeBubbleableMetadata() without calling the renderer service, which is not needed here.
I added a commit to the MR to illustrate.

pdureau’s picture

Assigned: christian.wiedemann » Unassigned
Status: Needs work » Fixed
pdureau’s picture

Status: Fixed » Closed (fixed)
nicocin’s picture

StatusFileSize
new929 bytes

#3 rerolled for UI patterns v1 & D11.

christian.wiedemann’s picture