Twig: Overriding block in included/embedded files

Created on 13 Mar 2014  路  13Comments  路  Source: twigphp/Twig

Including template should give the parent template ability to override blocks. Here comes the embedd tag. But when parent template is further included/embedded we have no ability to override those block anymore.

My problem:

{# _index_row.html.twig #}
<tr>
    <td>{% block actions %}{% endblock %}</td>
<tr>
{# _index.html.twig#}
<table>
    {% for entity in entities %}
        {% include _index_row | default('_index_row.html.twig') %}
    {% endfor %}
</table>
{# index.html.twig #}
{% extends layout | default( 'some_layout.html.twig') %}

{% block content %}
    <div>
        {% include _index | default('_index.html.twig') %}
        {% block foobar %}{% endblock %}
    </div>
{% endblock %}

Those templates come in reusable bundle, and they are cut to pieces so i can use table or table row to present data in another context or by using ajax.

When i need to override some parts of this structure i can simply use:

{# new_index.html.twig #}
{# here i can override included files like set _include 'overriding file' #}
{% extends 'index.html.twig' %}

{# here i can override block of parent template but not its included children #}
{% block actions %}
    add some content
{% endblock %}

The file "new_index.html.twig" doesn't have any ability to override blocks of included and/or embedded files down the template tree.

Shouldn't include tag work as 'copy and paste' for part of code? Or maybe embedd tag should open the block for overriding?

Most helpful comment

The second include can be substituted / replaced by the content of the included file internally.

So if I have one file:
some html content {% include2 'other-twig-file.html.twig' %} some other html content

And 'other-twig-file.html.twig' with content:
content of other file {% someblock %}block content{% endblock %}

Then twig should act as if the content of the first file was:
some html content content of other file {% someblock %}block content{% endblock %} some other html content

This would be some kind of a preprocessor-include.

No, {% use %} doesn't help here. I'm not talking about importing block definitions, but about separating the base layout into multiple files, because it can get very big and confusing for complex layouts.

All 13 comments

including a template will render the template and include the output. This is needed because the included template has its own template inheritance hierarchy. Overwriting blocks when including is precisely the goal of the {% embed %} tag: it creates a template extending the external template, and include this embedded template instead of including the external template directly.

Turning include into something including the structure of the template would be a huge nightmare, as you would need to merge 2 inheritance trees

I don't think {% embed %} covers this use-case though. You can only override the embedded blocks within the embed tag.

My use case:

  • layout1.twig and layout2.twig both contain {% include 'head.twig' %}
  • head.twig has in it {% block head %}{% endblock %}
  • When I extend either layout1.twig or layout2.twig I want to be able to override that block

Embedding head.twig instead of including it doesn't solve this problem because I can still only override the blocks from within the embed tag which would be in either layout1 or 2.

We need a construct that essentially copy-pastes the included file.

I found a workaround here but it forces you to use this big with chunk whenever you want to include the head _and_ you lose the ability to extend a block with parent().

@mnbayazit but how would this work when the "included" file uses inheritance too ?

@stof You mean if the included file includes other files?

{# foo.twig #}
<p>fooText</p>
{% block fooBlock %}fooBlock{% endblock %}
{# bar.twig #}
<p>barText</p>
{% block barBlock %}barBlock{% endblock %}
{% include 'foo.twig' %}
{% block fooBlock %}fooOverride{% endblock %}
{# baz.twig #}
<p>bazText</p>
{% include 'bar.twig' %}
{% block barBlock %}barFill{% endblock %}
{% block fooBlock %}{{ parent() }}Extend{% endblock %}

baz.twig compiles to:

<p>bazText</p>
<p>barText</p>
barFill
<p>fooText</p>
fooOverrideExtend

As long as you don't create a circular reference, I think it should be fine. Circular references would be easy to detect though: when compiling baz.twig, push the filename into a set. If it's included again down the chain, error.

Diamond inheritance is a minor problem; perhaps you could make any ambiguous blocks refer to the most recently included file, or just error.

No I think @stof speaks about something like:

{# parent.twig #}
{% block hello %}world{% endblock %}

{# brother.twig #}
{% block hello %}universe{% endblock %}

{# me.twig #}
{% extends 'parent.twig' %}
{% include 'brother.twig' %}

{{ block('hello') }} {# world or universe? #}

{% block hello %}
  {{ parent() }} {# world or universe? #}
{% endblock }

@ninsuo
That's similar to diamond problem I was describing.

In your example, I'd render "universe" for both because brother.twig was included last. If you want to use the block from parent.twig instead, you can do {% set copy = block('hello') %} before including it. Either that, or just error out.

{# parent.twig #}
{% block hello %}world{% endblock %}

{# brother.twig #}
{% block hello %}universe{% endblock %}

{# me.twig #}
{% extends 'parent.twig' %}
{% include 'brother.twig' %}

{{ block('hello') }} {# world or universe? #}

{% block hello %}
  {{ parent() }} {# world or universe? #}
{% endblock }

For me this should end up as:

{# me.twig #}
{% extends 'parent.twig' %}
{% block hello %}universe{% endblock %} {# because included #}

{{ block('hello') }}  {# universe because above block #}

{% block hello %} {# second hello block, error? #}
  {{ parent() }} {# world becase parent #}
{% endblock }

This is how we worked it out:

{# _index_row.html.twig #}
<tr>
    <td>{% block actions %}{% endblock %}</td>
<tr>

{# _index.html.twig#}
<table>
    {% for entity in entities %}
        {% include _index_row | default('_index_row.html.twig') %}
    {% endfor %}
</table>

{# index.html.twig #}
{% extends layout | default( 'some_layout.html.twig') %}

{% block content %}
    <div>
        {% render listAction with template =  _index | default('_index.html.twig') %}
        {% block foobar %}{% endblock %}
    </div>
{% endblock %}

so if we need to override actions block we can use:

{# _other_index.html.twig #}
{% set _index_row = '_other_index_row.html.twig' %}
{% extends '_index.html.twig' %}

{# _other_index_row.html.twig #}
{% extends '_index_row.html.twig' %}

{% block actions %}{% endblock %}

It's possible by using bundle/extension that allows us to override templates in route definitions. We end up with more files but they're very short and clean.

Why don't we create a twig preprocessor that literally includes the source instead of including the template as something that is called? Would that not solve this problem automatically?

PS:

Hi from DrupalCon

I think there should be two different keywords for

  1. render another template and include the output (this is what include currently does)
  2. include the source of another twig file

The second case is often needed when the base layout is split into multiple fragments, e.g. separate twig files for header, sidebar, footer and body.

if you want to import block definitions from another template, there is {% use %} for that.

Importing arbitrary source would not be possible, as Twig would not know what it should do with them (where should it display it ?)

The second include can be substituted / replaced by the content of the included file internally.

So if I have one file:
some html content {% include2 'other-twig-file.html.twig' %} some other html content

And 'other-twig-file.html.twig' with content:
content of other file {% someblock %}block content{% endblock %}

Then twig should act as if the content of the first file was:
some html content content of other file {% someblock %}block content{% endblock %} some other html content

This would be some kind of a preprocessor-include.

No, {% use %} doesn't help here. I'm not talking about importing block definitions, but about separating the base layout into multiple files, because it can get very big and confusing for complex layouts.

Hey im quite new to using twig, working with it for a few weeks now and i ran stuck as well as i tried to split my base layout in multiple fragments. As described above:

The second case is often needed when the base layout is split into multiple fragments, e.g. separate twig files for header, sidebar, footer and body.

For this same simple reason:

separating the base layout into multiple files, because it can get very big and confusing for complex layouts.

Since i am new to twig, i am a bit clueless how to achieve this now...

@hecht-software importing some source code when find the tag is quite hard to do, because it requires changing the source and restarting the parsing (as tokens have to change due to that, and the list of blocks as well, etc...). Twig does not have a pre-processor.

If you want to build a pre-processor, you could write a custom loader (wrapping existing loaders), which would preprocess the source code before giving it to the Twig engine (be careful with the handling of cache keys then, if multiple sources are involved in the building of a single template. And give up about line numbers in error messages for templates using the preprocessor, as they would report line numbers in the output of the preprocessing). However, such kind of preprocessor was already requested in the past (I don't remember what was the exact use case), and this was rejected from the core. People wanting to write such loader can do it outside the core (and assume the drawbacks of preprocessing)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Bilge picture Bilge  路  3Comments

reatang picture reatang  路  4Comments

garak picture garak  路  5Comments

unique1984 picture unique1984  路  4Comments

dvladimirov77 picture dvladimirov77  路  5Comments