Skip to content

Commit

Permalink
feat/add-support-for-key_by (#8)
Browse files Browse the repository at this point in the history
* Add support for `key_by` in `DataModelHelper.php`.

* Update `README.md`.

* Add tests.

---------

Co-authored-by: david_smith <[email protected]>
  • Loading branch information
zero-to-prod and david_smith authored Oct 29, 2024
1 parent 014c964 commit 867c11c
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 9 deletions.
59 changes: 59 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -139,6 +146,8 @@ elements
are coerced into an array.

```php
use Zerotoprod\DataModel\Describe;

class User
{
use \Zerotoprod\DataModel\DataModel;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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'
```
24 changes: 15 additions & 9 deletions src/DataModelHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
13 changes: 13 additions & 0 deletions tests/Unit/KeyBy/Alias.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Tests\Unit\KeyBy;

use Zerotoprod\DataModel\DataModel;

class Alias
{
use DataModel;

public string $id;
public string $name;
}
62 changes: 62 additions & 0 deletions tests/Unit/KeyBy/KeyByTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

namespace Tests\Unit\KeyBy;

use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

class KeyByTest extends TestCase
{
#[Test] public function from(): void
{
$User = User::from([
'Aliases' => [
[
'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);
}
}
30 changes: 30 additions & 0 deletions tests/Unit/KeyBy/User.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace Tests\Unit\KeyBy;

use Zerotoprod\DataModel\DataModel;
use Zerotoprod\DataModel\Describe;
use Zerotoprod\DataModelHelper\DataModelHelper;

class User
{
use DataModel;
use DataModelHelper;

/** @var Alias[] $Aliases */
#[Describe([
'cast' => [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;
}

0 comments on commit 867c11c

Please sign in to comment.