#4919·Twig

Add `block_data()` and `block_export()` functions

Author: seb-jeanCreated Sep 6, 2026Updated Sep 6, 2026

Description

Currently, Twig's block() function renders a block and returns the result as an HTML string. There is no way to extract structured data from a block.

This becomes a limitation when using Twig Components (<twig:> syntax), where the spread syntax ({{ ...hash }}) requires an associative array, not an HTML string.

Use case: Symfony Form themes with Twig Components

In Symfony's form themes, blocks like widget_attributes and attributes render HTML attribute strings:

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

A Twig Component form theme needs the same data as a hash for spread syntax:

twig
<twig:Input type="text" {{ ...block_data('widget_attributes') }} />

Proposal

Add two new Twig functions:

block_export(data)

Called inside a block to export structured data via a side-channel (stored on CoreExtension). The block continues to render HTML normally.

twig
{% block attributes %}
    {%- set attrs = {} -%}
    {%- for attrname, attrvalue in attr -%}
        {%- if 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 %}

block_data(name)

Renders the block (HTML output is discarded) and returns the data exported via block_export(). Returns [] if the block does not call block_export().

twig
{{ block_data('attributes').id }}
{# returns the structured hash instead of the HTML string #}

Implementation

  • block_data() uses a parser_callable (like block()) to compile to a BlockDataExpression node
  • BlockDataExpression compiles to $this->unwrap()->renderBlockData(...)
  • Template::renderBlockData() calls renderBlock() then returns the exported data from CoreExtension::getExportedBlockData()
  • The exported data is consumed on read (reset to null), preventing leaks between calls

Why not a different approach?

  • Returning data from blocks directly: Twig blocks compile to PHP methods with array $context passed by value (variables set inside don't propagate back)
  • New PHP Twig functions (e.g. in Symfony): would duplicate the logic already in the Twig blocks (translations, boolean normalization), and wouldn't benefit from block inheritance/override
  • block_data()/block_export(): keeps the logic in one place (the block), works with block inheritance, and is fully backward-compatible (existing blocks continue to render HTML as before)

Related: symfony/symfony#65713