From 27a2e756fdb74b5bd68478c459595006ac9b8697 Mon Sep 17 00:00:00 2001 From: david_smith Date: Tue, 29 Oct 2024 11:55:11 -0400 Subject: [PATCH 1/3] Add support for `key_by` in `DataModelHelper.php`. --- src/DataModelHelper.php | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/DataModelHelper.php b/src/DataModelHelper.php index b9a2e9a..27d05df 100644 --- a/src/DataModelHelper.php +++ b/src/DataModelHelper.php @@ -52,24 +52,30 @@ public static function mapOf(mixed $value, array $context, ?ReflectionAttribute $args = $Attribute?->getArguments(); $value = isset($args[0]['coerce']) && !isset($value[0]) ? [$value] : $value; - if (isset($Attribute?->getArguments()[0]['using'])) { - return ($Attribute?->getArguments()[0]['using'])($value); + if (isset($args[0]['using'])) { + return ($args[0]['using'])($value); } - $method = $Attribute?->getArguments()[0]['method'] ?? 'from'; + $method = $args[0]['method'] ?? 'from'; $type = $Property->getType()?->getName(); - $map = $Attribute?->getArguments()[0]['map_via'] ?? 'map'; + $map = $args[0]['map_via'] ?? 'map'; $classname = $args[0]['type']; + $keyBy = static fn($value, ?string $key_by) => $key_by && count(array_column($value, $key_by)) + ? array_combine(array_column($value, $key_by), $value) + : $value; - $mapper = static function ($value, $level = 1) use ($classname, $map, $type, $method, &$mapper) { + $mapper = static function ($value, $level = 1) use ($keyBy, $args, $classname, $map, $type, $method, &$mapper) { return $type === 'array' ? array_map(static fn($item) => $level <= 1 ? $classname::$method($item) : $mapper($item, $level - 1), - (array)$value) - : (new $type($value))->$map(fn($item) => $level <= 1 - ? $classname::$method($item) - : $mapper($item, $level - 1)); + $keyBy($value, $args[0]['key_by'] ?? null)) + : (new $type($keyBy($value, $args[0]['key_by'] ?? null))) + ->$map( + fn($item) => $level <= 1 + ? $classname::$method($item) + : $mapper($item, $level - 1) + ); }; return $mapper($value, $args[0]['level'] ?? 1); From 4ab4c8d9909594347417726ef5f3de8a838113bf Mon Sep 17 00:00:00 2001 From: david_smith Date: Tue, 29 Oct 2024 11:55:21 -0400 Subject: [PATCH 2/3] Update `README.md`. --- README.md | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/README.md b/README.md index 45282a0..6000bb3 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,8 @@ composer require zero-to-prod/data-model-helper Here’s how to use the `mapOf` helper with all its arguments: ```php +use Zerotoprod\DataModel\Describe; + class User { use \Zerotoprod\DataModel\DataModel; @@ -43,6 +45,7 @@ class User 'using' => [self::class, 'map'], // Custom mapping function 'map_via' => 'mapper', // Custom mapping method (defaults to 'map') 'level' => 1, // The dimension of the array. Defaults to 1. + 'key_by' => 'key', // Key an associative array by a field. ])] public Collection $Aliases; } @@ -66,6 +69,8 @@ class DataModelHelper The `mapOf()` method returns an array of `Alias` instances. ```php +use Zerotoprod\DataModel\Describe; + class User { use \Zerotoprod\DataModel\DataModel; @@ -102,6 +107,8 @@ echo $User->Aliases[1]->name; // Outputs: John Smith The `mapOf` helper is designed to work will with the `\Illuminate\Support\Collection` class. ```php +use Zerotoprod\DataModel\Describe; + class User { use \Zerotoprod\DataModel\DataModel; @@ -139,6 +146,8 @@ elements are coerced into an array. ```php +use Zerotoprod\DataModel\Describe; + class User { use \Zerotoprod\DataModel\DataModel; @@ -172,6 +181,8 @@ echo $User->Aliases[0]->name; // Outputs: John Doe Specify your mapping function by setting the `using` option. ```php +use Zerotoprod\DataModel\Describe; + class User { use \Zerotoprod\DataModel\DataModel; @@ -226,6 +237,8 @@ echo $User->Aliases->items[0]->name; // Outputs: John Doe By default, the map method is used to map over elements. You can specify a different method using the `map_via` option. ```php +use Zerotoprod\DataModel\Describe; + class User { use \Zerotoprod\DataModel\DataModel; @@ -277,6 +290,8 @@ echo $User->Aliases->items[0]->name; // Outputs: John Doe You can set the level for mapping deep arrays. ```php +use Zerotoprod\DataModel\Describe; + class User { use \Zerotoprod\DataModel\DataModel; @@ -309,4 +324,48 @@ $User = User::from([ echo $User->Aliases[0][0]->name; // Outputs: John Doe echo $User->Aliases[0][1]->name; // Outputs: John Smith +``` + +#### KeyBy +Key an array by an element value by using the `key_by` argument. + +This also supports deep mapping. +```php +class User +{ + use \Zerotoprod\DataModel\DataModel; + use \Zerotoprod\DataModelHelper\DataModelHelper; + + /** @var Alias[] $Aliases */ + #[Describe([ + 'cast' => [self::class, 'mapOf'], + 'type' => Alias::class, + 'key_by' => 'id', + ])] + public array $Aliases; +} + +class Alias +{ + use \Zerotoprod\DataModel\DataModel; + + public string $id; + public string $name; +} + +$User = User::from([ + 'Aliases' => [ + [ + 'id' => 'jd1', + 'name' => 'John Doe', + ], + [ + 'id' => 'js1', + 'name' => 'John Smith' + ], + ] +]); + +echo $User->Aliases['jd1']->name; // 'John Doe' +echo $User->Aliases['js1']->name); // 'John Smith' ``` \ No newline at end of file From 63c8ce8ab1c8b2b451ca8d9ce66712ca65504922 Mon Sep 17 00:00:00 2001 From: david_smith Date: Tue, 29 Oct 2024 11:55:28 -0400 Subject: [PATCH 3/3] Add tests. --- tests/Unit/KeyBy/Alias.php | 13 +++++++ tests/Unit/KeyBy/KeyByTest.php | 62 ++++++++++++++++++++++++++++++++++ tests/Unit/KeyBy/User.php | 30 ++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 tests/Unit/KeyBy/Alias.php create mode 100644 tests/Unit/KeyBy/KeyByTest.php create mode 100644 tests/Unit/KeyBy/User.php diff --git a/tests/Unit/KeyBy/Alias.php b/tests/Unit/KeyBy/Alias.php new file mode 100644 index 0000000..894d6e9 --- /dev/null +++ b/tests/Unit/KeyBy/Alias.php @@ -0,0 +1,13 @@ + [ + [ + 'id' => 'jd1', + 'name' => 'John Doe', + ], + [ + 'id' => 'js1', + 'name' => 'John Smith' + ], + ] + ]); + + self::assertEquals('John Doe', $User->Aliases['jd1']->name); + self::assertEquals('John Smith', $User->Aliases['js1']->name); + } + + #[Test] public function nested(): void + { + $User = User::from([ + 'AliasesNested' => [ + [ + [ + 'id' => 'jd1', + 'name' => 'John Doe', + ], + [ + 'id' => 'js1', + 'name' => 'John Smith' + ], + ], + [ + [ + 'id' => 'jd1', + 'name' => 'John Doe', + ], + [ + 'id' => 'js1', + 'name' => 'John Smith' + ], + ] + ] + ]); + + self::assertEquals('John Doe', $User->AliasesNested[0]['jd1']->name); + self::assertEquals('John Smith', $User->AliasesNested[0]['js1']->name); + + self::assertEquals('John Doe', $User->AliasesNested[1]['jd1']->name); + self::assertEquals('John Smith', $User->AliasesNested[1]['js1']->name); + } +} \ No newline at end of file diff --git a/tests/Unit/KeyBy/User.php b/tests/Unit/KeyBy/User.php new file mode 100644 index 0000000..12e4450 --- /dev/null +++ b/tests/Unit/KeyBy/User.php @@ -0,0 +1,30 @@ + [self::class, 'mapOf'], + 'type' => Alias::class, + 'key_by' => 'id' + ])] + public array $Aliases; + + /** @var Alias[][] $AliasesNested */ + #[Describe([ + 'cast' => [self::class, 'mapOf'], + 'type' => Alias::class, + 'level' => 2, + 'key_by' => 'id' + ])] + public array $AliasesNested; +} \ No newline at end of file