Skip to content

Commit e875b05

Browse files
authored
Merge pull request #8131 from kenjis/feat-ArrayHelper-dotKeyExists
feat: add ArrayHelper::dotKeyExists()
2 parents 76bc36d + dce6576 commit e875b05

File tree

2 files changed

+143
-3
lines changed

2 files changed

+143
-3
lines changed

system/Helpers/Array/ArrayHelper.php

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace CodeIgniter\Helpers\Array;
1313

14+
use InvalidArgumentException;
15+
1416
/**
1517
* @interal This is internal implementation for the framework.
1618
*
@@ -27,9 +29,21 @@ final class ArrayHelper
2729
*
2830
* @used-by dot_array_search()
2931
*
32+
* @param string $index The index as dot array syntax.
33+
*
3034
* @return array|bool|int|object|string|null
3135
*/
3236
public static function dotSearch(string $index, array $array)
37+
{
38+
return self::arraySearchDot(self::convertToArray($index), $array);
39+
}
40+
41+
/**
42+
* @param string $index The index as dot array syntax.
43+
*
44+
* @return list<string> The index as an array.
45+
*/
46+
private static function convertToArray(string $index): array
3347
{
3448
// See https://regex101.com/r/44Ipql/1
3549
$segments = preg_split(
@@ -39,9 +53,10 @@ public static function dotSearch(string $index, array $array)
3953
PREG_SPLIT_NO_EMPTY
4054
);
4155

42-
$segments = array_map(static fn ($key) => str_replace('\.', '.', $key), $segments);
43-
44-
return self::arraySearchDot($segments, $array);
56+
return array_map(
57+
static fn ($key) => str_replace('\.', '.', $key),
58+
$segments
59+
);
4560
}
4661

4762
/**
@@ -106,6 +121,59 @@ private static function arraySearchDot(array $indexes, array $array)
106121
return null;
107122
}
108123

124+
/**
125+
* array_key_exists() with dot array syntax.
126+
*
127+
* If wildcard `*` is used, all items for the key after it must have the key.
128+
*/
129+
public static function dotKeyExists(string $index, array $array): bool
130+
{
131+
if (str_ends_with($index, '*') || str_contains($index, '*.*')) {
132+
throw new InvalidArgumentException(
133+
'You must set key right after "*". Invalid index: "' . $index . '"'
134+
);
135+
}
136+
137+
$indexes = self::convertToArray($index);
138+
139+
// If indexes is empty, returns false.
140+
if ($indexes === []) {
141+
return false;
142+
}
143+
144+
$currentArray = $array;
145+
146+
// Grab the current index
147+
while ($currentIndex = array_shift($indexes)) {
148+
if ($currentIndex === '*') {
149+
$currentIndex = array_shift($indexes);
150+
151+
foreach ($currentArray as $item) {
152+
if (! array_key_exists($currentIndex, $item)) {
153+
return false;
154+
}
155+
}
156+
157+
// If indexes is empty, all elements are checked.
158+
if ($indexes === []) {
159+
return true;
160+
}
161+
162+
$currentArray = self::dotSearch('*.' . $currentIndex, $currentArray);
163+
164+
continue;
165+
}
166+
167+
if (! array_key_exists($currentIndex, $currentArray)) {
168+
return false;
169+
}
170+
171+
$currentArray = $currentArray[$currentIndex];
172+
}
173+
174+
return true;
175+
}
176+
109177
/**
110178
* Groups all rows by their index values. Result's depth equals number of indexes
111179
*
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
/**
4+
* This file is part of CodeIgniter 4 framework.
5+
*
6+
* (c) CodeIgniter Foundation <[email protected]>
7+
*
8+
* For the full copyright and license information, please view
9+
* the LICENSE file that was distributed with this source code.
10+
*/
11+
12+
namespace CodeIgniter\Helpers\Array;
13+
14+
use CodeIgniter\Test\CIUnitTestCase;
15+
use InvalidArgumentException;
16+
17+
/**
18+
* @group Others
19+
*
20+
* @internal
21+
*/
22+
final class ArrayHelperDotKeyExistsTest extends CIUnitTestCase
23+
{
24+
private array $array = [
25+
'contacts' => [
26+
'friends' => [
27+
['name' => 'Fred Flinstone', 'age' => 20],
28+
['age' => 21], // 'name' key does not exist
29+
],
30+
],
31+
];
32+
33+
public function testDotKeyExists(): void
34+
{
35+
$this->assertFalse(ArrayHelper::dotKeyExists('', $this->array));
36+
$this->assertTrue(ArrayHelper::dotKeyExists('contacts', $this->array));
37+
$this->assertFalse(ArrayHelper::dotKeyExists('not', $this->array));
38+
$this->assertTrue(ArrayHelper::dotKeyExists('contacts.friends', $this->array));
39+
$this->assertFalse(ArrayHelper::dotKeyExists('not.friends', $this->array));
40+
$this->assertTrue(ArrayHelper::dotKeyExists('contacts.friends.0.name', $this->array));
41+
$this->assertFalse(ArrayHelper::dotKeyExists('contacts.friends.1.name', $this->array));
42+
}
43+
44+
public function testDotKeyExistsWithEndingWildCard(): void
45+
{
46+
$this->expectException(InvalidArgumentException::class);
47+
$this->expectExceptionMessage('You must set key right after "*". Invalid index: "contacts.*"');
48+
49+
$this->assertTrue(ArrayHelper::dotKeyExists('contacts.*', $this->array));
50+
}
51+
52+
public function testDotKeyExistsWithDoubleWildCard(): void
53+
{
54+
$this->expectException(InvalidArgumentException::class);
55+
$this->expectExceptionMessage('You must set key right after "*". Invalid index: "contacts.*.*.age"');
56+
57+
$this->assertTrue(ArrayHelper::dotKeyExists('contacts.*.*.age', $this->array));
58+
}
59+
60+
public function testDotKeyExistsWithWildCard(): void
61+
{
62+
$this->assertTrue(ArrayHelper::dotKeyExists('*.friends', $this->array));
63+
$this->assertTrue(ArrayHelper::dotKeyExists('contacts.friends.*.age', $this->array));
64+
$this->assertFalse(ArrayHelper::dotKeyExists('contacts.friends.*.name', $this->array));
65+
$this->assertTrue(ArrayHelper::dotKeyExists('*.friends.*.age', $this->array));
66+
$this->assertFalse(ArrayHelper::dotKeyExists('*.friends.*.name', $this->array));
67+
$this->assertTrue(ArrayHelper::dotKeyExists('contacts.*.0.age', $this->array));
68+
$this->assertTrue(ArrayHelper::dotKeyExists('contacts.*.1.age', $this->array));
69+
$this->assertTrue(ArrayHelper::dotKeyExists('contacts.*.0.name', $this->array));
70+
$this->assertFalse(ArrayHelper::dotKeyExists('contacts.*.1.name', $this->array));
71+
}
72+
}

0 commit comments

Comments
 (0)