Skip to content

Commit abddd1d

Browse files
authored
Merge pull request #8130 from kenjis/refactor-ArrayHelper
refactor: move ArrayHelper class
2 parents d4388f0 + 112fcea commit abddd1d

File tree

6 files changed

+240
-205
lines changed

6 files changed

+240
-205
lines changed

phpstan-baseline.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2293,7 +2293,7 @@
22932293
];
22942294
$ignoreErrors[] = [
22952295
'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#',
2296-
'count' => 3,
2296+
'count' => 2,
22972297
'path' => __DIR__ . '/system/Helpers/array_helper.php',
22982298
];
22992299
$ignoreErrors[] = [

system/Commands/Translation/LocalizationFinder.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
use CodeIgniter\CLI\BaseCommand;
1515
use CodeIgniter\CLI\CLI;
16-
use CodeIgniter\Commands\Translation\LocalizationFinder\ArrayHelper;
16+
use CodeIgniter\Helpers\Array\ArrayHelper;
1717
use Config\App;
1818
use Locale;
1919
use RecursiveDirectoryIterator;

system/Commands/Translation/LocalizationFinder/ArrayHelper.php

Lines changed: 0 additions & 75 deletions
This file was deleted.

system/Helpers/Array/ArrayHelper.php

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
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+
/**
15+
* @interal This is internal implementation for the framework.
16+
*
17+
* If there are any methods that should be provided, make them
18+
* public APIs via helper functions.
19+
*
20+
* @see \CodeIgniter\Helpers\Array\ArrayHelperRecursiveDiffTest
21+
*/
22+
final class ArrayHelper
23+
{
24+
/**
25+
* Searches an array through dot syntax. Supports wildcard searches,
26+
* like `foo.*.bar`.
27+
*
28+
* @used-by dot_array_search()
29+
*
30+
* @return array|bool|int|object|string|null
31+
*/
32+
public static function dotSearch(string $index, array $array)
33+
{
34+
// See https://regex101.com/r/44Ipql/1
35+
$segments = preg_split(
36+
'/(?<!\\\\)\./',
37+
rtrim($index, '* '),
38+
0,
39+
PREG_SPLIT_NO_EMPTY
40+
);
41+
42+
$segments = array_map(static fn ($key) => str_replace('\.', '.', $key), $segments);
43+
44+
return self::arraySearchDot($segments, $array);
45+
}
46+
47+
/**
48+
* Recursively search the array with wildcards.
49+
*
50+
* @used-by dotSearch()
51+
*
52+
* @return array|bool|float|int|object|string|null
53+
*/
54+
private static function arraySearchDot(array $indexes, array $array)
55+
{
56+
// If index is empty, returns null.
57+
if ($indexes === []) {
58+
return null;
59+
}
60+
61+
// Grab the current index
62+
$currentIndex = array_shift($indexes);
63+
64+
if (! isset($array[$currentIndex]) && $currentIndex !== '*') {
65+
return null;
66+
}
67+
68+
// Handle Wildcard (*)
69+
if ($currentIndex === '*') {
70+
$answer = [];
71+
72+
foreach ($array as $value) {
73+
if (! is_array($value)) {
74+
return null;
75+
}
76+
77+
$answer[] = self::arraySearchDot($indexes, $value);
78+
}
79+
80+
$answer = array_filter($answer, static fn ($value) => $value !== null);
81+
82+
if ($answer !== []) {
83+
if (count($answer) === 1) {
84+
// If array only has one element, we return that element for BC.
85+
return current($answer);
86+
}
87+
88+
return $answer;
89+
}
90+
91+
return null;
92+
}
93+
94+
// If this is the last index, make sure to return it now,
95+
// and not try to recurse through things.
96+
if ($indexes === []) {
97+
return $array[$currentIndex];
98+
}
99+
100+
// Do we need to recursively search this value?
101+
if (is_array($array[$currentIndex]) && $array[$currentIndex] !== []) {
102+
return self::arraySearchDot($indexes, $array[$currentIndex]);
103+
}
104+
105+
// Otherwise, not found.
106+
return null;
107+
}
108+
109+
/**
110+
* Groups all rows by their index values. Result's depth equals number of indexes
111+
*
112+
* @used-by array_group_by()
113+
*
114+
* @param array $array Data array (i.e. from query result)
115+
* @param array $indexes Indexes to group by. Dot syntax used. Returns $array if empty
116+
* @param bool $includeEmpty If true, null and '' are also added as valid keys to group
117+
*
118+
* @return array Result array where rows are grouped together by indexes values.
119+
*/
120+
public static function groupBy(array $array, array $indexes, bool $includeEmpty = false): array
121+
{
122+
if ($indexes === []) {
123+
return $array;
124+
}
125+
126+
$result = [];
127+
128+
foreach ($array as $row) {
129+
$result = self::arrayAttachIndexedValue($result, $row, $indexes, $includeEmpty);
130+
}
131+
132+
return $result;
133+
}
134+
135+
/**
136+
* Recursively attach $row to the $indexes path of values found by
137+
* `dot_array_search()`.
138+
*
139+
* @used-by groupBy()
140+
*/
141+
private static function arrayAttachIndexedValue(
142+
array $result,
143+
array $row,
144+
array $indexes,
145+
bool $includeEmpty
146+
): array {
147+
if (($index = array_shift($indexes)) === null) {
148+
$result[] = $row;
149+
150+
return $result;
151+
}
152+
153+
$value = dot_array_search($index, $row);
154+
155+
if (! is_scalar($value)) {
156+
$value = '';
157+
}
158+
159+
if (is_bool($value)) {
160+
$value = (int) $value;
161+
}
162+
163+
if (! $includeEmpty && $value === '') {
164+
return $result;
165+
}
166+
167+
if (! array_key_exists($value, $result)) {
168+
$result[$value] = [];
169+
}
170+
171+
$result[$value] = self::arrayAttachIndexedValue($result[$value], $row, $indexes, $includeEmpty);
172+
173+
return $result;
174+
}
175+
176+
/**
177+
* Compare recursively two associative arrays and return difference as new array.
178+
* Returns keys that exist in `$original` but not in `$compareWith`.
179+
*/
180+
public static function recursiveDiff(array $original, array $compareWith): array
181+
{
182+
$difference = [];
183+
184+
if ($original === []) {
185+
return [];
186+
}
187+
188+
if ($compareWith === []) {
189+
return $original;
190+
}
191+
192+
foreach ($original as $originalKey => $originalValue) {
193+
if ($originalValue === []) {
194+
continue;
195+
}
196+
197+
if (is_array($originalValue)) {
198+
$diffArrays = [];
199+
200+
if (isset($compareWith[$originalKey]) && is_array($compareWith[$originalKey])) {
201+
$diffArrays = self::recursiveDiff($originalValue, $compareWith[$originalKey]);
202+
} else {
203+
$difference[$originalKey] = $originalValue;
204+
}
205+
206+
if ($diffArrays !== []) {
207+
$difference[$originalKey] = $diffArrays;
208+
}
209+
} elseif (is_string($originalValue) && ! array_key_exists($originalKey, $compareWith)) {
210+
$difference[$originalKey] = $originalValue;
211+
}
212+
}
213+
214+
return $difference;
215+
}
216+
217+
/**
218+
* Recursively count all keys.
219+
*/
220+
public static function recursiveCount(array $array, int $counter = 0): int
221+
{
222+
foreach ($array as $value) {
223+
if (is_array($value)) {
224+
$counter = self::recursiveCount($value, $counter);
225+
}
226+
227+
$counter++;
228+
}
229+
230+
return $counter;
231+
}
232+
}

0 commit comments

Comments
 (0)