From c6f1ef95219b1f8f7c593cff005f4002b53d60ac Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 27 Sep 2026 20:06:42 +0200 Subject: [PATCH] Fix the deprecated tag generating invalid PHP for an integer message --- CHANGELOG | 1 + src/Node/DeprecatedNode.php | 32 ++++++---------------- tests/Node/DeprecatedTest.php | 51 +++++++++++++++++++++++++++++------ 3 files changed, 52 insertions(+), 32 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ff50af4b7..5a87c2101 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,7 @@ * Speed up reading object attributes backed by getters or class constants * Fix the sandbox not reporting the line of a rejected `guard` tag * Fix the spread operator compiling to invalid PHP outside sequences, mappings, and call arguments + * Fix the `deprecated` tag generating invalid PHP for an integer message # 3.30.0 (2026-09-25) diff --git a/src/Node/DeprecatedNode.php b/src/Node/DeprecatedNode.php index fea8d355b..a45efe7ba 100644 --- a/src/Node/DeprecatedNode.php +++ b/src/Node/DeprecatedNode.php @@ -14,7 +14,6 @@ namespace Twig\Node; use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Node\Expression\AbstractExpression; -use Twig\Node\Expression\ConstantExpression; /** * Represents a deprecated node. @@ -33,17 +32,6 @@ class DeprecatedNode extends Node implements CoercesChildrenToStringInterface { $compiler->addDebugInfo($this); - $expr = $this->getNode('expr'); - - if (!$expr instanceof ConstantExpression) { - $varName = $compiler->getVarName(); - $compiler - ->write(\sprintf('$%s = ', $varName)) - ->subcompile($expr) - ->raw(";\n") - ; - } - $compiler->write('trigger_deprecation('); if ($this->hasNode('package')) { $compiler->subcompile($this->getNode('package')); @@ -56,24 +44,20 @@ class DeprecatedNode extends Node implements CoercesChildrenToStringInterface } else { $compiler->raw("''"); } - $compiler->raw(', '); - - if ($expr instanceof ConstantExpression) { - $compiler->subcompile($expr); - } else { - $compiler->write(\sprintf('$%s', $varName)); - } - $compiler - ->raw('.') - ->string(\sprintf(' in "%s" at line %d.', $this->getTemplateName(), $this->getTemplateLine())) - ->raw(");\n") + ->raw(', sprintf(') + ->string(\sprintf('%%s in "%%s" at line %d.', $this->getTemplateLine())) + ->raw(', ') + ->subcompile($this->getNode('expr')) + ->raw(', ') + ->string($this->getTemplateName()) + ->raw("));\n") ; } public function getStringCoercedChildNames(): array { - // the message is concatenated with `.`, and `package` / `version` are typed `string` on trigger_deprecation() + // the message is formatted by `sprintf()`, and `package` / `version` are typed `string` on trigger_deprecation() $names = ['expr']; if ($this->hasNode('package')) { $names[] = 'package'; diff --git a/tests/Node/DeprecatedTest.php b/tests/Node/DeprecatedTest.php index 5ae924270..2d4e5abd1 100644 --- a/tests/Node/DeprecatedTest.php +++ b/tests/Node/DeprecatedTest.php @@ -20,7 +20,7 @@ namespace Twig\Tests\Node; * file that was distributed with this source code. */ -use Twig\Compiler; +use PHPUnit\Framework\Attributes\Group; use Twig\Environment; use Twig\Loader\ArrayLoader; use Twig\Node\DeprecatedNode; @@ -43,6 +43,36 @@ class DeprecatedTest extends NodeTestCase $this->assertEquals($expr, $node->getNode('expr')); } + #[Group('legacy')] + public function testTriggeredMessage(): void + { + $environment = new Environment(new ArrayLoader([ + '100%.twig' => "{% deprecated 0 %}\n{% deprecated 4 %}\n{% deprecated 'The %s template' ~ name package='foo/bar' version='1.1' %}", + ])); + + $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 { + $environment->render('100%.twig', ['name' => '%d']); + } finally { + restore_error_handler(); + } + + $this->assertSame([ + '0 in "100%.twig" at line 1.', + '4 in "100%.twig" at line 2.', + 'Since foo/bar 1.1: The %s template%d in "100%.twig" at line 3.', + ], $deprecations); + } + public static function provideTests(): iterable { $tests = []; @@ -55,7 +85,7 @@ class DeprecatedTest extends NodeTestCase $tests[] = [$node, <<setNode('package', new ConstantExpression('twig/twig', 1)); $node->setNode('version', new ConstantExpression('1.1', 1)); - $compiler = new Compiler($environment); - $varName = $compiler->getVarName(); + $tests[] = [$node, <<setSourceContext(new Source('', '100%.twig')); $tests[] = [$node, <<