From a69d3dc71e197c2bdab040d73871d337ce06b2a6 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 4 Jun 2026 20:35:15 +0200 Subject: [PATCH] Keep captured block definitions supported --- doc/deprecated.rst | 7 ------ src/NodeVisitor/CorrectnessNodeVisitor.php | 24 +++++++++++-------- .../inheritance/capturing_block.legacy.test | 19 --------------- .../tags/inheritance/capturing_block.test | 6 ++--- ...=> capturing_block_after_sibling_tag.test} | 4 +--- 5 files changed, 17 insertions(+), 43 deletions(-) delete mode 100644 tests/Fixtures/tags/inheritance/capturing_block.legacy.test rename tests/Fixtures/tags/inheritance/{capturing_block_after_sibling_tag.legacy.test => capturing_block_after_sibling_tag.test} (60%) diff --git a/doc/deprecated.rst b/doc/deprecated.rst index f0ebd77e6..d8e548b57 100644 --- a/doc/deprecated.rst +++ b/doc/deprecated.rst @@ -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 diff --git a/src/NodeVisitor/CorrectnessNodeVisitor.php b/src/NodeVisitor/CorrectnessNodeVisitor.php index 9d86bfc14..6fd4e1773 100644 --- a/src/NodeVisitor/CorrectnessNodeVisitor.php +++ b/src/NodeVisitor/CorrectnessNodeVisitor.php @@ -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()); } /** diff --git a/tests/Fixtures/tags/inheritance/capturing_block.legacy.test b/tests/Fixtures/tags/inheritance/capturing_block.legacy.test deleted file mode 100644 index d6b595b0f..000000000 --- a/tests/Fixtures/tags/inheritance/capturing_block.legacy.test +++ /dev/null @@ -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 diff --git a/tests/Fixtures/tags/inheritance/capturing_block.test b/tests/Fixtures/tags/inheritance/capturing_block.test index d2e9cb35a..91db2c22f 100644 --- a/tests/Fixtures/tags/inheritance/capturing_block.test +++ b/tests/Fixtures/tags/inheritance/capturing_block.test @@ -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 %} diff --git a/tests/Fixtures/tags/inheritance/capturing_block_after_sibling_tag.legacy.test b/tests/Fixtures/tags/inheritance/capturing_block_after_sibling_tag.test similarity index 60% rename from tests/Fixtures/tags/inheritance/capturing_block_after_sibling_tag.legacy.test rename to tests/Fixtures/tags/inheritance/capturing_block_after_sibling_tag.test index 80efcd307..80fbde72a 100644 --- a/tests/Fixtures/tags/inheritance/capturing_block_after_sibling_tag.legacy.test +++ b/tests/Fixtures/tags/inheritance/capturing_block_after_sibling_tag.test @@ -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 %}