Closing on Twig

I am trying to perform a closure that is inside an array in a Twig template. Below you can find a simplified snippet I'm trying from:


//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 %} 

When Twig displays a pattern, throws an exception complaining about converting the array to string

 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 ยป 

I would be grateful for your help.

Thanks!

+5
source share
2 answers

You cannot directly close inside your Twig template. However, if you need to call some PHP inside your template, you must use the Twig extension and enable your logic inside.

+1
source

Twig does not allow you to do this directly. You can either add a simple Twig function to handle the execution of closures, or wrap the closure in the class to be able to use the Twig attribute function (since a direct call to attribute(_context, 'myclosure', args) causes Fatal Error, because Twig will return the closure directly and ignore these arguments since _context is an array).


A simple Twig extension that accomplishes this will look like this for Symfony 2.8+. (For symfony 4 see 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'; } } 

Then in your templates you just need to call execute:

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

Without the Twig extension, one way around this problem is to use a class that acts as a wrapper for your closure and use the attribute the Twig function, since it can be used to call an object's method.

 // 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()); } } 

Then you just need to provide an instance of ClosureWrapper for your template when rendering instead of closing itself:

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

In the end, in your template, you need to use attribute to execute the closure that you defined in your controller:

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

However, this is a bit redundant, because internally , the attribute Twig function also decompresses these arguments. Using the code above, for each call, the arguments are sequentially unpacked, packed, and unpacked again.

+1
source

Source: https://habr.com/ru/post/1241217/


All Articles