From be36fee09edaebb50953f95524eefe3a08aa07b7 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 28 Jul 2026 11:53:50 +0200 Subject: [PATCH] Rename macro variable AST nodes --- CHANGELOG | 1 + doc/deprecated.rst | 5 +++ .../Infix/DotExpressionParser.php | 6 +-- .../Expression/MacroReferenceExpression.php | 6 +-- .../Variable/AssignMacroVariable.php | 44 +++++++++++++++++++ .../Variable/AssignTemplateVariable.php | 31 +++---------- .../Expression/Variable/MacroVariable.php | 42 ++++++++++++++++++ .../Expression/Variable/TemplateVariable.php | 31 ++++--------- src/Node/ImportNode.php | 11 ++--- src/Parser.php | 14 +++--- src/TokenParser/FromTokenParser.php | 6 +-- src/TokenParser/ImportTokenParser.php | 6 +-- tests/Node/Expression/MacroReferenceTest.php | 31 +++++++++++-- tests/Node/ImportTest.php | 33 +++++++++++++- tests/Node/ModuleTest.php | 6 +-- 15 files changed, 193 insertions(+), 80 deletions(-) create mode 100644 src/Node/Expression/Variable/AssignMacroVariable.php create mode 100644 src/Node/Expression/Variable/MacroVariable.php diff --git a/CHANGELOG b/CHANGELOG index 8e08a7382..3599adb98 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ * Fix array access with a `Stringable` key coercing the key to string for `ArrayAccess` objects that use object keys (such as `SplObjectStorage`) * Fix duplicated macro argument names triggering a PHP fatal error instead of a `SyntaxError` * Deprecate defining a macro more than once in the same template + * Deprecate `TemplateVariable` and `AssignTemplateVariable`; use `MacroVariable` and `AssignMacroVariable` instead # 3.28.0 (2026-07-03) diff --git a/doc/deprecated.rst b/doc/deprecated.rst index 700f8be38..b26c228d9 100644 --- a/doc/deprecated.rst +++ b/doc/deprecated.rst @@ -180,6 +180,11 @@ Nodes * The ``MethodCallExpression`` class is deprecated as of Twig 3.15, use ``MacroReferenceExpression`` instead. +* The ``Twig\Node\Expression\Variable\TemplateVariable`` and + ``Twig\Node\Expression\Variable\AssignTemplateVariable`` classes are + deprecated as of Twig 3.29; use ``MacroVariable`` and + ``AssignMacroVariable`` instead. + * The ``Twig\Node\Expression\TempNameExpression`` class is deprecated as of Twig 3.15; use ``Twig\Node\Expression\Variable\LocalVariable`` instead. diff --git a/src/ExpressionParser/Infix/DotExpressionParser.php b/src/ExpressionParser/Infix/DotExpressionParser.php index 287fbd18c..c299cf112 100644 --- a/src/ExpressionParser/Infix/DotExpressionParser.php +++ b/src/ExpressionParser/Infix/DotExpressionParser.php @@ -23,7 +23,7 @@ use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\GetAttrExpression; use Twig\Node\Expression\MacroReferenceExpression; use Twig\Node\Expression\NameExpression; -use Twig\Node\Expression\Variable\TemplateVariable; +use Twig\Node\Expression\Variable\MacroVariable; use Twig\Parser; use Twig\Template; use Twig\Token; @@ -77,11 +77,11 @@ final class DotExpressionParser extends AbstractExpressionParser implements Infi && \is_string($name = $attribute->getAttribute('value')) && preg_match('#^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$#D', $name) ) { - return new MacroReferenceExpression(new TemplateVariable($expr->getAttribute('name'), $expr->getTemplateLine()), 'macro_'.$name, $arguments, $expr->getTemplateLine()); + return new MacroReferenceExpression(new MacroVariable($expr->getAttribute('name'), $expr->getTemplateLine()), 'macro_'.$name, $arguments, $expr->getTemplateLine()); } if ($isMacroTarget && !$attribute instanceof ConstantExpression) { - return new MacroReferenceExpression(new TemplateVariable($expr->getAttribute('name'), $expr->getTemplateLine()), $attribute, $arguments, $expr->getTemplateLine()); + return new MacroReferenceExpression(new MacroVariable($expr->getAttribute('name'), $expr->getTemplateLine()), $attribute, $arguments, $expr->getTemplateLine()); } return new GetAttrExpression($expr, $attribute, $arguments, $type, $lineno, $nullSafe); diff --git a/src/Node/Expression/MacroReferenceExpression.php b/src/Node/Expression/MacroReferenceExpression.php index fc7a7afcc..8a813692e 100644 --- a/src/Node/Expression/MacroReferenceExpression.php +++ b/src/Node/Expression/MacroReferenceExpression.php @@ -13,7 +13,7 @@ namespace Twig\Node\Expression; use Twig\Compiler; use Twig\Node\CoercesChildrenToStringInterface; -use Twig\Node\Expression\Variable\TemplateVariable; +use Twig\Node\Expression\Variable\MacroVariable; /** * Represents a macro call node. @@ -30,7 +30,7 @@ class MacroReferenceExpression extends AbstractExpression implements SupportDefi * call, an expression resolving to the macro name (without the * "macro_" prefix, which is added at runtime) */ - public function __construct(TemplateVariable $template, string|AbstractExpression $name, AbstractExpression $arguments, int $lineno) + public function __construct(MacroVariable $template, string|AbstractExpression $name, AbstractExpression $arguments, int $lineno) { $nodes = ['template' => $template, 'arguments' => $arguments]; $attributes = ['name' => null]; @@ -55,7 +55,7 @@ class MacroReferenceExpression extends AbstractExpression implements SupportDefi { // The template node must not be deep-cloned because its name is // lazily generated during compilation and must stay in sync with - // the AssignTemplateVariable that populates the $macros array. + // the AssignMacroVariable that populates the $macros array. $template = $this->nodes['template']; parent::__clone(); $this->nodes['template'] = $template; diff --git a/src/Node/Expression/Variable/AssignMacroVariable.php b/src/Node/Expression/Variable/AssignMacroVariable.php new file mode 100644 index 000000000..04ab0989d --- /dev/null +++ b/src/Node/Expression/Variable/AssignMacroVariable.php @@ -0,0 +1,44 @@ + $var], ['global' => $global], $var->getTemplateLine()); + } + + public function compile(Compiler $compiler): void + { + /** @var MacroVariable $var */ + $var = $this->nodes['var']; + + $compiler + ->addDebugInfo($this) + ->write('$macros[') + ->string($var->getName($compiler)) + ->raw('] = ') + ; + + if ($this->getAttribute('global')) { + $compiler + ->raw('$this->macros[') + ->string($var->getName($compiler)) + ->raw('] = ') + ; + } + } +} diff --git a/src/Node/Expression/Variable/AssignTemplateVariable.php b/src/Node/Expression/Variable/AssignTemplateVariable.php index 98bcdc10e..4b82e240a 100644 --- a/src/Node/Expression/Variable/AssignTemplateVariable.php +++ b/src/Node/Expression/Variable/AssignTemplateVariable.php @@ -11,34 +11,15 @@ namespace Twig\Node\Expression\Variable; -use Twig\Compiler; -use Twig\Node\Expression\AbstractExpression; - -final class AssignTemplateVariable extends AbstractExpression +/** + * @deprecated since Twig 3.29, use AssignMacroVariable instead + */ +final class AssignTemplateVariable extends AssignMacroVariable { public function __construct(TemplateVariable $var, bool $global = true) { - parent::__construct(['var' => $var], ['global' => $global], $var->getTemplateLine()); - } + trigger_deprecation('twig/twig', '3.29', 'The "%s" class is deprecated, use "%s" instead.', self::class, AssignMacroVariable::class); - public function compile(Compiler $compiler): void - { - /** @var TemplateVariable $var */ - $var = $this->nodes['var']; - - $compiler - ->addDebugInfo($this) - ->write('$macros[') - ->string($var->getName($compiler)) - ->raw('] = ') - ; - - if ($this->getAttribute('global')) { - $compiler - ->raw('$this->macros[') - ->string($var->getName($compiler)) - ->raw('] = ') - ; - } + parent::__construct($var, $global); } } diff --git a/src/Node/Expression/Variable/MacroVariable.php b/src/Node/Expression/Variable/MacroVariable.php new file mode 100644 index 000000000..81da75409 --- /dev/null +++ b/src/Node/Expression/Variable/MacroVariable.php @@ -0,0 +1,42 @@ +getAttribute('name')) { + $this->setAttribute('name', $compiler->getVarName()); + } + + return $this->getAttribute('name'); + } + + public function compile(Compiler $compiler): void + { + $name = $this->getName($compiler); + + if ('_self' === $name) { + $compiler->raw('$this'); + } else { + $compiler + ->raw('$macros[') + ->string($name) + ->raw(']') + ; + } + } +} diff --git a/src/Node/Expression/Variable/TemplateVariable.php b/src/Node/Expression/Variable/TemplateVariable.php index 4dd066273..aa99bd49d 100644 --- a/src/Node/Expression/Variable/TemplateVariable.php +++ b/src/Node/Expression/Variable/TemplateVariable.php @@ -11,32 +11,17 @@ namespace Twig\Node\Expression\Variable; -use Twig\Compiler; -use Twig\Node\Expression\TempNameExpression; - -class TemplateVariable extends TempNameExpression +/** + * @deprecated since Twig 3.29, use MacroVariable instead + */ +class TemplateVariable extends MacroVariable { - public function getName(Compiler $compiler): string + public function __construct(string|int|null $name, int $lineno) { - if (null === $this->getAttribute('name')) { - $this->setAttribute('name', $compiler->getVarName()); + if (self::class === static::class) { + trigger_deprecation('twig/twig', '3.29', 'The "%s" class is deprecated, use "%s" instead.', self::class, MacroVariable::class); } - return $this->getAttribute('name'); - } - - public function compile(Compiler $compiler): void - { - $name = $this->getName($compiler); - - if ('_self' === $name) { - $compiler->raw('$this'); - } else { - $compiler - ->raw('$macros[') - ->string($name) - ->raw(']') - ; - } + parent::__construct($name, $lineno); } } diff --git a/src/Node/ImportNode.php b/src/Node/ImportNode.php index ab7fe0ff5..154043b7f 100644 --- a/src/Node/ImportNode.php +++ b/src/Node/ImportNode.php @@ -14,8 +14,9 @@ namespace Twig\Node; use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Node\Expression\AbstractExpression; -use Twig\Node\Expression\Variable\AssignTemplateVariable; +use Twig\Node\Expression\Variable\AssignMacroVariable; use Twig\Node\Expression\Variable\ContextVariable; +use Twig\Node\Expression\Variable\MacroVariable; /** * Represents an import node. @@ -25,16 +26,16 @@ use Twig\Node\Expression\Variable\ContextVariable; #[YieldReady] class ImportNode extends Node implements CoercesChildrenToStringInterface { - public function __construct(AbstractExpression $expr, AbstractExpression|AssignTemplateVariable $var, int $lineno) + public function __construct(AbstractExpression $expr, AbstractExpression|AssignMacroVariable $var, int $lineno) { if (\func_num_args() > 3) { trigger_deprecation('twig/twig', '3.15', \sprintf('Passing more than 3 arguments to "%s()" is deprecated.', __METHOD__)); } - if (!$var instanceof AssignTemplateVariable) { - trigger_deprecation('twig/twig', '3.15', \sprintf('Passing a "%s" instance as the second argument of "%s" is deprecated, pass a "%s" instead.', $var::class, __CLASS__, AssignTemplateVariable::class)); + if (!$var instanceof AssignMacroVariable) { + trigger_deprecation('twig/twig', '3.15', \sprintf('Passing a "%s" instance as the second argument of "%s" is deprecated, pass a "%s" instead.', $var::class, __CLASS__, AssignMacroVariable::class)); - $var = new AssignTemplateVariable($var->getAttribute('name'), $lineno); + $var = new AssignMacroVariable(new MacroVariable($var->getAttribute('name'), $lineno)); } parent::__construct(['expr' => $expr, 'var' => $var], [], $lineno); diff --git a/src/Parser.php b/src/Parser.php index 9a32eb5da..89b9c42b7 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -24,8 +24,8 @@ use Twig\Node\BlockReferenceNode; use Twig\Node\BodyNode; use Twig\Node\EmptyNode; use Twig\Node\Expression\AbstractExpression; -use Twig\Node\Expression\Variable\AssignTemplateVariable; -use Twig\Node\Expression\Variable\TemplateVariable; +use Twig\Node\Expression\Variable\AssignMacroVariable; +use Twig\Node\Expression\Variable\MacroVariable; use Twig\Node\MacroNode; use Twig\Node\ModuleNode; use Twig\Node\Node; @@ -329,19 +329,19 @@ class Parser $this->embeddedTemplates[] = $template; } - public function addImportedSymbol(string $type, string $alias, ?string $name = null, AbstractExpression|AssignTemplateVariable|null $internalRef = null): void + public function addImportedSymbol(string $type, string $alias, ?string $name = null, AbstractExpression|AssignMacroVariable|null $internalRef = null): void { - if ($internalRef && !$internalRef instanceof AssignTemplateVariable) { - trigger_deprecation('twig/twig', '3.15', 'Not passing a "%s" instance as an internal reference is deprecated ("%s" given).', __METHOD__, AssignTemplateVariable::class, $internalRef::class); + if ($internalRef && !$internalRef instanceof AssignMacroVariable) { + trigger_deprecation('twig/twig', '3.15', 'Not passing a "%s" instance as an internal reference is deprecated ("%s" given).', __METHOD__, AssignMacroVariable::class, $internalRef::class); - $internalRef = new AssignTemplateVariable(new TemplateVariable($internalRef->getAttribute('name'), $internalRef->getTemplateLine()), $internalRef->getAttribute('global')); + $internalRef = new AssignMacroVariable(new MacroVariable($internalRef->getAttribute('name'), $internalRef->getTemplateLine()), $internalRef->getAttribute('global')); } $this->importedSymbols[0][$type][$alias] = ['name' => $name, 'node' => $internalRef]; } /** - * @return array{name: string, node: AssignTemplateVariable|null}|null + * @return array{name: string, node: AssignMacroVariable|null}|null */ public function getImportedSymbol(string $type, string $alias) { diff --git a/src/TokenParser/FromTokenParser.php b/src/TokenParser/FromTokenParser.php index 1c80a1717..af58e0b15 100644 --- a/src/TokenParser/FromTokenParser.php +++ b/src/TokenParser/FromTokenParser.php @@ -12,8 +12,8 @@ namespace Twig\TokenParser; use Twig\Node\Expression\Variable\AssignContextVariable; -use Twig\Node\Expression\Variable\AssignTemplateVariable; -use Twig\Node\Expression\Variable\TemplateVariable; +use Twig\Node\Expression\Variable\AssignMacroVariable; +use Twig\Node\Expression\Variable\MacroVariable; use Twig\Node\ImportNode; use Twig\Node\Node; use Twig\Token; @@ -52,7 +52,7 @@ final class FromTokenParser extends AbstractTokenParser $stream->expect(Token::BLOCK_END_TYPE); - $internalRef = new AssignTemplateVariable(new TemplateVariable(null, $token->getLine()), $this->parser->isMainScope()); + $internalRef = new AssignMacroVariable(new MacroVariable(null, $token->getLine()), $this->parser->isMainScope()); $node = new ImportNode($macro, $internalRef, $token->getLine()); foreach ($targets as $name => $alias) { diff --git a/src/TokenParser/ImportTokenParser.php b/src/TokenParser/ImportTokenParser.php index 6dcb7662c..500d8c391 100644 --- a/src/TokenParser/ImportTokenParser.php +++ b/src/TokenParser/ImportTokenParser.php @@ -11,8 +11,8 @@ namespace Twig\TokenParser; -use Twig\Node\Expression\Variable\AssignTemplateVariable; -use Twig\Node\Expression\Variable\TemplateVariable; +use Twig\Node\Expression\Variable\AssignMacroVariable; +use Twig\Node\Expression\Variable\MacroVariable; use Twig\Node\ImportNode; use Twig\Node\Node; use Twig\Token; @@ -31,7 +31,7 @@ final class ImportTokenParser extends AbstractTokenParser $macro = $this->parser->parseExpression(); $this->parser->getStream()->expect(Token::NAME_TYPE, 'as'); $name = $this->parser->getStream()->expect(Token::NAME_TYPE)->getValue(); - $var = new AssignTemplateVariable(new TemplateVariable($name, $token->getLine()), $this->parser->isMainScope()); + $var = new AssignMacroVariable(new MacroVariable($name, $token->getLine()), $this->parser->isMainScope()); $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); $this->parser->addImportedSymbol('template', $name); diff --git a/tests/Node/Expression/MacroReferenceTest.php b/tests/Node/Expression/MacroReferenceTest.php index e3b85c7b9..485e90142 100644 --- a/tests/Node/Expression/MacroReferenceTest.php +++ b/tests/Node/Expression/MacroReferenceTest.php @@ -12,12 +12,14 @@ namespace Twig\Tests\Node\Expression; use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; use Twig\Environment; use Twig\Loader\ArrayLoader; use Twig\Node\Expression\ArrayExpression; use Twig\Node\Expression\MacroReferenceExpression; use Twig\Node\Expression\Variable\ContextVariable; +use Twig\Node\Expression\Variable\MacroVariable; use Twig\Node\Expression\Variable\TemplateVariable; class MacroReferenceTest extends TestCase @@ -31,7 +33,7 @@ class MacroReferenceTest extends TestCase $this->expectException(\LogicException::class); $this->expectExceptionMessage(\sprintf('Macro name "%s" is not a valid PHP identifier.', $name)); - new MacroReferenceExpression(new TemplateVariable('foo', 1), $name, new ArrayExpression([], 1), 1); + new MacroReferenceExpression(new MacroVariable('foo', 1), $name, new ArrayExpression([], 1), 1); } public static function provideInvalidMacroNames(): iterable @@ -44,9 +46,32 @@ class MacroReferenceTest extends TestCase yield 'contains NUL byte' => ["foo\x00bar"]; } + #[Group('legacy')] + public function testConstructorAcceptsDeprecatedTemplateVariable(): void + { + $deprecations = []; + set_error_handler(static function (int $type, string $message) use (&$deprecations): bool { + if (\E_USER_DEPRECATED === $type) { + $deprecations[] = $message; + + return true; + } + + return false; + }); + try { + $node = new MacroReferenceExpression(new TemplateVariable('foo', 1), 'macro_foo', new ArrayExpression([], 1), 1); + } finally { + restore_error_handler(); + } + + $this->assertInstanceOf(TemplateVariable::class, $node->getNode('template')); + $this->assertSame(['Since twig/twig 3.29: The "Twig\\Node\\Expression\\Variable\\TemplateVariable" class is deprecated, use "Twig\\Node\\Expression\\Variable\\MacroVariable" instead.'], $deprecations); + } + public function testConstructorAcceptsAnExpressionAsName(): void { - $node = new MacroReferenceExpression(new TemplateVariable('foo', 1), new ContextVariable('name', 1), new ArrayExpression([], 1), 1); + $node = new MacroReferenceExpression(new MacroVariable('foo', 1), new ContextVariable('name', 1), new ArrayExpression([], 1), 1); $this->assertTrue($node->hasNode('name')); $this->assertNull($node->getAttribute('name')); @@ -58,7 +83,7 @@ class MacroReferenceTest extends TestCase $compiler = new \Twig\Compiler($env); $node = new MacroReferenceExpression( - new TemplateVariable('mac', 1), + new MacroVariable('mac', 1), new ContextVariable('name', 1), new ArrayExpression([], 1), 1, diff --git a/tests/Node/ImportTest.php b/tests/Node/ImportTest.php index cb96ba4da..23d969600 100644 --- a/tests/Node/ImportTest.php +++ b/tests/Node/ImportTest.php @@ -20,8 +20,11 @@ namespace Twig\Tests\Node; * file that was distributed with this source code. */ +use PHPUnit\Framework\Attributes\Group; use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Expression\Variable\AssignMacroVariable; use Twig\Node\Expression\Variable\AssignTemplateVariable; +use Twig\Node\Expression\Variable\MacroVariable; use Twig\Node\Expression\Variable\TemplateVariable; use Twig\Node\ImportNode; use Twig\Test\NodeTestCase; @@ -31,18 +34,44 @@ class ImportTest extends NodeTestCase public function testConstructor(): void { $macro = new ConstantExpression('foo.twig', 1); - $node = new ImportNode($macro, new AssignTemplateVariable(new TemplateVariable('macro', 1), true), 1); + $node = new ImportNode($macro, new AssignMacroVariable(new MacroVariable('macro', 1), true), 1); $this->assertEquals($macro, $node->getNode('expr')); $this->assertEquals('macro', $node->getNode('var')->getNode('var')->getAttribute('name')); } + #[Group('legacy')] + public function testConstructorAcceptsDeprecatedAssignTemplateVariable(): void + { + $deprecations = []; + set_error_handler(static function (int $type, string $message) use (&$deprecations): bool { + if (\E_USER_DEPRECATED === $type) { + $deprecations[] = $message; + + return true; + } + + return false; + }); + try { + $node = new ImportNode(new ConstantExpression('foo.twig', 1), new AssignTemplateVariable(new TemplateVariable('macro', 1), true), 1); + } finally { + restore_error_handler(); + } + + $this->assertInstanceOf(AssignTemplateVariable::class, $node->getNode('var')); + $this->assertSame([ + 'Since twig/twig 3.29: The "Twig\\Node\\Expression\\Variable\\TemplateVariable" class is deprecated, use "Twig\\Node\\Expression\\Variable\\MacroVariable" instead.', + 'Since twig/twig 3.29: The "Twig\\Node\\Expression\\Variable\\AssignTemplateVariable" class is deprecated, use "Twig\\Node\\Expression\\Variable\\AssignMacroVariable" instead.', + ], $deprecations); + } + public static function provideTests(): iterable { $tests = []; $macro = new ConstantExpression('foo.twig', 1); - $node = new ImportNode($macro, new AssignTemplateVariable(new TemplateVariable('macro', 1), true), 1); + $node = new ImportNode($macro, new AssignMacroVariable(new MacroVariable('macro', 1), true), 1); $tests[] = [$node, <<