From 267b75f924af8bafde5d243ff370df0316e24cab Mon Sep 17 00:00:00 2001 From: Nathan Boiron Date: Mon, 19 Dec 2022 13:58:25 +0100 Subject: [PATCH] misc: extract array path mapping to a dedicated class --- src/Mapper/Source/Modifier/PathMapping.php | 64 +----- .../Modifier => Utility/Path}/Mapping.php | 2 +- src/Utility/Path/PathMapper.php | 77 +++++++ .../Modifier => Utility/Path}/MappingTest.php | 4 +- tests/Unit/Utility/Path/PathMapperTest.php | 204 ++++++++++++++++++ 5 files changed, 286 insertions(+), 65 deletions(-) rename src/{Mapper/Source/Modifier => Utility/Path}/Mapping.php (94%) create mode 100644 src/Utility/Path/PathMapper.php rename tests/Unit/{Mapper/Source/Modifier => Utility/Path}/MappingTest.php (95%) create mode 100644 tests/Unit/Utility/Path/PathMapperTest.php diff --git a/src/Mapper/Source/Modifier/PathMapping.php b/src/Mapper/Source/Modifier/PathMapping.php index f487b409..ac243404 100644 --- a/src/Mapper/Source/Modifier/PathMapping.php +++ b/src/Mapper/Source/Modifier/PathMapping.php @@ -4,12 +4,10 @@ namespace CuyZ\Valinor\Mapper\Source\Modifier; +use CuyZ\Valinor\Utility\Path\PathMapper; use IteratorAggregate; use Traversable; -use function explode; -use function is_array; - /** * @api * @@ -26,69 +24,11 @@ final class PathMapping implements IteratorAggregate */ public function __construct(iterable $source, array $map) { - $this->source = $this->map($source, $this->prepareMappings($map)); + $this->source = (new PathMapper())->map($source, $map); } public function getIterator(): Traversable { yield from $this->source; } - - /** - * @param iterable $source - * @param array $mappings - * @return array - */ - private function map(iterable $source, array $mappings, int $depth = 0): array - { - $out = []; - - foreach ($source as $key => $value) { - /** @var int|string $key */ - $newMappings = array_filter($mappings, fn (Mapping $mapping) => $mapping->matches($key, $depth)); - - $newKey = $this->findMapping($newMappings, $depth, $key); - - if (is_array($value)) { - $out[$newKey] = $this->map($value, $newMappings, $depth + 1); - - continue; - } - - $out[$newKey] = $value; - } - - return $out; - } - - /** - * @param array $map - * @return array - */ - private function prepareMappings(array $map): array - { - $mappings = []; - - foreach ($map as $from => $to) { - $mappings[] = new Mapping(explode('.', $from), $to); - } - - return $mappings; - } - - /** - * @param array $mappings - */ - private function findMapping(array $mappings, int $atDepth, int|string $key): int|string - { - foreach ($mappings as $mapping) { - $mappedKey = $mapping->findMappedKey($key, $atDepth); - - if (null !== $mappedKey) { - return $mappedKey; - } - } - - return $key; - } } diff --git a/src/Mapper/Source/Modifier/Mapping.php b/src/Utility/Path/Mapping.php similarity index 94% rename from src/Mapper/Source/Modifier/Mapping.php rename to src/Utility/Path/Mapping.php index f3ce08cc..1a169872 100644 --- a/src/Mapper/Source/Modifier/Mapping.php +++ b/src/Utility/Path/Mapping.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace CuyZ\Valinor\Mapper\Source\Modifier; +namespace CuyZ\Valinor\Utility\Path; /** @internal */ final class Mapping diff --git a/src/Utility/Path/PathMapper.php b/src/Utility/Path/PathMapper.php new file mode 100644 index 00000000..8a33e82b --- /dev/null +++ b/src/Utility/Path/PathMapper.php @@ -0,0 +1,77 @@ + $source + * @param array $map + * @return array + */ + public function map(iterable $source, array $map): array + { + return $this->doMap($source, $this->prepareMappings($map)); + } + + /** + * @param iterable $source + * @param array $mappings + * @return array + */ + private function doMap(iterable $source, array $mappings, int $depth = 0): array + { + $out = []; + + foreach ($source as $key => $value) { + /** @var int|string $key */ + $newMappings = array_filter($mappings, static fn (Mapping $mapping) => $mapping->matches($key, $depth)); + + $newKey = $this->findMapping($newMappings, $depth, $key); + + if (is_array($value)) { + $out[$newKey] = $this->doMap($value, $newMappings, $depth + 1); + + continue; + } + + $out[$newKey] = $value; + } + + return $out; + } + + /** + * @param array $map + * @return array + */ + private function prepareMappings(array $map): array + { + $mappings = []; + + foreach ($map as $from => $to) { + $mappings[] = new Mapping(explode('.', $from), $to); + } + + return $mappings; + } + + /** + * @param array $mappings + */ + private function findMapping(array $mappings, int $atDepth, int|string $key): int|string + { + foreach ($mappings as $mapping) { + $mappedKey = $mapping->findMappedKey($key, $atDepth); + + if (null !== $mappedKey) { + return $mappedKey; + } + } + + return $key; + } +} diff --git a/tests/Unit/Mapper/Source/Modifier/MappingTest.php b/tests/Unit/Utility/Path/MappingTest.php similarity index 95% rename from tests/Unit/Mapper/Source/Modifier/MappingTest.php rename to tests/Unit/Utility/Path/MappingTest.php index f059a8df..831b1be0 100644 --- a/tests/Unit/Mapper/Source/Modifier/MappingTest.php +++ b/tests/Unit/Utility/Path/MappingTest.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace CuyZ\Valinor\Tests\Unit\Mapper\Source\Modifier; +namespace CuyZ\Valinor\Tests\Unit\Utility\Path; -use CuyZ\Valinor\Mapper\Source\Modifier\Mapping; +use CuyZ\Valinor\Utility\Path\Mapping; use PHPUnit\Framework\TestCase; final class MappingTest extends TestCase diff --git a/tests/Unit/Utility/Path/PathMapperTest.php b/tests/Unit/Utility/Path/PathMapperTest.php new file mode 100644 index 00000000..7d8b162b --- /dev/null +++ b/tests/Unit/Utility/Path/PathMapperTest.php @@ -0,0 +1,204 @@ +map( + ['A' => 'bar'], + ['A' => 'new_A'] + ); + + self::assertSame(['new_A' => 'bar'], $result); + } + + public function test_sub_path_is_mapped(): void + { + $result = (new PathMapper())->map( + [ + 'A' => [ + 'B' => 'foo', + ], + ], + ['A.B' => 'new_B'] + ); + + self::assertSame([ + 'A' => [ + 'new_B' => 'foo', + ], + ], $result); + } + + public function test_root_iterable_path_is_mapped(): void + { + $result = (new PathMapper())->map( + [ + ['A' => 'bar'], + ['A' => 'buz'], + ], + ['*.A' => 'new_A'] + ); + + self::assertSame([ + ['new_A' => 'bar'], + ['new_A' => 'buz'], + ], $result); + } + + public function test_sub_iterable_numeric_path_is_mapped(): void + { + $result = (new PathMapper())->map( + [ + 'A' => [ + ['C' => 'bar'], + ['C' => 'buz'], + ], + ], + ['A.*.C' => 'new_C'] + ); + + self::assertSame([ + 'A' => [ + ['new_C' => 'bar'], + ['new_C' => 'buz'], + ], + ], $result); + } + + public function test_sub_iterable_string_path_is_mapped(): void + { + $result = (new PathMapper())->map( + [ + 'A' => [ + 'B1' => ['C' => 'bar'], + 'B2' => ['C' => 'buz'], + ], + ], + ['A.*.C' => 'new_C'], + ); + + self::assertSame([ + 'A' => [ + 'B1' => ['new_C' => 'bar'], + 'B2' => ['new_C' => 'buz'], + ], + ], $result); + } + + public function test_path_with_sub_paths_are_mapped(): void + { + $result = (new PathMapper())->map( + [ + 'A' => [ + ['B' => 'bar'], + ['B' => 'buz'], + ], + ], + [ + 'A' => 'new_A', + 'A.*.B' => 'new_B', + ] + ); + + self::assertSame([ + 'new_A' => [ + ['new_B' => 'bar'], + ['new_B' => 'buz'], + ], + ], $result); + } + + public function test_conflicting_paths_are_mapped(): void + { + $result = (new PathMapper())->map( + [ + 'A1' => [ + 'B' => 'foo', + ], + 'A2' => [ + 'B' => 'bar', + ], + ], + [ + 'A1.B' => 'new_B1', + ] + ); + + self::assertSame([ + 'A1' => [ + 'new_B1' => 'foo', + ], + 'A2' => [ + 'B' => 'bar', + ], + ], $result); + } + + public function test_sub_iterable_numeric_path_with_sub_key_is_mapped(): void + { + $result = (new PathMapper())->map( + [ + 'A' => [ + [ + 'B' => ['C' => 'bar'], + ], + [ + 'B' => ['C' => 'buz'], + ], + ], + ], + [ + 'A.*.B.C' => 'new_C', + ] + ); + + self::assertSame([ + 'A' => [ + [ + 'B' => ['new_C' => 'bar'], + ], + [ + 'B' => ['new_C' => 'buz'], + ], + ], + ], $result); + } + + public function test_sub_iterable_string_path_with_sub_key_is_mapped(): void + { + $result = (new PathMapper())->map( + [ + 'A' => [ + 'B1' => [ + 'C' => ['D' => 'bar'], + ], + 'B2' => [ + 'C' => ['D' => 'buz'], + ], + ], + ], + [ + 'A.*.C.D' => 'new_D', + ] + ); + + self::assertSame([ + 'A' => [ + 'B1' => [ + 'C' => ['new_D' => 'bar'], + ], + 'B2' => [ + 'C' => ['new_D' => 'buz'], + ], + ], + ], $result); + } +}