Twig:子模板可以覆盖父模板中包含的文件中的块吗? [英] Twig: Can a child template override a block from a file included in the parent template?

查看:25
本文介绍了Twig:子模板可以覆盖父模板中包含的文件中的块吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个较大的 base.twig 文件,我想把它分成三个文件:header.twig、content.twig 和 footer.twig.我无法从我的子模板获取块以覆盖包含在我的父模板中的块,我想知道它是否可能,如果不可能,Twig-ish 解决方案可能是什么样子.

我已经设置了一个简单的例子来说明这个问题.我正在检索 Wordpress 页面并使用 Timber 处理 Twig 模板.被调用的 PHP 模板是 page-test.php:

<代码>

被渲染的 Twig 模板是 test_child.twig:

{% 扩展 'test_base.twig' %}{% 块 content_main %}<h1>随机 HTML</h1>{% endblock content_main %}

父模板 test_base.twig 是:

<头><title>Twig test</title><身体>{% 包含 'test_content.twig' %}</html>

最后,包含的模板 test_content.twig 是这样的:

{% 块 content_main %}{% endblock content_main %}

结果输出如下所示:

<头><title>Twig test</title><身体><div class="main">

</html>

如您所见,

没有内容.我期望它包含来自 test_child.twig 的

Random HTML

片段.

为什么 test_child.twig 中的块没有将 test_content.twig 中包含的同名块覆盖到 test_base.twig 中?如果这种方法根本行不通,那么完成一些接近的事情的最佳 Twig-ish 方式是什么?

解决方案

这对于 twig 确实是不可能的,因为包含的文件与调用它们的模板没有关联.为了解释自己,看看这个片段

{{ include('foo.twig') }}

这段代码会被twig编译器解析成PHP,编译成的代码是这样的

$this->loadTemplate("foo.twig", "main.twig", 6)->display($context);

现在我们可以通过查看Twig_Template::loadTemplate 的源代码来进一步调查这个问题.如果您查看该特定函数,我们将看到,因为您将 string 传递给该函数,所以函数 loadTemplate 将在 类中调用Twig_Environment

在最后一个函数中,我们可以清楚地看到 Twig_Environment::loadTemplate 函数没有向您包含的模板传递任何信息或您呈现的模板实例.唯一被传递(by value)的是变量$context,它保存你从控制器发送到你正在渲染的模板的所有变量.>

我猜这样编码的主要原因之一是因为包含的文件应该在任何情况下都可以重用,并且不应该有像(不存在的)块这样的依赖项来渲染它们

<小时>

TwigTemplate.php

受保护的函数 loadTemplate($template, $templateName = null, $line = null, $index = null) {尝试 {if (is_array($template)) 返回 $this->env->resolveTemplate($template);if ($template instanceof self) 返回 $template;if ($template instanceof Twig_TemplateWrapper) 返回 $template;返回 $this->env->loadTemplate($template, $index);} catch (Twig_Error $e) {if (!$e->getSourceContext()) $e->setSourceContext($templateName ? new Twig_Source('', $templateName) : $this->getSourceContext());if ($e->getTemplateLine()) 抛出 $e;如果(!$行){$e->guess();} 别的 {$e->setTemplateLine($line);}扔 $e;}}

<小时>

Environment.php

public function loadTemplate($name, $index = null) {$cls = $mainCls = $this->getTemplateClass($name);如果(空!== $index){$cls .= '_'.$index;}如果 (isset($this->loadedTemplates[$cls])) {返回 $this->loadedTemplates[$cls];}如果 (!class_exists($cls, false)) {$key = $this->cache->generateKey($name, $mainCls);if (!$this->isAutoReload() || $this->isTemplateFresh($name, $this->cache->getTimestamp($key))) {$this->cache->load($key);}如果 (!class_exists($cls, false)) {$source = $this->getLoader()->getSourceContext($name);$content = $this->compileSource($source);$this->cache->write($key, $content);$this->cache->load($key);如果 (!class_exists($mainCls, false)) {/* 最后一道防线,如果使用了 $this->bcWriteCacheFile,* $this->cache 被实现为无操作或者我们有竞争条件* 在上述写入和加载调用之间清除缓存的位置* 缓存.*/eval('?>'.$content);}如果 (!class_exists($cls, false)) {throw new Twig_Error_Runtime(sprintf('无法加载 Twig 模板 "%s",索引 "%s":缓存已损坏.', $name, $index), -1, $source);}}}

<小时>

因为我不知道你为什么有这个设置,这将是一个更 twig 风格的设置.请注意,您必须先在基类中定义块,因为在扩展类中定义"块会尝试显示父类的块,而不会创建新块.

test_main.twig

<头><title>Twig test</title><身体>{% 块 content_main %}{% 包含 'block.twig' %}{% 结束块 %}</html>

test_child.twig

{% 扩展 "test_main.twig" %}{% 块 content_main %}{% 包含 "test_content.twig" %}{% 结束块 %}

test_content.twig

Lorem Lipsum

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?

解决方案

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>

这篇关于Twig:子模板可以覆盖父模板中包含的文件中的块吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆