-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
1a665f8
refactor: move ArrayHelper to Helpers/Array/ArrayHelper.php
kenjis 514fe62
refactor: make ArrayHelper final
kenjis bc4f308
refactor: move code from array_helper.php to ArrayHelper.php
kenjis d1f1ec4
refactor: replace empty()
kenjis 775ded8
chore: update phpstan-baseline.php
kenjis abb664e
refactor: make method names shorter
kenjis 629a1c1
test: rename test classname
kenjis b225032
docs: add @used-by
kenjis 3d167be
style: break long line
kenjis 112fcea
docs: add @internal
kenjis File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
75 changes: 0 additions & 75 deletions
75
system/Commands/Translation/LocalizationFinder/ArrayHelper.php
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
{ | ||
/** | ||
* 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; | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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.There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.