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_attrfunction 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_relaxedescaping strategy (@mpdude)