From 4f2f3f814e6d5dfab62a4fd5197ae9dfb021ef36 Mon Sep 17 00:00:00 2001 From: ju1ius Date: Thu, 19 Jan 2023 11:01:38 +0100 Subject: [PATCH] pass the current key to reduce filter's callback --- CHANGELOG | 1 + doc/filters/reduce.rst | 10 +++++----- src/Extension/CoreExtension.php | 15 ++++++++------- tests/Fixtures/filters/reduce_key.test | 14 ++++++++++++++ 4 files changed, 28 insertions(+), 12 deletions(-) create mode 100644 tests/Fixtures/filters/reduce_key.test diff --git a/CHANGELOG b/CHANGELOG index 131bdadb1..28d5c7fae 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,7 @@ * Fix optimizing closures callbacks * Add a better exception when getting an undefined constant via `constant` * Fix `if` nodes when outside of a block and with an empty body + * Arrow functions passed to the "reduce" filter now accept the current key as a third argument # 3.4.3 (2022-09-28) diff --git a/doc/filters/reduce.rst b/doc/filters/reduce.rst index 7df4646c7..72c68d0de 100644 --- a/doc/filters/reduce.rst +++ b/doc/filters/reduce.rst @@ -4,21 +4,21 @@ The ``reduce`` filter iteratively reduces a sequence or a mapping to a single value using an arrow function, so as to reduce it to a single value. The arrow function receives the return value of the previous iteration and the current -value of the sequence or mapping: +value and key of the sequence or mapping: .. code-block:: twig {% set numbers = [1, 2, 3] %} - {{ numbers|reduce((carry, v) => carry + v) }} - {# output 6 #} + {{ numbers|reduce((carry, v, k) => carry + v * k) }} + {# output 8 #} The ``reduce`` filter takes an ``initial`` value as a second argument: .. code-block:: twig - {{ numbers|reduce((carry, v) => carry + v, 10) }} - {# output 16 #} + {{ numbers|reduce((carry, v, k) => carry + v * k, 10) }} + {# output 18 #} Note that the arrow function has access to the current context. diff --git a/src/Extension/CoreExtension.php b/src/Extension/CoreExtension.php index ca3d28bf3..ce846e91f 100644 --- a/src/Extension/CoreExtension.php +++ b/src/Extension/CoreExtension.php @@ -1703,15 +1703,16 @@ function twig_array_reduce(Environment $env, $array, $arrow, $initial = null) { twig_check_arrow_in_sandbox($env, $arrow, 'reduce', 'filter'); - if (!\is_array($array)) { - if (!$array instanceof \Traversable) { - throw new RuntimeError(sprintf('The "reduce" filter only works with arrays or "Traversable", got "%s" as first argument.', \gettype($array))); - } - - $array = iterator_to_array($array); + if (!\is_array($array) && !$array instanceof \Traversable) { + throw new RuntimeError(sprintf('The "reduce" filter only works with arrays or "Traversable", got "%s" as first argument.', \gettype($array))); } - return array_reduce($array, $arrow, $initial); + $accumulator = $initial; + foreach ($array as $key => $value) { + $accumulator = $arrow($accumulator, $value, $key); + } + + return $accumulator; } function twig_array_some(Environment $env, $array, $arrow) diff --git a/tests/Fixtures/filters/reduce_key.test b/tests/Fixtures/filters/reduce_key.test new file mode 100644 index 000000000..fe1fb0a7a --- /dev/null +++ b/tests/Fixtures/filters/reduce_key.test @@ -0,0 +1,14 @@ +--TEST-- +"reduce" filter passes iterable key to callback +--TEMPLATE-- +{% set status_classes = { + 'success': 200, + 'warning': 400, + 'error': 500, +} %} + +{{ status_classes|reduce((carry, v, k) => status_code >= v ? k : carry, '') }} +--DATA-- +return ['status_code' => 404] +--EXPECT-- +warning