Skip to content

Commit

Permalink
Merge pull request #8131 from kenjis/feat-ArrayHelper-dotKeyExists
Browse files Browse the repository at this point in the history
feat: add ArrayHelper::dotKeyExists()
  • Loading branch information
kenjis authored Nov 6, 2023
2 parents 76bc36d + dce6576 commit e875b05
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 3 deletions.
74 changes: 71 additions & 3 deletions system/Helpers/Array/ArrayHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

namespace CodeIgniter\Helpers\Array;

use InvalidArgumentException;

/**
* @interal This is internal implementation for the framework.
*
Expand All @@ -27,9 +29,21 @@ final class ArrayHelper
*
* @used-by dot_array_search()
*
* @param string $index The index as dot array syntax.
*
* @return array|bool|int|object|string|null
*/
public static function dotSearch(string $index, array $array)
{
return self::arraySearchDot(self::convertToArray($index), $array);
}

/**
* @param string $index The index as dot array syntax.
*
* @return list<string> The index as an array.
*/
private static function convertToArray(string $index): array
{
// See https://regex101.com/r/44Ipql/1
$segments = preg_split(
Expand All @@ -39,9 +53,10 @@ public static function dotSearch(string $index, array $array)
PREG_SPLIT_NO_EMPTY
);

$segments = array_map(static fn ($key) => str_replace('\.', '.', $key), $segments);

return self::arraySearchDot($segments, $array);
return array_map(
static fn ($key) => str_replace('\.', '.', $key),
$segments
);
}

/**
Expand Down Expand Up @@ -106,6 +121,59 @@ private static function arraySearchDot(array $indexes, array $array)
return null;
}

/**
* array_key_exists() with dot array syntax.
*
* If wildcard `*` is used, all items for the key after it must have the key.
*/
public static function dotKeyExists(string $index, array $array): bool
{
if (str_ends_with($index, '*') || str_contains($index, '*.*')) {
throw new InvalidArgumentException(
'You must set key right after "*". Invalid index: "' . $index . '"'
);
}

$indexes = self::convertToArray($index);

// If indexes is empty, returns false.
if ($indexes === []) {
return false;
}

$currentArray = $array;

// Grab the current index
while ($currentIndex = array_shift($indexes)) {
if ($currentIndex === '*') {
$currentIndex = array_shift($indexes);

foreach ($currentArray as $item) {
if (! array_key_exists($currentIndex, $item)) {
return false;
}
}

// If indexes is empty, all elements are checked.
if ($indexes === []) {
return true;
}

$currentArray = self::dotSearch('*.' . $currentIndex, $currentArray);

continue;
}

if (! array_key_exists($currentIndex, $currentArray)) {
return false;
}

$currentArray = $currentArray[$currentIndex];
}

return true;
}

/**
* Groups all rows by their index values. Result's depth equals number of indexes
*
Expand Down
72 changes: 72 additions & 0 deletions tests/system/Helpers/Array/ArrayHelperDotKeyExistsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <[email protected]>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/

namespace CodeIgniter\Helpers\Array;

use CodeIgniter\Test\CIUnitTestCase;
use InvalidArgumentException;

/**
* @group Others
*
* @internal
*/
final class ArrayHelperDotKeyExistsTest extends CIUnitTestCase
{
private array $array = [
'contacts' => [
'friends' => [
['name' => 'Fred Flinstone', 'age' => 20],
['age' => 21], // 'name' key does not exist
],
],
];

public function testDotKeyExists(): void
{
$this->assertFalse(ArrayHelper::dotKeyExists('', $this->array));
$this->assertTrue(ArrayHelper::dotKeyExists('contacts', $this->array));
$this->assertFalse(ArrayHelper::dotKeyExists('not', $this->array));
$this->assertTrue(ArrayHelper::dotKeyExists('contacts.friends', $this->array));
$this->assertFalse(ArrayHelper::dotKeyExists('not.friends', $this->array));
$this->assertTrue(ArrayHelper::dotKeyExists('contacts.friends.0.name', $this->array));
$this->assertFalse(ArrayHelper::dotKeyExists('contacts.friends.1.name', $this->array));
}

public function testDotKeyExistsWithEndingWildCard(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('You must set key right after "*". Invalid index: "contacts.*"');

$this->assertTrue(ArrayHelper::dotKeyExists('contacts.*', $this->array));
}

public function testDotKeyExistsWithDoubleWildCard(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('You must set key right after "*". Invalid index: "contacts.*.*.age"');

$this->assertTrue(ArrayHelper::dotKeyExists('contacts.*.*.age', $this->array));
}

public function testDotKeyExistsWithWildCard(): void
{
$this->assertTrue(ArrayHelper::dotKeyExists('*.friends', $this->array));
$this->assertTrue(ArrayHelper::dotKeyExists('contacts.friends.*.age', $this->array));
$this->assertFalse(ArrayHelper::dotKeyExists('contacts.friends.*.name', $this->array));
$this->assertTrue(ArrayHelper::dotKeyExists('*.friends.*.age', $this->array));
$this->assertFalse(ArrayHelper::dotKeyExists('*.friends.*.name', $this->array));
$this->assertTrue(ArrayHelper::dotKeyExists('contacts.*.0.age', $this->array));
$this->assertTrue(ArrayHelper::dotKeyExists('contacts.*.1.age', $this->array));
$this->assertTrue(ArrayHelper::dotKeyExists('contacts.*.0.name', $this->array));
$this->assertFalse(ArrayHelper::dotKeyExists('contacts.*.1.name', $this->array));
}
}

0 comments on commit e875b05

Please sign in to comment.