New in Twig 3.15 (part 1)


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

Contributed by
Fabien Potencier
in
#4349

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:

{{
# 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

Contributed by
Nicolas Grekas
in
#4352

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:

{# 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

Contributed by
HypeMC
in
#4369

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:

{% 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

Contributed by
Fabien Potencier
in
#4363
and #4367

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:

{# ❌ 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

Contributed by
Fabien Potencier
in
#4302

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

Contributed by
Fabien Potencier
in
#4293

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:

{# ❌ this is deprecated #}
{% sandbox %}
{{ include('untrusted_template.html') }}
{% endsandbox %}

{# ✅ do this instead #}
{{ include('untrusted_template.html', sandboxed: true) }}

Better Deprecation of Twig Callables

Contributed by
Fabien Potencier
in
#4291

Twig allows marking filters and functions as deprecated using the deprecated
option:

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:

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

Contributed by
Fabien Potencier
in
#4304

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:

{% 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:

{% block stylesheets %}
{% guard function encore_entry_link_tags %}
{{ encore_entry_link_tags('some-asset') }}
{% endguard %}

{% guard function importmap %}
{{ importmap('some-asset') }}
{% endguard %}
{% endblock %}

➡️ Read the second part of this blog post

Sponsor the Symfony project.