diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a0e85b..1bdeb0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## 3.1.1 under development +- 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) 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 diff --git a/src/ArrayHelper.php b/src/ArrayHelper.php index d71a7fd..63d7d3b 100644 --- a/src/ArrayHelper.php +++ b/src/ArrayHelper.php @@ -1436,4 +1436,80 @@ private static function parseMixedPath(array|float|int|string $path, string $del return is_string($path) ? StringHelper::parsePath($path, $delimiter) : $path; } + + /** + * Get the first element in an array that pass the test implemented by the provided callback. + * + * @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, callable $predicate): mixed + { + foreach ($array as $key => $value) { + if ($predicate($value, $key)) { + return $value; + } + } + + return null; + } + + /** + * 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 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, callable $predicate): int|string|null + { + foreach ($array as $key => $value) { + if ($predicate($value, $key)) { + return $key; + } + } + + return null; + } + + /** + * Check whether at least one element 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 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, callable $predicate): bool + { + foreach ($array as $key => $value) { + if ($predicate($value, $key)) { + return true; + } + } + + return false; + } + + /** + * 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 `false` if one element for which predicate callback returns falsy value. Otherwise the function returns `true`. + */ + public static function all(array $array, callable $predicate): bool + { + foreach ($array as $key => $value) { + if (!$predicate($value, $key)) { + return false; + } + } + + return true; + } } diff --git a/tests/ArrayHelper/FindTest.php b/tests/ArrayHelper/FindTest.php new file mode 100644 index 0000000..f4c012e --- /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)); + } +}