Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement value retrieval and checks by predicate callback #142

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ Overall the helper has the following method groups.
- getValueByPath
- getColumn
- getObjectVars
- find
- findKey

### Setting data

Expand Down Expand Up @@ -78,6 +80,8 @@ Overall the helper has the following method groups.

- isIn
- isSubset
- any
- all

### Transformation

Expand Down
76 changes: 76 additions & 0 deletions src/ArrayHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -753,7 +753,7 @@
foreach ($array as $element) {
if (!is_array($element) && !is_object($element)) {
throw new InvalidArgumentException(
'index() can not get value from ' . gettype($element) .

Check warning on line 756 in src/ArrayHelper.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.2-ubuntu-latest

Escaped Mutant for Mutator "Concat": --- Original +++ New @@ @@ /** @var mixed $element */ foreach ($array as $element) { if (!is_array($element) && !is_object($element)) { - throw new InvalidArgumentException('index() can not get value from ' . gettype($element) . '. The $array should be either multidimensional array or an array of objects.'); + throw new InvalidArgumentException(gettype($element) . 'index() can not get value from ' . '. The $array should be either multidimensional array or an array of objects.'); } $lastArray =& $result; foreach ($groups as $group) {

Check warning on line 756 in src/ArrayHelper.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.2-ubuntu-latest

Escaped Mutant for Mutator "ConcatOperandRemoval": --- Original +++ New @@ @@ /** @var mixed $element */ foreach ($array as $element) { if (!is_array($element) && !is_object($element)) { - throw new InvalidArgumentException('index() can not get value from ' . gettype($element) . '. The $array should be either multidimensional array or an array of objects.'); + throw new InvalidArgumentException(gettype($element) . '. The $array should be either multidimensional array or an array of objects.'); } $lastArray =& $result; foreach ($groups as $group) {

Check warning on line 756 in src/ArrayHelper.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.2-ubuntu-latest

Escaped Mutant for Mutator "ConcatOperandRemoval": --- Original +++ New @@ @@ /** @var mixed $element */ foreach ($array as $element) { if (!is_array($element) && !is_object($element)) { - throw new InvalidArgumentException('index() can not get value from ' . gettype($element) . '. The $array should be either multidimensional array or an array of objects.'); + throw new InvalidArgumentException('index() can not get value from ' . '. The $array should be either multidimensional array or an array of objects.'); } $lastArray =& $result; foreach ($groups as $group) {

Check warning on line 756 in src/ArrayHelper.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.2-ubuntu-latest

Escaped Mutant for Mutator "Concat": --- Original +++ New @@ @@ /** @var mixed $element */ foreach ($array as $element) { if (!is_array($element) && !is_object($element)) { - throw new InvalidArgumentException('index() can not get value from ' . gettype($element) . '. The $array should be either multidimensional array or an array of objects.'); + throw new InvalidArgumentException('index() can not get value from ' . '. The $array should be either multidimensional array or an array of objects.' . gettype($element)); } $lastArray =& $result; foreach ($groups as $group) {

Check warning on line 756 in src/ArrayHelper.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.2-ubuntu-latest

Escaped Mutant for Mutator "ConcatOperandRemoval": --- Original +++ New @@ @@ /** @var mixed $element */ foreach ($array as $element) { if (!is_array($element) && !is_object($element)) { - throw new InvalidArgumentException('index() can not get value from ' . gettype($element) . '. The $array should be either multidimensional array or an array of objects.'); + throw new InvalidArgumentException('index() can not get value from ' . gettype($element)); } $lastArray =& $result; foreach ($groups as $group) {
'. The $array should be either multidimensional array or an array of objects.'
);
}
Expand Down Expand Up @@ -937,7 +937,7 @@
*
* @return bool Whether the array contains the specified key.
*/
public static function keyExists(array $array, array|float|int|string $key, bool $caseSensitive = true): bool

Check warning on line 940 in src/ArrayHelper.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.2-ubuntu-latest

Escaped Mutant for Mutator "TrueValue": --- Original +++ New @@ @@ * * @return bool Whether the array contains the specified key. */ - public static function keyExists(array $array, array|float|int|string $key, bool $caseSensitive = true) : bool + public static function keyExists(array $array, array|float|int|string $key, bool $caseSensitive = false) : bool { if (is_array($key)) { if (count($key) === 1) {
{
if (is_array($key)) {
if (count($key) === 1) {
Expand Down Expand Up @@ -1010,7 +1010,7 @@
public static function pathExists(
array $array,
array|float|int|string $path,
bool $caseSensitive = true,

Check warning on line 1013 in src/ArrayHelper.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.2-ubuntu-latest

Escaped Mutant for Mutator "TrueValue": --- Original +++ New @@ @@ * * @psalm-param ArrayPath $path */ - public static function pathExists(array $array, array|float|int|string $path, bool $caseSensitive = true, string $delimiter = '.') : bool + public static function pathExists(array $array, array|float|int|string $path, bool $caseSensitive = false, string $delimiter = '.') : bool { return self::keyExists($array, self::parseMixedPath($path, $delimiter), $caseSensitive); }
string $delimiter = '.'
): bool {
return self::keyExists($array, self::parseMixedPath($path, $delimiter), $caseSensitive);
Expand Down Expand Up @@ -1311,7 +1311,7 @@
$numNestedKeys = count($keys) - 1;
foreach ($keys as $i => $key) {
if (!is_array($excludeNode) || !array_key_exists($key, $excludeNode)) {
continue 2; // Jump to next filter.

Check warning on line 1314 in src/ArrayHelper.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.2-ubuntu-latest

Escaped Mutant for Mutator "DecrementInteger": --- Original +++ New @@ @@ $numNestedKeys = count($keys) - 1; foreach ($keys as $i => $key) { if (!is_array($excludeNode) || !array_key_exists($key, $excludeNode)) { - continue 2; + continue 1; // Jump to next filter. } if ($i < $numNestedKeys) {

Check warning on line 1314 in src/ArrayHelper.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.2-ubuntu-latest

Escaped Mutant for Mutator "Continue_": --- Original +++ New @@ @@ $numNestedKeys = count($keys) - 1; foreach ($keys as $i => $key) { if (!is_array($excludeNode) || !array_key_exists($key, $excludeNode)) { - continue 2; + break; // Jump to next filter. } if ($i < $numNestedKeys) {
}

if ($i < $numNestedKeys) {
Expand All @@ -1319,7 +1319,7 @@
$excludeNode = &$excludeNode[$key];
} else {
unset($excludeNode[$key]);
break;

Check warning on line 1322 in src/ArrayHelper.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.2-ubuntu-latest

Escaped Mutant for Mutator "Break_": --- Original +++ New @@ @@ $excludeNode =& $excludeNode[$key]; } else { unset($excludeNode[$key]); - break; + continue; } } }
}
}
}
Expand Down Expand Up @@ -1436,4 +1436,80 @@

return is_string($path) ? StringHelper::parsePath($path, $delimiter) : $path;
}

/**
yus-ham marked this conversation as resolved.
Show resolved Hide resolved
* 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;
}
}
115 changes: 115 additions & 0 deletions tests/ArrayHelper/FindTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Arrays\Tests\ArrayHelper;

use Closure;
use PHPUnit\Framework\TestCase;
use Yiisoft\Arrays\ArrayHelper;

final class FindTest extends TestCase
{
private array $array = [
[
'a' => 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));
}
}
Loading