Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[TwigBridge] Support Twig Component form themes via block_data()/block_export() #65713

Description

@seb-jean

Description

I'm working on integrating a form theme for a custom kit based on Symfony UX Toolkit. This form theme uses Twig Components (<twig:> syntax) instead of plain HTML tags.

The problem

In the default form theme (form_div_layout.html.twig), blocks like widget_attributes and attributes render HTML attribute strings:

<input type="text" {{ block('widget_attributes') }} />
{# outputs: id="name" name="form[name]" required="required" #}

The <twig:> spread syntax ({{ ...hash }}) works with Twig Components but requires an associative array, not an HTML string. Today, there is no built-in way to get the processed attributes (with translations applied and booleans normalized) as a hash.

Solution

Instead of adding new PHP functions that would duplicate the logic already in the Twig blocks (translations, boolean normalization), the approach leverages a new Twig core feature: block_data() and block_export() (see twigphp/Twig#4919, PR twigphp/Twig#4920).

How it works:

  1. The attributes block in form_div_layout.html.twig is modified to call block_export(attrs) after building the normalized attributes hash — exporting the structured data via a side-channel while still rendering the HTML string as before.

  2. A Twig Component form theme can then use block_data('widget_attributes') to get the attributes as a hash:

{%- block form_widget_simple -%}
    <twig:Input type="{{ type }}" {{ ...block_data('widget_attributes')|merge({value: value|default}) }} />
{%- endblock form_widget_simple -%}

Changes to form_div_layout.html.twig:

The widget_attributes, widget_container_attributes, and button_attributes blocks set attr with proper ordering (standard attributes first, user attributes merged after):

{%- block widget_attributes -%}
    {%- set attr = {id: id, name: full_name, disabled: disabled, required: required}|merge(attr) -%}
    {{- block('attributes') -}}
{%- endblock widget_attributes -%}

{%- block widget_container_attributes -%}
    {%- if id is not empty -%}
        {%- set attr = {id: id}|merge(attr) -%}
    {%- endif -%}
    {{- block('attributes') -}}
{%- endblock widget_container_attributes -%}

{%- block button_attributes -%}
    {%- set attr = {id: id, name: full_name, disabled: disabled}|merge(attr) -%}
    {{- block('attributes') -}}
{%- endblock button_attributes -%}

The attributes block now builds and exports a hash, then renders it as HTML:

{% block attributes -%}
    {%- set attrs = {} -%}
    {%- for attrname, attrvalue in attr -%}
        {%- if attrname in ['placeholder', 'title'] -%}
            {%- set attrs = attrs|merge({(attrname): translation_domain is same as(false) or attrvalue is null ? attrvalue : attrvalue|trans(attr_translation_parameters, translation_domain)}) -%}
        {%- elseif attrvalue is same as(true) -%}
            {%- set attrs = attrs|merge({(attrname): attrname}) -%}
        {%- elseif attrvalue is not same as(false) -%}
            {%- set attrs = attrs|merge({(attrname): attrvalue}) -%}
        {%- endif -%}
    {%- endfor -%}
    {%- do block_export(attrs) -%}
    {%- for attrname, attrvalue in attrs -%}
        {{- ' ' ~ attrname }}="{{ attrvalue }}"
    {%- endfor -%}
{%- endblock attributes -%}

Key benefits:

  • No logic duplication: The attribute processing logic stays in the Twig blocks, benefiting from block inheritance and overrides
  • Fully backward-compatible: Existing form themes continue to work unchanged — block('widget_attributes') still renders HTML
  • Clean integration: Twig Component form themes use block_data() for spread syntax, getting a proper hash

Example Twig Component form theme:

{%- block form_widget_simple -%}
    <twig:Input type="{{ type }}" {{ ...block_data('widget_attributes')|merge({value: value|default}) }} />
{%- endblock form_widget_simple -%}

{%- block checkbox_widget -%}
    <twig:Checkbox :checked="checked" {{ ...block_data('widget_attributes')|merge({value: value|default}) }} />
{%- endblock checkbox_widget -%}

{%- block button_widget -%}
    <twig:Button type="{{ type|default('button') }}" {{ ...block_data('button_attributes') }}>
        {{- block(outerBlocks.form_label_content) -}}
    </twig:Button>
{%- endblock button_widget -%}

{%- block form_row_render -%}
    {%- set attr = row_attr -%}
    <twig:Fieldset:Field {{ ...block_data('attributes') }}>
        {{- form_label(form) -}}
        {{- form_help(form) -}}
        {{- form_widget(form, widget_attr) -}}
        {{- form_errors(form) -}}
    </twig:Fieldset:Field>
{%- endblock form_row_render -%}

Dependencies

This depends on twigphp/Twig#4919 (PR twigphp/Twig#4920) which adds block_data() and block_export() to Twig core.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions