Twig: Can a child template override a block from a

2019-06-05 00:07发布

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?

2条回答
男人必须洒脱
2楼-- · 2019-06-05 00:50

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

查看更多
Ridiculous、
3楼-- · 2019-06-05 00:51

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 the twig 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 a string to the function, the function loadTemplate will be called in the class Twig_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

protected function loadTemplate($template, $templateName = null, $line = null, $index = null) {
    try {
        if (is_array($template)) return $this->env->resolveTemplate($template);
        if ($template instanceof self) return $template;
        if ($template instanceof Twig_TemplateWrapper) return $template;

        return $this->env->loadTemplate($template, $index);
    } catch (Twig_Error $e) {
        if (!$e->getSourceContext()) $e->setSourceContext($templateName ? new Twig_Source('', $templateName) : $this->getSourceContext());
        if ($e->getTemplateLine()) throw $e;

        if (!$line) {
            $e->guess();
        } else {
            $e->setTemplateLine($line);
        }
        throw $e;
    }
}

Environment.php

public function loadTemplate($name, $index = null) {
    $cls = $mainCls = $this->getTemplateClass($name);
    if (null !== $index) {
        $cls .= '_'.$index;
    }
    if (isset($this->loadedTemplates[$cls])) {
        return $this->loadedTemplates[$cls];
    }
    if (!class_exists($cls, false)) {
        $key = $this->cache->generateKey($name, $mainCls);
        if (!$this->isAutoReload() || $this->isTemplateFresh($name, $this->cache->getTimestamp($key))) {
            $this->cache->load($key);
        }
        if (!class_exists($cls, false)) {
            $source = $this->getLoader()->getSourceContext($name);
            $content = $this->compileSource($source);
            $this->cache->write($key, $content);
            $this->cache->load($key);
            if (!class_exists($mainCls, false)) {
                /* Last line of defense if either $this->bcWriteCacheFile was used,
                 * $this->cache is implemented as a no-op or we have a race condition
                 * where the cache was cleared between the above calls to write to and load from
                 * the cache.
                 */
                eval('?>'.$content);
            }
            if (!class_exists($cls, false)) {
                throw new Twig_Error_Runtime(sprintf('Failed to load Twig template "%s", index "%s": cache is corrupted.', $name, $index), -1, $source);
            }
        }
    }

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

<!DOCTYPE html>
<head>
    <title>Twig test</title>
</head>
<body>
    {% block content_main %}
        {% include 'block.twig' %}
    {% endblock %}
</body>
</html>

test_child.twig

{% extends "test_main.twig" %}
{% block content_main %}
      {% include "test_content.twig" %}
{% endblock %}

test_content.twig

<div class="main">
    Lorem Lipsum
</div>
查看更多
登录 后发表回答