Overriding blocks within included Twig templates

Solution 1:

I found a solution. Use the block() function to get the child's block content and pass it to header.html.twig as a variable in the include statement:

#base.html.twig
{% include 'elements/header.html.twig' with {page_title: block('page_title')} %}
{% block content %}{% endblock %}
{% include 'elements/footer.html.twig' %}

#header.html.twig
<h1>This is my header</h1>
{% if page_title is empty %}
Default Page Title
{% else %}
{{ page_title }}
{% endif %}

#index.html.twig
{% extends 'layouts/base.html.twig' %}
{% block page_title %} This is my overridden page title {% endblock %}
{% block content %} here is the index page content {% endblock %}

Solution 2:

I found a good and real solution reading the documentation for the embed tag. I'm using Twig 2.0 in Symfony 4.

My structure

#templates/base.html.twig
{% block navbar %}
    <nav>
    {% block menu %}
        {% include '_menu' %}
    {% endblock menu %}
    </nav>
{% endblock navbar %}
{% block content %}
    {# several blocks defined #}
    {% block footer '' %}
{% endblock content %}

#templates/_menu.html.twig
<ul>
    {% block sub_app_menu_itens %}
        <li>Main App Menu Item</li>
    {% endblock sub_app_menu_itens %}
    <li>Menu item</li>
    <li>Another menu item</li>
    <li>Yet another menu item</li>
</ul>

With code above, when I create my index.html.twig the only code needed to show de default things is

#templates/index.html.twig
{% extends 'base.html.twig' %}

and override blocks defined. But, when I need to create another page, that use these skeleton, if I try to override block sub_app_menu_itens in another _partial template included, doesn't work. <li>Main App Menu Item</li> is always showed and never overwritten (code above)

#templates/subapp/index.html.twig
{% extends 'base.html.twig' %}
{% block title %}{{ 'agencia.homepage'|trans }}{% endblock %}
{% block menu %}
    {% include 'subapp/_menu.html.twig' %}
{% endblock menu %}
{% block user_content %}Content Here{% endblock %}

#templates/subapp/_menu.html.twig
{% extends '_menu.html.twig' %}
{% block sub_app_menu_itens %}
        <li>SubApp Menu Item</li> 
{% endblock sub_app_menu_itens %}

<li>SubApp Menu Item</li> is never showed. Tried a lot of things like extends and even conditionals with no luck.

THE Solution

The embed tag solve the child partial templates blocks to be overwritten.

#templates/subapp/index.html.twig
{% block menu %}
    {% embed '_menu.html.twig' %}
        {% block sub_app_menu_itens %}
            {% include 'subapp/_menu.html.twig' %}
        {% endblock sub_app_menu_itens %}
    {% endembed %}
{% endblock menu %}

And simplifing _partial template to have only the html needed

#templates/subapp/_menu.html.twig 
    <li>SubApp Menu Item</li> 

Now <li>SubApp Menu Item</li> will be displayed on the right place of _menu template.

Thinking in levels, a include works like a sublevel (parsed) and all things are only override on the same level, so include doesn't allow to override in another include. Instead, embed templates are bring to the same level (not parsed) and so you can override things.

Solution 3:

It can be done. Here's my solution to the problem by passing a variable to the view.

#layout.twig
{% if sidebar is empty %}
    This is the default sidebar text.
{% else %}
    {% block sidebar %}{% endblock %}
{% endif %}
{% block content %}{% endblock %}

#index.twig
{% extends "layout.twig" %}
{% block sidebar %}
    This is the sidebar. It will override the default text.
{% endblock %}

{% block content %}
    This is the content.
{% endblock %}

#index.php (SlimPHP)
$app->render('index.twig', ['sidebar' => true]);

Since I'm using SlimPHP the way I call it is by passing sidebar variable to the the view. This can be extended further by using different variables passed to the view, so you can selected sidebar1, sidebar2 etc.