I have a largish base.twig file, and I want to break it up into three includes: header.twig, content.twig, and footer.twig. I'm having trouble getting the block from my child template to override the block included into my parent template and would like to know if it's even possible, and if not, what a Twig-ish solution might look like.
I've setup a simple example to illustrate the question. I'm retrieving a Wordpress page and using Timber to process the Twig templates. The PHP template that gets invoked is page-test.php:
<?
$context = Timber::get_context();
Timber::render('test_child.twig', $context);
?>
The Twig template that gets rendered is test_child.twig:
{% extends 'test_base.twig' %}
{% block content_main %}
<h1>Random HTML</h1>
{% endblock content_main %}
The parent template, test_base.twig is:
<!DOCTYPE html>
<head>
<title>Twig test</title>
</head>
<body>
{% include 'test_content.twig' %}
</body>
</html>
And finally, the included template, test_content.twig, is like this:
<div class="main">
{% block content_main %}
{% endblock content_main %}
</div>
The resulting output looks like this:
<!DOCTYPE html>
<head>
<title>Twig test</title>
</head>
<body>
<div class="main">
</div>
</body>
</html>
As you can see, the <div>
has no content. What I expected was for it to contain the <h1>Random HTML</h1>
fragment from test_child.twig.
Why isn't the block from test_child.twig overriding the same-named block included from test_content.twig into test_base.twig? And if the approach simply won't work, what's the best Twig-ish way of accomplishing something close?
Unfortunately this does not work with include.
I had this issue as well when I was trying to pass some SEO values from my product controller to the base template that included the meta tags.
You have to use "extends" for the inner template as well, and point your controller to use the inner template instead of the middle/layout one.
You can then define a separate block on your inner template, which can directly override the base template's block.
You can see a working example in this Fiddle (Note that the inner template is the main one) https://twigfiddle.com/1ve5kt
This is indeed not possible with
twig
, due to the fact included files have no affinity with the templates who called them. To explain myself have a look at this snippet{{ include('foo.twig') }}
This snippet will be parsed into
PHP
by thetwig
compiler and the code it compiles into is this$this->loadTemplate("foo.twig", "main.twig", 6)->display($context);
Now we can investigate this further with looking at the source of
Twig_Template::loadTemplate
. If you have a look at that particular function we will see, that because u are passing astring
to the function, the functionloadTemplate
will be called in the classTwig_Environment
In this last function we can cleary see that the
Twig_Environment::loadTemplate
function is not passing any information nor instance of the template you rendered towards the template you are including. The only thing that gets passed (by value
) is the variable$context
, which hold all variables you've sent from your controllller to the template you are rendering.I'm guessing one of the main reasons this is coded as such is because included files should be reusable in any situation and should not have dependencies like a (non-existant) block to make them being rendered
TwigTemplate.php
Environment.php
As I'm not sure why you have this set-up,this would be a more
twig
-style setup. Please note you have to define the blocks in your base class first, because "defining" block in an extended class, tries to display the blocks of your parent and does not create a new one.test_main.twig
test_child.twig
test_content.twig