Overriding blocks within included Twig templates

PhpTwig

Php Problem Overview


Is there a generally "good" way to achieve this functionality? I have read about the 'use' tag, which seems like the best option so far, but I still don't like that it wont let me bring in any outside html, only blocks.

I will use the 'include' tag in the example below to demonstrate the intent I'm trying to describe.

#base.html.twig
{% include 'elements/header.html.twig' %}
{% block content %}{% endblock %}
{% include 'elements/footer.html.twig' %}

#header.html.twig
<h1>This is my header</h1>
{% block page_title %} Default Page Title {% endblock %}

#index.html.twig
{% extends 'layouts/base.html.twig' %}
{# I want to be able to do this somehow #}
{% block page_title %} This is my overridden page title {% endblock %}
{% block content %} here is the index page content {% endblock %}

Php Solutions


Solution 1 - Php

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 - Php

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 - Php

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.

Solution 4 - Php

I got it to work with a very simple hack:

Basically it behaves this way because without a {% extends "foo.html.twig" %} it doesn't understand the blocks and simply renders them in place.

So let's extend... nothing:

{% extends "nothing.html.twig" %}

This nothing is really just 1 block:

# nothing.html.twig
{% block main %}
{% endblock %}

The only thing is that you need to wrap everything in a block, this fake "main" block.

Solution 5 - Php

Improvement on @webberig's solution, that fixes blocks that are not overridden in all extending templates (comment by @HerrNentu'):

#base.html.twig
{% if block("page_title") is defined %}
     {% include 'elements/header.html.twig' with {page_title: block('page_title')} %}
{% else %}
     {% include 'elements/header.html.twig' with {page_title: null} %}
{% endif %}
{% 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 %}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionbruchowskiView Question on Stackoverflow
Solution 1 - PhpWebberigView Answer on Stackoverflow
Solution 2 - PhpMarcos RegisView Answer on Stackoverflow
Solution 3 - PhpkacperekView Answer on Stackoverflow
Solution 4 - PhplipsumarView Answer on Stackoverflow
Solution 5 - PhpWiRaView Answer on Stackoverflow