【问题标题】:Executing closure on Twig在 Twig 上执行关闭
【发布时间】:2016-04-28 22:42:12
【问题描述】:

我正在尝试执行一个位于 Twig 模板上的数组中的闭包。您可以在下面找到我正在尝试的简化 sn-p:


//Symfony controller
...
$funcs = array(
    "conditional" => function($obj){
        return $obj->getFoo() === $obj::TRUE_FOO
    }
);
$this->render('template_name', array('funcs' => $funcs));

{# Twig template #}
{# obj var is set #}
...
{% if funcs.conditional(obj)%}
<p>Got it</p>
{% endif %}

当 Twig 渲染模板时,抛出一个异常,抱怨数组到字符串的转换

An exception has been thrown during the rendering of a template ("Notice: Array to string conversion") in "template_name.html.twig".
500 Internal Server Error - Twig_Error_Runtime
1 linked Exception: ContextErrorException »

感谢您的帮助。

谢谢!

【问题讨论】:

    标签: php symfony twig closures symfony-2.8


    【解决方案1】:

    您不能在 Twig 模板中直接执行闭包。但是,如果您需要在模板中调用一些 PHP,您应该使用 create a Twig Extension 并在其中包含您的逻辑。

    【讨论】:

    • 谢谢!但是这个解决方案对我不起作用,因为逻辑是可变的
    • “逻辑是可变的”是什么意思?
    • 它可以是许多不同的条件函数,具有不同的条件。我认为将尽可能多的树枝扩展放置在不同的条件下效率不高,因为它会降低模板渲染的性能。
    【解决方案2】:

    Twig 不允许直接执行此操作。您可以向 Twig 添加一个简单的函数来处理闭包的执行,或者将闭包包装在一个类中以便能够使用 Twig 的属性函数(因为直接调用 attribute(_context, 'myclosure', args) 将触发致命错误,因为 Twig 将返回闭包直接忽略给定的参数,因为_context 是一个数组)。


    实现此目的的简单 Twig 扩展在 Symfony 2.8+ 中看起来像这样。 (对于 Symfony 4,请参阅 new documentation

    // src/AppBundle/Twig/Extension/CoreExtensions.php
    namespace AppBundle\Twig\Extension;
    
    class CoreExtensions extends \Twig_Extension
    {
        public function getFunctions()
        {
            return [
                new \Twig_SimpleFunction('execute', [$this, 'executeClosure'])
            ];
        }
    
        public function executeClosure(\Closure $closure, $arguments)
        {
            return $closure(...$arguments);
        }
    
        public function getName()
        {
            return 'core_extensions_twig_extension';
        }
    }
    

    然后,在您的模板中,您只需调用 execute:

    {{ execute(closure, [argument1, argument2]) }}
    

    在不扩展 Twig 的情况下,解决此问题的一种方法是使用一个充当闭包包装器的类,并使用 Twig 的 attribute 函数,因为它可用于调用对象的方法。

    // src/AppBundle/Twig/ClosureWrapper.php
    namespace AppBundle\Twig;
    
    /**
     * Wrapper to get around the issue of not being able to use closures in Twig
     * Since it is possible to call a method of a given object in Twig via "attribute",
     * the only purpose of this class is to store the closure and give a method to execute it
     */
    class ClosureWrapper
    {
        private $closure;
    
        public function __construct($closure)
        {
            $this->closure = $closure;
        }
    
        public function execute()
        {
            return ($this->closure)(...func_get_args());
        }
    }
    

    然后,您只需在渲染时为您的模板提供一个 ClosureWrapper 实例,而不是闭包本身:

    use AppBundle\Twig\ClosureWrapper;
    
    class MyController extends Controller
    {
        public function myAction()
        {
            $localValue = 2;
            $closure = new ClosureWrapper(function($param1, $param2) use ($localValue) {
                return $localValue + $param1 + $param2;
            });
    
            return $this->render('mytemplate.html.twig', ['closure' => $closure]);
        }
    
        ...
    

    最终,在您的模板中,您需要使用attribute 来执行您在控制器中定义的闭包:

    // Displays 12
    {{ attribute(closure, 'execute', [4, 6]) }}
    

    然而,这有点多余,因为internally,Twig 的attribute 函数也解包给定的参数。通过使用上面的代码,对于每次调用,参数都被依次解包、打包和再次解包。

    【讨论】:

      【解决方案3】:

      如果你使用的是闭包,你可以使用闭包的调用方法

      http://php.net/manual/en/closure.call.php

      你最终会得到这样的东西

      {{ funcs.conditional.call(obj, obj) }}
      

      由于第一个参数也必须是 'this' 将引用的对象,因此我传递的对象与第一个参数相同。

      无需 Twig 扩展,也无需额外的 PHP 代码;)

      【讨论】:

      • 在这种情况下,我们应该调用 {{ funcs.conditional.call(funcs.conditional, obj) }}。对吗?!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-10-16
      • 2020-07-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-03
      • 1970-01-01
      相关资源
      最近更新 更多