Выполнение закрытия на Twig

Я пытаюсь выполнить закрытие, которое находится внутри массива в шаблоне Twig. Ниже вы можете найти упрощенный фрагмент, который я пытаюсь использовать:


//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 »

Я буду признателен за вашу помощь.

Спасибо!


person Carles    schedule 22.01.2016    source источник


Ответы (3)


Если вы используете закрытие, вы можете использовать метод вызова закрытия

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

Вы получите что-то вроде этого

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

Поскольку первым параметром должен быть объект, на который будет ссылаться this, я передаю тот же объект, что и первый параметр.

Нет расширения twig и дополнительного PHP-кода;)

person Martin Poirier Théorêt    schedule 15.11.2018
comment
в этом случае мы должны вызывать {{ funcs.conditional.call(funcs.conditional, obj) }}. Это так?! - person the manh Nguyen; 17.06.2020

Вы не можете напрямую выполнить замыкание внутри вашего шаблона Twig. Однако, если вам нужно вызвать PHP внутри вашего шаблона, вы должны использовать создать Twig Расширьте и включите в него свою логику.

person Terenoth    schedule 22.01.2016
comment
Спасибо! Но это решение не работает для меня, так как логика переменная - person Carles; 22.01.2016
comment
Что вы подразумеваете под логикой переменной? - person Terenoth; 22.01.2016
comment
Это может быть много разных условных функций с разными условиями. Я считаю неэффективным помещать столько расширений ветки, сколько разных условий, потому что это убьет производительность при рендеринге шаблона. - person Carles; 23.01.2016

Twig не позволяет сделать это напрямую. Вы можете либо добавить в Twig простую функцию для выполнения замыканий, либо обернуть замыкание в класс, чтобы иметь возможность использовать атрибутивную функцию Twig (поскольку прямой вызов attribute(_context, 'myclosure', args) вызовет фатальную ошибку, поскольку Twig вернет замыкание напрямую и игнорировать заданные аргументы, поскольку _context — это массив).


Простое расширение Twig, которое достигает этой цели, будет выглядеть так для Symfony 2.8+. (Для Symfony 4 см. новую документацию)

// 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 один из способов обойти эту проблему — использовать класс, который действует как оболочка для вашего замыкания, и использовать attribute функции Twig, поскольку ее можно использовать для вызова метода объекта.

// 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]) }}

Однако это немного избыточно, поскольку внутренне функция Twig attribute распаковывает также приведены аргументы. Используя приведенный выше код, для каждого вызова аргументы последовательно распаковываются, упаковываются и снова распаковываются.

person Alan T.    schedule 24.12.2017