diff --git a/CHANGELOG b/CHANGELOG index ee07fd909..bd327f589 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -19,7 +19,9 @@ * 2.11.0 (2019-XX-XX) - * n/a + * fixed macros "import" when using the same name in the parent and child templates + * fixed recursive macros + * macros imported "globally" in a template are now available in macros without re-importing them * 2.10.0 (2019-05-14) diff --git a/src/Node/BlockNode.php b/src/Node/BlockNode.php index bd00a356d..0632ba747 100644 --- a/src/Node/BlockNode.php +++ b/src/Node/BlockNode.php @@ -32,6 +32,7 @@ class BlockNode extends Node ->addDebugInfo($this) ->write(sprintf("public function block_%s(\$context, array \$blocks = [])\n", $this->getAttribute('name')), "{\n") ->indent() + ->write("\$macros = \$this->macros;\n") ; $compiler diff --git a/src/Node/Expression/MethodCallExpression.php b/src/Node/Expression/MethodCallExpression.php index da3ef4a92..3adfe8aea 100644 --- a/src/Node/Expression/MethodCallExpression.php +++ b/src/Node/Expression/MethodCallExpression.php @@ -27,8 +27,9 @@ class MethodCallExpression extends AbstractExpression public function compile(Compiler $compiler): void { $compiler - ->subcompile($this->getNode('node')) - ->raw('->') + ->raw('$macros[') + ->repr($this->getNode('node')->getAttribute('name')) + ->raw(']->') ->raw($this->getAttribute('method')) ->raw('(') ; diff --git a/src/Node/ImportNode.php b/src/Node/ImportNode.php index 4ebbc146c..147acc331 100644 --- a/src/Node/ImportNode.php +++ b/src/Node/ImportNode.php @@ -22,19 +22,27 @@ use Twig\Node\Expression\NameExpression; */ class ImportNode extends Node { - public function __construct(AbstractExpression $expr, AbstractExpression $var, int $lineno, string $tag = null) + public function __construct(AbstractExpression $expr, AbstractExpression $var, int $lineno, string $tag = null, bool $global = true) { - parent::__construct(['expr' => $expr, 'var' => $var], [], $lineno, $tag); + parent::__construct(['expr' => $expr, 'var' => $var], ['global' => $global], $lineno, $tag); } public function compile(Compiler $compiler): void { $compiler ->addDebugInfo($this) - ->write('') - ->subcompile($this->getNode('var')) - ->raw(' = ') + ->write('$macros[') + ->repr($this->getNode('var')->getAttribute('name')) + ->raw('] = ') ; + + if ($this->getAttribute('global')) { + $compiler + ->raw('$this->macros[') + ->repr($this->getNode('var')->getAttribute('name')) + ->raw('] = ') + ; + } if ($this->getNode('expr') instanceof NameExpression && '_self' === $this->getNode('expr')->getAttribute('name')) { $compiler->raw('$this'); diff --git a/src/Node/MacroNode.php b/src/Node/MacroNode.php index 2835393c6..876ef2efa 100644 --- a/src/Node/MacroNode.php +++ b/src/Node/MacroNode.php @@ -63,9 +63,7 @@ class MacroNode extends Node ->raw(")\n") ->write("{\n") ->indent() - ; - - $compiler + ->write("\$macros = \$this->macros;\n") ->write("\$context = \$this->env->mergeGlobals([\n") ->indent() ; diff --git a/src/Node/ModuleNode.php b/src/Node/ModuleNode.php index 4efa4dd34..93eef125f 100644 --- a/src/Node/ModuleNode.php +++ b/src/Node/ModuleNode.php @@ -160,7 +160,8 @@ final class ModuleNode extends Node ->raw(" extends Template\n") ->write("{\n") ->indent() - ->write("private \$source;\n\n") + ->write("private \$source;\n") + ->write("private \$macros = [];\n\n") ; } @@ -304,6 +305,7 @@ final class ModuleNode extends Node $compiler ->write("protected function doDisplay(array \$context, array \$blocks = [])\n", "{\n") ->indent() + ->write("\$macros = \$this->macros;\n") ->subcompile($this->getNode('display_start')) ->subcompile($this->getNode('body')) ; diff --git a/src/Parser.php b/src/Parser.php index 7005eef4d..aaa6af6f4 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -266,19 +266,8 @@ class Parser public function getImportedSymbol(string $type, string $alias) { - if (null !== $this->peekBlockStack()) { - foreach ($this->importedSymbols as $functions) { - if (isset($functions[$type][$alias])) { - if (count($this->blockStack) > 1) { - return null; - } - - return $functions[$type][$alias]; - } - } - } else { - return $this->importedSymbols[0][$type][$alias] ?? null; - } + // if the symbol does not exist in the current scope (0), try in the main/global scope (last index) + return $this->importedSymbols[0][$type][$alias] ?? ($this->importedSymbols[\count($this->importedSymbols) - 1][$type][$alias] ?? null); } public function isMainScope(): bool diff --git a/src/TokenParser/FromTokenParser.php b/src/TokenParser/FromTokenParser.php index 2ec8c1466..c1d1f4cba 100644 --- a/src/TokenParser/FromTokenParser.php +++ b/src/TokenParser/FromTokenParser.php @@ -48,7 +48,7 @@ final class FromTokenParser extends AbstractTokenParser $stream->expect(/* Token::BLOCK_END_TYPE */ 3); $var = new AssignNameExpression($this->parser->getVarName(), $token->getLine()); - $node = new ImportNode($macro, $var, $token->getLine(), $this->getTag()); + $node = new ImportNode($macro, $var, $token->getLine(), $this->getTag(), $this->parser->isMainScope()); foreach ($targets as $name => $alias) { $this->parser->addImportedSymbol('function', $alias, 'macro_'.$name, $var); diff --git a/src/TokenParser/ImportTokenParser.php b/src/TokenParser/ImportTokenParser.php index 6be1fa8ae..ca18193db 100644 --- a/src/TokenParser/ImportTokenParser.php +++ b/src/TokenParser/ImportTokenParser.php @@ -32,7 +32,7 @@ final class ImportTokenParser extends AbstractTokenParser $this->parser->addImportedSymbol('template', $var->getAttribute('name')); - return new ImportNode($macro, $var, $token->getLine(), $this->getTag()); + return new ImportNode($macro, $var, $token->getLine(), $this->getTag(), $this->parser->isMainScope()); } public function getTag(): string diff --git a/test/Twig/Tests/Fixtures/tags/macro/from_embed_with_global_macro.test b/test/Twig/Tests/Fixtures/tags/macro/from_embed_with_global_macro.test new file mode 100644 index 000000000..f06c31c93 --- /dev/null +++ b/test/Twig/Tests/Fixtures/tags/macro/from_embed_with_global_macro.test @@ -0,0 +1,21 @@ +--TEST-- +"macro" tag +--TEMPLATE-- +{% from _self import input %} + +{% embed 'embed' %} + {% block foo %} + {{ input("username") }} + {% endblock %} +{% endembed %} + +{% macro input(name) -%} + +{% endmacro %} +--TEMPLATE(embed)-- + {% block foo %} + {% endblock %} +--DATA-- +return [] +--EXCEPTION-- +Twig\Error\SyntaxError: Unknown "input" function in "index.twig" at line 6. diff --git a/test/Twig/Tests/Fixtures/tags/macro/from_local_override.test b/test/Twig/Tests/Fixtures/tags/macro/from_local_override.test new file mode 100644 index 000000000..27bfbaee1 --- /dev/null +++ b/test/Twig/Tests/Fixtures/tags/macro/from_local_override.test @@ -0,0 +1,28 @@ +--TEST-- +"macro" tag +--TEMPLATE-- +{%- from _self import input %} + +{% block foo %} + {%- from "macros" import input %} + {{- input('username') }} +{% endblock %} + +{% block bar %} + {{- input('username') }} +{% endblock %} + +{% macro input(name) -%} + +{% endmacro %} +--TEMPLATE(macros)-- +{% macro input(name) %} + +{% endmacro %} +--DATA-- +return [] +--EXPECT-- + + + + diff --git a/test/Twig/Tests/Fixtures/tags/macro/from_macro_in_a_macro.test b/test/Twig/Tests/Fixtures/tags/macro/from_macro_in_a_macro.test index 87ac25c31..168c9b3a9 100644 --- a/test/Twig/Tests/Fixtures/tags/macro/from_macro_in_a_macro.test +++ b/test/Twig/Tests/Fixtures/tags/macro/from_macro_in_a_macro.test @@ -14,5 +14,5 @@ {% endmacro %} --DATA-- return [] ---EXCEPTION-- -Twig\Error\SyntaxError: Unknown "another" function in "index.twig" at line 7. +--EXPECT-- +OK diff --git a/test/Twig/Tests/Fixtures/tags/macro/from_nested_blocks_with_global_macro.test b/test/Twig/Tests/Fixtures/tags/macro/from_nested_blocks_with_global_macro.test index f737bf0d8..384b02d8f 100644 --- a/test/Twig/Tests/Fixtures/tags/macro/from_nested_blocks_with_global_macro.test +++ b/test/Twig/Tests/Fixtures/tags/macro/from_nested_blocks_with_global_macro.test @@ -14,5 +14,5 @@ {% endmacro %} --DATA-- return [] ---EXCEPTION-- -Twig\Error\SyntaxError: Unknown "input" function in "index.twig" at line 6. +--EXPECT-- + diff --git a/test/Twig/Tests/Fixtures/tags/macro/from_recursive.test b/test/Twig/Tests/Fixtures/tags/macro/from_recursive.test new file mode 100644 index 000000000..09a29839d --- /dev/null +++ b/test/Twig/Tests/Fixtures/tags/macro/from_recursive.test @@ -0,0 +1,27 @@ +--TEST-- +"import" tag +--TEMPLATE-- +{% from _self import recursive_macro %} + +{{ recursive_macro(10) }} + +{% macro recursive_macro(n) %} + {% if n > 0 %} + {{- recursive_macro(n - 1) -}} + {% endif %} + {{- n }} +{% endmacro %} +--DATA-- +return [] +--EXPECT-- +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 diff --git a/test/Twig/Tests/Fixtures/tags/macro/import_embed_with_global_macro.test b/test/Twig/Tests/Fixtures/tags/macro/import_embed_with_global_macro.test new file mode 100644 index 000000000..3609881f0 --- /dev/null +++ b/test/Twig/Tests/Fixtures/tags/macro/import_embed_with_global_macro.test @@ -0,0 +1,21 @@ +--TEST-- +"macro" tag +--TEMPLATE-- +{% import _self as macros %} + +{% embed 'embed' %} + {% block foo %} + {{ macros.input("username") }} + {% endblock %} +{% endembed %} + +{% macro input(name) -%} + +{% endmacro %} +--TEMPLATE(embed)-- + {% block foo %} + {% endblock %} +--DATA-- +return [] +--EXCEPTION-- +Twig\Error\RuntimeError: Variable "macros" does not exist in "index.twig" at line 6. diff --git a/test/Twig/Tests/Fixtures/tags/macro/import_local_override.test b/test/Twig/Tests/Fixtures/tags/macro/import_local_override.test new file mode 100644 index 000000000..7cf0552f8 --- /dev/null +++ b/test/Twig/Tests/Fixtures/tags/macro/import_local_override.test @@ -0,0 +1,28 @@ +--TEST-- +"macro" tag +--TEMPLATE-- +{%- import _self as macros %} + +{% block foo %} + {%- import "macros" as macros %} + {{- macros.input('username') }} +{% endblock %} + +{% block bar %} + {{- macros.input('username') }} +{% endblock %} + +{% macro input(name) -%} + +{% endmacro %} +--TEMPLATE(macros)-- +{% macro input(name) %} + +{% endmacro %} +--DATA-- +return [] +--EXPECT-- + + + + diff --git a/test/Twig/Tests/Fixtures/tags/macro/import_macro_in_a_macro.test b/test/Twig/Tests/Fixtures/tags/macro/import_macro_in_a_macro.test index d224482e1..1851f0974 100644 --- a/test/Twig/Tests/Fixtures/tags/macro/import_macro_in_a_macro.test +++ b/test/Twig/Tests/Fixtures/tags/macro/import_macro_in_a_macro.test @@ -14,5 +14,5 @@ {% endmacro %} --DATA-- return [] ---EXCEPTION-- -Twig\Error\RuntimeError: Variable "foo" does not exist in "index.twig" at line 7. +--EXPECT-- +OK diff --git a/test/Twig/Tests/Fixtures/tags/macro/import_nested_blocks.test b/test/Twig/Tests/Fixtures/tags/macro/import_nested_blocks.test index 35906f851..821f64bf7 100644 --- a/test/Twig/Tests/Fixtures/tags/macro/import_nested_blocks.test +++ b/test/Twig/Tests/Fixtures/tags/macro/import_nested_blocks.test @@ -15,4 +15,4 @@ --DATA-- return [] --EXCEPTION-- -Twig\Error\RuntimeError: Accessing \Twig\Template attributes is forbidden in "index.twig" at line 6. +Twig\Error\RuntimeError: Variable "lmacros" does not exist in "index.twig" at line 6. diff --git a/test/Twig/Tests/Fixtures/tags/macro/import_nested_blocks_with_global_macro.test b/test/Twig/Tests/Fixtures/tags/macro/import_nested_blocks_with_global_macro.test index 1f0e74ccd..697d665f8 100644 --- a/test/Twig/Tests/Fixtures/tags/macro/import_nested_blocks_with_global_macro.test +++ b/test/Twig/Tests/Fixtures/tags/macro/import_nested_blocks_with_global_macro.test @@ -14,5 +14,5 @@ {% endmacro %} --DATA-- return [] ---EXCEPTION-- -Twig\Error\RuntimeError: Accessing \Twig\Template attributes is forbidden in "index.twig" at line 6. +--EXPECT-- + diff --git a/test/Twig/Tests/Fixtures/tags/macro/import_same_parent_and_child.test b/test/Twig/Tests/Fixtures/tags/macro/import_same_parent_and_child.test new file mode 100644 index 000000000..8d9b3caa4 --- /dev/null +++ b/test/Twig/Tests/Fixtures/tags/macro/import_same_parent_and_child.test @@ -0,0 +1,30 @@ +--TEST-- +"import" tag +--TEMPLATE-- +{% extends "parent" %} + +{% macro anotherThing() -%} + Do it too +{% endmacro %} + +{% import _self as macros %} +{% block content %} + {{ parent() }} + {{ macros.anotherThing() }} +{% endblock %} +--TEMPLATE(parent)-- +{% macro thing() %} + Do it +{% endmacro %} + +{% import _self as macros %} +{% block content %} + {{ macros.thing() }} +{% endblock %} +--DATA-- +return [] +--EXPECT-- +Do it + + + Do it too diff --git a/test/Twig/Tests/Node/BlockTest.php b/test/Twig/Tests/Node/BlockTest.php index 8b4a1eca4..c91a4d0f7 100644 --- a/test/Twig/Tests/Node/BlockTest.php +++ b/test/Twig/Tests/Node/BlockTest.php @@ -34,6 +34,7 @@ class Twig_Tests_Node_BlockTest extends NodeTestCase // line 1 public function block_foo(\$context, array \$blocks = []) { + \$macros = \$this->macros; echo "foo"; } EOF diff --git a/test/Twig/Tests/Node/ImportTest.php b/test/Twig/Tests/Node/ImportTest.php index 2118bff59..f811bfb4c 100644 --- a/test/Twig/Tests/Node/ImportTest.php +++ b/test/Twig/Tests/Node/ImportTest.php @@ -36,7 +36,7 @@ class Twig_Tests_Node_ImportTest extends NodeTestCase $tests[] = [$node, <<loadTemplate("foo.twig", null, 1)->unwrap(); +\$macros["macro"] = \$this->macros["macro"] = \$this->loadTemplate("foo.twig", null, 1)->unwrap(); EOF ]; diff --git a/test/Twig/Tests/Node/MacroTest.php b/test/Twig/Tests/Node/MacroTest.php index 1dda543cd..afa68adf4 100644 --- a/test/Twig/Tests/Node/MacroTest.php +++ b/test/Twig/Tests/Node/MacroTest.php @@ -43,6 +43,7 @@ class Twig_Tests_Node_MacroTest extends NodeTestCase // line 1 public function macro_foo(\$__foo__ = null, \$__bar__ = "Foo", ...\$__varargs__) { + \$macros = \$this->macros; \$context = \$this->env->mergeGlobals([ "foo" => \$__foo__, "bar" => \$__bar__, diff --git a/test/Twig/Tests/Node/ModuleTest.php b/test/Twig/Tests/Node/ModuleTest.php index d9d1ecf62..7a5ae1636 100644 --- a/test/Twig/Tests/Node/ModuleTest.php +++ b/test/Twig/Tests/Node/ModuleTest.php @@ -74,6 +74,7 @@ use Twig\Template; class __TwigTemplate_%x extends Template { private \$source; + private \$macros = []; public function __construct(Environment \$env) { @@ -89,6 +90,7 @@ class __TwigTemplate_%x extends Template protected function doDisplay(array \$context, array \$blocks = []) { + \$macros = \$this->macros; // line 1 echo "foo"; } @@ -100,7 +102,7 @@ class __TwigTemplate_%x extends Template public function getDebugInfo() { - return array ( 35 => 1,); + return array ( 37 => 1,); } public function getSourceContext() @@ -136,6 +138,7 @@ use Twig\Template; class __TwigTemplate_%x extends Template { private \$source; + private \$macros = []; public function __construct(Environment \$env) { @@ -155,8 +158,9 @@ class __TwigTemplate_%x extends Template protected function doDisplay(array \$context, array \$blocks = []) { + \$macros = \$this->macros; // line 2 - \$context["macro"] = \$this->loadTemplate("foo.twig", "foo.twig", 2)->unwrap(); + \$macros["macro"] = \$this->macros["macro"] = \$this->loadTemplate("foo.twig", "foo.twig", 2)->unwrap(); // line 1 \$this->parent = \$this->loadTemplate("layout.twig", "foo.twig", 1); \$this->parent->display(\$context, array_merge(\$this->blocks, \$blocks)); @@ -174,7 +178,7 @@ class __TwigTemplate_%x extends Template public function getDebugInfo() { - return array ( 41 => 1, 39 => 2, 33 => 1,); + return array ( 43 => 1, 41 => 2, 34 => 1,); } public function getSourceContext() @@ -215,6 +219,7 @@ use Twig\Template; class __TwigTemplate_%x extends Template { private \$source; + private \$macros = []; public function __construct(Environment \$env) { @@ -234,6 +239,7 @@ class __TwigTemplate_%x extends Template protected function doDisplay(array \$context, array \$blocks = []) { + \$macros = \$this->macros; // line 4 \$context["foo"] = "foo"; // line 2 @@ -252,7 +258,7 @@ class __TwigTemplate_%x extends Template public function getDebugInfo() { - return array ( 41 => 2, 39 => 4, 33 => 2,); + return array ( 43 => 2, 41 => 4, 34 => 2,); } public function getSourceContext()