Skip to content

Commit b5826e2

Browse files
authored
Merge pull request #3 from nuxtifyts/feature/aliases
Introducing Aliases Attribute
2 parents 80d5639 + eb765dc commit b5826e2

File tree

11 files changed

+148
-9
lines changed

11 files changed

+148
-9
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Then you can define the properties of the class and their types.
1313

1414
```php
1515
use Nuxtifyts\PhpDto\Data;
16+
use Nuxtifyts\PhpDto\Attributes\Property\Aliases;
1617
use Nuxtifyts\PhpDto\Attributes\Property\Computed;
1718

1819
final readonly class UserData extends Data
@@ -22,6 +23,7 @@ final readonly class UserData extends Data
2223

2324
public function __construct(
2425
public string $firstName,
26+
#[Aliases('familyName')]
2527
public stirng $lastName
2628
) {
2729
$this->fullName = "$this->firstName $this->lastName";

docs/PropertyAttributes.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ Property Attributes
33

44
In order to provide more functionality to your DTOs, you can use the following attributes:
55
- [Computed](#Computed) - To define a property that is computed from other properties.
6+
- [Aliases](#Aliases) - To define aliases for a property.
67

78
Computed
89
-
@@ -29,3 +30,26 @@ final readonly class Person extends Data
2930
```
3031

3132
This will make the DTO aware of the `fullName` property, and it will not be serialized or deserialized.
33+
34+
Aliases
35+
-
36+
37+
Sometimes, we may need to specify that a property can be hydrated from multiple keys in the data array.
38+
This can be done using the `Aliases` attribute.
39+
40+
```php
41+
use Nuxtifyts\PhpDto\Data;
42+
use Nuxtifyts\PhpDto\Attributes\Property\Aliases;
43+
44+
final readonly class Person extends Data
45+
{
46+
public function __construct(
47+
#[Aliases('first_name')]
48+
public string $firstName,
49+
#[Aliases('last_name')]
50+
public string $lastName
51+
) {}
52+
}
53+
```
54+
55+
This will make it possible to hydrate properties from multiple array keys.

src/Attributes/Property/Aliases.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace Nuxtifyts\PhpDto\Attributes\Property;
4+
5+
use Attribute;
6+
7+
#[Attribute(Attribute::TARGET_PROPERTY)]
8+
class Aliases
9+
{
10+
/** @var list<string> */
11+
private(set) array $aliases;
12+
13+
public function __construct(
14+
string $alias,
15+
string ...$aliases
16+
) {
17+
$this->aliases = array_values([$alias, ...$aliases]);
18+
}
19+
}

src/Concerns/BaseData.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
namespace Nuxtifyts\PhpDto\Concerns;
44

55
use Nuxtifyts\PhpDto\Contexts\ClassContext;
6-
use Nuxtifyts\PhpDto\Data;
76
use Nuxtifyts\PhpDto\Exceptions\DeserializeException;
87
use Nuxtifyts\PhpDto\Exceptions\SerializeException;
98
use Nuxtifyts\PhpDto\Pipelines\DeserializePipeline\DeserializePipelinePassable;
109
use Nuxtifyts\PhpDto\Pipelines\DeserializePipeline\RefineDataPipe;
10+
use Nuxtifyts\PhpDto\Pipelines\DeserializePipeline\ResolveValuesFromAliasesPipe;
1111
use Nuxtifyts\PhpDto\Support\Pipeline;
1212
use Nuxtifyts\PhpDto\Support\Traits\HasNormalizers;
1313
use ReflectionClass;
@@ -35,6 +35,7 @@ final public static function from(mixed $value): static
3535
$context = ClassContext::getInstance(new ReflectionClass(static::class));
3636

3737
$data = new Pipeline(DeserializePipelinePassable::class)
38+
->through(ResolveValuesFromAliasesPipe::class)
3839
->through(RefineDataPipe::class)
3940
->sendThenReturn(new DeserializePipelinePassable(
4041
classContext: $context,

src/Contexts/PropertyContext.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Nuxtifyts\PhpDto\Contexts;
44

5+
use Nuxtifyts\PhpDto\Attributes\Property\Aliases;
56
use Nuxtifyts\PhpDto\Attributes\Property\Computed;
67
use Nuxtifyts\PhpDto\Attributes\Property\WithRefiner;
78
use Nuxtifyts\PhpDto\DataRefiners\DataRefiner;
@@ -29,6 +30,11 @@ class PropertyContext
2930
*/
3031
private static array $_instances = [];
3132

33+
/**
34+
* @var list<string>
35+
*/
36+
private(set) array $aliases = [];
37+
3238
private(set) bool $isComputed = false;
3339

3440
/** @var list<DataRefiner> */
@@ -85,6 +91,11 @@ private function syncPropertyAttributes(): void
8591
/** @var ReflectionAttribute<WithRefiner> $withRefinerAttribute */
8692
$this->dataRefiners[] = $withRefinerAttribute->newInstance()->getRefiner();
8793
}
94+
95+
if ($aliasesAttribute = $this->reflection->getAttributes(Aliases::class)[0] ?? null) {
96+
/** @var ReflectionAttribute<Aliases> $aliasesAttribute */
97+
$this->aliases = $aliasesAttribute->newInstance()->aliases;
98+
}
8899
}
89100

90101
public function getValue(object $object): mixed

src/Pipelines/DeserializePipeline/RefineDataPipe.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,6 @@ public function handle(Passable $passable): DeserializePipelinePassable
3232
}
3333
}
3434

35-
return $passable->with($data);
35+
return $passable->with(data: $data);
3636
}
3737
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace Nuxtifyts\PhpDto\Pipelines\DeserializePipeline;
4+
5+
use Nuxtifyts\PhpDto\Support\Passable;
6+
use Nuxtifyts\PhpDto\Support\Pipe;
7+
8+
/**
9+
* @extends Pipe<DeserializePipelinePassable>
10+
*/
11+
readonly class ResolveValuesFromAliasesPipe extends Pipe
12+
{
13+
public function handle(Passable $passable): DeserializePipelinePassable
14+
{
15+
$data = $passable->data;
16+
17+
foreach ($passable->classContext->properties as $propertyContext) {
18+
$propertyName = $propertyContext->propertyName;
19+
20+
if (array_key_exists($propertyName, $data)) {
21+
continue;
22+
}
23+
24+
$aliases = $propertyContext->aliases;
25+
26+
foreach ($aliases as $alias) {
27+
if (array_key_exists($alias, $data)) {
28+
$data[$propertyName] = $data[$alias];
29+
break;
30+
}
31+
}
32+
}
33+
34+
return $passable->with(data: $data);
35+
}
36+
}

tests/Dummies/PersonData.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@
22

33
namespace Nuxtifyts\PhpDto\Tests\Dummies;
44

5+
use Nuxtifyts\PhpDto\Attributes\Property\Aliases;
6+
use Nuxtifyts\PhpDto\Attributes\Property\Computed;
57
use Nuxtifyts\PhpDto\Data;
68

79
final readonly class PersonData extends Data
810
{
11+
#[Computed]
912
public string $fullName;
1013

1114
public function __construct(
15+
#[Aliases('first_name', 'name')]
1216
public string $firstName,
17+
#[Aliases('last_name', 'family_name')]
1318
public string $lastName,
1419
) {
1520
$this->fullName = $this->firstName . ' ' . $this->lastName;
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
namespace Nuxtifyts\PhpDto\Tests\Unit\Attributes;
4+
5+
use Nuxtifyts\PhpDto\Attributes\Property\Aliases;
6+
use Nuxtifyts\PhpDto\Contexts\PropertyContext;
7+
use Nuxtifyts\PhpDto\Pipelines\DeserializePipeline\ResolveValuesFromAliasesPipe;
8+
use Nuxtifyts\PhpDto\Tests\Dummies\PersonData;
9+
use Nuxtifyts\PhpDto\Tests\Unit\UnitCase;
10+
use PHPUnit\Framework\Attributes\CoversClass;
11+
use PHPUnit\Framework\Attributes\Test;
12+
use PHPUnit\Framework\Attributes\UsesClass;
13+
use Throwable;
14+
15+
#[CoversClass(PropertyContext::class)]
16+
#[CoversClass(ResolveValuesFromAliasesPipe::class)]
17+
#[CoversClass(Aliases::class)]
18+
#[UsesClass(PersonData::class)]
19+
final class AliasesAttributeTest extends UnitCase
20+
{
21+
/**
22+
* @throws Throwable
23+
*/
24+
#[Test]
25+
public function will_be_able_to_resolve_value_from_aliases(): void
26+
{
27+
$person = PersonData::from([
28+
'first_name' => 'John',
29+
'last_name' => 'Doe'
30+
]);
31+
32+
self::assertEquals('John', $person->firstName);
33+
self::assertEquals('Doe', $person->lastName);
34+
35+
$person = PersonData::from([
36+
'first_name' => 'John',
37+
'family_name' => 'Doe'
38+
]);
39+
40+
self::assertEquals('John', $person->firstName);
41+
self::assertEquals('Doe', $person->lastName);
42+
}
43+
}

tests/Unit/Concerns/BaseDataTest.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,7 @@ public function base_data_supports_scalar_types(): void
6767
self::assertEquals(
6868
[
6969
'firstName' => 'John',
70-
'lastName' => 'Doe',
71-
'fullName' => 'John Doe'
70+
'lastName' => 'Doe'
7271
],
7372
$personData = $person->jsonSerialize()
7473
);
@@ -171,8 +170,7 @@ public static function will_perform_serialization_and_deserialization_data_provi
171170
'dtoClass' => PersonData::class,
172171
'data' => $data = [
173172
'firstName' => 'John',
174-
'lastName' => 'Doe',
175-
'fullName' => 'John Doe'
173+
'lastName' => 'Doe'
176174
],
177175
'expectedProperties' => [
178176
'firstName' => 'John',

0 commit comments

Comments
 (0)