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:
<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: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.
{% 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().
{{ 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
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_attributesandattributesrender HTML attribute strings:A Twig Component form theme needs the same data as a hash for spread syntax:
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.{% 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 callblock_export().{{ block_data('attributes').id }} {# returns the structured hash instead of the HTML string #}Implementation
block_data()uses aparser_callable(likeblock()) to compile to aBlockDataExpressionnodeBlockDataExpressioncompiles to$this->unwrap()->renderBlockData(...)Template::renderBlockData()callsrenderBlock()then returns the exported data fromCoreExtension::getExportedBlockData()null), preventing leaks between callsWhy not a different approach?
array $contextpassed by value (variables set inside don't propagate back)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