mirror of
https://github.com/twigphp/Twig.git
synced 2026-09-13 10:56:38 +00:00
bug #4797 Make embeds deterministic (itsalmostchristmas)
This PR was squashed before being merged into the 3.x branch.
Discussion
----------
Make embeds deterministic
Using `{% embed 'foo.twig' %}...{% endembed %}` makes compiled templates non-deterministic. This poses a problem for developers who wish to provide reproducible, pre-compiled builds of Twig templates.
The cause is `mt_rand()` used to generate template indices. The change I propose uses an incrementing counter instead. To keep tests passing, the number must be non-zero and unique per PHP process (reusing indices across `Twig\Parser` instances causes fatal errors).
A demonstration of the problem is available on the [Twig Playground](https://twig.symfony.com/play?data=eyJ0ZW1wbGF0ZXMiOltbImluZGV4LnR3aWciLCJ7JSBlbWJlZCAnZGlhbG9nLnR3aWcnICV9XG4gICAgeyUgYmxvY2sgbWVzc2FnZSAlfVxuICAgICAgICBUYXNrIGZhaWxlZCBzdWNjZXNzZnVsbHlcbiAgICB7JSBlbmRibG9jayAlfVxueyUgZW5kZW1iZWQgJX0iXSxbImRpYWxvZy50d2lnIiwiPGRpYWxvZz57JSBibG9jayBtZXNzYWdlICV9eyUgZW5kYmxvY2sgJX08L2RpYWxvZz4iXV0sImNvbnRleHQiOnt9LCJ2ZXJzaW9uIjoiMy4yMS4xIiwib3B0aW9ucyI6eyJzdHJpY3RfdmFyaWFibGVzIjp0cnVlLCJjaGFyc2V0IjoiVVRGLTgiLCJhdXRvZXNjYXBlIjoiIn19), where the compiled output of `index.twig` differs on every recompilation.
Commits
-------
2b4aa4458a Make embeds deterministic
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# 3.24.1 (2026-XX-XX)
|
||||
# 3.25.0 (2026-XX-XX)
|
||||
|
||||
* n/a
|
||||
* Use deterministic suffixes for generated embed classes
|
||||
|
||||
# 3.24.0 (2026-03-17)
|
||||
|
||||
|
||||
+8
-2
@@ -54,6 +54,7 @@ class Parser
|
||||
private $importedSymbols;
|
||||
private $traits;
|
||||
private $embeddedTemplates = [];
|
||||
private int $lastEmbedIndex = 0;
|
||||
private $varNameSalt = 0;
|
||||
private $ignoreUnknownTwigCallables = false;
|
||||
private ExpressionParsers $parsers;
|
||||
@@ -81,8 +82,13 @@ class Parser
|
||||
*/
|
||||
public function parse(TokenStream $stream, $test = null, bool $dropNeedle = false): ModuleNode
|
||||
{
|
||||
// reset on root parse() calls only, so the counter spans nested/reentrant parses
|
||||
if (!$this->stack) {
|
||||
$this->lastEmbedIndex = 0;
|
||||
}
|
||||
|
||||
$vars = get_object_vars($this);
|
||||
unset($vars['stack'], $vars['env'], $vars['handlers'], $vars['visitors'], $vars['expressionParser'], $vars['reservedMacroNames'], $vars['varNameSalt']);
|
||||
unset($vars['stack'], $vars['env'], $vars['handlers'], $vars['visitors'], $vars['expressionParser'], $vars['reservedMacroNames'], $vars['lastEmbedIndex'], $vars['varNameSalt']);
|
||||
$this->stack[] = $vars;
|
||||
|
||||
// node visitors
|
||||
@@ -319,7 +325,7 @@ class Parser
|
||||
*/
|
||||
public function embedTemplate(ModuleNode $template)
|
||||
{
|
||||
$template->setIndex(mt_rand());
|
||||
$template->setIndex(++$this->lastEmbedIndex);
|
||||
|
||||
$this->embeddedTemplates[] = $template;
|
||||
}
|
||||
|
||||
@@ -206,6 +206,20 @@ EOF, 'index')));
|
||||
$this->assertTrue($argumentNodes->getNode(3)->getAttribute('value'));
|
||||
}
|
||||
|
||||
public function testEmbeddedTemplatesHaveSequentialIndices(): void
|
||||
{
|
||||
$template = new Source('{% embed "first" %}{% endembed %}{% embed "second" %}{% endembed %}', 'index');
|
||||
$lexer = new Lexer(new Environment(new ArrayLoader()));
|
||||
$stream = $lexer->tokenize($template);
|
||||
|
||||
$embeds = $this->getParser()
|
||||
->parse($stream)
|
||||
->getAttribute('embedded_templates');
|
||||
|
||||
$this->assertSame(1, $embeds->getNode(0)->getAttribute('index'));
|
||||
$this->assertSame(2, $embeds->getNode(1)->getAttribute('index'));
|
||||
}
|
||||
|
||||
protected function getParser()
|
||||
{
|
||||
$parser = new Parser(new Environment(new ArrayLoader()));
|
||||
|
||||
Reference in New Issue
Block a user