Выполнение закрытия на 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 и включить свою логику внутрь.