1313
1414namespace CodeIgniter \Helpers \Array ;
1515
16+ use ArrayAccess ;
1617use CodeIgniter \Exceptions \InvalidArgumentException ;
18+ use ReflectionMethod ;
1719
1820/**
1921 * @internal This is internal implementation for the framework.
@@ -34,11 +36,12 @@ final class ArrayHelper
3436 *
3537 * @used-by dot_array_search()
3638 *
37- * @param string $index The index as dot array syntax.
39+ * @param string $index The index as dot array syntax.
40+ * @param array<array-key, mixed>|object $array
3841 *
39- * @return array|bool|int|object|string|null
42+ * @return array<array-key, mixed> |bool|int|object|string|null
4043 */
41- public static function dotSearch (string $ index , array $ array )
44+ public static function dotSearch (string $ index , array | object $ array )
4245 {
4346 return self ::arraySearchDot (self ::convertToArray ($ index ), $ array );
4447 }
@@ -78,9 +81,12 @@ private static function convertToArray(string $index): array
7881 *
7982 * @used-by dotSearch()
8083 *
81- * @return array|bool|float|int|object|string|null
84+ * @param list<string> $indexes
85+ * @param array<array-key, mixed>|object $array
86+ *
87+ * @return array<array-key, mixed>|bool|float|int|object|string|null
8288 */
83- private static function arraySearchDot (array $ indexes , array $ array )
89+ private static function arraySearchDot (array $ indexes , array | object $ array )
8490 {
8591 // If index is empty, returns null.
8692 if ($ indexes === []) {
@@ -90,7 +96,7 @@ private static function arraySearchDot(array $indexes, array $array)
9096 // Grab the current index
9197 $ currentIndex = array_shift ($ indexes );
9298
93- if (! isset ($ array[ $ currentIndex] ) && $ currentIndex !== '* ' ) {
99+ if (! self :: valueExists ($ array, $ currentIndex ) && $ currentIndex !== '* ' ) {
94100 return null ;
95101 }
96102
@@ -99,7 +105,7 @@ private static function arraySearchDot(array $indexes, array $array)
99105 $ answer = [];
100106
101107 foreach ($ array as $ value ) {
102- if (! is_array ($ value )) {
108+ if (! is_array ($ value ) && ! is_object ( $ value ) ) {
103109 return null ;
104110 }
105111
@@ -119,12 +125,14 @@ private static function arraySearchDot(array $indexes, array $array)
119125 // If this is the last index, make sure to return it now,
120126 // and not try to recurse through things.
121127 if ($ indexes === []) {
122- return $ array[ $ currentIndex] ;
128+ return self :: value ( $ array, $ currentIndex) ;
123129 }
124130
131+ $ value = self ::value ($ array , $ currentIndex );
132+
125133 // Do we need to recursively search this value?
126- if (is_array ($ array [ $ currentIndex ] ) && $ array [ $ currentIndex ] !== []) {
127- return self ::arraySearchDot ($ indexes , $ array [ $ currentIndex ] );
134+ if (( is_array ($ value ) && $ value !== []) || is_object ( $ value ) ) {
135+ return self ::arraySearchDot ($ indexes , $ value );
128136 }
129137
130138 // Otherwise, not found.
@@ -136,9 +144,9 @@ private static function arraySearchDot(array $indexes, array $array)
136144 *
137145 * If wildcard `*` is used, all items for the key after it must have the key.
138146 *
139- * @param array<array-key, mixed> $array
147+ * @param array<array-key, mixed>|object $array
140148 */
141- public static function dotHas (string $ index , array $ array ): bool
149+ public static function dotHas (string $ index , array | object $ array ): bool
142150 {
143151 self ::ensureValidWildcardPattern ($ index );
144152
@@ -154,10 +162,10 @@ public static function dotHas(string $index, array $array): bool
154162 /**
155163 * Recursively check key existence by dot path, including wildcard support.
156164 *
157- * @param array<array-key, mixed> $array
158- * @param list<string> $indexes
165+ * @param array<array-key, mixed>|object $array
166+ * @param list<string> $indexes
159167 */
160- private static function hasByDotPath (array $ array , array $ indexes ): bool
168+ private static function hasByDotPath (array | object $ array , array $ indexes ): bool
161169 {
162170 if ($ indexes === []) {
163171 return true ;
@@ -167,27 +175,29 @@ private static function hasByDotPath(array $array, array $indexes): bool
167175
168176 if ($ currentIndex === '* ' ) {
169177 foreach ($ array as $ item ) {
170- if (! is_array ($ item ) || ! self ::hasByDotPath ($ item , $ indexes )) {
178+ if (( ! is_array ($ item) && ! is_object ( $ item ) ) || ! self ::hasByDotPath ($ item , $ indexes )) {
171179 return false ;
172180 }
173181 }
174182
175183 return true ;
176184 }
177185
178- if (! array_key_exists ( $ currentIndex , $ array )) {
186+ if (! self :: keyExists ( $ array , $ currentIndex )) {
179187 return false ;
180188 }
181189
182190 if ($ indexes === []) {
183191 return true ;
184192 }
185193
186- if (! is_array ($ array [$ currentIndex ])) {
194+ $ value = self ::value ($ array , $ currentIndex );
195+
196+ if (! is_array ($ value ) && ! is_object ($ value )) {
187197 return false ;
188198 }
189199
190- return self ::hasByDotPath ($ array [ $ currentIndex ] , $ indexes );
200+ return self ::hasByDotPath ($ value , $ indexes );
191201 }
192202
193203 /**
@@ -333,13 +343,16 @@ public static function groupBy(array $array, array $indexes, bool $includeEmpty
333343
334344 /**
335345 * Recursively attach $row to the $indexes path of values found by
336- * `dot_array_search()` .
346+ * dot syntax .
337347 *
338348 * @used-by groupBy()
349+ *
350+ * @param array<array-key, mixed>|object $row
351+ * @param list<string> $indexes
339352 */
340353 private static function arrayAttachIndexedValue (
341354 array $ result ,
342- array $ row ,
355+ array | object $ row ,
343356 array $ indexes ,
344357 bool $ includeEmpty ,
345358 ): array {
@@ -349,7 +362,7 @@ private static function arrayAttachIndexedValue(
349362 return $ result ;
350363 }
351364
352- $ value = dot_array_search ($ index , $ row );
365+ $ value = self :: dotSearch ($ index , $ row );
353366
354367 if (! is_scalar ($ value )) {
355368 $ value = '' ;
@@ -447,6 +460,118 @@ public static function sortValuesByNatural(array &$array, $sortByIndex = null):
447460 });
448461 }
449462
463+ /**
464+ * @param array<array-key, mixed>|object $data
465+ */
466+ private static function keyExists (array |object $ data , string $ key ): bool
467+ {
468+ if (is_array ($ data )) {
469+ return array_key_exists ($ key , $ data );
470+ }
471+
472+ $ array = self ::objectToArray ($ data );
473+
474+ if ($ array !== null ) {
475+ return array_key_exists ($ key , $ array );
476+ }
477+
478+ if ($ data instanceof ArrayAccess && $ data ->offsetExists ($ key )) {
479+ return true ;
480+ }
481+
482+ if (array_key_exists ($ key , get_object_vars ($ data ))) {
483+ return true ;
484+ }
485+
486+ return isset ($ data ->{$ key });
487+ }
488+
489+ /**
490+ * @param array<array-key, mixed>|object $data
491+ */
492+ private static function valueExists (array |object $ data , string $ key ): bool
493+ {
494+ if (is_array ($ data )) {
495+ return isset ($ data [$ key ]);
496+ }
497+
498+ $ array = self ::objectToArray ($ data );
499+
500+ if ($ array !== null ) {
501+ return isset ($ array [$ key ]);
502+ }
503+
504+ if ($ data instanceof ArrayAccess && $ data ->offsetExists ($ key )) {
505+ return true ;
506+ }
507+
508+ if (isset (get_object_vars ($ data )[$ key ])) {
509+ return true ;
510+ }
511+
512+ return isset ($ data ->{$ key });
513+ }
514+
515+ /**
516+ * @param array<array-key, mixed>|object $data
517+ */
518+ private static function value (array |object $ data , string $ key ): mixed
519+ {
520+ if (is_array ($ data )) {
521+ return $ data [$ key ];
522+ }
523+
524+ $ array = self ::objectToArray ($ data );
525+
526+ if ($ array !== null ) {
527+ return $ array [$ key ];
528+ }
529+
530+ if ($ data instanceof ArrayAccess && $ data ->offsetExists ($ key )) {
531+ return $ data ->offsetGet ($ key );
532+ }
533+
534+ $ properties = get_object_vars ($ data );
535+
536+ if (array_key_exists ($ key , $ properties )) {
537+ return $ properties [$ key ];
538+ }
539+
540+ return $ data ->{$ key };
541+ }
542+
543+ /**
544+ * @return array<array-key, mixed>|null
545+ */
546+ private static function objectToArray (object $ data ): ?array
547+ {
548+ if (method_exists ($ data , 'toRawArray ' )) {
549+ $ method = new ReflectionMethod ($ data , 'toRawArray ' );
550+
551+ if ($ method ->isPublic () && $ method ->getNumberOfRequiredParameters () === 0 ) {
552+ $ array = $ data ->toRawArray ();
553+
554+ if (is_array ($ array )) {
555+ return $ array ;
556+ }
557+ }
558+ }
559+
560+ if (method_exists ($ data , 'toArray ' )) {
561+ $ method = new ReflectionMethod ($ data , 'toArray ' );
562+
563+ if ($ method ->isPublic () && $ method ->getNumberOfRequiredParameters () === 0 ) {
564+ $ array = $ data ->toArray ();
565+
566+ if (is_array ($ array )) {
567+ return $ array ;
568+ }
569+ }
570+ }
571+
572+ return null ;
573+ }
574+
450575 /**
451576 * Throws exception for invalid wildcard patterns.
452577 */
@@ -606,7 +731,7 @@ private static function projectByDotPath(
606731 $ currentIndex = array_shift ($ indexes );
607732
608733 if ($ currentIndex === '* ' ) {
609- if (! is_array ($ source )) {
734+ if (! is_array ($ source ) && ! is_object ( $ source ) ) {
610735 return ;
611736 }
612737
@@ -617,10 +742,10 @@ private static function projectByDotPath(
617742 return ;
618743 }
619744
620- if (! is_array ($ source ) || ! array_key_exists ( $ currentIndex , $ source )) {
745+ if (( ! is_array ($ source ) && ! is_object ( $ source )) || ! self :: keyExists ( $ source , $ currentIndex )) {
621746 return ;
622747 }
623748
624- self ::projectByDotPath ($ source[ $ currentIndex] , $ indexes , $ result , [...$ prefix , $ currentIndex ]);
749+ self ::projectByDotPath (self :: value ( $ source, $ currentIndex) , $ indexes , $ result , [...$ prefix , $ currentIndex ]);
625750 }
626751}
0 commit comments