Twig 3.28.0 is out. This release sharpens error reporting with column numbers, brings back dynamic macro calls through the dot operator, and continues to polish the sandbox with less runtime overhead and finer-grained allow-listing. As usual, it also ships a batch of deprecations that pave the way for Twig 4.0.
Column numbers in syntax errors
Until now, a Twig error only told you which line was at fault. On a dense line, that left you scanning for the actual problem. Twig now tracks the source offset of every token, so errors can point at the exact column.
The column is exposed through the new Error::getTemplateColumn() method, and
the underlying token offset through Token::getOffset().
Dynamic macro names through the dot operator
Calling a macro whose name is only known at runtime used to rely on the
attribute() function, which was deprecated in favor of the dot operator; but
the dot operator did not support macros. That gap is now closed:
{% import "forms.html.twig" as forms %}
{% set field = "text" %}
{{ forms.(field)(name, value) }}
The parenthesized expression is evaluated and used as the macro name, so you can pick a macro dynamically without falling back to a deprecated function.
Sandbox: mark callables and tags as always allowed
Some filters, functions, tests, and tags are pure and inherently safe in a sandboxed template. Forcing every security policy to allow-list them by hand is noisy and error-prone.
Filters, functions, and tests now accept an always_allowed_in_sandbox
option, and token parsers can implement isAlwaysAllowedInSandbox(). When
set, the sandbox skips recording the name entirely, so it never reaches the
policy's security check: no allow-list entry required and no runtime cost. The
security policy also gains a dedicated allow-list for tests, with the safe
built-in tests flagged as always allowed so they keep working out of the box.
Faster sandboxed templates
The sandbox used to wrap every argument of every callable with a __toString
check. Twig now wraps an argument only when its PHP parameter type can actually
coerce a value to a string. Arguments typed as int, for instance, are left
untouched, which cuts the amount of generated code and speeds up sandboxed
rendering. The approach stays conservative: untyped, mixed, string,
array, iterable, object, Stringable, and unknown class types all
keep their check.
Stricter template correctness checks
Being able to parse and compile a template does not mean it is semantically
correct. A new CorrectnessNodeVisitor centralizes those checks and
deprecates a few constructs that happened to work but were never meant to:
using a block tag inside a capture node such as set, and using a
macro, extends, or use tag somewhere other than the root of a
template.
Markup is now effectively final
Twig\Markup is now marked @final and will become truly final in Twig
4.0.
Notable bug fixes
- The
include()function now returns aMarkupobject, so a result assigned
to a variable is no longer re-escaped when printed.
Nested block() calls now
resolve against the overriding template when a block rendered through
block(name, template) calls parent().
Printed expressions are cast to string so values that cannot be converted (arrays, non-Stringable objects)
report a usable stack trace at the print location.
Backed enums are now rendered using their backing value in the html_attr function.
Empty Markup values are no longer treated as truthy in boolean expressions.
Full Changelog
- #4850 Render backed enums using their backing value in the
html_attrfunction (@fabpot) - #4841 Fix Markup truthiness in boolean expressions (@xtrime-ru)
- #4842 Fix a PHP 8.5
chr()deprecation when decoding octal string escapes (@austinderrick) - #4292 Introduce a CorrectnessNodeVisitor to validate that templates are semantically correct (@fabpot)
- #4840 Mark Markup as final (@fabpot)
- #4838 Allow calling a macro with a dynamic name via the dot operator (@fabpot)
- #4835 Fix markdown_to_html mangling content that starts with a blank line (@fabpot)
- #4819 Add an allow-list for tests to the sandbox security policy (@fabpot)
- #4816 Add an always_allowed_in_sandbox flag for filters, functions, and tags (@fabpot)
- #4834 Track the source offset of each token and expose it in syntax errors (@fabpot)
- #4825 Make the include() function return a Markup object (@fabpot)
- #4830 Fix nested block() resolution when a directly rendered block calls parent() (@fabpot)
- #4828 Stop reporting a skipped test in IntegrationTestCase when there is no legacy test to run (@fabpot)
- #4824 Cast printed expressions to string so unconvertible values report a usable stack trace at the print location (@stof, @fabpot)
- #4826 Make IntegrationTestCase and NodeTestCase compatible with PHPUnit 11 (@fabpot)
- #4823 Skip the sandbox
__toStringcheck on arguments whose PHP parameter type cannot implicitly coerce to string (@fabpot)