Twig icon indicating copy to clipboard operation
Twig copied to clipboard

Documentation for `values` filter is missing

Open alinnert opened this issue 6 months ago • 5 comments

I was about to create a feature request to add the ability to access array items relative to the current one in a loop because using index access (e.g. items[loop.index0 - 1]) doesn't work in many cases, including simple arrays that got sorted with the sort filter.

But I found out that problem can be solved with the values filter. It seems to be available in my environment but this filter is missing in the docs, even though a keys filter is documented. Is there a reason for that or is that a mistake?

alinnert avatar Jun 11 '25 00:06 alinnert

The values filter is not part of the Twig CoreExtension. It might have been registered by a third-party extension you have added in your environment

stof avatar Jun 19 '25 18:06 stof

What about using {% for value in items %}?

fabpot avatar Jun 21 '25 11:06 fabpot

@stof You're right. The values filter is coming from Craft CMS. It was #2081 plus the fact that there's a keys filter that made me believe it's part of Twig. But that means my original problem actually cannot be solved the way I did it in this specific case.

@fabpot Yes, I am using that kind of loop. The actual problem I'm facing is a little more complex. I have an array like this:

{% set items = ['b', 'c', 'a'] %}

Which is like the following in PHP:

[0 => 'b', 1 => 'c', 2 => 'a']

Now I use the sort filter to sort the array by value. It took me a while to figure out that the result actually looks like this:

[2 => 'a', 0 => 'b', 1 => 'c']

Notice the keys being shuffled. I do understand why this happens. Now I loop over this sorted array which in itself is still fine:

{% for item in items|sort %}{# ... #}{% endfor %}

But inside this loop I now need to change the output depending on the item of the previous loop iteration. So my intuition was to do the following:

{% for item in items|sort %}
  {% set prevItem = items[loop.index0 - 1] %}
{% endfor %}

But this doesn't work because of the indexes all being shuffled. To sum it up:

  • Iteration 1: item == 'a'
    • expected: prevItem to be not defined
    • actually: prevItem == 'c'
  • Iteration 2: item == 'b'
    • expected: prevItem == 'a'
    • actually: prevItem is not defined (out of range)
  • Iteration 3: item == 'c'
    • expected: prevItem == 'b'
    • actually: prevItem == 'b' (correct by accident)

The values filter does solve this issue in a way by "re-indexing" the array because I can do this:

{% for item in items|sort|values %}{# ... #}{% endfor %}

I just played around and found a way to solve this in pure Twig (Playground link):

{% set items = ['b', 'c', 'a'] %}
{% set sortedItems = items|sort %}
{% set indexes = sortedItems|keys %}

{% for key, item in sortedItems %}
    {% set prevIndex = indexes[loop.index0 - 1] ?? null %}
    {% set prevItem = prevIndex is not null ? sortedItems[prevIndex] %}

    {% set nextIndex = indexes[loop.index0 + 1] ?? null %}
    {% set nextItem = nextIndex is not null ? sortedItems[nextIndex] %}

    * ({{ key }} => {{ item }})
      * PREV: ({{ prevIndex|default('-') }} => {{ prevItem|default('-') }})
      * NEXT: ({{ nextIndex|default('-') }} => {{ nextItem|default('-') }})
{% endfor %}

It is still a bit lengthy though. Is it worth finding a shortcut for this, maybe also from a performance stand point? Essentially it would be helpful to get an array item using its positional index instead of its array key.

alinnert avatar Jun 21 '25 17:06 alinnert

In Twig 4 this will become easier with the new loop.previous and loop.next variables.

xabbuh avatar Jun 21 '25 18:06 xabbuh

That is great to hear and should cover most use-cases.

While writing this comment and thinking more about this I just realized a few things though: |slice(0) actually has the same effect as a hypothetical |values, which means it could make my example a bit simpler already. But more importantly, this problem could be completely avoided if we had somehow access to the sort() function of PHP. Then, simple index access like {{ sortedItems[loop.index0 - 1] }} would just work as expected.

Now, I don't know, is Twig a library that prefers to be "smart"? If so, a way to add access to it would be if the sort filter did something like the following under the hood:

if (array_is_list($array)) {
  sort($array);
} else {
  asort($array);
}

If not, can we have explicit access to PHP sort()? Either through a separate filter or an argument for the current sort filter? What are your thoughts on this?

alinnert avatar Jun 21 '25 23:06 alinnert