English spoken conference

Symfony 5: The Fast Track

A new book to learn about developing modern Symfony 5 applications.

Support this project

Twig Adds Filter, Map and Reduce Features

Twig is the template language used in Symfony and thousands of other projects. In the last six months alone, Twig has released 30 versions for its 1.x and 2.x branches, adding lots of interesting new features. This article focuses on some of the new filters and tags added recently.

Filter, map and reduce

Contributed by
Fabien Potencier
in #2996.

The "filter, map and reduce" pattern is getting more and more popular in other programming languages and paradigms (e.g. functional programming) to transform collections and sequences of elements. You can now use them in Twig thanks to the new filter, map and reduce filters in combination with the new arrow function.

The filter filter removes from a sequence all the elements that don't match the given expression. For example, to ignore products without enough stock:

1
2
3
{% for product in related_products|filter(product => product.stock > 10) %}
    {# ... #}
{% endfor %}

The arrow function receives the value of the sequence or mapping as its argument. The name of this argument can be freely chosen and it doesn't have to be the same as the variable used to iterate the collection:

1
2
3
{% for product in related_products|filter(p => p.id not in user.recentPurchases) %}
    {# ... #}
{% endfor %}

If you also need the key of the sequence element, define two arguments for the arrow function (and wrap them in parenthesis):

1
2
3
{% set components = all_components|filter(
    (v, k) => v.published is true and not (k starts with 'Deprecated')
) %}

Thanks to the new filter option, the if condition is no longer needed for the for loops, so we've deprecated it in favor of always using filter:

1
2
3
4
-{% for product in related_products if product.stock > 10 %}
+{% for product in related_products|filter(p => p.stock > 10) %}
    {# ... #}
{% endfor %}

If you prefer to keep using the for ... if pattern, include the if in the for loop:

1
2
3
4
5
{% for product in related_products %}
    {% if product.stock > 10 %}
        {# ... #}
    {% endif %}
{% endfor %}

The map filter applies an arrow function to the elements of a sequence or a mapping (it's similar to PHP's array_map()):

1
2
3
4
5
6
7
{% set people = [
    {first: "Alice", last: "Dupond"},
    {first: "Bob", last: "Smith"},
] %}

{{ people|map(p => p.first ~ ' ' ~ p.last)|join(', ') }}
{# outputs Alice Dupond, Bob Smith #}

The reduce filter iteratively reduces a sequence or a mapping to a single value using an arrow function. Because of this behavior, the arrow function always receives two arguments: the current value and the result of reducing the previous elements (usually called "carry"):

1
2
{% set num_products = cart.rows|reduce((previousTotal, row) => previousTotal + row.totalUnits) %}
{{ num_products }} products in total.

In addition to filter, map and reduce, recent Twig versions have added other useful filters and tags.

The "column" filter

Contributed by
Pablo Schläpfer
in #2992.

This new column filter returns the values from a single column in the given array (internally it uses the PHP array_column() function):

1
Your oldest friend is {{ max(user.friends|column('age')) }} years old.

The "apply" tag

Contributed by
Fabien Potencier
in #2977.

The filter tag has been deprecated (to not confuse it with the new filter filter explained above) and it has been replaced by the new apply tag which works exactly the same as the previous tag:

1
2
3
4
-{% filter upper %}
+{% apply upper %}
    This text becomes uppercase.
{% endapply %}

Allowed using Traversable objects

Contributed by
Ondřej Exner
in #3016.

Another important change related to filters and tags is that you can now use objects that implement the Traversable PHP interface everywhere you can use iterators or arrays: the with tag, the with argument of the include and embed tags, the filter filter, etc.

Comments

I like the changes, but did you test them on frontend coders? For example, I've managed to explain this one to ours:

```
{% for product in related_products if product.stock > 10 %}
```
because it's like English. He can read and write it.

The replacement is going to take a bit more education, because now I have to explain what a filter is and the new syntax. He's not a JavaScript programmer, he's a HTML and CSS coder.

```
{% for product in related_products|filter(p => p.stock > 10) %}
```

If Twig's audience is wider than programmers, please keep the old syntax alongside the new filter.
This is certainly nice, but I can't help but feel that filter/map/reduce are something you *probably* should avoid in a template. They can be abused quickly and risk letting inexperienced developers add business logic in a template.
These features are useful and I missed them here and there. However, there is an important question to answer: How much damage can average coder do with them?

There should be a big fat warning that you should not do this in template and a proper example how it should be done right.
This is a nice addition that removes the need to use a bundle (https://github.com/dpolac/twig-lambda).
However, I tried using it recently and found something that looks like a bug. I have detailed the issue at https://stackoverflow.com/questions/56448878/cant-iterate-the-filter-filter-multiple-times but unfortunately got no reply.
@Peter You are not forced to use the next syntax. Using an "if" tag in a "for" loop is still possible and probably clearer for many. We have removed the "if" support on the "for" tag as it was confusing for people as there are some edge cases that made it quite annoying to use/explain/debug.

@Davide They are available for display and not for implementing some business logic. But that's true with almost all other features (even a "for" loop is sometimes not something you should use in a template).

@Josef You cannot cause any damage using those new filters. As for examples, there are plenty in the docs. So, not sure what we can change here. Any ideas?

@Jean-Guilhem This was a bug that has been fixed now.
@Peter, I've updated the blog post to show the alternative "for ... if" pattern mentioned by Fabien that doesn't use the filter and arrow function.
@Fabien, nice, thanks!
Great addition - I can already see how this can save us from having to 1) spit out our twig variables into js in order to map/reduce/etc 2) perform various tasks in php and have multiple variations as twig variables.
Awesome!
So glad you removed the for if weird yoda style already love the filter and map function :-)
👍

Comments are closed.

To ensure that comments stay relevant, they are closed for old posts.