1

While upgrading twig v1.30 to v3.22.0 i encountered some problem using macros from an external file and calling them by using a variable value as dynamic name.

Given the twig file with the macros:

{# file twig_v3_macros.twig #}

{% macro element1(data) %}
    This is my element1 with {{ data.name }} and its value: <b>{{ data.value }}</b><br><br>
{% endmacro %}

{% macro element2(data) %}
    This is my element2 with {{ data.name }} and its value: <b>{{ data.value }}</b><br><br>
{% endmacro %}

{% macro element3(data) %}
    This is my element3 with {{ data.name }} and its value: <b>{{ data.value }}</b><br><br>
{% endmacro %}

you can import these in a template file like so:

{# file twig_v3_template.twig #}

{% import "twig_v3_macros.twig" as twigmacros %}

{% set myfunction='element1' %}
{% set mydata={ name: 'dataName', value: 'dataValue'} %}

{#
in twig v1.30 you could use the attribute function to dynamically call macros:
{{ attribute(twigmacros, myfunction, [mydata]) }}

The following line breaks in twig 3.22 although it should work
{{ twigmacros.(myfunction)(mydata) }}<br><br>
#}

{# this one works in v3.22, since you directly specify the macro name #}
{{ twigmacros.element1(mydata) }}<br><br>

the php script to use these is mostly:

<?php

error_reporting(E_ALL);
ini_set("display_errors", 1);

require_once '../vendor/autoload.php';

$loader = new \Twig\Loader\FilesystemLoader('../includes/templates');

$twig = new \Twig\Environment($loader, [
    'debug' => true,
]);

$twig->addExtension(new \Twig\Extension\DebugExtension());

$templateFile = "twig_v3_template.twig";
$objTemplate = $twig->load($templateFile);

$html = $objTemplate->render(
    [
        'elements' => $arrElements,
    ]
);
echo $html;

?>

problem description

When using V1.30, you could use the attributes(macroalias, macroname, parameters) function as described in twig attributes function documentation. The page states as well, that this function is deprecated as of twig 3.15 and should be substituted with the dot operator. And using the attributes function in v3.22 results in no output (but no error whatsoever).

from what i understand you should be able to use dynamic names for the methods there as well, so instead of using twigmacros.mymacro(params) you should be able to set a variable with the value mymacro and then call twigmacros.(mymacroname)(params).

unfortunately this does not work but produces an error, which i don't really get right:

Fatal error:  Uncaught Twig\Error\SyntaxError: Expected name or number, got value "(" of type punctuation. in includes/templates/twig_v3_template.twig:9
Stack trace:
#0 vendor/twig/twig/src/ExpressionParser.php(421): Twig\ExpressionParser->parseSubscriptExpression(Object(Twig\Node\Expression\NameExpression))
#1 vendor/twig/twig/src/ExpressionParser.php(291): Twig\ExpressionParser->parsePostfixExpression(Object(Twig\Node\Expression\NameExpression))
#2 vendor/twig/twig/src/ExpressionParser.php(177): Twig\ExpressionParser->parsePrimaryExpression()
#3 vendor/twig/twig/src/ExpressionParser.php(72): Twig\ExpressionParser->getPrimary()
#4 vendor/twig/twig/src/Parser.php(130): Twig\ExpressionParser->parseExpression()
#5 vendor/twig/twig/src/Parser.php(83): Twig\Parser->subparse(NULL, false)
#6 vendor/twig/twig/src/Environment.php(522): Twig\Parser->parse(Object(Twig\TokenStream))
#7 vendor/twig/twig/src/Environment.php(550): Twig\Environment->parse(Object(Twig\TokenStream))
#8 vendor/twig/twig/src/Environment.php(380): Twig\Environment->compileSource(Object(Twig\Source))
#9 vendor/twig/twig/src/Environment.php(342): Twig\Environment->loadTemplate('__TwigTemplate_...', 'twig_v3_templat...')
#10 twigtest.php(38): Twig\Environment->load('twig_v3_templat...')
#11 {main}
  thrown in includes/templates/twig_v3_template.twig on line 9

the question now is if i am misreading the documentation about the dot operator and that you are not able to dynamically call macros with a name in a variable or if i try using it the wrong way.

It really worked well with the attributes function and i would really be surprised if this would have been removed somehow.

1
  • Looking at the fact that {{ twigmacros.('element1')(mydata) }} does work I would suggest that you open a bug issue about this in Twig's github Commented 20 hours ago

1 Answer 1

-1

Dynamic calling of imported macros with a variable name is intentionally removed in Twig 3 (the old attribute() trick was the only way and it's gone for good, the .(var) syntax never existed for macros, and bracket syntax like twigmacros[myvar] doesn't work on macro namespaces).

the only working solution in Twig 3.22 (and the one literally everyone uses now) is template_from_string + include.

here's the exact index.twig code that works perfectly (just add the StringLoaderExtension once in your PHP setup, which you probably already have in Symfony):

{% import "twig_v3_macros.twig" as twigmacros %}

{% set myfunction = 'element1' %}
{% set mydata = { name: 'dataName', value: 'dataValue' } %}

{{ include(template_from_string('{% import "twig_v3_macros.twig" as twigmacros %}{{ twigmacros.' ~ myfunction ~ '(mydata) }}'), { mydata: mydata }) }}

add this line in your Twig environment init (you only need it once):

PHP$twig->addExtension(new \Twig\Extension\StringLoaderExtension());

outputs exactly:

textThis is my element1 with dataName and its value: <b>dataValue</b><br><br>

the official/accepted way in twig 3 when you really need dynamic macro names. the playground can't demo it bc (no StringLoaderExtension there). works in real projects tho.

Sign up to request clarification or add additional context in comments.

5 Comments

This does not appear to work - demo
square brackets straight on the imported macro namespace.. works since Twig 2.10> and still perfect in 3.22>. no _self, no dummy set macros = {} needed. Updated the code, will u try again?
Your initial answer didn't include any reference of the StringLoaderExtension - Furthermore, what is your source for this statement: "Dynamic calling of imported macros with a variable name is intentionally removed *". Furthermore as pointed out in the comments {{ twigmacros.('element1')(mydata) }} *does work, so without any credible source I'd assume this is a bug.
I did, see the demo
the playground can't demo beccause StringLoaderExtension isn't there, please give another try to updated answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.