5

我有代表盒子的 CSS div,它们包装 html 代码。

<div class="box indent">
  <div class="padding">
    my code here
  </div>
</div>

我创建了一个“layoutbundle”,其中每个 HTML 包装器(例如框、选项卡、网格等)都放在单独的 twig 文件中。通过这种方式,可以使用其他布局来实现其他捆绑包上的视图。

但我厌倦了包含。每个小的 html 包装器都需要一个包含,我想知道是否有更简单的方法来包装 HTML 代码。

让我们举一个简单盒子的例子。实际上,我创建了几个文件:

box.html.twig包含框并包含内容的文件:

<div class="box indent">
  <div class="padding">
    {% include content %}
  </div>
</div>

几个box-content .html.twig 文件,包含我的盒子的内容。

最后,我通过执行以下操作在视图中创建了一个框:

{%
  include 'AcmeDemoBundle:layout:box.html.twig'
  with {
    'content': 'ReusableBundle:feature:xxx.html.twig'
  }
%}

有没有办法创建包装器,例如:

a)我声明一次新的包装器:

{% wrapperheader "box" %}
    <div class="box indent">
      <div class="padding">
{% endwrapperheader %}

{% wrapperfooter "box" %}
      </div>
    </div>
{% endwrapperfooter %}

b)然后在我的页面中,我使用:

{% wrapper "box" %}
  {# here my content #}
{% endwrapper %}

我想我需要在 Twig 中添加新的标签扩展,但首先我想知道类似的东西是否在本机是可能的。

4

2 回答 2

12

块法

该方法由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 %}没有任何通知。

  • 这个扩展的想法是:当解析器遇到一个wrapperheaderwrapperfooter标记时,编译器将标记的内容存储在某个地方以供以后与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` 之间的内容)。wrapperheaderwrapperfooterNAMEwrapper

包装器的令牌解析器: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 %}

这显示:

头哦是的脚

于 2013-04-14T15:22:20.610 回答
5

Twig 变量和宏有一个相当直接的方法。

<div class="box indent">
  <div class="padding">
    my code here
  </div>
</div>

创建一个宏:

{% macro box(content) %}
    <div class="box indent">
        <div class="padding">
            {{ content }}
        </div>
    </div>
{% endmacro %}

并这样称呼它:

{% set content %}
    my code here
{% endset %}

{{ _self.box(content) }}

不是特别优雅,但代码山更少!

于 2016-12-08T19:10:52.720 回答