Skip to content

Commit ea8aadf

Browse files
committed
Merge branch '3.x' into 4.x
* 3.x: Exempt test files from the void_return rule regardless of the config location fix version in deprecation message for tag usage outside of root template Fix array access with a Stringable key on ArrayAccess objects using object keys Throw a SyntaxError instead of a PHP fatal error when a macro argument is defined twice Bump version Prepare the 3.28.0 release Render backed enums using their backing value in the html_attr function Tweak previous merge Add documention note about variable scope of override blocks in {% embed ... only %} Define the macro at the template root in the cache macro fixture Update CHANGELOG Fix a PHP 8.5 chr() deprecation when decoding octal string escapes Fix Markup truthiness in boolean expressions # Conflicts: # CHANGELOG # doc/deprecated.rst # src/Environment.php # src/Node/Expression/TempNameExpression.php # src/Node/IfNode.php # src/Node/MacroNode.php # src/NodeVisitor/CorrectnessNodeVisitor.php # tests/Fixtures/tags/inheritance/extends_in_condition.test # tests/Fixtures/tags/inheritance/use_in_condition.test # tests/Fixtures/tags/inheritance/use_in_macro.test # tests/Fixtures/tags/macro/macro_in_block.test # tests/Fixtures/tags/macro/macro_in_condition.test
2 parents eb8ca3b + 4a7cd70 commit ea8aadf

22 files changed

Lines changed: 239 additions & 35 deletions

.php-cs-fixer.dist.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public function getRuleCustomisers(): array
3535
return false;
3636
}
3737

38-
return !str_contains($file->getRelativePathname(), '/tests/');
38+
return !preg_match('#(^|/)tests/#', $file->getRelativePathname());
3939
},
4040
];
4141
}

doc/tags/embed.rst

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ two boxes side by side:
7373
┌─── page layout ─────────────────────┐
7474
│ │
7575
│ ┌── block "content" ──┐ │
76-
│ │ │ │
76+
│ │ │ │
7777
│ │ ┌ block ┐ ┌ block ┐ │ │
7878
│ │ │"left" │ │"right"│ │ │
7979
│ │ │ │ │ │ │ │
@@ -164,6 +164,30 @@ The ``embed`` tag takes the exact same arguments as the ``include`` tag:
164164
...
165165
{% endembed %}
166166
167+
.. note::
168+
169+
Blocks you override inside an ``embed`` are evaluated in the embedded
170+
template's context, not the context of the template containing the
171+
``embed`` tag. As with ``include``, embedded templates have access to the
172+
variables of the active context. You can disable access to the context by
173+
appending the ``only`` keyword. This prevents override blocks
174+
from accessing the surrounding context, so any variable they need must also
175+
be passed explicitly through ``with``:
176+
177+
.. code-block:: twig
178+
179+
{% set name = 'Fabien' %}
180+
181+
{% embed "base" only %}
182+
{# "name" is undefined inside the block #}
183+
{% block content %}{{ name }}{% endblock %}
184+
{% endembed %}
185+
186+
{% embed "base" with {'name': name} only %}
187+
{# "name" is passed explicitly and is available #}
188+
{% block content %}{{ name }}{% endblock %}
189+
{% endembed %}
190+
167191
.. warning::
168192

169193
As embedded templates do not have "names", auto-escaping strategies based

doc/tags/include.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,9 @@ inclusion. The first template that exists will be included:
108108
109109
If ``ignore missing`` is given, it will fall back to rendering nothing if none
110110
of the templates exist, otherwise it will throw an exception.
111+
112+
.. seealso::
113+
114+
:doc:`embed<../tags/embed>` allows you to include another template's
115+
contents like ``include``, but also allows you to override blocks defined
116+
inside the included template.

extra/cache-extra/Tests/Fixtures/macro.test

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
--TEST--
22
macro call inside "cache" tag
33
--TEMPLATE--
4-
{% macro macro_out(bar) %}{{ bar }}{% endmacro %}
4+
{% macro out(bar) %}{{ bar }}{% endmacro %}
55
1
66
{% cache "testmacro1" ttl(3) %}
7-
{%~ macro macro_in(bar) %}{{ bar }}{% endmacro %}
87
2
9-
{{ _self.macro_out(3) }}
10-
{{ _self.macro_in(4) }}
8+
{{ _self.out(3) }}
9+
{{ _self.out(4) }}
1110
{% endcache %}
1211
5
13-
{{ _self.macro_in(6) }}
12+
{{ _self.out(6) }}
1413
--DATA--
1514
return []
1615
--EXPECT--

extra/html-extra/HtmlExtension.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,10 @@ public static function htmlAttr(Environment $env, iterable|string|false|null ...
200200
$runtime = $env->getRuntime(EscaperRuntime::class);
201201

202202
foreach ($attr as $name => $value) {
203+
if ($value instanceof \BackedEnum) {
204+
$value = $value->value;
205+
}
206+
203207
if (str_starts_with($name, 'aria-')) {
204208
// For aria-*, convert booleans to "true" and "false" strings
205209
if (true === $value) {

extra/html-extra/Tests/HtmlAttrTest.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,42 @@ public static function htmlAttrProvider(): \Generator
246246
['value' => new StringableStub('stringable-object')],
247247
],
248248
];
249+
250+
// Backed enums are rendered using their backing value
251+
yield 'string-backed enum renders its value' => [
252+
'class="card"',
253+
[
254+
['class' => StringBackedStub::CARD],
255+
],
256+
];
257+
258+
yield 'int-backed enum renders its value' => [
259+
'tabindex="10"',
260+
[
261+
['tabindex' => IntBackedStub::HIGH],
262+
],
263+
];
264+
265+
yield 'string-backed enum in data-* attribute renders its value without JSON encoding' => [
266+
'data-view="card"',
267+
[
268+
['data-view' => StringBackedStub::CARD],
269+
],
270+
];
271+
272+
yield 'int-backed enum in data-* attribute renders its value' => [
273+
'data-level="10"',
274+
[
275+
['data-level' => IntBackedStub::HIGH],
276+
],
277+
];
278+
279+
yield 'backed enum in aria-* attribute renders its value' => [
280+
'aria-label="card"',
281+
[
282+
['aria-label' => StringBackedStub::CARD],
283+
],
284+
];
249285
}
250286

251287
public function testIterableObjectCastedToArray()
@@ -316,3 +352,15 @@ public function getValue(): ?string
316352
return $this->value;
317353
}
318354
}
355+
356+
enum StringBackedStub: string
357+
{
358+
case CARD = 'card';
359+
case TABLE = 'table';
360+
}
361+
362+
enum IntBackedStub: int
363+
{
364+
case LOW = 1;
365+
case HIGH = 10;
366+
}

src/Lexer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@ private function stripcslashes(string $str, string $quoteType): string
443443
while ($i + 1 < $length && ctype_digit($str[$i + 1]) && $str[$i + 1] < '8' && \strlen($octal) < 3) {
444444
$octal .= $str[++$i];
445445
}
446-
$result .= \chr(octdec($octal));
446+
$result .= \chr(octdec($octal) % 256);
447447
} else {
448448
$result .= '\\'.$nextChar;
449449
}

src/Node/Expression/Binary/AndBinary.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,16 @@
1414

1515
use Twig\Compiler;
1616
use Twig\Node\Expression\ReturnBoolInterface;
17+
use Twig\Node\Expression\Test\TrueTest;
18+
use Twig\Node\Node;
1719

1820
class AndBinary extends AbstractBinary implements ReturnBoolInterface
1921
{
22+
public function __construct(Node $left, Node $right, int $lineno)
23+
{
24+
parent::__construct(TrueTest::wrap($left), TrueTest::wrap($right), $lineno);
25+
}
26+
2027
public function operator(Compiler $compiler): Compiler
2128
{
2229
return $compiler->raw('&&');

src/Node/Expression/Binary/ElvisBinary.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Twig\Compiler;
1515
use Twig\Node\Expression\AbstractExpression;
1616
use Twig\Node\Expression\OperatorEscapeInterface;
17+
use Twig\Node\Expression\Test\TrueTest;
1718
use Twig\Node\Node;
1819

1920
final class ElvisBinary extends AbstractBinary implements OperatorEscapeInterface
@@ -26,7 +27,7 @@ public function __construct(Node $left, Node $right, int $lineno)
2627
{
2728
parent::__construct($left, $right, $lineno);
2829

29-
$this->setNode('test', clone $left);
30+
$this->setNode('test', TrueTest::wrap(clone $left));
3031
$left->setAttribute('always_defined', true);
3132
}
3233

src/Node/Expression/Binary/OrBinary.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,16 @@
1414

1515
use Twig\Compiler;
1616
use Twig\Node\Expression\ReturnBoolInterface;
17+
use Twig\Node\Expression\Test\TrueTest;
18+
use Twig\Node\Node;
1719

1820
class OrBinary extends AbstractBinary implements ReturnBoolInterface
1921
{
22+
public function __construct(Node $left, Node $right, int $lineno)
23+
{
24+
parent::__construct(TrueTest::wrap($left), TrueTest::wrap($right), $lineno);
25+
}
26+
2027
public function operator(Compiler $compiler): Compiler
2128
{
2229
return $compiler->raw('||');

0 commit comments

Comments
 (0)