块法
该方法由Sebastiaan Stok在 GitHub 上提出。
这个想法使用了块函数。它写入给定的块内容,并且可以多次调用。
包装文件:
{# src/Fuz/LayoutBundle/Resources/views/Default/wrappers.html.twig #}
{% block box_head %}
<div class="box indent">
<div class="padding">
{% enblock %}
{% block box_foot %}
</div>
</div>
{% enblock %}
特色页面:
{{ block('box_head') }}
Some content
{{ block('box_foot') }}
带有宏的 wrap 扩展
这个想法是由Charles在 GitHub 上提出的。
macro.html.twig
首先,您在文件中声明一个宏。
{% macro box(content) %}
<div class="box indent">
<div class="padding">
{{ content | raw }}
</div>
</div>
{% endmacro %}
然后,Amd 而不是调用{{ macros.box('my content') }}
(参见文档,您开发了一个{% wrap %}
处理宏调用的标记,其中的[% wrap %}
和{% endwrap %}
作为参数。
这个扩展很容易开发。我认为访问宏可能很难,但实际上,它们作为对象存储在上下文中,并且可以轻松编译调用。
只是一些变化:我们将使用以下语法:
{# to access a macro from an object #}
{% wrap macro_object macro_name %}
my content here
{% endwrap %}
{# to access a macro declared in the same file #}
{% wrap macro_name %}
macro
{% endwrap %}
在下面的代码中,如果你想让它工作,不要忘记更改命名空间!
首先,在你的 services.yml 中添加扩展:
parameters:
fuz_tools.twig.wrap_extension.class: Fuz\ToolsBundle\Twig\Extension\WrapExtension
services:
fuz_tools.twig.wrap_extension:
class: '%fuz_tools.twig.wrap_extension.class%'
tags:
- { name: twig.extension }
在你的包中,创建一个 Twig 目录。
添加扩展,它将返回新的TokenParser
(英文:它将声明新标签)。
Twig/Extension/WrapExtension.php:
<?php
// src/Fuz/ToolsBundle/Twig/Extension/WrapExtension.php
namespace Fuz\ToolsBundle\Twig\Extension;
use Fuz\ToolsBundle\Twig\TokenParser\WrapHeaderTokenParser;
use Fuz\ToolsBundle\Twig\TokenParser\WrapFooterTokenParser;
use Fuz\ToolsBundle\Twig\TokenParser\WrapTokenParser;
class WrapExtension extends \Twig_Extension
{
public function getTokenParsers()
{
return array (
new WrapTokenParser(),
);
}
public function getName()
{
return 'wrap';
}
}
{% wrap %}
然后添加TokenParser本身,当解析器找到标签时会遇到。这TokenParser
将检查标签是否被正确调用(对于我们的示例,它有 2 个参数),存储这些参数并获取{% wrap %}
和 {% endwrap %}` 之间的内容。
Twig/TokenParser/WrapTokenParser.php:
<?php
// src/Fuz/ToolsBundle/Twig/TokenParser/WrapTokenParser.php
namespace Fuz\ToolsBundle\Twig\TokenParser;
use Fuz\ToolsBundle\Twig\Node\WrapNode;
class WrapTokenParser extends \Twig_TokenParser
{
public function parse(\Twig_Token $token)
{
$lineno = $token->getLine();
$stream = $this->parser->getStream();
$object = null;
$name = $stream->expect(\Twig_Token::NAME_TYPE)->getValue();
if ($stream->test(\Twig_Token::BLOCK_END_TYPE))
{
if (!$this->parser->hasMacro($name))
{
throw new \Twig_Error_Syntax("The macro '$name' does not exist", $lineno);
}
}
else
{
$object = $name;
$name = $stream->expect(\Twig_Token::NAME_TYPE)->getValue();
}
$this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE);
$body = $this->parser->subparse(array ($this, 'decideWrapEnd'), true);
$this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE);
return new WrapNode($object, $name, $body, $token->getLine(), $this->getTag());
}
public function decideWrapEnd(\Twig_Token $token)
{
return $token->test('endwrap');
}
public function getTag()
{
return 'wrap';
}
}
接下来,我们需要一个编译器(树枝方言中的节点),它将生成与我们的{% wrap %}
标签关联的 PHP 代码。
这个标签是 的别名{{ macro_object.box(content) }}
,所以我在模板中写了这行代码,并在生成的 php 文件(存储在您的app/cache/dev/twig
目录中)中观察了生成的代码。我有 :
echo $this->getAttribute($this->getContext($context, "(macro object name)"), "(name)", array("(body)"), "method");
所以我的编译器变成了:
树枝/节点/WrapNode.php:
<?php
// src/Fuz/ToolsBundle/Twig/Node/WrapNode.php
namespace Fuz\ToolsBundle\Twig\Node;
class WrapNode extends \Twig_Node
{
public function __construct($object, $name, $body, $lineno = 0, $tag = null)
{
parent::__construct(array ('body' => $body), array ('object' => $object, 'name' => $name), $lineno, $tag);
}
public function compile(\Twig_Compiler $compiler)
{
$compiler
->addDebugInfo($this)
->write('ob_start();');
$compiler
->addDebugInfo($this)
->subcompile($this->getNode('body'));
if (is_null($this->getAttribute('object')))
{
$compiler
->write(sprintf('echo $this->get%s(ob_get_clean());', $this->getAttribute('name')) . "\n");
}
else
{
$compiler
->write('echo $this->getAttribute($this->getContext($context, ')
->repr($this->getAttribute('object'))
->raw('), ')
->repr($this->getAttribute('name'))
->raw(', array(ob_get_clean()), "method");')
->raw("\n");
}
}
}
注意:要了解子解析/子编译的工作原理,我阅读了spaceless
扩展源代码。
就这样!我们得到一个别名,让我们可以使用具有大主体的宏。尝试一下:
宏.html.twig:
{% macro box(content) %}
<div class="box indent">
<div class="padding">
{{ content | raw }} {# Don't forget the raw filter! #}
</div>
</div>
{% endmacro %}
一些layout.html.twig:
{% import "FuzLayoutBundle:Default:macros.html.twig" as macros %}
{% wrap macros box %}
test
{% endwrap %}
{% macro test(content) %}
some {{ content | raw }} in the same file
{% endmacro %}
{% wrap test %}
macro
{% endwrap %}
输出:
<div class="box indent">
<div class="padding">
test
</div>
</div>
some macro in the same file
wrapperheader、wrapperfooter、wrapper extension
这种方法是我在我的问题中告诉你的方法。如果你想用令牌解析器训练自己,你可以阅读/实现它,但从功能上讲,这不如以前的方法好。
在一个wrapper.html.twig
文件中,您声明所有包装器:
{% wrapperheader box %}
<div class="box">
{% endwrapper %}
{% wrapperfooter box %}
</div>
{% endwrapperfooter %}
在您的功能树枝文件中,您使用您的包装器:
{% wrapper box %}
This is my content
{% endwrapper %}
以下扩展有 3 个问题:
无法在 Twig 环境中存储数据(例如上下文变量)。因此,当您定义 a 时{% wrapperheader NAME %}
,您基本上没有干净的方法来检查是否NAME
已经定义了标头(在此扩展中,我使用静态属性)。
当你include
是一个 twig 文件时,它是在运行时解析的,而不是立即解析(我的意思是,包含的 twig 模板是在执行生成的文件时解析的,而不是在include
解析标签时解析)。{% wrapper NAME %}
因此,当您解析标签时,不可能知道先前包含的文件中是否存在包装器。如果您的包装器不存在,则此扩展程序仅显示介于两者之间的内容{% wrapper %}
,而{% endwrapper %}
没有任何通知。
这个扩展的想法是:当解析器遇到一个wrapperheader
和wrapperfooter
标记时,编译器将标记的内容存储在某个地方以供以后与wrapper
标记一起使用。{% include %}
但是树枝上下文是作为副本传递的,而不是通过引用传递的。因此,不可能将{% wrapperheader %}
and{% wrapperfooter %}
信息存储在该上下文中,以供上层使用(在包含文件的文件中)。我也需要使用全局上下文。
这是代码,请注意更改名称空间。
首先,我们需要创建一个扩展,它将向 Twig 添加新的令牌解析器。
在 bundle 的 services.yml 中,添加以下行来激活扩展:
parameters:
fuz_tools.twig.wrapper_extension.class: Fuz\ToolsBundle\Twig\Extension\WrapperExtension
services:
fuz_tools.twig.wrapper_extension:
class: '%fuz_tools.twig.wrapper_extension.class%'
tags:
- { name: twig.extension }
在你的包中,创建一个 Twig 目录。
创建以下 Twig\Extension\WrapperExtension.php 文件:
<?php
// src/Fuz/ToolsBundle/Twig/Extension/WrapperExtension.php
namespace Fuz\ToolsBundle\Twig\Extension;
use Fuz\ToolsBundle\Twig\TokenParser\WrapperHeaderTokenParser;
use Fuz\ToolsBundle\Twig\TokenParser\WrapperFooterTokenParser;
use Fuz\ToolsBundle\Twig\TokenParser\WrapperTokenParser;
class WrapperExtension extends \Twig_Extension
{
public function getTokenParsers()
{
return array(
new WrapperHeaderTokenParser(),
new WrapperFooterTokenParser(),
new WrapperTokenParser(),
);
}
public function getName()
{
return 'wrapper';
}
}
现在我们需要添加标记解析器:我们的语法与and{% wrapper NAME %} ... {% endwrapper %}
相同。因此,这些令牌解析器用于声明标签,检索包装器,并检索正文(和 endwrapper` 之间的内容)。wrapperheader
wrapperfooter
NAME
wrapper
包装器的令牌解析器:Twig\TokenParser\WrapperTokenParser.php:
<?php
// src/Fuz/ToolsBundle/Twig/TokenParser/WrapperTokenParser.php
namespace Fuz\ToolsBundle\Twig\TokenParser;
use Fuz\ToolsBundle\Twig\Node\WrapperNode;
class WrapperTokenParser extends \Twig_TokenParser
{
public function parse(\Twig_Token $token)
{
$stream = $this->parser->getStream();
$name = $stream->expect(\Twig_Token::NAME_TYPE)->getValue();
$this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE);
$body = $this->parser->subparse(array($this, 'decideWrapperEnd'), true);
$this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE);
return new WrapperNode($name, $body, $token->getLine(), $this->getTag());
}
public function decideWrapperEnd(\Twig_Token $token)
{
return $token->test('endwrapper');
}
public function getTag()
{
return 'wrapper';
}
}
wrapperheader 的令牌解析器:Twig\TokenParser\WrapperHeaderTokenParser.php:
<?php
// src/Fuz/ToolsBundle/Twig/TokenParser/WrapperHeaderTokenParser.php
namespace Fuz\ToolsBundle\Twig\TokenParser;
use Fuz\ToolsBundle\Twig\Node\WrapperHeaderNode;
class WrapperHeaderTokenParser extends \Twig_TokenParser
{
static public $wrappers = array ();
public function parse(\Twig_Token $token)
{
$lineno = $token->getLine();
$stream = $this->parser->getStream();
$name = $stream->expect(\Twig_Token::NAME_TYPE)->getValue();
if (in_array($name, self::$wrappers))
{
throw new \Twig_Error_Syntax("The wrapper '$name''s header has already been defined.", $lineno);
}
self::$wrappers[] = $name;
$this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE);
$body = $this->parser->subparse(array($this, 'decideWrapperHeaderEnd'), true);
$this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE);
return new WrapperHeaderNode($name, $body, $token->getLine(), $this->getTag());
}
public function decideWrapperHeaderEnd(\Twig_Token $token)
{
return $token->test('endwrapperheader');
}
public function getTag()
{
return 'wrapperheader';
}
}
wrapperfooter 的令牌解析器:Twig\TokenParser\WrapperFooterTokenParser.php:
<?php
// src/Fuz/ToolsBundle/Twig/TokenParser/WrapperFooterTokenParser.php
namespace Fuz\ToolsBundle\Twig\TokenParser;
use Fuz\ToolsBundle\Twig\Node\WrapperFooterNode;
class WrapperFooterTokenParser extends \Twig_TokenParser
{
static public $wrappers = array ();
public function parse(\Twig_Token $token)
{
$lineno = $token->getLine();
$stream = $this->parser->getStream();
$name = $stream->expect(\Twig_Token::NAME_TYPE)->getValue();
if (in_array($name, self::$wrappers))
{
throw new \Twig_Error_Syntax("The wrapper '$name''s footer has already been defined.", $lineno);
}
self::$wrappers[] = $name;
$this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE);
$body = $this->parser->subparse(array($this, 'decideWrapperFooterEnd'), true);
$this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE);
return new WrapperFooterNode($name, $body, $token->getLine(), $this->getTag());
}
public function decideWrapperFooterEnd(\Twig_Token $token)
{
return $token->test('endwrapperfooter');
}
public function getTag()
{
return 'wrapperfooter';
}
}
令牌解析器检索所有需要的信息,我们现在需要将这些信息编译成 PHP。此 PHP 代码将由 Twig_Template 实现中的 twig 引擎生成(您可以在缓存目录中找到生成的类)。它在方法中生成代码,并且包含文件的上下文不可用(因为上下文数组不是通过引用给出的)。这样,如果没有全局上下文,就不可能访问包含文件中的内容。这就是为什么在这里,我使用静态属性......这根本不好,但我不知道如何避免它们(如果你有想法,请告诉我!:))。
包装标签的编译器:Twig\Nodes\WrapperNode.php
<?php
// src/Fuz/ToolsBundle/Twig/Node/WrapperNode.php
namespace Fuz\ToolsBundle\Twig\Node;
class WrapperNode extends \Twig_Node
{
public function __construct($name, $body, $lineno = 0, $tag = null)
{
parent::__construct(array ('body' => $body), array ('name' => $name), $lineno, $tag);
}
public function compile(\Twig_Compiler $compiler)
{
$compiler
->addDebugInfo($this)
->write('if (isset(\\')
->raw(__NAMESPACE__)
->raw('\WrapperHeaderNode::$headers[')
->repr($this->getAttribute('name'))
->raw('])) {')
->raw("\n")
->indent()
->write('echo \\')
->raw(__NAMESPACE__)
->raw('\WrapperHeaderNode::$headers[')
->repr($this->getAttribute('name'))
->raw('];')
->raw("\n")
->outdent()
->write('}')
->raw("\n");
$compiler
->addDebugInfo($this)
->subcompile($this->getNode('body'));
$compiler
->addDebugInfo($this)
->write('if (isset(\\')
->raw(__NAMESPACE__)
->raw('\WrapperFooterNode::$footers[')
->repr($this->getAttribute('name'))
->raw('])) {')
->raw("\n")
->indent()
->write('echo \\')
->raw(__NAMESPACE__)
->raw('\WrapperFooterNode::$footers[')
->repr($this->getAttribute('name'))
->raw('];')
->raw("\n")
->outdent()
->write('}')
->raw("\n");
}
}
wrapperheader 标签的编译器:Twig\Nodes\WrapperHeaderNode.php
<?php
// src/Fuz/ToolsBundle/Twig/Node/WrapperHeaderNode.php
namespace Fuz\ToolsBundle\Twig\Node;
/**
* @author alain tiemblo
*/
class WrapperHeaderNode extends \Twig_Node
{
static public $headers = array();
public function __construct($name, $body, $lineno = 0, $tag = null)
{
parent::__construct(array ('body' => $body), array ('name' => $name), $lineno, $tag);
}
public function compile(\Twig_Compiler $compiler)
{
$compiler
->write("ob_start();")
->raw("\n")
->subcompile($this->getNode('body'))
->write(__CLASS__)
->raw('::$headers[')
->repr($this->getAttribute('name'))
->raw('] = ob_get_clean();')
->raw("\n");
}
}
wrapperfooter 标签的编译器:Twig\Nodes\WrapperFooterNode.php
<?php
// src/Fuz/ToolsBundle/Twig/Node/WrapperFooterNode.php
namespace Fuz\ToolsBundle\Twig\Node;
class WrapperFooterNode extends \Twig_Node
{
static public $footers = array();
public function __construct($name, $body, $lineno = 0, $tag = null)
{
parent::__construct(array ('body' => $body), array ('name' => $name), $lineno, $tag);
}
public function compile(\Twig_Compiler $compiler)
{
$compiler
->write("ob_start();")
->raw("\n")
->subcompile($this->getNode('body'))
->write(__CLASS__)
->raw('::$footers[')
->repr($this->getAttribute('name'))
->raw('] = ob_get_clean();')
->raw("\n");
}
}
现在执行正常。让我们试试吧!
创建一个名为 wrappers.html.twig 的视图:
{# src/Fuz/LayoutBundle/Resources/views/Default/wrappers.html.twig #}
{% wrapperheader demo %}
HEAD
{% endwrapperheader %}
{% wrapperfooter demo %}
FOOT
{% endwrapperfooter %}
创建一个名为 what you want.html.twig 的视图:
{# src/Fuz/HomeBundle/Resources/views/Default/index.html.twig #}
{% include 'FuzLayoutBundle:Default:wrappers.html.twig' %}
{% wrapper demo %}
O YEAH
{% endwrapper %}
这显示:
头哦是的脚