Skip to content

refactor: move ArrayHelper class #8130

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Nov 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion phpstan-baseline.php
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = [
Expand Down
2 changes: 1 addition & 1 deletion system/Commands/Translation/LocalizationFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
75 changes: 0 additions & 75 deletions system/Commands/Translation/LocalizationFinder/ArrayHelper.php

This file was deleted.

232 changes: 232 additions & 0 deletions system/Helpers/Array/ArrayHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
<?php

/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <[email protected]>
*
* 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a huge fan of making this type of class final, since there might be some use cases that can benefit from extending this class. However, it's not a deal breaker for me.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need it really, I will remove final.
But we can remove it anytime when request comes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't feel like we need it here, but I may be the only one, so that's cool. And you're right, we can always remove it later.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's wait for others opinions.

{
/**
* 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(
'/(?<!\\\\)\./',
rtrim($index, '* '),
0,
PREG_SPLIT_NO_EMPTY
);

$segments = array_map(static fn ($key) => 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;
}
}
Loading