From 3dedf9b9f15454e6ae78d42cc34aa8932c439de2 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 31 Oct 2023 13:32:16 +0900 Subject: [PATCH 1/3] refactor: extract convertToArray() --- system/Helpers/Array/ArrayHelper.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/system/Helpers/Array/ArrayHelper.php b/system/Helpers/Array/ArrayHelper.php index 37382af4d012..28e14d849aa4 100644 --- a/system/Helpers/Array/ArrayHelper.php +++ b/system/Helpers/Array/ArrayHelper.php @@ -30,6 +30,11 @@ final class ArrayHelper * @return array|bool|int|object|string|null */ public static function dotSearch(string $index, array $array) + { + return self::arraySearchDot(self::convertToArray($index), $array); + } + + private static function convertToArray(string $index) { // See https://regex101.com/r/44Ipql/1 $segments = preg_split( @@ -39,9 +44,10 @@ public static function dotSearch(string $index, array $array) PREG_SPLIT_NO_EMPTY ); - $segments = array_map(static fn ($key) => str_replace('\.', '.', $key), $segments); - - return self::arraySearchDot($segments, $array); + return array_map( + static fn ($key) => str_replace('\.', '.', $key), + $segments + ); } /** From 3c4b238fe63ec9d8dd404f09caaf80d3a8ff7b14 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 31 Oct 2023 14:49:46 +0900 Subject: [PATCH 2/3] feat: add ArrayHelper::dotKeyExists() --- system/Helpers/Array/ArrayHelper.php | 55 ++++++++++++++ .../Array/ArrayHelperDotKeyExistsTest.php | 72 +++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 tests/system/Helpers/Array/ArrayHelperDotKeyExistsTest.php diff --git a/system/Helpers/Array/ArrayHelper.php b/system/Helpers/Array/ArrayHelper.php index 28e14d849aa4..e2c8f5feb8d3 100644 --- a/system/Helpers/Array/ArrayHelper.php +++ b/system/Helpers/Array/ArrayHelper.php @@ -11,6 +11,8 @@ namespace CodeIgniter\Helpers\Array; +use InvalidArgumentException; + /** * @interal This is internal implementation for the framework. * @@ -112,6 +114,59 @@ private static function arraySearchDot(array $indexes, array $array) return null; } + /** + * array_key_exists() with dot array syntax. + * + * If wildcard `*` is used, all items for the key after it must have the key. + */ + public static function dotKeyExists(string $index, array $array): bool + { + if (str_ends_with($index, '*') || str_contains($index, '*.*')) { + throw new InvalidArgumentException( + 'You must set key right after "*". Invalid index: "' . $index . '"' + ); + } + + $indexes = self::convertToArray($index); + + // If indexes is empty, returns false. + if ($indexes === []) { + return false; + } + + $currentArray = $array; + + // Grab the current index + while ($currentIndex = array_shift($indexes)) { + if ($currentIndex === '*') { + $currentIndex = array_shift($indexes); + + foreach ($currentArray as $item) { + if (! array_key_exists($currentIndex, $item)) { + return false; + } + } + + // If indexes is empty, all elements are checked. + if ($indexes === []) { + return true; + } + + $currentArray = self::dotSearch('*.' . $currentIndex, $currentArray); + + continue; + } + + if (! array_key_exists($currentIndex, $currentArray)) { + return false; + } + + $currentArray = $currentArray[$currentIndex]; + } + + return true; + } + /** * Groups all rows by their index values. Result's depth equals number of indexes * diff --git a/tests/system/Helpers/Array/ArrayHelperDotKeyExistsTest.php b/tests/system/Helpers/Array/ArrayHelperDotKeyExistsTest.php new file mode 100644 index 000000000000..c4168688cf53 --- /dev/null +++ b/tests/system/Helpers/Array/ArrayHelperDotKeyExistsTest.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Helpers\Array; + +use CodeIgniter\Test\CIUnitTestCase; +use InvalidArgumentException; + +/** + * @group Others + * + * @internal + */ +final class ArrayHelperDotKeyExistsTest extends CIUnitTestCase +{ + private array $array = [ + 'contacts' => [ + 'friends' => [ + ['name' => 'Fred Flinstone', 'age' => 20], + ['age' => 21], // 'name' key does not exist + ], + ], + ]; + + public function testDotKeyExists(): void + { + $this->assertFalse(ArrayHelper::dotKeyExists('', $this->array)); + $this->assertTrue(ArrayHelper::dotKeyExists('contacts', $this->array)); + $this->assertFalse(ArrayHelper::dotKeyExists('not', $this->array)); + $this->assertTrue(ArrayHelper::dotKeyExists('contacts.friends', $this->array)); + $this->assertFalse(ArrayHelper::dotKeyExists('not.friends', $this->array)); + $this->assertTrue(ArrayHelper::dotKeyExists('contacts.friends.0.name', $this->array)); + $this->assertFalse(ArrayHelper::dotKeyExists('contacts.friends.1.name', $this->array)); + } + + public function testDotKeyExistsWithEndingWildCard(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('You must set key right after "*". Invalid index: "contacts.*"'); + + $this->assertTrue(ArrayHelper::dotKeyExists('contacts.*', $this->array)); + } + + public function testDotKeyExistsWithDoubleWildCard(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('You must set key right after "*". Invalid index: "contacts.*.*.age"'); + + $this->assertTrue(ArrayHelper::dotKeyExists('contacts.*.*.age', $this->array)); + } + + public function testDotKeyExistsWithWildCard(): void + { + $this->assertTrue(ArrayHelper::dotKeyExists('*.friends', $this->array)); + $this->assertTrue(ArrayHelper::dotKeyExists('contacts.friends.*.age', $this->array)); + $this->assertFalse(ArrayHelper::dotKeyExists('contacts.friends.*.name', $this->array)); + $this->assertTrue(ArrayHelper::dotKeyExists('*.friends.*.age', $this->array)); + $this->assertFalse(ArrayHelper::dotKeyExists('*.friends.*.name', $this->array)); + $this->assertTrue(ArrayHelper::dotKeyExists('contacts.*.0.age', $this->array)); + $this->assertTrue(ArrayHelper::dotKeyExists('contacts.*.1.age', $this->array)); + $this->assertTrue(ArrayHelper::dotKeyExists('contacts.*.0.name', $this->array)); + $this->assertFalse(ArrayHelper::dotKeyExists('contacts.*.1.name', $this->array)); + } +} From dce657695e4d2e8fd3fe7e210b49a945bdb61ccf Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 31 Oct 2023 15:11:20 +0900 Subject: [PATCH 3/3] docs: add PHPDoc --- system/Helpers/Array/ArrayHelper.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/system/Helpers/Array/ArrayHelper.php b/system/Helpers/Array/ArrayHelper.php index e2c8f5feb8d3..5b359a761384 100644 --- a/system/Helpers/Array/ArrayHelper.php +++ b/system/Helpers/Array/ArrayHelper.php @@ -29,6 +29,8 @@ final class ArrayHelper * * @used-by dot_array_search() * + * @param string $index The index as dot array syntax. + * * @return array|bool|int|object|string|null */ public static function dotSearch(string $index, array $array) @@ -36,7 +38,12 @@ public static function dotSearch(string $index, array $array) return self::arraySearchDot(self::convertToArray($index), $array); } - private static function convertToArray(string $index) + /** + * @param string $index The index as dot array syntax. + * + * @return list The index as an array. + */ + private static function convertToArray(string $index): array { // See https://regex101.com/r/44Ipql/1 $segments = preg_split(