Выполнение закрытия на 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 »

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

Спасибо!

3 ответа

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

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

Вы закончите с чем-то вроде этого

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

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

Нет расширения ветки и никакого дополнительного кода PHP, чтобы сделать;)

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

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

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

Другие вопросы по тегам