Reuse the template wrapper of an already loaded template

This commit is contained in:
Fabien Potencier
2026-09-22 16:56:27 +01:00
parent ff4a1c6575
commit 6f94a47ce1
3 changed files with 66 additions and 1 deletions
+1
View File
@@ -1,6 +1,7 @@
# 3.30.0 (2026-XX-XX)
* Fix the escaping safe analysis retaining every compiled template node for the lifetime of the environment
* Speed up loading a template that the environment has already loaded
* Fix `IntlExtension` letting the pattern derived from a date formatter prototype override an explicit locale
* Fix `IntlExtension` not honoring the locale of a date formatter prototype configured with no date and time styles
* Speed up macro calls
+8 -1
View File
@@ -72,6 +72,10 @@ class Environment
private $useYield;
private $defaultRuntimeLoader;
private array $hotCache = [];
/**
* @var array<string, TemplateWrapper>
*/
private array $loadedWrappers = [];
/**
* Constructor.
@@ -255,6 +259,7 @@ class Environment
{
$cls = $this->getTemplateClass($name);
$this->hotCache[$name] = $cls.'_'.bin2hex(random_bytes(16));
unset($this->loadedWrappers[$cls]);
if ($this->cache instanceof RemovableCacheInterface) {
$this->cache->remove($name, $cls);
@@ -374,7 +379,9 @@ class Environment
return $name;
}
return new TemplateWrapper($this, $this->loadTemplate($this->getTemplateClass($name), $name));
$cls = $this->getTemplateClass($name);
return $this->loadedWrappers[$cls] ??= new TemplateWrapper($this, $this->loadTemplate($cls, $name));
}
/**
+57
View File
@@ -598,6 +598,63 @@ EOF
FilesystemHelper::removeDir($dir);
}
}
public function testLoadReusesTheWrapperOfAnAlreadyLoadedTemplate(): void
{
$twig = new Environment(new ArrayLoader(['index.twig' => 'x']));
$this->assertSame($twig->load('index.twig'), $twig->load('index.twig'));
}
public function testRepeatedlyRemovingACacheDoesNotAccumulateWrappers(): void
{
$dir = sys_get_temp_dir().'/twig-wrapper-growth-test';
if (is_dir($dir)) {
FilesystemHelper::removeDir($dir);
}
mkdir($dir.'/cache', 0777, true);
file_put_contents($dir.'/index.twig', 'x');
try {
$twig = new Environment(new FilesystemLoader($dir), ['cache' => $dir.'/cache', 'auto_reload' => false]);
for ($i = 0; $i < 10; ++$i) {
$twig->load('index.twig');
$twig->removeCache('index.twig');
}
$twig->load('index.twig');
$wrappers = (new \ReflectionObject($twig))->getProperty('loadedWrappers')->getValue($twig);
$this->assertCount(1, $wrappers);
} finally {
FilesystemHelper::removeDir($dir);
}
}
public function testLoadFollowsALoaderThatResolvesANameDifferently(): void
{
$dir = sys_get_temp_dir().'/twig-loader-change-test';
if (is_dir($dir)) {
FilesystemHelper::removeDir($dir);
}
mkdir($dir.'/a', 0777, true);
mkdir($dir.'/b', 0777, true);
file_put_contents($dir.'/a/index.twig', 'a');
file_put_contents($dir.'/b/index.twig', 'b');
try {
$loader = new FilesystemLoader([$dir.'/a']);
$twig = new Environment($loader, ['cache' => false]);
$this->assertSame('a', $twig->load('index.twig')->render([]));
// the loader now resolves the same name to another file
$loader->prependPath($dir.'/b');
$this->assertSame('b', $twig->load('index.twig')->render([]));
} finally {
FilesystemHelper::removeDir($dir);
}
}
}
class EnvironmentTest_Extension_WithGlobals extends AbstractExtension