Executing closure on Twig

可紊 提交于 2019-12-01 06:20:34

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.

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.

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 ;)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!