Twig: Pass variables to block function

Created on 20 Dec 2013  路  9Comments  路  Source: twigphp/Twig

Please add ability to pass variables to block function.

For example this block could be rewritten

{% block integer_widget %}
{% spaceless %}
    {% set type = type|default('number') %}
    {{ block('form_widget_simple') }}
{% endspaceless %}
{% endblock integer_widget %}

to

{% block integer_widget %}
{% spaceless %}
    {{ block('form_widget_simple', {type: type|default('number')}) }}
{% endspaceless %}
{% endblock integer_widget %}

and this:

{% block tree_node %}
<ul>
    {# we need to save original root variable #}
    {% set oldRoot = root %}
    {% for node in root.children  %}
        {{ node.title }}
        {% set root = node %}
        {{ block('tree_node') }}
    {% endfor %}
    <!-- {{ oldRoot.children|length }} -->
</ul>
{% endblock %}

to

{% block tree_node %}
<ul>
    {% for node in root.children  %}
        {{ node.title }}
        {{ block('tree_node', {root: node}) }}
    {% endfor %}
    <!-- {{ root.children|length }} -->
</ul>
{% endblock %}

Most helpful comment

For the time being I've added a custom 'display_block'-function which simulates this behavior:

File YourVendor\Twig\DisplayBlockFunction.php:

namespace YourVendor\Twig;

class DisplayBlockFunction extends \Twig_SimpleFunction
{
    public function __construct()
    {
        parent::__construct('display_block', null, array('node_class' => '\\YourVendor\\Twig\\DisplayBlockNodeExpression'));
    }
}

File: YourVendor\TwigDisplayBlockNodeExpression

namespace YourVendor\Twig;

use Twig_Compiler;

class DisplayBlockNodeExpression extends \Twig_Node_Expression
{
    public function __construct($name, \Twig_NodeInterface $arguments, $lineno)
    {
        parent::__construct(array('arguments' => $arguments), array('name' => $name), $lineno);
    }

    public function compile(Twig_Compiler $compiler)
    {
        $compiler->addDebugInfo($this)
            ->write("\$this->displayBlock(");
        foreach ($this->getNode('arguments') as $index => $node) {
            switch ($index) {
                case 0:
                    if (!$node instanceof \Twig_Node_Expression_Constant) {
                        throw new \Twig_Error_Syntax(sprintf('First argument of %s should be a constant value', $this->getAttribute('name')));
                    }
                    $compiler->subcompile($node);
                    break;
                case 1:
                    if (!$node instanceof \Twig_Node_Expression_Array) {
                        throw new \Twig_Error_Syntax(sprintf('Second argument of %s should be an array of variables to be passed', $this->getAttribute('name')));
                    }
                    $compiler->raw(", array_merge(\$context, ")
                        ->subcompile($node)
                        ->raw(")");
                    break;
            }
        }
        $compiler->raw(", \$blocks)\n");
    }
}

Register the function in your environment

$twigEnv->addFunction(new DisplayBlockFunction());

Display a block and pass variables to it:

{{ display_block('form_element_label', { 'element': option } ) }}

All 9 comments

This would indeed be a nice addition!

Currently I'm using the same workaround: setting all variables used within the block prior to including it. This isn't very optimal in cases where the same variable name is used within the main scope (collision). A temporary variable has to be used in these cases to restore the variable value after the block inclusion.

As another workaround it's also possible to put the block contents into a separate file, and then use the 'include'-tag to pass variables to it. Sadly enough this won't work in my project, since I want to embed the main template so I can (optionally) redefine the block.

For the time being I've added a custom 'display_block'-function which simulates this behavior:

File YourVendor\Twig\DisplayBlockFunction.php:

namespace YourVendor\Twig;

class DisplayBlockFunction extends \Twig_SimpleFunction
{
    public function __construct()
    {
        parent::__construct('display_block', null, array('node_class' => '\\YourVendor\\Twig\\DisplayBlockNodeExpression'));
    }
}

File: YourVendor\TwigDisplayBlockNodeExpression

namespace YourVendor\Twig;

use Twig_Compiler;

class DisplayBlockNodeExpression extends \Twig_Node_Expression
{
    public function __construct($name, \Twig_NodeInterface $arguments, $lineno)
    {
        parent::__construct(array('arguments' => $arguments), array('name' => $name), $lineno);
    }

    public function compile(Twig_Compiler $compiler)
    {
        $compiler->addDebugInfo($this)
            ->write("\$this->displayBlock(");
        foreach ($this->getNode('arguments') as $index => $node) {
            switch ($index) {
                case 0:
                    if (!$node instanceof \Twig_Node_Expression_Constant) {
                        throw new \Twig_Error_Syntax(sprintf('First argument of %s should be a constant value', $this->getAttribute('name')));
                    }
                    $compiler->subcompile($node);
                    break;
                case 1:
                    if (!$node instanceof \Twig_Node_Expression_Array) {
                        throw new \Twig_Error_Syntax(sprintf('Second argument of %s should be an array of variables to be passed', $this->getAttribute('name')));
                    }
                    $compiler->raw(", array_merge(\$context, ")
                        ->subcompile($node)
                        ->raw(")");
                    break;
            }
        }
        $compiler->raw(", \$blocks)\n");
    }
}

Register the function in your environment

$twigEnv->addFunction(new DisplayBlockFunction());

Display a block and pass variables to it:

{{ display_block('form_element_label', { 'element': option } ) }}

:+1: I want this too!

But you should have an option to say that you only want the given variables be available inside the block's scope. Like http://twig.sensiolabs.org/doc/tags/include.html

Something like this:

{% render_block "hello" with { name: 'Ruud' } only %}

This would be great if it's a core functionality

Any progress here?

I have created a PR #1433 which solves it.

+1 here

Closing this PR as I prefer to support the with tag instead.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

unique1984 picture unique1984  路  4Comments

Seldaek picture Seldaek  路  6Comments

su-narthur picture su-narthur  路  6Comments

yguedidi picture yguedidi  路  4Comments

xxfaxy picture xxfaxy  路  6Comments