diff --git a/phpstan-baseline.php b/phpstan-baseline.php index 894db3a12616..3a9e665be916 100644 --- a/phpstan-baseline.php +++ b/phpstan-baseline.php @@ -2293,7 +2293,7 @@ ]; $ignoreErrors[] = [ 'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#', - 'count' => 3, + 'count' => 2, 'path' => __DIR__ . '/system/Helpers/array_helper.php', ]; $ignoreErrors[] = [ diff --git a/system/Commands/Translation/LocalizationFinder.php b/system/Commands/Translation/LocalizationFinder.php index e5147093d2dc..d8fbbf1aad50 100644 --- a/system/Commands/Translation/LocalizationFinder.php +++ b/system/Commands/Translation/LocalizationFinder.php @@ -13,7 +13,7 @@ use CodeIgniter\CLI\BaseCommand; use CodeIgniter\CLI\CLI; -use CodeIgniter\Commands\Translation\LocalizationFinder\ArrayHelper; +use CodeIgniter\Helpers\Array\ArrayHelper; use Config\App; use Locale; use RecursiveDirectoryIterator; diff --git a/system/Commands/Translation/LocalizationFinder/ArrayHelper.php b/system/Commands/Translation/LocalizationFinder/ArrayHelper.php deleted file mode 100644 index c3cf4b9c53d9..000000000000 --- a/system/Commands/Translation/LocalizationFinder/ArrayHelper.php +++ /dev/null @@ -1,75 +0,0 @@ - - * - * For the full copyright and license information, please view - * the LICENSE file that was distributed with this source code. - */ - -namespace CodeIgniter\Commands\Translation\LocalizationFinder; - -/** - * @see \CodeIgniter\Commands\Translation\LocalizationFinder\ArrayHelperTest - */ -class ArrayHelper -{ - /** - * Compare recursively two associative arrays and return difference as new array. - * Returns keys that exist in `$original` but not in `$compareWith`. - */ - public static function recursiveDiff(array $original, array $compareWith): array - { - $difference = []; - - if ($original === []) { - return []; - } - - if ($compareWith === []) { - return $original; - } - - foreach ($original as $originalKey => $originalValue) { - if ($originalValue === []) { - continue; - } - - if (is_array($originalValue)) { - $diffArrays = []; - - if (isset($compareWith[$originalKey]) && is_array($compareWith[$originalKey])) { - $diffArrays = self::recursiveDiff($originalValue, $compareWith[$originalKey]); - } else { - $difference[$originalKey] = $originalValue; - } - - if ($diffArrays !== []) { - $difference[$originalKey] = $diffArrays; - } - } elseif (is_string($originalValue) && ! array_key_exists($originalKey, $compareWith)) { - $difference[$originalKey] = $originalValue; - } - } - - return $difference; - } - - /** - * Recursively count all keys. - */ - public static function recursiveCount(array $array, int $counter = 0): int - { - foreach ($array as $value) { - if (is_array($value)) { - $counter = self::recursiveCount($value, $counter); - } - - $counter++; - } - - return $counter; - } -} diff --git a/system/Helpers/Array/ArrayHelper.php b/system/Helpers/Array/ArrayHelper.php new file mode 100644 index 000000000000..37382af4d012 --- /dev/null +++ b/system/Helpers/Array/ArrayHelper.php @@ -0,0 +1,232 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Helpers\Array; + +/** + * @interal This is internal implementation for the framework. + * + * If there are any methods that should be provided, make them + * public APIs via helper functions. + * + * @see \CodeIgniter\Helpers\Array\ArrayHelperRecursiveDiffTest + */ +final class ArrayHelper +{ + /** + * Searches an array through dot syntax. Supports wildcard searches, + * like `foo.*.bar`. + * + * @used-by dot_array_search() + * + * @return array|bool|int|object|string|null + */ + public static function dotSearch(string $index, array $array) + { + // See https://regex101.com/r/44Ipql/1 + $segments = preg_split( + '/(? str_replace('\.', '.', $key), $segments); + + return self::arraySearchDot($segments, $array); + } + + /** + * Recursively search the array with wildcards. + * + * @used-by dotSearch() + * + * @return array|bool|float|int|object|string|null + */ + private static function arraySearchDot(array $indexes, array $array) + { + // If index is empty, returns null. + if ($indexes === []) { + return null; + } + + // Grab the current index + $currentIndex = array_shift($indexes); + + if (! isset($array[$currentIndex]) && $currentIndex !== '*') { + return null; + } + + // Handle Wildcard (*) + if ($currentIndex === '*') { + $answer = []; + + foreach ($array as $value) { + if (! is_array($value)) { + return null; + } + + $answer[] = self::arraySearchDot($indexes, $value); + } + + $answer = array_filter($answer, static fn ($value) => $value !== null); + + if ($answer !== []) { + if (count($answer) === 1) { + // If array only has one element, we return that element for BC. + return current($answer); + } + + return $answer; + } + + return null; + } + + // If this is the last index, make sure to return it now, + // and not try to recurse through things. + if ($indexes === []) { + return $array[$currentIndex]; + } + + // Do we need to recursively search this value? + if (is_array($array[$currentIndex]) && $array[$currentIndex] !== []) { + return self::arraySearchDot($indexes, $array[$currentIndex]); + } + + // Otherwise, not found. + return null; + } + + /** + * Groups all rows by their index values. Result's depth equals number of indexes + * + * @used-by array_group_by() + * + * @param array $array Data array (i.e. from query result) + * @param array $indexes Indexes to group by. Dot syntax used. Returns $array if empty + * @param bool $includeEmpty If true, null and '' are also added as valid keys to group + * + * @return array Result array where rows are grouped together by indexes values. + */ + public static function groupBy(array $array, array $indexes, bool $includeEmpty = false): array + { + if ($indexes === []) { + return $array; + } + + $result = []; + + foreach ($array as $row) { + $result = self::arrayAttachIndexedValue($result, $row, $indexes, $includeEmpty); + } + + return $result; + } + + /** + * Recursively attach $row to the $indexes path of values found by + * `dot_array_search()`. + * + * @used-by groupBy() + */ + private static function arrayAttachIndexedValue( + array $result, + array $row, + array $indexes, + bool $includeEmpty + ): array { + if (($index = array_shift($indexes)) === null) { + $result[] = $row; + + return $result; + } + + $value = dot_array_search($index, $row); + + if (! is_scalar($value)) { + $value = ''; + } + + if (is_bool($value)) { + $value = (int) $value; + } + + if (! $includeEmpty && $value === '') { + return $result; + } + + if (! array_key_exists($value, $result)) { + $result[$value] = []; + } + + $result[$value] = self::arrayAttachIndexedValue($result[$value], $row, $indexes, $includeEmpty); + + return $result; + } + + /** + * Compare recursively two associative arrays and return difference as new array. + * Returns keys that exist in `$original` but not in `$compareWith`. + */ + public static function recursiveDiff(array $original, array $compareWith): array + { + $difference = []; + + if ($original === []) { + return []; + } + + if ($compareWith === []) { + return $original; + } + + foreach ($original as $originalKey => $originalValue) { + if ($originalValue === []) { + continue; + } + + if (is_array($originalValue)) { + $diffArrays = []; + + if (isset($compareWith[$originalKey]) && is_array($compareWith[$originalKey])) { + $diffArrays = self::recursiveDiff($originalValue, $compareWith[$originalKey]); + } else { + $difference[$originalKey] = $originalValue; + } + + if ($diffArrays !== []) { + $difference[$originalKey] = $diffArrays; + } + } elseif (is_string($originalValue) && ! array_key_exists($originalKey, $compareWith)) { + $difference[$originalKey] = $originalValue; + } + } + + return $difference; + } + + /** + * Recursively count all keys. + */ + public static function recursiveCount(array $array, int $counter = 0): int + { + foreach ($array as $value) { + if (is_array($value)) { + $counter = self::recursiveCount($value, $counter); + } + + $counter++; + } + + return $counter; + } +} diff --git a/system/Helpers/array_helper.php b/system/Helpers/array_helper.php index 65645c3cc316..6d501e273202 100644 --- a/system/Helpers/array_helper.php +++ b/system/Helpers/array_helper.php @@ -9,6 +9,8 @@ * the LICENSE file that was distributed with this source code. */ +use CodeIgniter\Helpers\Array\ArrayHelper; + // CodeIgniter Array Helpers if (! function_exists('dot_array_search')) { @@ -20,82 +22,7 @@ */ function dot_array_search(string $index, array $array) { - // See https://regex101.com/r/44Ipql/1 - $segments = preg_split( - '/(? str_replace('\.', '.', $key), $segments); - - return _array_search_dot($segments, $array); - } -} - -if (! function_exists('_array_search_dot')) { - /** - * Used by `dot_array_search` to recursively search the - * array with wildcards. - * - * @internal This should not be used on its own. - * - * @return array|bool|float|int|object|string|null - */ - function _array_search_dot(array $indexes, array $array) - { - // If index is empty, returns null. - if ($indexes === []) { - return null; - } - - // Grab the current index - $currentIndex = array_shift($indexes); - - if (! isset($array[$currentIndex]) && $currentIndex !== '*') { - return null; - } - - // Handle Wildcard (*) - if ($currentIndex === '*') { - $answer = []; - - foreach ($array as $value) { - if (! is_array($value)) { - return null; - } - - $answer[] = _array_search_dot($indexes, $value); - } - - $answer = array_filter($answer, static fn ($value) => $value !== null); - - if ($answer !== []) { - if (count($answer) === 1) { - // If array only has one element, we return that element for BC. - return current($answer); - } - - return $answer; - } - - return null; - } - - // If this is the last index, make sure to return it now, - // and not try to recurse through things. - if (empty($indexes)) { - return $array[$currentIndex]; - } - - // Do we need to recursively search this value? - if (is_array($array[$currentIndex]) && $array[$currentIndex] !== []) { - return _array_search_dot($indexes, $array[$currentIndex]); - } - - // Otherwise, not found. - return null; + return ArrayHelper::dotSearch($index, $array); } } @@ -231,55 +158,6 @@ function array_flatten_with_dots(iterable $array, string $id = ''): array */ function array_group_by(array $array, array $indexes, bool $includeEmpty = false): array { - if ($indexes === []) { - return $array; - } - - $result = []; - - foreach ($array as $row) { - $result = _array_attach_indexed_value($result, $row, $indexes, $includeEmpty); - } - - return $result; - } -} - -if (! function_exists('_array_attach_indexed_value')) { - /** - * Used by `array_group_by` to recursively attach $row to the $indexes path of values found by - * `dot_array_search` - * - * @internal This should not be used on its own - */ - function _array_attach_indexed_value(array $result, array $row, array $indexes, bool $includeEmpty): array - { - if (($index = array_shift($indexes)) === null) { - $result[] = $row; - - return $result; - } - - $value = dot_array_search($index, $row); - - if (! is_scalar($value)) { - $value = ''; - } - - if (is_bool($value)) { - $value = (int) $value; - } - - if (! $includeEmpty && $value === '') { - return $result; - } - - if (! array_key_exists($value, $result)) { - $result[$value] = []; - } - - $result[$value] = _array_attach_indexed_value($result[$value], $row, $indexes, $includeEmpty); - - return $result; + return ArrayHelper::groupBy($array, $indexes, $includeEmpty); } } diff --git a/tests/system/Commands/Translation/LocalizationFinder/ArrayHelperTest.php b/tests/system/Helpers/Array/ArrayHelperRecursiveDiffTest.php similarity index 98% rename from tests/system/Commands/Translation/LocalizationFinder/ArrayHelperTest.php rename to tests/system/Helpers/Array/ArrayHelperRecursiveDiffTest.php index cc64f3cd3cbf..81cf87ecfc46 100644 --- a/tests/system/Commands/Translation/LocalizationFinder/ArrayHelperTest.php +++ b/tests/system/Helpers/Array/ArrayHelperRecursiveDiffTest.php @@ -9,7 +9,7 @@ * the LICENSE file that was distributed with this source code. */ -namespace CodeIgniter\Commands\Translation\LocalizationFinder; +namespace CodeIgniter\Helpers\Array; use CodeIgniter\Test\CIUnitTestCase; @@ -18,7 +18,7 @@ * * @internal */ -final class ArrayHelperTest extends CIUnitTestCase +final class ArrayHelperRecursiveDiffTest extends CIUnitTestCase { private array $compareWith;