Twig 3.24.0 has just been released with a major new feature for working with HTML attributes, improved null-safe operator behavior, and variable renaming in object destructuring.

The html_attr function

Building HTML attributes in templates has always been tedious: you need to handle escaping, conditional attributes, merging CSS classes, and boolean attributes like disabled or required. The new html_attr function takes care of all of that:

1
2
3
4
5
<div {{ html_attr({class: ['btn', 'btn-primary'], id: 'submit'}) }}>
    Click me
</div>

{# Output: <div class="btn btn-primary" id="submit">Click me</div> #}

The function accepts multiple attribute maps, merging them automatically. Boolean values, null, and aria-*/data-* attributes are handled with sensible defaults: false and null omit the attribute, true prints the attribute with an empty value, and aria-* attributes render "true"/"false" strings as expected by the spec:

1
2
3
4
5
6
{% set base = {class: ['btn']} %}
{% set variant = {class: ['btn-primary'], disabled: true} %}

<button {{ html_attr(base, variant) }}>Submit</button>

{# Output: <button class="btn btn-primary" disabled="">Submit</button> #}

Two companion filters are also available: html_attr_merge to merge attribute maps without rendering them, and html_attr_type to handle multi-valued attributes like comma-separated srcset or sizes (thanks to @mpdude and @polarbirke).

See #3930.

The html_attr_relaxed escaping strategy

Some front-end frameworks like Vue.js use special characters in attribute names: :checked, @click, v-bind:[prop]. The standard html_attr escaping strategy encodes these characters, breaking the framework integration. The new html_attr_relaxed strategy keeps :, @, [, and ] unescaped while still protecting against injection:

1
2
3
<input {{ html_attr({':disabled': 'isLoading', '@input': 'validate'})|e('html_attr_relaxed') }}>

{# Output: <input :disabled="isLoading" @input="validate"> #}

This strategy is also used internally by the html_attr function for attribute names (thanks to @mpdude).

See #4743.

Short-circuiting in null-safe operator chains

The null-safe operator (?.) now properly short-circuits the entire chain when null is encountered, matching the behavior of PHP, Symfony PropertyAccess, and the ExpressionLanguage component. Previously, only the immediate access was guarded, so user?.address.city would still try to access .city on the null result. Now, the rest of the chain is skipped:

1
2
{{ user?.address.city }}
{# Returns null if user is null; address.city is not evaluated #}

This makes null-safe chains much safer and more predictable (thanks to @HypeMC).

See #4748.

Renaming variables in object destructuring

Object destructuring, introduced in Twig 3.23, now supports renaming variables using the key: variable syntax. The key is the property to extract, and the variable is the local name to assign it to, just like JavaScript:

1
2
3
4
{% do {name: userName, email: userEmail} = user %}

{{ userName }}   {# user.name #}
{{ userEmail }}  {# user.email #}

This is especially useful when destructuring multiple objects that share the same property names:

1
2
{% do {data: product, error: productError} = loadProduct() %}
{% do {data: stock, error: stockError} = loadStock() %}

See #4759.

Full Changelog

  • #3930 Add an html_attr function to make outputting HTML attributes easier (@mpdude, @polarbirke)
  • #4778 Fix null coalescing operator with imported macros (@fabpot)
  • #4775 Add getOperatorTokens() to ExpressionParserInterface to separate operator token registration from parser identity (@fabpot)
  • #4774 Ensure filters/attributes aren't mistaken for operators (@brandonkelly)
  • #4771 Deprecate passing non AbstractExpression nodes to MatchesBinary (@fabpot)
  • #4769 Deprecate passing a non-AbstractExpression node to Parser::setParent() (@fabpot)
  • #4759 Add support for renaming variables in object destructuring (@fabpot)
  • #4748 Support short-circuiting in null-safe operator chains (@HypeMC)
  • #4743 Add html_attr_relaxed escaping strategy (@mpdude)
Published in #Releases #Twig