1111
1212namespace 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 *
0 commit comments