Executing closure on Twig

前端 未结 3 1413
梦谈多话
梦谈多话 2021-01-13 08:51

I\'m trying to execute a closure that resides inside an array on a Twig template. Below you could find a simplified snippet of which I\'m trying:


//Sym         


        
相关标签:
3条回答
  • 2021-01-13 09:20

    You cannot directly execute a closure inside your Twig template. However, if you need to call some PHP inside your template, you should use create a Twig Extension and include your logic inside.

    0 讨论(0)
  • 2021-01-13 09:29

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


    A simple Twig extension that achieves this purpose would look like this for Symfony 2.8+. (For Symfony 4, see the 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 simply have to call execute:

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

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

    // 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 simply need to give a ClosureWrapper instance to your template when rendering instead of the closure 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]);
        }
    
        ...
    

    Eventually, in your template, you need to use attribute to execute the closure you defined in your controller:

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

    However, this is a bit redundant as, internally, the attribute function of Twig unpacks the given arguments as well. By using the above code, for each call, arguments are successively unpacked, packed and unpacked again.

    0 讨论(0)
  • If you are using a closure you can use the the call method of the closure

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

    You will end up with something like this

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

    Since the first parameter must be the object that 'this' will refer too I am passing the same object as the first parameter.

    No twig extension and no extra PHP code to do ;)

    0 讨论(0)
提交回复
热议问题