Resolve constant parent templates once instead of on every lookup

This commit is contained in:
Fabien Potencier
2026-09-11 06:10:11 -07:00
parent 4de6bc3b06
commit c53b6468c9
4 changed files with 25 additions and 10 deletions
+1
View File
@@ -8,6 +8,7 @@
* Fix `TemplateWrapper::streamBlock()`, `TemplateWrapper::hasBlock()`, and `TemplateWrapper::getBlockNames()` omitting environment globals
* Fix exceptions from dynamic parent expressions escaping without template context
* Add the `BlockChain` class to compose blocks from multiple templates without using template internals
* Fix `TemplateWrapper::hasBlock()` and `TemplateWrapper::getBlockNames()` losing the `extends` line when the parent template does not exist
* Fix an output buffer leak when a parent block rendered in an expression throws in non-yield mode
* Add the `HtmlExtension::htmlAttrValue()` method to resolve a single HTML attribute value the way the `html_attr` function renders it
* Fix `html_attr` JSON encoding a `Stringable` value in a `data-*` attribute instead of using its string representation
+10 -9
View File
@@ -143,17 +143,18 @@ final class ModuleNode extends Node implements CoercesChildrenToStringInterface
;
if ($parent instanceof ConstantExpression) {
$compiler->subcompile($parent);
} else {
$compiler
->raw('$this->load(')
->subcompile($parent)
->raw(', ')
->repr($parent->getTemplateLine())
->raw(')')
;
// a constant parent never depends on the context, so resolve it once
$compiler->raw('$this->parent ??= ');
}
$compiler
->raw('$this->load(')
->subcompile($parent)
->raw(', ')
->repr($parent->getTemplateLine())
->raw(')')
;
$compiler
->raw(";\n")
->outdent()
+1 -1
View File
@@ -232,7 +232,7 @@ class __TwigTemplate_%x extends Template
protected function doGetParent(array \$context): bool|string|Template|TemplateWrapper
{
// line 1
return "layout.twig";
return \$this->parent ??= \$this->load("layout.twig", 1);
}
protected function doDisplay(array \$context, array \$blocks = []): iterable
+13
View File
@@ -139,6 +139,19 @@ class TemplateWrapperTest extends TestCase
yield 'exception thrown by parent expression' => ['{% extends boom() %}', new TwigFunction('boom', static function (): never { throw new \DomainException('kaboom'); }), \DomainException::class];
}
public function testBlockIntrospectionReportsTheExtendsLineForAMissingParent(): void
{
$twig = new Environment(new ArrayLoader(['index' => "\n\n{% extends 'missing' %}"]));
try {
$twig->load('index')->hasBlock('foo');
$this->fail('Introspecting a template with a missing parent must fail.');
} catch (LoaderError $e) {
$this->assertSame('Template "missing" is not defined.', $e->getRawMessage());
$this->assertSame(3, $e->getTemplateLine());
}
}
public function testRenderBlock(): void
{
$twig = new Environment(new ArrayLoader([