From a4e40b31691b1a4f3e1b594a66a7907005bd673a Mon Sep 17 00:00:00 2001 From: Yusup Hambali Date: Fri, 26 Jan 2024 03:14:56 +0700 Subject: [PATCH 01/16] Implement value retrieval by matcher --- src/ArrayHelper.php | 34 +++++++++++++++++++++++------- tests/ArrayHelper/GetValueTest.php | 20 ++++++++++++++++++ 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/src/ArrayHelper.php b/src/ArrayHelper.php index 3034749..327951f 100644 --- a/src/ArrayHelper.php +++ b/src/ArrayHelper.php @@ -167,9 +167,9 @@ public static function parametrizedMerge(array $arrays, ?int $depth): array * // Working with object: * $username = \Yiisoft\Arrays\ArrayHelper::getValue($user, 'username'); * - * // Working with anonymous function: - * $fullName = \Yiisoft\Arrays\ArrayHelper::getValue($user, function ($user, $defaultValue) { - * return $user->firstName . ' ' . $user->lastName; + * // Retrieve the value by matcher function: + * $firstActiveMember = \Yiisoft\Arrays\ArrayHelper::getValue($users, function ($user, $key) { + * return $user->type === 'member' && $user->isActive; * }); * * // Using an array of keys to retrieve the value: @@ -178,15 +178,14 @@ public static function parametrizedMerge(array $arrays, ?int $depth): array * * @param array|object $array Array or object to extract value from. * @param array|Closure|float|int|string $key Key name of the array element, - * an array of keys, object property name, object method like `getName()`, or an anonymous function - * returning the value. The anonymous function signature should be: - * `function($array, $defaultValue)`. + * an array of keys, object property name, object method like `getName()` or an anonymous function. The anonymous function signature should be: + * `function($value, $key)`. * @param mixed $default The default value to be returned if the specified array key does not exist. Not used when * getting value from an object. * * @psalm-param ArrayKey|Closure $key * - * @return mixed The value of the element if found, default value otherwise. + * @return mixed The value of the element if found or the return value of anonymous function is truthy, default value otherwise. */ public static function getValue( array|object $array, @@ -194,7 +193,7 @@ public static function getValue( mixed $default = null ): mixed { if ($key instanceof Closure) { - return $key($array, $default); + return self::getValueByMatcher($array, $key, $default); } if (is_array($key)) { @@ -253,6 +252,25 @@ private static function getRootValue(mixed $array, float|int|string $key, mixed return $default; } + private static function getValueByMatcher( + array|object $array, + Closure $match, + mixed $default = null + ): mixed + { + if (is_object($array)) { + $array = self::getObjectVars($array); + } + + foreach ($array as $key => $value) { + if ($match($value, $key)) { + return $value; + } + } + + return $default; + } + /** * Retrieves the value of an array element or object property with the given key or property name. * If the key does not exist in the array or object, the default value will be returned instead. diff --git a/tests/ArrayHelper/GetValueTest.php b/tests/ArrayHelper/GetValueTest.php index bdcad98..9e45c1f 100644 --- a/tests/ArrayHelper/GetValueTest.php +++ b/tests/ArrayHelper/GetValueTest.php @@ -89,6 +89,26 @@ public function testGetValueFromArray($key, $expected, $default = null): void $this->assertEquals($expected, ArrayHelper::getValue($this->array, $key, $default)); } + public function testGetValueByMatcher(): void + { + $users = [ + ['name' => 'Cebe', 'status' => 'active'], + ['name' => 'John', 'status' => 'not active'], + ]; + + $activeUser = ArrayHelper::getValue($users, fn ($user) => $user['status'] === 'active'); + $this->assertEquals('Cebe', $activeUser['name']); + + + $posts = [ + new ArrayObject(['title' => 'hello world']), + new ArrayObject(['title' => 'hello test', 'tag' => 'test']), + ]; + + $taggedPost = ArrayHelper::getValue($posts, fn ($post) => isset($post->tag)); + $this->assertEquals('hello test', $taggedPost->title); + } + public function dataProviderGetValueByPathFromArray(): array { return array_merge($this->commonDataProviderFromArray(), [ From a3817b6cdd166291e246ba3cfb9e7f75522df07e Mon Sep 17 00:00:00 2001 From: Yusup Hambali Date: Fri, 26 Jan 2024 03:22:53 +0700 Subject: [PATCH 02/16] changelog --- CHANGELOG.md | 3 ++- src/ArrayHelper.php | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4e0a0f..cce08bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ # Yii Arrays Change Log -## 3.0.1 under development +## 4.0.0 under development +- Chg #142: Redefine the callable parameter in `ArrayHelper::getValue()` to be a value matcher (yus-ham) - New #139: Add `ArrayHelper::parametrizedMerge()` method that allows to merge two or more arrays recursively with specified depth (@vjik) - Enh #140: Remove `null` from return type of `ArrayHelper::getObjectVars()` method (@Tigrov) diff --git a/src/ArrayHelper.php b/src/ArrayHelper.php index 327951f..1adcf16 100644 --- a/src/ArrayHelper.php +++ b/src/ArrayHelper.php @@ -178,14 +178,14 @@ public static function parametrizedMerge(array $arrays, ?int $depth): array * * @param array|object $array Array or object to extract value from. * @param array|Closure|float|int|string $key Key name of the array element, - * an array of keys, object property name, object method like `getName()` or an anonymous function. The anonymous function signature should be: + * an array of keys, object property name, object method like `getName()` or a callable. The callable function signature should be: * `function($value, $key)`. * @param mixed $default The default value to be returned if the specified array key does not exist. Not used when * getting value from an object. * * @psalm-param ArrayKey|Closure $key * - * @return mixed The value of the element if found or the return value of anonymous function is truthy, default value otherwise. + * @return mixed The value of the element if found or the return value of callable is truthy, default value otherwise. */ public static function getValue( array|object $array, From c7092fb7ff43f8426e0b01923a14b3282d3354c2 Mon Sep 17 00:00:00 2001 From: Yusup Hambali Date: Fri, 26 Jan 2024 04:34:49 +0700 Subject: [PATCH 03/16] Fix failing tests --- src/ArrayHelper.php | 24 ++++++++++-------------- tests/ArrayHelper/GetValueTest.php | 9 ++------- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/src/ArrayHelper.php b/src/ArrayHelper.php index 1adcf16..d04c098 100644 --- a/src/ArrayHelper.php +++ b/src/ArrayHelper.php @@ -98,7 +98,7 @@ public static function toArray(mixed $object, array $properties = [], bool $recu if (is_int($key)) { $result[$name] = $object->$name; } else { - $result[$key] = self::getValue($object, $name); + $result[$key] = $name instanceof Closure ? $name($object) : self::getValue($object, $name); } } @@ -253,15 +253,11 @@ private static function getRootValue(mixed $array, float|int|string $key, mixed } private static function getValueByMatcher( - array|object $array, + array $array, Closure $match, mixed $default = null ): mixed { - if (is_object($array)) { - $array = self::getObjectVars($array); - } - foreach ($array as $key => $value) { if ($match($value, $key)) { return $value; @@ -779,7 +775,7 @@ public static function index( $lastArray[] = $element; } } else { - $value = self::getValue($element, $key); + $value = $key instanceof Closure ? $key($element) : self::getValue($element, $key); if ($value !== null) { $lastArray[self::normalizeArrayKey($value)] = $element; } @@ -841,11 +837,11 @@ public static function getColumn(iterable $array, Closure|string $name, bool $ke $result = []; if ($keepKeys) { foreach ($array as $k => $element) { - $result[$k] = self::getValue($element, $name); + $result[$k] = $name instanceof Closure ? $name($element) : self::getValue($element, $name); } } else { foreach ($array as $element) { - $result[] = self::getValue($element, $name); + $result[] = $name instanceof Closure ? $name($element) : self::getValue($element, $name); } } @@ -906,8 +902,8 @@ public static function map( if ($from instanceof Closure || $to instanceof Closure || !is_array($array)) { $result = []; foreach ($array as $element) { - $key = (string)self::getValue($element, $from); - $result[$key] = self::getValue($element, $to); + $key = (string)($from instanceof Closure ? $from($element) : self::getValue($element, $from)); + $result[$key] = $to instanceof Closure ? $to($element) : self::getValue($element, $to); } return $result; @@ -918,9 +914,9 @@ public static function map( $result = []; foreach ($array as $element) { - $groupKey = (string)self::getValue($element, $group); - $key = (string)self::getValue($element, $from); - $result[$groupKey][$key] = self::getValue($element, $to); + $groupKey = (string)($group instanceof Closure ? $group($element) : self::getValue($element, $group)); + $key = (string)($from instanceof Closure ? $from($element) : self::getValue($element, $from)); + $result[$groupKey][$key] = $to instanceof Closure ? $to($element) : self::getValue($element, $to); } return $result; diff --git a/tests/ArrayHelper/GetValueTest.php b/tests/ArrayHelper/GetValueTest.php index 9e45c1f..08ec87a 100644 --- a/tests/ArrayHelper/GetValueTest.php +++ b/tests/ArrayHelper/GetValueTest.php @@ -53,11 +53,6 @@ private function commonDataProviderFromArray(): array ['name', 'test'], ['noname', null], ['noname', 'test', 'test'], - [ - static fn ($array, $defaultValue) => $array['date'] . $defaultValue, - '31-12-2113test', - 'test', - ], [['version', 2], 'two'], [['version', 2.0], 'two'], [42.7, 500], @@ -101,8 +96,8 @@ public function testGetValueByMatcher(): void $posts = [ - new ArrayObject(['title' => 'hello world']), - new ArrayObject(['title' => 'hello test', 'tag' => 'test']), + (object)['title' => 'hello world'], + (object)['title' => 'hello test', 'tag' => 'test'], ]; $taggedPost = ArrayHelper::getValue($posts, fn ($post) => isset($post->tag)); From c60cd4b67bc61cb8deeb1fd0933d5e174e58440a Mon Sep 17 00:00:00 2001 From: Yusup Hambali Date: Sat, 27 Jan 2024 09:56:24 +0700 Subject: [PATCH 04/16] fix object data handling --- src/ArrayHelper.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ArrayHelper.php b/src/ArrayHelper.php index d04c098..504c977 100644 --- a/src/ArrayHelper.php +++ b/src/ArrayHelper.php @@ -184,7 +184,8 @@ public static function parametrizedMerge(array $arrays, ?int $depth): array * getting value from an object. * * @psalm-param ArrayKey|Closure $key - * + * + * @throws InvalidArgumentException if `$array` is an object. * @return mixed The value of the element if found or the return value of callable is truthy, default value otherwise. */ public static function getValue( @@ -193,6 +194,9 @@ public static function getValue( mixed $default = null ): mixed { if ($key instanceof Closure) { + if (is_object($array)) { + throw new InvalidArgumentException('Matcher cannot be applied to an object'); + } return self::getValueByMatcher($array, $key, $default); } From 2f81148c2a0a7cbff0b6fd94c4ec96aacaec4a82 Mon Sep 17 00:00:00 2001 From: Yusup Hambali Date: Sat, 27 Jan 2024 09:57:02 +0700 Subject: [PATCH 05/16] fix psalm fails --- src/ArrayHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ArrayHelper.php b/src/ArrayHelper.php index 504c977..da639c5 100644 --- a/src/ArrayHelper.php +++ b/src/ArrayHelper.php @@ -92,7 +92,7 @@ public static function toArray(mixed $object, array $properties = [], bool $recu $result = []; /** * @var int|string $key - * @var string $name + * @var string|Closure $name */ foreach ($properties[$className] as $key => $name) { if (is_int($key)) { From c86c0faec554ebdcb76f7cddef4081e96664b096 Mon Sep 17 00:00:00 2001 From: Yusup Hambali Date: Sat, 27 Jan 2024 09:59:40 +0700 Subject: [PATCH 06/16] fix phpdocs --- src/ArrayHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ArrayHelper.php b/src/ArrayHelper.php index da639c5..4bca521 100644 --- a/src/ArrayHelper.php +++ b/src/ArrayHelper.php @@ -185,7 +185,7 @@ public static function parametrizedMerge(array $arrays, ?int $depth): array * * @psalm-param ArrayKey|Closure $key * - * @throws InvalidArgumentException if `$array` is an object. + * @throws InvalidArgumentException if `$key` is callable and `$array` is an object. * @return mixed The value of the element if found or the return value of callable is truthy, default value otherwise. */ public static function getValue( From 20da553d5a4f8f616dd26f86866ec0ea774f9ef1 Mon Sep 17 00:00:00 2001 From: Yusup Hambali Date: Sat, 27 Jan 2024 10:11:23 +0700 Subject: [PATCH 07/16] fix psalm fails --- src/ArrayHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ArrayHelper.php b/src/ArrayHelper.php index 4bca521..21724e6 100644 --- a/src/ArrayHelper.php +++ b/src/ArrayHelper.php @@ -96,7 +96,7 @@ public static function toArray(mixed $object, array $properties = [], bool $recu */ foreach ($properties[$className] as $key => $name) { if (is_int($key)) { - $result[$name] = $object->$name; + $result[(string)$name] = $object->$name; } else { $result[$key] = $name instanceof Closure ? $name($object) : self::getValue($object, $name); } From b3dde8ea88b6ae113112184c77d282902b9ede9d Mon Sep 17 00:00:00 2001 From: Yusup Hambali Date: Wed, 22 May 2024 21:51:35 +0700 Subject: [PATCH 08/16] Revert "fix object data handling" This reverts commit c60cd4b67bc61cb8deeb1fd0933d5e174e58440a. --- src/ArrayHelper.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/ArrayHelper.php b/src/ArrayHelper.php index 0c4c14f..e670ad1 100644 --- a/src/ArrayHelper.php +++ b/src/ArrayHelper.php @@ -201,7 +201,6 @@ public static function parametrizedMerge(array $arrays, ?int $depth): array * * @psalm-param ArrayKey|Closure $key * - * @throws InvalidArgumentException if `$key` is callable and `$array` is an object. * @return mixed The value of the element if found or the return value of callable is truthy, default value otherwise. */ public static function getValue( @@ -210,9 +209,6 @@ public static function getValue( mixed $default = null ): mixed { if ($key instanceof Closure) { - if (is_object($array)) { - throw new InvalidArgumentException('Matcher cannot be applied to an object'); - } return self::getValueByMatcher($array, $key, $default); } From c04e4e864967a8c4ac35a02f9d1d3815b0ee28c3 Mon Sep 17 00:00:00 2001 From: Yusup Hambali Date: Wed, 22 May 2024 22:28:46 +0700 Subject: [PATCH 09/16] Revert "Implement value retrieval by matcher" This reverts commit a4e40b31691b1a4f3e1b594a66a7907005bd673a. --- CHANGELOG.md | 2 +- src/ArrayHelper.php | 51 +++++++++++------------------- tests/ArrayHelper/GetValueTest.php | 25 +++------------ 3 files changed, 25 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5af4ad8..cd213c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 3.1.1 under development -- New #142: Add `ArrayHelper::find()`, `ArrayHelper::findKey()`, `ArrayHelper::findAny()` and `ArrayHelper::findAll()` method for value retrieval or checks by a predicate function (@yus-ham) +- New #142: Add `ArrayHelper::find()`, `ArrayHelper::findKey()`, `ArrayHelper::findAny()` and `ArrayHelper::findAll()` methods for value retrieval or checks by a predicate function (@yus-ham) - Enh #156: Improve psalm types in `ArrayHelper::getObjectVars()`, `ArrayableInterface`, `ArrayableTrait` and `ArrayAccessTrait` (@vjik) diff --git a/src/ArrayHelper.php b/src/ArrayHelper.php index e670ad1..d71a7fd 100644 --- a/src/ArrayHelper.php +++ b/src/ArrayHelper.php @@ -108,13 +108,13 @@ public static function toArray(mixed $object, array $properties = [], bool $recu $result = []; /** * @var int|string $key - * @var Closure|string $name + * @var string $name */ foreach ($properties[$className] as $key => $name) { if (is_int($key)) { - $result[(string)$name] = $object->$name; + $result[$name] = $object->$name; } else { - $result[$key] = $name instanceof Closure ? $name($object) : self::getValue($object, $name); + $result[$key] = self::getValue($object, $name); } } @@ -183,9 +183,9 @@ public static function parametrizedMerge(array $arrays, ?int $depth): array * // Working with object: * $username = \Yiisoft\Arrays\ArrayHelper::getValue($user, 'username'); * - * // Retrieve the value by matcher function: - * $firstActiveMember = \Yiisoft\Arrays\ArrayHelper::getValue($users, function ($user, $key) { - * return $user->type === 'member' && $user->isActive; + * // Working with anonymous function: + * $fullName = \Yiisoft\Arrays\ArrayHelper::getValue($user, function ($user, $defaultValue) { + * return $user->firstName . ' ' . $user->lastName; * }); * * // Using an array of keys to retrieve the value: @@ -194,14 +194,15 @@ public static function parametrizedMerge(array $arrays, ?int $depth): array * * @param array|object $array Array or object to extract value from. * @param array|Closure|float|int|string $key Key name of the array element, - * an array of keys, object property name, object method like `getName()` or a callable. The callable function signature should be: - * `function($value, $key)`. + * an array of keys, object property name, object method like `getName()`, or an anonymous function + * returning the value. The anonymous function signature should be: + * `function($array, $defaultValue)`. * @param mixed $default The default value to be returned if the specified array key does not exist. Not used when * getting value from an object. * * @psalm-param ArrayKey|Closure $key * - * @return mixed The value of the element if found or the return value of callable is truthy, default value otherwise. + * @return mixed The value of the element if found, default value otherwise. */ public static function getValue( array|object $array, @@ -209,7 +210,7 @@ public static function getValue( mixed $default = null ): mixed { if ($key instanceof Closure) { - return self::getValueByMatcher($array, $key, $default); + return $key($array, $default); } if (is_array($key)) { @@ -268,20 +269,6 @@ private static function getRootValue(mixed $array, float|int|string $key, mixed return $default; } - private static function getValueByMatcher( - array $array, - Closure $match, - mixed $default = null - ): mixed { - foreach ($array as $key => $value) { - if ($match($value, $key)) { - return $value; - } - } - - return $default; - } - /** * Retrieves the value of an array element or object property with the given key or property name. * If the key does not exist in the array or object, the default value will be returned instead. @@ -790,7 +777,7 @@ public static function index( $lastArray[] = $element; } } else { - $value = $key instanceof Closure ? $key($element) : self::getValue($element, $key); + $value = self::getValue($element, $key); if ($value !== null) { $lastArray[self::normalizeArrayKey($value)] = $element; } @@ -852,11 +839,11 @@ public static function getColumn(iterable $array, Closure|string $name, bool $ke $result = []; if ($keepKeys) { foreach ($array as $k => $element) { - $result[$k] = $name instanceof Closure ? $name($element) : self::getValue($element, $name); + $result[$k] = self::getValue($element, $name); } } else { foreach ($array as $element) { - $result[] = $name instanceof Closure ? $name($element) : self::getValue($element, $name); + $result[] = self::getValue($element, $name); } } @@ -917,8 +904,8 @@ public static function map( if ($from instanceof Closure || $to instanceof Closure || !is_array($array)) { $result = []; foreach ($array as $element) { - $key = (string)($from instanceof Closure ? $from($element) : self::getValue($element, $from)); - $result[$key] = $to instanceof Closure ? $to($element) : self::getValue($element, $to); + $key = (string)self::getValue($element, $from); + $result[$key] = self::getValue($element, $to); } return $result; @@ -929,9 +916,9 @@ public static function map( $result = []; foreach ($array as $element) { - $groupKey = (string)($group instanceof Closure ? $group($element) : self::getValue($element, $group)); - $key = (string)($from instanceof Closure ? $from($element) : self::getValue($element, $from)); - $result[$groupKey][$key] = $to instanceof Closure ? $to($element) : self::getValue($element, $to); + $groupKey = (string)self::getValue($element, $group); + $key = (string)self::getValue($element, $from); + $result[$groupKey][$key] = self::getValue($element, $to); } return $result; diff --git a/tests/ArrayHelper/GetValueTest.php b/tests/ArrayHelper/GetValueTest.php index 50e1fb7..bdcad98 100644 --- a/tests/ArrayHelper/GetValueTest.php +++ b/tests/ArrayHelper/GetValueTest.php @@ -53,6 +53,11 @@ private function commonDataProviderFromArray(): array ['name', 'test'], ['noname', null], ['noname', 'test', 'test'], + [ + static fn ($array, $defaultValue) => $array['date'] . $defaultValue, + '31-12-2113test', + 'test', + ], [['version', 2], 'two'], [['version', 2.0], 'two'], [42.7, 500], @@ -84,26 +89,6 @@ public function testGetValueFromArray($key, $expected, $default = null): void $this->assertEquals($expected, ArrayHelper::getValue($this->array, $key, $default)); } - public function testGetValueByMatcher(): void - { - $users = [ - ['name' => 'Cebe', 'status' => 'active'], - ['name' => 'John', 'status' => 'not active'], - ]; - - $activeUser = ArrayHelper::getValue($users, fn ($user) => $user['status'] === 'active'); - $this->assertEquals('Cebe', $activeUser['name']); - - - $posts = [ - (object)['title' => 'hello world'], - (object)['title' => 'hello test', 'tag' => 'test'], - ]; - - $taggedPost = ArrayHelper::getValue($posts, fn ($post) => isset($post->tag)); - $this->assertEquals('hello test', $taggedPost->title); - } - public function dataProviderGetValueByPathFromArray(): array { return array_merge($this->commonDataProviderFromArray(), [ From 640280315d784d6d78c4f9c406053203547cb353 Mon Sep 17 00:00:00 2001 From: Yusup Hambali Date: Wed, 22 May 2024 22:47:34 +0700 Subject: [PATCH 10/16] Add failing tests --- tests/ArrayHelper/FindTest.php | 115 +++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 tests/ArrayHelper/FindTest.php diff --git a/tests/ArrayHelper/FindTest.php b/tests/ArrayHelper/FindTest.php new file mode 100644 index 0000000..e443e49 --- /dev/null +++ b/tests/ArrayHelper/FindTest.php @@ -0,0 +1,115 @@ + 1, + "b" => 2, + "c" => 3, + "d" => 4, + "e" => 5, + ], + [ + 1, 2, 3, 4, 5 + ], + ]; + + public function dataProviderFindFromArray(): array + { + return [ + [$this->array[0], fn ($value) => $value > 3, 4], + [$this->array[1], fn ($value) => $value > 3, 4], + [$this->array[1], fn ($value) => $value > 5, null], + [$this->array[0], fn ($value, $key) => $key === "c", 3], + [$this->array[0], fn () => false, null], + [[], fn () => true, null], + ]; + } + + /** + * @dataProvider dataProviderFindFromArray + * + * @param Closure $predicate + * @param $expected + */ + public function testFind($array, $predicate, $expected): void + { + $this->assertEquals($expected, ArrayHelper::find($array, $predicate)); + } + + public function dataProviderFindKeyFromArray(): array + { + return [ + [$this->array[0], fn ($value) => $value > 3, "d"], + [$this->array[1], fn ($value) => $value > 3, 3], + [$this->array[1], fn ($value) => $value > 5, null], + [$this->array[0], fn ($value, $key) => $key === "c", "c"], + [$this->array[0], fn () => false, null], + [[], fn () => true, null], + ]; + } + + /** + * @dataProvider dataProviderFindKeyFromArray + * + * @param Closure $predicate + * @param $expected + */ + public function testFindKey($array, $predicate, $expected): void + { + $this->assertEquals($expected, ArrayHelper::findKey($array, $predicate)); + } + + public function dataProviderAnyFromArray(): array + { + return [ + [$this->array[0], fn ($value) => $value > 3, true], + [$this->array[1], fn ($value) => $value > 3, true], + [$this->array[1], fn ($value) => $value > 5, false], + [$this->array[0], fn ($value, $key) => $key === "c", true], + [$this->array[0], fn () => false, false], + [[], fn () => true, false], + ]; + } + + /** + * @dataProvider dataProviderAnyFromArray + * + * @param Closure $predicate + * @param $expected + */ + public function testAny($array, $predicate, $expected): void + { + $this->assertEquals($expected, ArrayHelper::any($array, $predicate)); + } + + public function dataProviderAllFromArray(): array + { + return [ + [$this->array[0], fn ($value) => $value > 0, true], + [$this->array[1], fn ($value) => $value > 0, true], + [$this->array[1], fn ($value) => $value > 1, false], + [[], fn () => true, true], + ]; + } + + /** + * @dataProvider dataProviderAllFromArray + * + * @param Closure $predicate + * @param $expected + */ + public function testAll($array, $predicate, $expected): void + { + $this->assertEquals($expected, ArrayHelper::all($array, $predicate)); + } +} From 1131c207993b46a3b54b955e9e7a91a7bd4ef477 Mon Sep 17 00:00:00 2001 From: Yusup Hambali Date: Thu, 23 May 2024 02:32:14 +0700 Subject: [PATCH 11/16] Implement `find()`, `findKey()`, `any()` and `all()` methods --- src/ArrayHelper.php | 68 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/src/ArrayHelper.php b/src/ArrayHelper.php index d71a7fd..2fc7bc4 100644 --- a/src/ArrayHelper.php +++ b/src/ArrayHelper.php @@ -1436,4 +1436,72 @@ private static function parseMixedPath(array|float|int|string $path, string $del return is_string($path) ? StringHelper::parsePath($path, $delimiter) : $path; } + + /** + * @param array $array The array that should be searched + * @param Closure $predicate The predicate callback to call to check each element. The first parameter contains the value, the second parameter contains the corresponding key. If this function returns truthy value, the value is returned from `find()` and the callback will not be called for further elements + * + * @return mixed Returns the value of the first element for which the `$predicate` callback returns true. If no matching element is found the function returns `null` + */ + public static function find(array $array, Closure $predicate): mixed + { + foreach ($array as $key => $value) { + if ($predicate($value, $key)) { + return $value; + } + } + + return null; + } + + /** + * @param array The array that should be searched + * @param Closure $predicate The predicate callback to call to check each element. The first parameter contains the value, the second parameter contains the corresponding key. If this function returns truthy value, the key is returned from `findKey()` and the callback will not be called for further elements + * + * @return string|int|null Returns the key of the first element for which the `$predicate` callback returns `true`. If no matching element is found the function returns `null` + */ + public static function findKey(array $array, Closure $predicate): string|int|null + { + foreach ($array as $key => $value) { + if ($predicate($value, $key)) { + return $key; + } + } + + return null; + } + + /** + * @param array The array that should be searched + * @param Closure $predicate The predicate callback to call to check each element. The first parameter contains the value, the second parameter contains the corresponding key. If this function returns truthy value, `true` is returned from `any()` and the callback will not be called for further elements + * + * @return bool Returns `true`, if one element for which predicate callback returns truthy value. Otherwise the function returns `false` + */ + public static function any(array $array, Closure $predicate): bool + { + foreach ($array as $key => $value) { + if ($predicate($value, $key)) { + return true; + } + } + + return false; + } + + /** + * @param array The array that should be searched + * @param Closure $predicate The predicate callback to call to check each element. The first parameter contains the value, the second parameter contains the corresponding key. If this function returns falsy value, `false` is returned from `all()` and the callback will not be called for further elements + * + * @return bool Returns `false`, if one element for which predicate callback returns truthy value. Otherwise the function returns `false` + */ + public static function all(array $array, Closure $predicate): bool + { + foreach ($array as $key => $value) { + if (!$predicate($value, $key)) { + return false; + } + } + + return true; + } } From a4affab6610c08635cc2f634846d105607e1b7ca Mon Sep 17 00:00:00 2001 From: Yusup Hambali Date: Thu, 23 May 2024 02:54:28 +0700 Subject: [PATCH 12/16] Update readme --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 30412d0..9c57cf1 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,8 @@ Overall the helper has the following method groups. - getValueByPath - getColumn - getObjectVars +- find +- findKey ### Setting data @@ -78,6 +80,8 @@ Overall the helper has the following method groups. - isIn - isSubset +- any +- all ### Transformation From f47a0fa1262eafcd5a09d8ac0a641896fbf5c234 Mon Sep 17 00:00:00 2001 From: Yusup Hambali Date: Thu, 23 May 2024 03:10:23 +0700 Subject: [PATCH 13/16] fix --- src/ArrayHelper.php | 12 ++++++------ tests/ArrayHelper/FindTest.php | 20 ++++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/ArrayHelper.php b/src/ArrayHelper.php index 2fc7bc4..e430930 100644 --- a/src/ArrayHelper.php +++ b/src/ArrayHelper.php @@ -1440,7 +1440,7 @@ private static function parseMixedPath(array|float|int|string $path, string $del /** * @param array $array The array that should be searched * @param Closure $predicate The predicate callback to call to check each element. The first parameter contains the value, the second parameter contains the corresponding key. If this function returns truthy value, the value is returned from `find()` and the callback will not be called for further elements - * + * * @return mixed Returns the value of the first element for which the `$predicate` callback returns true. If no matching element is found the function returns `null` */ public static function find(array $array, Closure $predicate): mixed @@ -1457,10 +1457,10 @@ public static function find(array $array, Closure $predicate): mixed /** * @param array The array that should be searched * @param Closure $predicate The predicate callback to call to check each element. The first parameter contains the value, the second parameter contains the corresponding key. If this function returns truthy value, the key is returned from `findKey()` and the callback will not be called for further elements - * - * @return string|int|null Returns the key of the first element for which the `$predicate` callback returns `true`. If no matching element is found the function returns `null` + * + * @return int|string|null Returns the key of the first element for which the `$predicate` callback returns `true`. If no matching element is found the function returns `null` */ - public static function findKey(array $array, Closure $predicate): string|int|null + public static function findKey(array $array, Closure $predicate): int|string|null { foreach ($array as $key => $value) { if ($predicate($value, $key)) { @@ -1474,7 +1474,7 @@ public static function findKey(array $array, Closure $predicate): string|int|nul /** * @param array The array that should be searched * @param Closure $predicate The predicate callback to call to check each element. The first parameter contains the value, the second parameter contains the corresponding key. If this function returns truthy value, `true` is returned from `any()` and the callback will not be called for further elements - * + * * @return bool Returns `true`, if one element for which predicate callback returns truthy value. Otherwise the function returns `false` */ public static function any(array $array, Closure $predicate): bool @@ -1491,7 +1491,7 @@ public static function any(array $array, Closure $predicate): bool /** * @param array The array that should be searched * @param Closure $predicate The predicate callback to call to check each element. The first parameter contains the value, the second parameter contains the corresponding key. If this function returns falsy value, `false` is returned from `all()` and the callback will not be called for further elements - * + * * @return bool Returns `false`, if one element for which predicate callback returns truthy value. Otherwise the function returns `false` */ public static function all(array $array, Closure $predicate): bool diff --git a/tests/ArrayHelper/FindTest.php b/tests/ArrayHelper/FindTest.php index e443e49..f4c012e 100644 --- a/tests/ArrayHelper/FindTest.php +++ b/tests/ArrayHelper/FindTest.php @@ -12,14 +12,14 @@ final class FindTest extends TestCase { private array $array = [ [ - "a" => 1, - "b" => 2, - "c" => 3, - "d" => 4, - "e" => 5, + 'a' => 1, + 'b' => 2, + 'c' => 3, + 'd' => 4, + 'e' => 5, ], [ - 1, 2, 3, 4, 5 + 1, 2, 3, 4, 5, ], ]; @@ -29,7 +29,7 @@ public function dataProviderFindFromArray(): array [$this->array[0], fn ($value) => $value > 3, 4], [$this->array[1], fn ($value) => $value > 3, 4], [$this->array[1], fn ($value) => $value > 5, null], - [$this->array[0], fn ($value, $key) => $key === "c", 3], + [$this->array[0], fn ($value, $key) => $key === 'c', 3], [$this->array[0], fn () => false, null], [[], fn () => true, null], ]; @@ -49,10 +49,10 @@ public function testFind($array, $predicate, $expected): void public function dataProviderFindKeyFromArray(): array { return [ - [$this->array[0], fn ($value) => $value > 3, "d"], + [$this->array[0], fn ($value) => $value > 3, 'd'], [$this->array[1], fn ($value) => $value > 3, 3], [$this->array[1], fn ($value) => $value > 5, null], - [$this->array[0], fn ($value, $key) => $key === "c", "c"], + [$this->array[0], fn ($value, $key) => $key === 'c', 'c'], [$this->array[0], fn () => false, null], [[], fn () => true, null], ]; @@ -75,7 +75,7 @@ public function dataProviderAnyFromArray(): array [$this->array[0], fn ($value) => $value > 3, true], [$this->array[1], fn ($value) => $value > 3, true], [$this->array[1], fn ($value) => $value > 5, false], - [$this->array[0], fn ($value, $key) => $key === "c", true], + [$this->array[0], fn ($value, $key) => $key === 'c', true], [$this->array[0], fn () => false, false], [[], fn () => true, false], ]; From 160ae19f639e148599e46fb31148bf21ba0f10ed Mon Sep 17 00:00:00 2001 From: Yusup Hambali Date: Thu, 23 May 2024 03:16:08 +0700 Subject: [PATCH 14/16] fix changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd213c6..1bdeb0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 3.1.1 under development -- New #142: Add `ArrayHelper::find()`, `ArrayHelper::findKey()`, `ArrayHelper::findAny()` and `ArrayHelper::findAll()` methods for value retrieval or checks by a predicate function (@yus-ham) +- New #142: Add `ArrayHelper::find()`, `ArrayHelper::findKey()`, `ArrayHelper::any()` and `ArrayHelper::all()` methods for value retrieval or checks by a predicate function (@yus-ham) - Enh #156: Improve psalm types in `ArrayHelper::getObjectVars()`, `ArrayableInterface`, `ArrayableTrait` and `ArrayAccessTrait` (@vjik) From 0cc7a60a0ebd412eefb71eda7f9edb4edd65660b Mon Sep 17 00:00:00 2001 From: Yusup Hambali Date: Thu, 23 May 2024 23:05:20 +0700 Subject: [PATCH 15/16] Fix API docs --- src/ArrayHelper.php | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/ArrayHelper.php b/src/ArrayHelper.php index e430930..c620fc4 100644 --- a/src/ArrayHelper.php +++ b/src/ArrayHelper.php @@ -1438,10 +1438,12 @@ private static function parseMixedPath(array|float|int|string $path, string $del } /** - * @param array $array The array that should be searched - * @param Closure $predicate The predicate callback to call to check each element. The first parameter contains the value, the second parameter contains the corresponding key. If this function returns truthy value, the value is returned from `find()` and the callback will not be called for further elements + * Get the first element in an array that pass the test implemented by the provided callback. * - * @return mixed Returns the value of the first element for which the `$predicate` callback returns true. If no matching element is found the function returns `null` + * @param array $array The array that should be searched. + * @param Closure $predicate The predicate callback to call to check each element. The first parameter contains the value, the second parameter contains the corresponding key. If this function returns truthy value, the value is returned from `find()` and the callback will not be called for further elements. + * + * @return mixed The value of the first element for which the `$predicate` callback returns true. If no matching element is found the function returns `null`. */ public static function find(array $array, Closure $predicate): mixed { @@ -1455,10 +1457,12 @@ public static function find(array $array, Closure $predicate): mixed } /** - * @param array The array that should be searched - * @param Closure $predicate The predicate callback to call to check each element. The first parameter contains the value, the second parameter contains the corresponding key. If this function returns truthy value, the key is returned from `findKey()` and the callback will not be called for further elements + * Get the key of the first element in an array that pass the test implemented by the provided callback. + * + * @param array The array that should be searched. + * @param Closure $predicate The predicate callback to call to check each element. The first parameter contains the value, the second parameter contains the corresponding key. If this function returns truthy value, the key is returned from `findKey()` and the callback will not be called for further elements. * - * @return int|string|null Returns the key of the first element for which the `$predicate` callback returns `true`. If no matching element is found the function returns `null` + * @return int|string|null The key of the first element for which the `$predicate` callback returns `true`. If no matching element is found the function returns `null`. */ public static function findKey(array $array, Closure $predicate): int|string|null { @@ -1472,10 +1476,12 @@ public static function findKey(array $array, Closure $predicate): int|string|nul } /** - * @param array The array that should be searched - * @param Closure $predicate The predicate callback to call to check each element. The first parameter contains the value, the second parameter contains the corresponding key. If this function returns truthy value, `true` is returned from `any()` and the callback will not be called for further elements + * Check whether at least one element in an array pass the test implemented by the provided callback. * - * @return bool Returns `true`, if one element for which predicate callback returns truthy value. Otherwise the function returns `false` + * @param array The array which each element will be tested against callback. + * @param Closure $predicate The predicate callback to call to check each element. The first parameter contains the value, the second parameter contains the corresponding key. If this function returns truthy value, `true` is returned from `any()` and the callback will not be called for further elements. + * + * @return bool `true` if one element for which predicate callback returns truthy value. Otherwise the function returns `false`. */ public static function any(array $array, Closure $predicate): bool { @@ -1489,10 +1495,12 @@ public static function any(array $array, Closure $predicate): bool } /** - * @param array The array that should be searched - * @param Closure $predicate The predicate callback to call to check each element. The first parameter contains the value, the second parameter contains the corresponding key. If this function returns falsy value, `false` is returned from `all()` and the callback will not be called for further elements + * Check whether all elements in an array pass the test implemented by the provided callback. + * + * @param array The array which each element will be tested against callback. + * @param Closure $predicate The predicate callback to call to check each element. The first parameter contains the value, the second parameter contains the corresponding key. If this function returns falsy value, `false` is returned from `all()` and the callback will not be called for further elements. * - * @return bool Returns `false`, if one element for which predicate callback returns truthy value. Otherwise the function returns `false` + * @return bool `false` if one element for which predicate callback returns falsy value. Otherwise the function returns `true`. */ public static function all(array $array, Closure $predicate): bool { From 6135f7f78a7d3d3223124a2f69c47637da0c85e1 Mon Sep 17 00:00:00 2001 From: Yusup Hambali Date: Thu, 23 May 2024 23:34:04 +0700 Subject: [PATCH 16/16] use `callable` --- src/ArrayHelper.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ArrayHelper.php b/src/ArrayHelper.php index c620fc4..63d7d3b 100644 --- a/src/ArrayHelper.php +++ b/src/ArrayHelper.php @@ -1445,7 +1445,7 @@ private static function parseMixedPath(array|float|int|string $path, string $del * * @return mixed The value of the first element for which the `$predicate` callback returns true. If no matching element is found the function returns `null`. */ - public static function find(array $array, Closure $predicate): mixed + public static function find(array $array, callable $predicate): mixed { foreach ($array as $key => $value) { if ($predicate($value, $key)) { @@ -1464,7 +1464,7 @@ public static function find(array $array, Closure $predicate): mixed * * @return int|string|null The key of the first element for which the `$predicate` callback returns `true`. If no matching element is found the function returns `null`. */ - public static function findKey(array $array, Closure $predicate): int|string|null + public static function findKey(array $array, callable $predicate): int|string|null { foreach ($array as $key => $value) { if ($predicate($value, $key)) { @@ -1483,7 +1483,7 @@ public static function findKey(array $array, Closure $predicate): int|string|nul * * @return bool `true` if one element for which predicate callback returns truthy value. Otherwise the function returns `false`. */ - public static function any(array $array, Closure $predicate): bool + public static function any(array $array, callable $predicate): bool { foreach ($array as $key => $value) { if ($predicate($value, $key)) { @@ -1502,7 +1502,7 @@ public static function any(array $array, Closure $predicate): bool * * @return bool `false` if one element for which predicate callback returns falsy value. Otherwise the function returns `true`. */ - public static function all(array $array, Closure $predicate): bool + public static function all(array $array, callable $predicate): bool { foreach ($array as $key => $value) { if (!$predicate($value, $key)) {