Twig 3.15 was released a few weeks ago and includes an impressive list of new features and improvements. This two-part blog post highlights the most important ones.
Inline Comments
In Twig, single-line or multiline comments are defined with the {# ... #}
syntax. However, you can't add comments using this syntax inside blocks or variables.
Twig 3.15 introduces a new type of inline comments that uses the syntax
# ...
and be added almost anywhere:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
{{
# this is a comment inside an expression
"Hello World"|upper
}}
{{
{
# this is a comment inside a variable
fruit: 'apple', # this is another comment
color: 'red', # this is another comment
}|join(', ')
}}
{% props
# inline comments are ideal to document Twig Component properties
content = null,
active = true,
%}
Added an enum()
Function
PHP enumerations are an increasingly popular feature in PHP applications for
defining closed sets of possible values for a type. Twig 3.15 adds an enum()
function to help you work with enums in templates:
1 2 3 4 5 6 7 8 9 10
{# display one specific case of a backed enum #}
{{ enum('App\\Config\\SomeOption').SomeCase.value }}
{# call any methods of the enum class #}
{% enum('App\\Config\\SomeOption').someMethod() %}
{# get all cases of an enum #}
{% for case in enum('App\\Config\\SomeOption').cases() %}
{# ... #}
{% endif %}
Support for xor
Logical Operator
Twig 3.15 adds support for the xor
operator, which evaluates to true
when
exactly one of its operands is true
, but not both. This complements the
logical operators supported by Twig:
1 2 3 4 5
{% if coupon.isValid xor user.hasLoyaltyDiscount %}
<p>You are eligible for a discount!</p>
{% else %}
<p>Either no discount applies, or you cannot combine a coupon with your loyalty discount.</p>
{% endif %}
Operator Precedence Fixes
Twig operators match the behavior of the equivalent PHP operators, except for some differences in operator precedence. These differences were introduced by PHP 7.4 when changing concatenation precedence.
Twig 3.15 deprecates the use of certain operators without parentheses to help you fix expressions before upgrading to Twig 4, where operator precedence will change:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
{# ❌ this is deprecated #}
{{ foo ?? bar ~ baz }}
{# ✅ use this (Twig 3.x behaves like this) #}
{{ (foo ?? bar) ~ baz }}
{# ✅ or this (Twig 4.x will behave like this) #}
{{ foo ?? (bar ~ baz) }}
{# ❌ this is deprecated #}
{{ foo ~ bar + baz }}
{# ✅ use this (Twig 3.x behaves like this) #}
{{ (foo ~ bar) + baz }}
{# ✅ or this (Twig 4.x will behave like this) #}
{{ foo ~ (bar + baz) }}
{# ❌ this is deprecated #}
{{ not foo * bar }}
{# ✅ use this (Twig 3.x behaves like this) #}
{{ not (foo * bar) }}
{# ✅ or this (Twig 4.x will behave like this) #}
{{ (not foo) * bar }}
Automatic Escaping Strategy for JSON Files
Twig applies different escaping strategies automatically based on the file
extension. For example, files named template_name.js.twig
apply the js
escaping strategy by default.
Starting with Twig 3.15, the js
escaping strategy is also applied to JSON files
(e.g. template_name.json.twig
). Previously, these used the html
strategy,
which could potentially cause security issues.
Deprecated the sandbox
Tag
The Twig sandbox feature allows evaluating untrusted code in a safe way. In
previous versions, this was done using the sandbox
tag. Starting with Twig 3.15,
this tag is deprecated. Instead, use the sandboxed
attribute of the include()
function:
1 2 3 4 5 6 7
{# ❌ this is deprecated #}
{% sandbox %}
{{ include('untrusted_template.html') }}
{% endsandbox %}
{# ✅ do this instead #}
{{ include('untrusted_template.html', sandboxed: true) }}
Better Deprecation of Twig Callables
Twig allows marking filters and functions as deprecated using the deprecated
option:
1
new TwigFilter('...', ..., ['deprecated' => true, 'alternative' => 'new_one'])
Starting with Twig 3.15, you must use the new deprecation_info
option, which
provides more details about the deprecation and its alternatives:
1 2 3 4 5 6 7 8 9
use Twig\DeprecatedCallableInfo;
new TwigFilter('...', ..., ['deprecation_info' => new DeprecatedCallableInfo(
'vendor/package', # Package triggering the deprecation
'3.14', # Version triggering the deprecation
'new_one', # Alternative filter (optional)
'other-vendor/some-package', # Package of the alternative filter (optional)
'4.2.0' # Version of the alternative package (optional)
)])
Checking Twig Callables at Compilation-Time
Twig templates don't support using try ... catch
to check if a filter, function,
or tag exists before calling it. This limitation can lead to errors in third-party
bundles that depend on optional Symfony features.
Twig 3.15 introduces a new guard
tag that checks Twig callables during
compilation and skips the associated code if the callable doesn't exist.
Consider a bundle that supports both Webpack Encore and AssetMapper, and tries
to use the following template:
1 2 3 4 5 6 7
{% block stylesheets %}
{% if app_uses_webpack_encore %}
{{ encore_entry_link_tags('some-asset') }}
{% else %}
{{ importmap('some-asset') }}
{% endif %}
{% endblock %}
This template will only work if both Webpack Encore and AssetMapper are installed
in the application. If either encore_entry_link_tags()
or importmap()
is missing,
Twig will fail during template compilation.
With the new guard
tag in Twig 3.15, you can avoid such errors by skipping
unavailable callables entirely. The previous example can now be written as:
1 2 3 4 5 6 7 8 9
{% block stylesheets %}
{% guard function encore_entry_link_tags %}
{{ encore_entry_link_tags('some-asset') }}
{% endguard %}
{% guard function importmap %}
{{ importmap('some-asset') }}
{% endguard %}
{% endblock %}
Does the JS Escaping Strategy also apply to .jsonl.twig and .json5.twig?
Awesome, thanks to all contributors, Fabien and Nicolas on those examples.