Ala*_*blo 4 php tags wrapper symfony twig
我有用CSS代表盒子的div,它们包装了html代码.
<div class="box indent">
<div class="padding">
my code here
</div>
</div>
Run Code Online (Sandbox Code Playgroud)
我创建了一个"layoutbundle",其中每个HTML包装器(如框,选项卡,网格等)都放在单独的twig文件中.通过这种方式,可以使用其他布局实现对其他包的视图.
但我厌倦了包括.每个小的html包装器都需要一个include,我想知道是否有一种更简单的方法来包装HTML代码.
让我们举个简单的例子.实际上,我创建了几个文件:
一个box.html.twig包含框,包含内容的文件:
<div class="box indent">
<div class="padding">
{% include content %}
</div>
</div>
Run Code Online (Sandbox Code Playgroud)
几个箱内容 .html.twig文件,包含我的箱子的内容.
最后,我通过执行以下操作在视图中创建一个框:
{%
include 'AcmeDemoBundle:layout:box.html.twig'
with {
'content': 'ReusableBundle:feature:xxx.html.twig'
}
%}
Run Code Online (Sandbox Code Playgroud)
有没有办法创建包装器,如:
a)我宣布一个新的包装器:
{% wrapperheader "box" %}
<div class="box indent">
<div class="padding">
{% endwrapperheader %}
{% wrapperfooter "box" %}
</div>
</div>
{% endwrapperfooter %}
Run Code Online (Sandbox Code Playgroud)
b)然后在我的页面中,我使用:
{% wrapper "box" %}
{# here my content #}
{% endwrapper %}
Run Code Online (Sandbox Code Playgroud)
我想我需要在Twig中添加新的标签扩展,但首先我想知道是否有类似的东西是原生的.
Ala*_*blo 10
这种方法是由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 %}
Run Code Online (Sandbox Code Playgroud)
专题页面:
{{ block('box_head') }}
Some content
{{ block('box_foot') }}
Run Code Online (Sandbox Code Playgroud)
这个想法是由Charles在GitHub上提出的.
首先,在macro.html.twig文件中声明一个宏.
{% macro box(content) %}
<div class="box indent">
<div class="padding">
{{ content | raw }}
</div>
</div>
{% endmacro %}
Run Code Online (Sandbox Code Playgroud)
然后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 %}
Run Code Online (Sandbox Code Playgroud)
在下面的代码中,如果要使命名空间有效,请不要忘记更改命名空间!
首先,在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 }
Run Code Online (Sandbox Code Playgroud)
在您的包中,创建一个Twig目录.
添加扩展名,它将返回新的TokenParser(英文:它将声明新标记).
小枝/分机/ 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';
}
}
Run Code Online (Sandbox Code Playgroud)
然后添加TokenParser本身,当解析器找到{% wrap %}标签时它将被满足.这TokenParser将检查标签是否被正确调用(对于我们的示例,它有2个参数),存储这些参数并获取{% wrap %}和{%endwrap%} 之间的内容.
小枝/ 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';
}
}
Run Code Online (Sandbox Code Playgroud)
接下来,我们需要一个编译器(twig方言中的Node),它将生成与我们的{% wrap %}标签相关的PHP代码.
这个标签是别名{{ macro_object.box(content) }},所以我在模板中写了这行,并在生成的生成的php文件中查看了生成的代码(存储在你的app/cache/dev/twig目录中).我有 :
echo $this->getAttribute($this->getContext($context, "(macro object name)"), "(name)", array("(body)"), "method");
Run Code Online (Sandbox Code Playgroud)
所以我的编译器成了:
小枝/节点/ 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");
}
}
}
Run Code Online (Sandbox Code Playgroud)
注意:要知道subsparsing/subcompiling的工作原理,我阅读了spaceless扩展源代码.
就这样!我们得到一个别名,让我们使用大体积的宏.尝试一下:
macros.html.twig:
{% macro box(content) %}
<div class="box indent">
<div class="padding">
{{ content | raw }} {# Don't forget the raw filter! #}
</div>
</div>
{% endmacro %}
Run Code Online (Sandbox Code Playgroud)
一些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 %}
Run Code Online (Sandbox Code Playgroud)
输出:
<div class="box indent">
<div class="padding">
test
</div>
</div>
some macro in the same file
Run Code Online (Sandbox Code Playgroud)
这个方法是我在问题中告诉你的方法.如果你想训练自己使用令牌解析器,你可以阅读/实现它,但是功能上,这不如前面的方法好.
在wrapper.html.twig文件中,您声明所有包装器:
{% wrapperheader box %}
<div class="box">
{% endwrapper %}
{% wrapperfooter box %}
</div>
{% endwrapperfooter %}
Run Code Online (Sandbox Code Playgroud)
在您的功能twig文件中,您使用包装器:
{% wrapper box %}
This is my content
{% endwrapper %}
Run Code Online (Sandbox Code Playgroud)
The following extension has 3 issues :
There is no way to store data (such as context variables) in the Twig Environnement. So when you define a {% wrapperheader NAME %}, you have basically no clean way to check if a header for NAME is already defined (in this extension, I use static properties).
When you include a twig file, it is parsed at runtime, not immediately (I mean, the included twig template is parsed while the generated file is executed, and not when the include tag is parsed). So that's not possible to know if a wrapper exists on a previousely included file when you parse the {% wrapper NAME %} tag. If your wrapper does not exist, this extension just displays what's between {% wrapper %} and {% endwrapper %} without any notice.
The idea of this extension is : when the parser meet a wrapperheader and wrapperfooter tag, the compiler store the content of the tag somewhere for a later use with the wrapper tag. But the twig context is passed to {% include %} as a copy, not by reference. So that's not possible to store the {% wrapperheader %} and {% wrapperfooter %} information inside that context, for an usage at upper level (in files that include files). I needed to use a global context too.
Here is the code, take care to change your namespaces.
First we need to create an extension that will add new token parsers to Twig.
Inside services.yml of a bundle, add the following lines to activate the extension :
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 }
Run Code Online (Sandbox Code Playgroud)
Inside your bundle, create a Twig directory.
Create the following Twig\Extension\WrapperExtension.php file :
<?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';
}
}
Run Code Online (Sandbox Code Playgroud)
Now we need to add the token parsers : our syntax is {% wrapper NAME %} ... {% endwrapper %} and the same with wrapperheader and wrapperfooter. So those token parsers are used to declare the tags, to retrive the wrapper's NAME, and to retrieve the body (what's between wrapper and endwrapper`).
The token parser for 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';
}
}
Run Code Online (Sandbox Code Playgroud)
The token parser for 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';
}
}
Run Code Online (Sandbox Code Playgroud)
The token parser for 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';
}
}
Run Code Online (Sandbox Code Playgroud)
The token parsers retrieve all the required information, we now need to compile those information into PHP. This PHP code will be generated by the twig engine inside a Twig_Template implementation (you can find generated classes in your cache directory). It generates code in a method, and the context of included files is not available (because the context array is not given by reference). In such a way, this is not possible to access what's inside the included file without a global context. That's why here, I use static attributes... That's not nice at all but I don't know how to avoid them (if you have ideas, please let me know! :)).
Compiler for the wrapper tag : 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");
}
}
Run Code Online (Sandbox Code Playgroud)
Compiler for the wrapperheader tag : 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");
}
}
Run Code Online (Sandbox Code Playgroud)
Compiler for the wrapperfooter tag : 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");
}
}
Run Code Online (Sandbox Code Playgroud)
The implementation is ok now. Let's try it!
Create a view named wrappers.html.twig :
{# src/Fuz/LayoutBundle/Resources/views/Default/wrappers.html.twig #}
{% wrapperheader demo %}
HEAD
{% endwrapperheader %}
{% wrapperfooter demo %}
FOOT
{% endwrapperfooter %}
Run Code Online (Sandbox Code Playgroud)
Create a view named 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 %}
Run Code Online (Sandbox Code Playgroud)
This shows up :
HEAD O YEAH FOOT
Twig 变量和宏有一个相当直接的方法。
<div class="box indent">
<div class="padding">
my code here
</div>
</div>
Run Code Online (Sandbox Code Playgroud)
创建宏:
{% macro box(content) %}
<div class="box indent">
<div class="padding">
{{ content }}
</div>
</div>
{% endmacro %}
Run Code Online (Sandbox Code Playgroud)
并这样称呼它:
{% set content %}
my code here
{% endset %}
{{ _self.box(content) }}
Run Code Online (Sandbox Code Playgroud)
不是特别优雅,但代码堆积如山!
| 归档时间: |
|
| 查看次数: |
4936 次 |
| 最近记录: |