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:
- if the array is not a render array, do a recursive loop on items
- 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
- move the non-printable properties (
- 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.
| Comment | File | Size | Author |
|---|---|---|---|
| #37 | ui_patterns-3383544-37.diff | 929 bytes | nicocin |
| #3 | ui_patterns-3383544-3.diff | 1.01 KB | tim-diels |
Issue fork ui_patterns-3383544
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
Comment #2
pdureau commentedComment #3
tim-dielsNot 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.
Comment #4
pdureau commentedHi 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
Comment #5
fboulangerNot 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 %}Comment #6
ravi kant commented{% if description.0 is not empty %}<p class="fr-tile__desc">{{ description }}</p>{% endif %}Comment #7
ravi kant commented{% if description|render|trim %}<p class="fr-tile__desc">{{ description }}</p>{% endif %}Comment #8
tim-dielsHey 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.
Comment #9
fboulanger@Tim-Diels
You're right, sorry.
Comment #10
pdureau commentedLet'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
Comment #11
pdureau commentedIf the issue is found only in Layout Builder, we can do the fix in:
Comment #12
pdureau commentedWe also have this weird behaviour with empty markup:
{{ test ? "true" : "false" }}Result:
trueMaybe we have to create an issue for Drupal Core.
Comment #13
smustgrave commentedFollowing
Comment #14
pdureau commentedTarget: 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)
Comment #15
pdureau commentedComment #16
pdureau commentedLet's do it in 2.x finally
Comment #17
pdureau commentedComment #18
christian.wiedemann commentedComment #19
christian.wiedemann commentedComment #20
christian.wiedemann commentedThis 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.
Comment #21
pdureau commentedYes, 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
Comment #23
christian.wiedemann commentedI implemented a empty check for render arrays for 1 slots.
Comment #24
christian.wiedemann commentedComment #25
pdureau commentedComment #26
pdureau commentedI have added a few coding related comments, but my main concern is the logic being located in
ComponentElementBuilderinstead ofComponentElementAlter.Because those 2 methods are only called from
ComponentElementAlterfor now, and we want to keep the calls here so all renderables will benefit from the logic instead of only the renderables built fromComponentElementBuilder.Am I missing something?
Comment #27
christian.wiedemann commentedI 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.
Comment #28
christian.wiedemann commentedComment #29
pdureau commentedThat looks great. I will add a few comments for the future us, and I will merge.
Comment #30
pdureau commentedI 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:
Christian, have you seen this comment?
Comment #31
christian.wiedemann commentedI checked BubbleableMetadata right now. There is only BubbleableMetadata::createFromRenderArray static. All other methods are not static. Do I miss somtehing?
Comment #33
duaelfr@christian.wiedemann Sorry my comment was unclear.
The idea was to replicate the feature of
\Drupal\Core\Render\Renderer::mergeBubbleableMetadata()without calling therendererservice, which is not needed here.I added a commit to the MR to illustrate.
Comment #35
pdureau commentedComment #36
pdureau commentedComment #37
nicocin commented#3 rerolled for UI patterns v1 & D11.
Comment #38
christian.wiedemann commented