@@ -146,9 +146,9 @@ private static function arraySearchDot(array $indexes, array|object $array)
146146 *
147147 * If wildcard `*` is used, all items for the key after it must have the key.
148148 *
149- * @param array<array-key, mixed> $array
149+ * @param array<array-key, mixed>|object $array
150150 */
151- public static function dotHas (string $ index , array $ array ): bool
151+ public static function dotHas (string $ index , array | object $ array ): bool
152152 {
153153 self ::ensureValidWildcardPattern ($ index );
154154
@@ -164,10 +164,10 @@ public static function dotHas(string $index, array $array): bool
164164 /**
165165 * Recursively check key existence by dot path, including wildcard support.
166166 *
167- * @param array<array-key, mixed> $array
168- * @param list<string> $indexes
167+ * @param array<array-key, mixed>|object $array
168+ * @param list<string> $indexes
169169 */
170- private static function hasByDotPath (array $ array , array $ indexes ): bool
170+ private static function hasByDotPath (array | object $ array , array $ indexes ): bool
171171 {
172172 if ($ indexes === []) {
173173 return true ;
@@ -176,28 +176,32 @@ private static function hasByDotPath(array $array, array $indexes): bool
176176 $ currentIndex = array_shift ($ indexes );
177177
178178 if ($ currentIndex === '* ' ) {
179- foreach ($ array as $ item ) {
180- if (! is_array ($ item ) || ! self ::hasByDotPath ($ item , $ indexes )) {
179+ $ iterable = is_object ($ array ) ? self ::toIterable ($ array ) : $ array ;
180+
181+ foreach ($ iterable as $ item ) {
182+ if ((! is_array ($ item ) && ! is_object ($ item )) || ! self ::hasByDotPath ($ item , $ indexes )) {
181183 return false ;
182184 }
183185 }
184186
185187 return true ;
186188 }
187189
188- if (! array_key_exists ( $ currentIndex , $ array )) {
190+ if (! self :: valueExists ( $ array , $ currentIndex )) {
189191 return false ;
190192 }
191193
192194 if ($ indexes === []) {
193195 return true ;
194196 }
195197
196- if (! is_array ($ array [$ currentIndex ])) {
198+ $ value = self ::value ($ array , $ currentIndex );
199+
200+ if (! is_array ($ value ) && ! is_object ($ value )) {
197201 return false ;
198202 }
199203
200- return self ::hasByDotPath ($ array [ $ currentIndex ] , $ indexes );
204+ return self ::hasByDotPath ($ value , $ indexes );
201205 }
202206
203207 /**
@@ -247,12 +251,12 @@ public static function dotUnset(array &$array, string $index): bool
247251 /**
248252 * Gets only the specified keys using dot syntax.
249253 *
250- * @param array<array-key, mixed> $array
251- * @param list<string>|string $indexes
254+ * @param array<array-key, mixed>|object $array
255+ * @param list<string>|string $indexes
252256 *
253257 * @return array<array-key, mixed>
254258 */
255- public static function dotOnly (array $ array , array |string $ indexes ): array
259+ public static function dotOnly (array | object $ array , array |string $ indexes ): array
256260 {
257261 $ indexes = is_string ($ indexes ) ? [$ indexes ] : $ indexes ;
258262 $ result = [];
@@ -261,7 +265,7 @@ public static function dotOnly(array $array, array|string $indexes): array
261265 self ::ensureValidWildcardPattern ($ index , true );
262266
263267 if ($ index === '* ' ) {
264- $ result = [...$ result , ...$ array ];
268+ $ result = [...$ result , ...( is_object ( $ array) ? self :: toIterable ( $ array ) : $ array ) ];
265269
266270 continue ;
267271 }
@@ -280,15 +284,15 @@ public static function dotOnly(array $array, array|string $indexes): array
280284 /**
281285 * Gets all keys except the specified ones using dot syntax.
282286 *
283- * @param array<array-key, mixed> $array
284- * @param list<string>|string $indexes
287+ * @param array<array-key, mixed>|object $array
288+ * @param list<string>|string $indexes
285289 *
286290 * @return array<array-key, mixed>
287291 */
288- public static function dotExcept (array $ array , array |string $ indexes ): array
292+ public static function dotExcept (array | object $ array , array |string $ indexes ): array
289293 {
290294 $ indexes = is_string ($ indexes ) ? [$ indexes ] : $ indexes ;
291- $ result = $ array ;
295+ $ result = self :: toArrayView ( $ array) ;
292296
293297 foreach ($ indexes as $ index ) {
294298 self ::ensureValidWildcardPattern ($ index , true );
@@ -466,20 +470,20 @@ public static function sortValuesByNatural(array &$array, $sortByIndex = null):
466470 private static function valueExists (array |object $ data , string $ key ): bool
467471 {
468472 if (is_array ($ data )) {
469- return isset ( $ data [ $ key] );
473+ return array_key_exists ( $ key, $ data );
470474 }
471475
472476 $ array = self ::entityToArray ($ data );
473477
474478 if ($ array !== null ) {
475- return isset ( $ array [ $ key] );
479+ return array_key_exists ( $ key, $ array );
476480 }
477481
478482 if ($ data instanceof ArrayAccess && $ data ->offsetExists ($ key )) {
479483 return true ;
480484 }
481485
482- if (isset ( get_object_vars ($ data )[ $ key ] )) {
486+ if (array_key_exists ( $ key , get_object_vars ($ data ))) {
483487 return true ;
484488 }
485489
@@ -550,6 +554,26 @@ private static function toIterable(object $data): array
550554 return get_object_vars ($ data );
551555 }
552556
557+ /**
558+ * Normalize arrays or objects to an array view safe for dotExcept().
559+ *
560+ * @param array<array-key, mixed>|object $data
561+ *
562+ * @return array<array-key, mixed>
563+ */
564+ private static function toArrayView (array |object $ data ): array
565+ {
566+ $ array = is_object ($ data ) ? self ::toIterable ($ data ) : $ data ;
567+
568+ foreach ($ array as $ key => $ value ) {
569+ if (is_array ($ value ) || is_object ($ value )) {
570+ $ array [$ key ] = self ::toArrayView ($ value );
571+ }
572+ }
573+
574+ return $ array ;
575+ }
576+
553577 /**
554578 * Throws exception for invalid wildcard patterns.
555579 */
@@ -688,14 +712,15 @@ private static function clearByDotPath(array &$array, array $indexes): int
688712 }
689713
690714 /**
691- * Projects matching paths from source array into result with preserved structure.
715+ * Projects matching paths from source into result with preserved structure.
692716 *
693- * @param list<string> $indexes
694- * @param list<string> $prefix
695- * @param array<array-key, mixed> $result
717+ * @param array<array-key, mixed>|object $source
718+ * @param list<string> $indexes
719+ * @param list<string> $prefix
720+ * @param array<array-key, mixed> $result
696721 */
697722 private static function projectByDotPath (
698- mixed $ source ,
723+ array | object $ source ,
699724 array $ indexes ,
700725 array &$ result ,
701726 array $ prefix = [],
@@ -709,21 +734,37 @@ private static function projectByDotPath(
709734 $ currentIndex = array_shift ($ indexes );
710735
711736 if ($ currentIndex === '* ' ) {
712- if (! is_array ($ source )) {
713- return ;
714- }
737+ $ iterable = is_object ($ source ) ? self ::toIterable ($ source ) : $ source ;
738+
739+ foreach ($ iterable as $ key => $ value ) {
740+ if (! is_array ($ value ) && ! is_object ($ value )) {
741+ if ($ indexes === []) {
742+ self ::setByDotPath ($ result , [...$ prefix , (string ) $ key ], $ value );
743+ }
744+
745+ continue ;
746+ }
715747
716- foreach ($ source as $ key => $ value ) {
717748 self ::projectByDotPath ($ value , $ indexes , $ result , [...$ prefix , (string ) $ key ]);
718749 }
719750
720751 return ;
721752 }
722753
723- if (! is_array ($ source ) || ! array_key_exists ($ currentIndex , $ source )) {
754+ if (! self ::valueExists ($ source , $ currentIndex )) {
755+ return ;
756+ }
757+
758+ $ value = self ::value ($ source , $ currentIndex );
759+
760+ if (! is_array ($ value ) && ! is_object ($ value )) {
761+ if ($ indexes === []) {
762+ self ::setByDotPath ($ result , [...$ prefix , $ currentIndex ], $ value );
763+ }
764+
724765 return ;
725766 }
726767
727- self ::projectByDotPath ($ source [ $ currentIndex ] , $ indexes , $ result , [...$ prefix , $ currentIndex ]);
768+ self ::projectByDotPath ($ value , $ indexes , $ result , [...$ prefix , $ currentIndex ]);
728769 }
729770}
0 commit comments