From 4e262511930e408e4c7eda07b1c977f2ea98575c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Andr=C3=A9?= Date: Sun, 14 Jan 2024 00:40:23 +0100 Subject: [PATCH] Add new "find" filter --- CHANGELOG | 1 + doc/filters/find.rst | 57 ++++++++++++++++++++++++++++++++ src/Extension/CoreExtension.php | 17 ++++++++++ tests/Fixtures/filters/find.test | 46 ++++++++++++++++++++++++++ 4 files changed, 121 insertions(+) create mode 100644 doc/filters/find.rst create mode 100644 tests/Fixtures/filters/find.test diff --git a/CHANGELOG b/CHANGELOG index 39b049889..40fc1b4a0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ # 3.11.0 (2024-XX-XX) + * Add the `find` filter * Fix optimizer mode validation in `OptimizerNodeVisitor` * Add the possibility to yield from a generator in `PrintNode` * Add the `shuffle` filter diff --git a/doc/filters/find.rst b/doc/filters/find.rst new file mode 100644 index 000000000..f11b68e36 --- /dev/null +++ b/doc/filters/find.rst @@ -0,0 +1,57 @@ +``find`` +======== + +.. versionadded:: 3.11 + + The ``find`` filter was added in Twig 3.11. + +The ``find`` filter returns the first element of a sequence matching an arrow +function. The arrow function receives the value of the sequence: + +.. code-block:: twig + + {% set sizes = [34, 36, 38, 40, 42] %} + + {{ sizes|find(v => v > 38) }} + {# output 40 #} + +It also works with mappings: + +.. code-block:: twig + + {% set sizes = { + xxs: 32, + xs: 34, + s: 36, + m: 38, + l: 40, + xl: 42, + } %} + + {{ sizes|find(v => v > 38) }} + + {# output 40 #} + +The arrow function also receives the key as a second argument: + +.. code-block:: twig + + {{ sizes|find((v, k) => 's' not in k) }} + + {# output 38 #} + +Note that the arrow function has access to the current context: + +.. code-block:: twig + + {% set my_size = 39 %} + + {{ sizes|find(v => v >= my_size) }} + + {# output 40 #} + +Arguments +--------- + +* ``array``: The sequence or mapping +* ``arrow``: The arrow function diff --git a/src/Extension/CoreExtension.php b/src/Extension/CoreExtension.php index d0e1dccc7..d212e5ccd 100644 --- a/src/Extension/CoreExtension.php +++ b/src/Extension/CoreExtension.php @@ -218,6 +218,7 @@ final class CoreExtension extends AbstractExtension new TwigFilter('filter', [self::class, 'filter'], ['needs_environment' => true]), new TwigFilter('map', [self::class, 'map'], ['needs_environment' => true]), new TwigFilter('reduce', [self::class, 'reduce'], ['needs_environment' => true]), + new TwigFilter('find', [self::class, 'find'], ['needs_environment' => true]), // string/array filters new TwigFilter('reverse', [self::class, 'reverse'], ['needs_charset' => true]), @@ -1775,6 +1776,22 @@ final class CoreExtension extends AbstractExtension return new \CallbackFilterIterator(new \IteratorIterator($array), $arrow); } + /** + * @internal + */ + public static function find(Environment $env, $array, $arrow) + { + self::checkArrowInSandbox($env, $arrow, 'find', 'filter'); + + foreach ($array as $k => $v) { + if ($arrow($v, $k)) { + return $v; + } + } + + return null; + } + /** * @internal */ diff --git a/tests/Fixtures/filters/find.test b/tests/Fixtures/filters/find.test new file mode 100644 index 000000000..3d1dbd422 --- /dev/null +++ b/tests/Fixtures/filters/find.test @@ -0,0 +1,46 @@ +--TEST-- +"filter" filter +--TEMPLATE-- + +{{ [1, 2]|find((v) => v > 3) }} + +{{ [1, 5, 3, 4, 5]|find((v) => v > 3) }} + +{{ [1, 5, 3, 4, 5]|find((v) => v > 3) }} + +{{ {a: 1, b: 2, c: 5, d: 8}|find(v => v > 3) }} + +{{ {a: 1, b: 2, c: 5, d: 8}|find((v, k) => (v > 3) and (k != "c")) }} + +{{ [1, 5, 3, 4, 5]|find(v => v > 3) }} + +{{ it|find((v) => v > 3) }} + +{{ ita|find(v => v > 3) }} + +{{ xml|find(x => true) }} + +--DATA-- +return [ + 'it' => new \ArrayIterator(['a' => 1, 'b' => 2, 'c' => 5, 'd' => 8]), + 'ita' => new Twig\Tests\IteratorAggregateStub(['a' => 1, 'b' => 2, 'c' => 5, 'd' => 8]), + 'xml' => new \SimpleXMLElement('foobarbaz'), +] +--EXPECT-- + + +5 + +5 + +5 + +8 + +5 + +5 + +5 + +foo