Problem/Motivation

The attribute of a single directory component is shared and not reset between instances, at least on the same page.
This leads to unexpected behaviors, if the developer alter them in the twig file.

Steps to reproduce

Set a basic sdc.

'$schema': 'https://git.drupalcode.org/project/drupal/-/raw/10.1.x/core/modules/sdc/src/metadata.schema.json'
name: test
status: stable
props:
  type: object
  properties:
    my_classes:
      type: array
      items:
        type: string
<div {{ attributes.addClass(my_classes) }}>
  {# attributes defined by sdc are problematic #}
  {{ dd(attributes.getClass) }}

  {# I won't add attributes created manually in the results below, they work as expected #}
  {% set my_attribute = create_attribute() %}
  {% do my_attribute.addClass(my_classes) %}
  {{ dd(my_attribute.getClass) }}

  {% block example_block %}
    The contents of the example block
  {% endblock %}
</div>

Include or embed that sdc into a page, multiple time.
E.g. inside node.hmtl.twig put:

{{ include('olivero:test', { my_classes: ['class-1', 'class-2']}) }}

{{ include('olivero:test', { my_classes: ['class-3', 'class-4']}) }}

{{ include('olivero:test') }}

Expected (?) result of dd(attributes.getClass)

 Drupal\Core\Template\AttributeArray {#2103 ▼
  #value: array:3 [▼
    0 => "contextual-region"
    1 => "class-1"
    2 => "class-2"
  ]
  #name: "class"
}

 Drupal\Core\Template\AttributeArray {#2142 ▼
  #value: array:5 [▼
    0 => "contextual-region"
    3 => "class-3"
    4 => "class-4"
  ]
  #name: "class"
}

 Drupal\Core\Template\AttributeArray {#2083 ▼
  #value: array:1 [▼
    0 => "contextual-region"
  ]
  #name: "class"
}

Actual result of dd(attributes.getClass)

 Drupal\Core\Template\AttributeArray {#2103 ▼
  #value: array:3 [▼
    0 => "contextual-region"
    1 => "class-1"
    2 => "class-2"
  ]
  #name: "class"

 Drupal\Core\Template\AttributeArray {#2142 ▼
  #value: array:5 [▼
    0 => "contextual-region"
    1 => "class-1"
    2 => "class-2"
    3 => "class-3"
    4 => "class-4"
  ]
  #name: "class"
}

 Drupal\Core\Template\AttributeArray {#1641 ▼
  #value: array:5 [▼
    0 => "contextual-region"
    1 => "class-1"
    2 => "class-2"
    3 => "class-3"
    4 => "class-4"
  ]
  #name: "class"
}

Proposed resolution

The altering a sdc' attribute isn't documented (yet) anywhere, but I took for granted, as I was expecting a full separation of context from them.
I'd suggest they should have a "clean state" between instances.

Comments

Giuseppe87 created an issue. See original summary.

giuseppe87’s picture

Issue summary: View changes
giuseppe87’s picture

Issue summary: View changes
larowlan’s picture

Status: Active » Postponed (maintainer needs more info)
Issue tags: +Bug Smash Initiative

Does this still happen if you use the only keyword in your include statements https://twig.symfony.com/doc/3.x/tags/include.html

giuseppe87’s picture

Either {% include 'template.html' with {'foo': 'bar'} only %} or {{ include('template.html', {foo: 'bar'}, with_context = false) }} help to mitigate the issue.

Namely:

{{ include('olivero:test', { my_classes: ['class-1', 'class-2']}, with_context = false) }}

{{ include('olivero:test') }}

{{ include('olivero:test', with_context = false) }}

The second and third include won't have the classes of the first.

{{ include('olivero:test', { my_classes: ['class-1', 'class-2']}) }}

{{ include('olivero:test') }}

{{ include('olivero:test', with_context = false) }}

The second include will have the class of the first, the third won't have them.

I'm saying mitigate because:

  • the context is passed by default, so it this sdc's attribute behavior is considered a good pattern and not a bug, if should at be at least documented
  • relying on disabling the context force to pass props naturally available. For example, I discovered this behavior while placing multiple instance of the same sdc inside user.html.twig, where I am using the user variable, already present inside the context
giuseppe87’s picture

Status: Postponed (maintainer needs more info) » Active
larowlan’s picture

I think what is needed here is a preprocess hook for all SDC components that creates a new Attributes variable.

That's what most theme hooks have to prevent the leakage by default.

giuseppe87’s picture

Using a bit more sdc, I've found another leakage.
I'm also putting into this issue because the cause and solution may be the same.
It's a very weird and corner case I got using the set command.

You need two templates: the first, outer_sdc must use a set, e.g.:

{% set my_content %}
  <div class="my-content">
    {% block my_block %}
       My test sdc
    {% endblock %}
  </div>
{% endset %}


<div {{ attributes }}>
  {% if 1 > 0 %}
    <div>
      {{ my_content }}
    </div>
  {% else %}
    <a href='...'>
     {{ my_content }}
    </a>
  {% endif %}
</div>

This sdc must be embeded in another sdc/theme twig. The block section must include another sdc:

{% embed 'my_theme:outer_sdc' %}
  {% block my_block %}
    {{ include('my_theme:base_sdc') }}
  {% endblock %}
{% endembed %}

base_sdc can be a skeleton:

<div {{ attributes }}>
  {% block example_block %}
    The contents of the example block
  {% endblock %}
</div>

The expected output should be:

<!-- 🥘 Component start: my_theme:outer_sdc -->
<div data-component-id="my_theme:outer_sdc">
    <div class="my-content">
        <!-- 🥓 Component start: my_theme:base_sdc -->
        <div data-component-id="my_theme:base">
            The contents of the example block
        </div>
        <!-- 🥓 Component end: my_theme:base -->
    </div>
</div>
<!-- 🥘 Component end: my_theme:outer_sdc -->

But actually is

<!-- 🥘 Component start: my_theme:outer_sdc -->
<div data-component-id="my_theme:base">
    <div>
        <div class="my-content">
            <!-- 🥓 Component start: my_theme:base -->
            <div data-component-id="my_theme:base">
                The contents of the example block
            </div>
            <!-- 🥓 Component end: my_theme:base -->
        </div>
    </div>
</div>
<!-- 🥘 Component end: my_theme:outer_sdc -->

Namely, the <div data-component-id="my_theme:outer_sdc"> has been printed as <div data-component-id="my_theme:base">

By the way, this problem doesn't depend on having print my_content twice conditionally.
For example if outer_sdc is

{% set my_content %}
  {% block my_block %}
    LOREM IPSUM
  {% endblock %}
{% endset %}


<div {{ attributes }}>
  <div class="my-content">
    {{ my_content }}
  </div>
</div>

The html render suffers of the same error, so I suppose the problem is something about the set command including a block statement, etc...

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.