Keep captured block definitions supported

This commit is contained in:
Fabien Potencier
2026-06-04 20:35:15 +02:00
parent 16e5a937ed
commit a69d3dc71e
5 changed files with 17 additions and 43 deletions
-7
View File
@@ -301,13 +301,6 @@ Templates
in ``Environment::resolveTemplate()`` and ``Environment::load()``); pass
instances of ``Twig\TemplateWrapper`` instead.
* Having a "block" definition nested in another node that captures the output
(like "set") in a child template is deprecated in Twig 3.14 and will throw
in Twig 4.0. In Twig 4.0, root-level "block" definitions in child
templates must be direct children of the template body. Blocks nested in a
capture inside another block body or in a template that does not extend
another one are not affected.
* Using a ``macro``, ``extends``, or ``use`` tag outside the root of a template
(for instance nested under an ``if`` or inside a ``block`` or ``macro``) is
deprecated as of Twig 3.27 and will throw in Twig 4.0. These tags have a
+14 -10
View File
@@ -42,6 +42,7 @@ final class CorrectnessNodeVisitor implements NodeVisitorInterface
private bool $hasParent = false;
private int $blockDepth = 0;
private int $macroDepth = 0;
private int $capturingNodeDepth = 0;
private bool $hasExtends = false;
public function enterNode(Node $node, Environment $env): Node
@@ -104,6 +105,7 @@ final class CorrectnessNodeVisitor implements NodeVisitorInterface
$this->hasParent = false;
$this->blockDepth = 0;
$this->macroDepth = 0;
$this->capturingNodeDepth = 0;
$this->hasExtends = false;
}
@@ -122,6 +124,10 @@ final class CorrectnessNodeVisitor implements NodeVisitorInterface
private function enterScope(Node $node): void
{
if ($node instanceof NodeCaptureInterface) {
++$this->capturingNodeDepth;
}
if ($node instanceof BlockNode) {
++$this->blockDepth;
} elseif ($node instanceof MacroNode) {
@@ -133,6 +139,10 @@ final class CorrectnessNodeVisitor implements NodeVisitorInterface
private function leaveScope(Node $node): void
{
if ($node instanceof NodeCaptureInterface) {
--$this->capturingNodeDepth;
}
if ($node instanceof BlockNode) {
--$this->blockDepth;
} elseif ($node instanceof MacroNode) {
@@ -173,20 +183,14 @@ final class CorrectnessNodeVisitor implements NodeVisitorInterface
{
// A "block" definition nested under an output-wrapping tag is registered globally
// regardless of that tag, so the nesting is misleading. This only matters at the root
// of a child template's body: once inside a block or a macro (or in a standalone
// template), the block is rendered in place and behaves like any other, so there is
// no restriction.
if (!$this->hasParent || $this->blockDepth || $this->macroDepth || !$this->tagStack) {
// of a child template's body: once inside a block, a macro, an output capture, or in
// a standalone template, the block is rendered in place and behaves like any other.
if (!$this->hasParent || $this->blockDepth || $this->macroDepth || $this->capturingNodeDepth || !$this->tagStack) {
return;
}
$tag = $this->tagStack[array_key_last($this->tagStack)];
if (!$tag instanceof NodeCaptureInterface) {
throw new SyntaxError(\sprintf('A "block" tag cannot be under a "%s" tag (line %d).', $tag->getNodeTag(), $tag->getTemplateLine()), $node->getTemplateLine(), $node->getSourceContext());
}
// capturing tags (e.g. "set") used to allow this, so only deprecate it
trigger_deprecation('twig/twig', '3.14', \sprintf('Having a "block" tag under a "%s" tag (line %d) is deprecated in %s at line %d.', $tag->getNodeTag(), $tag->getTemplateLine(), $node->getSourceContext()->getName(), $node->getTemplateLine()));
throw new SyntaxError(\sprintf('A "block" tag cannot be under a "%s" tag (line %d).', $tag->getNodeTag(), $tag->getTemplateLine()), $node->getTemplateLine(), $node->getSourceContext());
}
/**
@@ -1,19 +0,0 @@
--TEST--
capturing "block" tag with "extends" tag
--DEPRECATION--
Since twig/twig 3.14: Having a "block" tag under a "set" tag (line 4) is deprecated in index.twig at line 5.
--TEMPLATE--
{% extends "layout.twig" %}
{% set foo %}
{%- block content %}FOO{% endblock %}
{% endset %}
{% block content1 %}BAR{{ foo }}{% endblock %}
--TEMPLATE(layout.twig)--
{% block content %}{% endblock %}
{% block content1 %}{% endblock %}
--DATA--
return []
--EXPECT--
FOOBARFOO
@@ -3,10 +3,8 @@ capturing "block" tag with "extends" tag
--TEMPLATE--
{% extends "layout.twig" %}
{% block content %}FOO{% endblock %}
{% set foo -%}
{{ block('content') }}
{% set foo %}
{%- block content %}FOO{% endblock %}
{% endset %}
{% block content1 %}BAR{{ foo }}{% endblock %}
@@ -1,7 +1,5 @@
--TEST--
capturing "block" tag is still detected after a sibling tag at the same level
--DEPRECATION--
Since twig/twig 3.14: Having a "block" tag under a "set" tag (line 3) is deprecated in index.twig at line 3.
capturing "block" tag still works after a sibling tag at the same level
--TEMPLATE--
{% extends "layout.twig" %}
{% set foo %}{% if true %}{% endif %}{% block content %}FOO{% endblock %}{% endset %}