Skip to content

Commit 502ecc6

Browse files
committed
feat: support object values in array dot helpers
1 parent 1a3987f commit 502ecc6

6 files changed

Lines changed: 375 additions & 81 deletions

File tree

system/Helpers/Array/ArrayHelper.php

Lines changed: 150 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313

1414
namespace CodeIgniter\Helpers\Array;
1515

16+
use ArrayAccess;
1617
use 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
}

system/Helpers/array_helper.php

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@
2020
* Searches an array through dot syntax. Supports
2121
* wildcard searches, like foo.*.bar
2222
*
23-
* @return array|bool|int|object|string|null
23+
* @param array<array-key, mixed>|object $array
24+
*
25+
* @return array<array-key, mixed>|bool|int|object|string|null
2426
*/
25-
function dot_array_search(string $index, array $array)
27+
function dot_array_search(string $index, array|object $array)
2628
{
2729
return ArrayHelper::dotSearch($index, $array);
2830
}
@@ -32,9 +34,9 @@ function dot_array_search(string $index, array $array)
3234
/**
3335
* Checks if an array key exists using dot syntax.
3436
*
35-
* @param array<array-key, mixed> $array
37+
* @param array<array-key, mixed>|object $array
3638
*/
37-
function dot_array_has(string $index, array $array): bool
39+
function dot_array_has(string $index, array|object $array): bool
3840
{
3941
return ArrayHelper::dotHas($index, $array);
4042
}

0 commit comments

Comments
 (0)