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

Fabien Potencier
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:

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

Nicolas Grekas
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:

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

HypeMC
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:

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

Fabien Potencier
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:

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

Fabien Potencier
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

Fabien Potencier
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:

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

Fabien Potencier
Contributed by Fabien Potencier in #4291

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

Fabien Potencier
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:

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 %}
Published in #Living on the edge