栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 面试经验 > 面试问答

如何创建执行回调的树枝自定义标签?

面试问答 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

如何创建执行回调的树枝自定义标签?

理论

在谈论标签之前,您应该了解Twig在内部如何工作。

  • 首先,由于Twig代码可以放在文件,字符串甚至数据库中,因此Twig打开并使用Loader读取流。最著名的加载器是

    Twig_Loader_Filesystem
    从文件中打开树枝代码,并
    Twig_Loader_Array
    直接从字符串中获取树枝代码。

  • 然后,对该树枝代码进行解析以构建一个分析树,其中包含该树枝代码的对象表示。每个对象都称为

    Node
    ,因为它们是树的一部分。至于其他语言,嫩枝制成的令牌,如
    {%
    {#
    function()
    "string"
    …等等枝杈语言结构会读几令牌来建立正确的节点。

  • 然后遍历分析树,并将其编译为PHP代码。生成的PHP类遵循该

    Twig_Template
    接口,因此渲染器可以调用
    doDisplay
    该类的方法以生成最终结果。

如果启用缓存,则可以查看那些生成的文件并了解发生了什么。


让我们开始平稳练习…

所有内部树枝标签,如

{% block %}
{% set%}
…都使用相同的接口,自定义标签开发的,所以如果你需要一些特定的样品,你可以看看枝杈源代码。

但是,无论如何,您想要的示例都是一个好的开始,所以让我们进行开发。

TokenParser

令牌解析器的目标是解析和验证标签参数。例如,

{% macro %}
标记需要一个名称,而如果您输入字符串,则会崩溃。

当Twig找到一个标签时,它将查看所​​有注册的

TokenParser
类中
getTag()
方法返回的标签名称。如果名称匹配,则Twig调用
parse()
该类的方法。

parse()
被调用时,流指针仍然是标签上的名称标记。因此,我们应该获取所有内联参数,并通过找到
BLOCK_END_TYPE
标记来完成标签声明。然后,我们将标签主体细分(标签内部包含什么内容,因为它还可能包含树枝逻辑,例如标签和其他内容):
decideMyTagFork
每次在该主体中找到新标签时都会调用该方法:子解析是否返回true。请注意,此方法名称不属于接口,这只是Twig内置扩展中使用的标准。

作为参考,Twig令牌可以是以下几种:

  • EOF_TYPE
    :流的最后一个标记,指示结束。

  • TEXT_TYPE
    :不属于树枝语言的文本:例如,在Twig代码中
    Hello, {{ var }}
    hello,
    TEXT_TYPE
    令牌。

  • BLOCK_START_TYPE
    :“开始执行语句”令牌,
    {%

  • VAR_START_TYPE
    :“开始获取表达式结果”令牌,
    {{

  • BLOCK_END_TYPE
    :“完成执行语句”令牌,
    %}

  • VAR_END_TYPE
    :“完成以获取表达结果”令牌,
    }}

  • NAME_TYPE
    :此令牌就像没有引号的字符串,就像树枝中的变量名一样,
    {{ i_am_a_name_type }}

  • NUMBER_TYPE
    :此类型的节点包含数字,例如3,-2、4.5 …

  • STRING_TYPE
    :包含用引号或双引号封装的字符串,例如
    'foo'
    "bar"

  • OPERATOR_TYPE
    :包含一个运算符,例如
    +
    ,,
    -
    以及…,因为Twig已经提供了表达式解析器
    ~
    ,所以
    ?
    您将永远不需要此标记。

  • INTERPOLATION_START_TYPE
    ,即“开始插值”标记(自Twig> = 1.5起),插值是Twig字符串内部的表达式解释,例如
    "my string, my #{variable} and 1+1 = #{1+1}"
    。插值的开始是
    #{

  • INTERPOLATION_END_TYPE
    ,即“结束插值”令牌(自Twig> = 1.5起),
    }
    例如在打开插值时未在字符串内转义。

MyTagTokenParser.php

<?phpclass MyTagTokenParser extends Twig_TokenParser{   public function parse(Twig_Token $token)   {      $lineno = $token->getLine();      $stream = $this->parser->getStream();      // recovers all inline parameters close to your tag name      $params = array_merge(array (), $this->getInlineParams($token));      $continue = true;      while ($continue)      {         // create subtree until the decideMyTagFork() callback returns true         $body = $this->parser->subparse(array ($this, 'decideMyTagFork'));         // I like to put a switch here, in case you need to add middle tags, such         // as: {% mytag %}, {% nextmytag %}, {% endmytag %}.         $tag = $stream->next()->getValue();         switch ($tag)         { case 'endmytag':    $continue = false;    break; default:    throw new Twig_Error_Syntax(sprintf('Unexpected end of template. Twig was looking for the following tags "endmytag" to close the "mytag" block started at line %d)', $lineno), -1);         }         // you want $body at the beginning of your arguments         array_unshift($params, $body);         // if your endmytag can also contains params, you can uncomment this line:         // $params = array_merge($params, $this->getInlineParams($token));         // and comment this one:         $stream->expect(Twig_Token::BLOCK_END_TYPE);      }      return new MyTagNode(new Twig_Node($params), $lineno, $this->getTag());   }      protected function getInlineParams(Twig_Token $token)   {      $stream = $this->parser->getStream();      $params = array ();      while (!$stream->test(Twig_Token::BLOCK_END_TYPE))      {         $params[] = $this->parser->getexpressionParser()->parseexpression();      }      $stream->expect(Twig_Token::BLOCK_END_TYPE);      return $params;   }      public function decideMyTagFork(Twig_Token $token)   {      return $token->test(array ('endmytag'));   }      public function getTag()   {      return 'mytag';   }}

编译器

编译器是将用PHP编写标签应执行的代码。在您的示例中,您要调用一个函数,其中body作为第一个参数,而所有tag参数作为其他参数。

由于正文在之间插入

{% mytag %}
并且
{% endmytag%}
可能很复杂,并且还编译了自己的代码,因此,我们应该使用输出缓冲(
ob_start()
/
ob_get_clean()
)来填充
functionToCall()
的参数。

MyTagNode.php

<?phpclass MyTagNode extends Twig_Node{   public function __construct($params, $lineno = 0, $tag = null)   {      parent::__construct(array ('params' => $params), array (), $lineno, $tag);   }   public function compile(Twig_Compiler $compiler)   {      $count = count($this->getNode('params'));      $compiler         ->addDebugInfo($this);      for ($i = 0; ($i < $count); $i++)      {         // argument is not an expression (such as, a Twig_Node_Textbody)         // we should trick with output buffering to get a valid argument to pass         // to the functionToCall() function.         if (!($this->getNode('params')->getNode($i) instanceof Twig_Node_expression))         { $compiler    ->write('ob_start();')    ->raw(PHP_EOL); $compiler    ->subcompile($this->getNode('params')->getNode($i)); $compiler    ->write('$_mytag[] = ob_get_clean();')    ->raw(PHP_EOL);         }         else         { $compiler    ->write('$_mytag[] = ')    ->subcompile($this->getNode('params')->getNode($i))    ->raw(';')    ->raw(PHP_EOL);         }      }      $compiler         ->write('call_user_func_array(')         ->string('functionToCall')         ->raw(', $_mytag);')         ->raw(PHP_EOL);      $compiler         ->write('unset($_mytag);')         ->raw(PHP_EOL);   }}

扩展名

创建扩展来公开TokenParser更加干净,因为如果扩展需要更多,则可以在此处声明所有必需的内容。

MyTagExtension.php

<?phpclass MyTagExtension extends Twig_Extension{   public function getTokenParsers()   {      return array (   new MyTagTokenParser(),      );   }   public function getName()   {      return 'mytag';   }}

让我们测试一下!

mytag.php

<?phprequire_once(__DIR__ . '/Twig-1.15.1/lib/Twig/Autoloader.php');Twig_Autoloader::register();require_once("MyTagExtension.php");require_once("MyTagTokenParser.php");require_once("MyTagNode.php");$loader = new Twig_Loader_Filesystem(__DIR__);$twig = new Twig_Environment($loader, array (// if you want to look at the generated pre, uncomment this line// and create the ./generated directory//        'cache' => __DIR__ . '/generated',   ));function functionToCall(){   $params = func_get_args();   $body = array_shift($params);   echo "body = {$body}", PHP_EOL;   echo "params = ", implode(', ', $params), PHP_EOL;}$twig->addExtension(new MyTagExtension());echo $twig->render("mytag.twig", array('firstname' => 'alain'));

mytag.twig

{% mytag 1 "test" (2+3) firstname %}Hello, world!{% endmytag %}

Result

body = Hello, world!params = 1, test, 5, alain

更进一步

如果启用缓存,则可以看到生成的结果:

protected function doDisplay(array $context, array $blocks = array()){    // line 1    ob_start();    echo "Hello, world!";    $_mytag[] = ob_get_clean();    $_mytag[] = 1;    $_mytag[] = "test";    $_mytag[] = (2 + 3);    $_mytag[] = (isset($context["firstname"]) ? $context["firstname"] : null);    call_user_func_array("functionToCall", $_mytag);    unset($_mytag);}

对于这种特定情况,即使您将其他内容

{% mytag %}
放在内
{% mytag %}
(例如
{% mytag %}Hello, world!{%mytag %}foo bar{% endmytag %}{% endmytag%}
),也可以使用。但是,如果要构建这样的标记,则可能会使用更复杂的代码,并且
$_mytag
即使您在解析树中更深,它也会因变量具有相同的名称而覆盖您的变量。

因此,让我们通过使其健壮来完成此示例。

NodeVisitor

A

NodeVisitor
就像一个侦听器:当编译器将读取解析树以生成代码时,它将
NodeVisitor
在进入或离开节点时输入所有已注册的内容。

所以我们的目标很简单:当我们输入类型为Node的节点时

MyTagNode
,我们将增加一个深计数器,而当我们离开Node时,我们将对该计数器进行递减。在编译器中,我们将能够使用此计数器生成要使用的正确变量名。

MyTagNodeVisitor.php

<?phpclass MyTagNodevisitor implements Twig_NodeVisitorInterface{   private $counter = 0;   public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)   {      if ($node instanceof MyTagNode)      {         $node->setAttribute('counter', $this->counter++);      }      return $node;   }   public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env)   {      if ($node instanceof MyTagNode)      {         $node->setAttribute('counter', $this->counter--);      }      return $node;   }   public function getPriority()   {      return 0;   }}

然后在扩展中注册NodeVisitor:

MyTagExtension.php

class MyTagExtension{    // ...    public function getNodeVisitors()    {        return array (     new MyTagNodeVisitor(),        );    }}

在编译器中,将所有替换

"$_mytag"
sprintf("$mytag[%d]",$this->getAttribute('counter'))

MyTagNode.php

  // ...  // replace the compile() method by this one:  public function compile(Twig_Compiler $compiler)   {      $count = count($this->getNode('params'));      $compiler         ->addDebugInfo($this);      for ($i = 0; ($i < $count); $i++)      {         // argument is not an expression (such as, a Twig_Node_Textbody)         // we should trick with output buffering to get a valid argument to pass         // to the functionToCall() function.         if (!($this->getNode('params')->getNode($i) instanceof Twig_Node_expression))         { $compiler    ->write('ob_start();')    ->raw(PHP_EOL); $compiler    ->subcompile($this->getNode('params')->getNode($i)); $compiler    ->write(sprintf('$_mytag[%d][] = ob_get_clean();', $this->getAttribute('counter')))    ->raw(PHP_EOL);         }         else         { $compiler    ->write(sprintf('$_mytag[%d][] = ', $this->getAttribute('counter')))    ->subcompile($this->getNode('params')->getNode($i))    ->raw(';')    ->raw(PHP_EOL);         }      }      $compiler         ->write('call_user_func_array(')         ->string('functionToCall')         ->raw(sprintf(', $_mytag[%d]);', $this->getAttribute('counter')))         ->raw(PHP_EOL);      $compiler         ->write(sprintf('unset($_mytag[%d]);', $this->getAttribute('counter')))         ->raw(PHP_EOL);   }

不要忘记在示例中包含NodeVisitor:

mytag.php

// ...require_once("MyTagNodeVisitor.php");

结论

自定义标签是扩展树枝的一种非常有效的方法,本介绍为您提供了一个良好的开端。这里有许多功能未作描述,但是通过仔细查看twig内置扩展,由我们编写的类扩展的抽象类,以及通过阅读由twig文件生成的php代码,您将获得创建任何内容的一切标记你想要的。

下载此样本



转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/594767.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号