Skip to content

Commit 73827b9

Browse files
committed
Add support for class-level normalizers via attributes
Introduces `WithNormalizer` attribute for class-level normalizers, enhancing flexibility in data transformation. Updates relevant tests and normalizer logic to accommodate and validate class-level normalizers alongside existing ones.
1 parent 11c38d3 commit 73827b9

File tree

12 files changed

+336
-115
lines changed

12 files changed

+336
-115
lines changed

clover.xml

Lines changed: 117 additions & 96 deletions
Large diffs are not rendered by default.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace Nuxtifyts\PhpDto\Attributes\Class;
4+
5+
use Attribute;
6+
use InvalidArgumentException;
7+
use Nuxtifyts\PhpDto\Normalizers\Normalizer;
8+
use Nuxtifyts\PhpDto\Support\Arr;
9+
10+
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
11+
class WithNormalizer
12+
{
13+
/** @var array<array-key, class-string<Normalizer>> */
14+
public array $classStrings;
15+
16+
/**
17+
* @param class-string<Normalizer> $classString
18+
* @param class-string<Normalizer> ...$classStrings
19+
*/
20+
public function __construct(string $classString, string ...$classStrings)
21+
{
22+
$arrOfClassStrings = [$classString, ...$classStrings];
23+
24+
if (!Arr::isArrayOfClassStrings($arrOfClassStrings, Normalizer::class)) {
25+
throw new InvalidArgumentException('expects a list of class strings of normalizers');
26+
}
27+
28+
$this->classStrings = $arrOfClassStrings;
29+
}
30+
}

src/Concerns/BaseData.php

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,16 @@ trait BaseData
2222
final public static function create(mixed ...$args): static
2323
{
2424
try {
25-
$value = static::normalizeValue($args, static::class)
26-
?: static::normalizeValue($args[0] ?? [], static::class);
25+
/** @var ClassContext<static> $context */
26+
$context = ClassContext::getInstance(new ReflectionClass(static::class));
27+
28+
$value = static::normalizeValue($args, static::class, $context->normalizers)
29+
?: static::normalizeValue($args[0] ?? [], static::class, $context->normalizers);
2730

2831
if ($value === false) {
2932
throw DataCreationException::invalidParamsPassed(static::class);
3033
}
3134

32-
/** @var ClassContext<static> $context */
33-
$context = ClassContext::getInstance(new ReflectionClass(static::class));
34-
3535
$data = DeserializePipeline::createFromArray()
3636
->sendThenReturn(new DeserializePipelinePassable(
3737
classContext: $context,
@@ -51,15 +51,15 @@ classContext: $context,
5151
final public static function from(mixed $value): static
5252
{
5353
try {
54-
$value = static::normalizeValue($value, static::class);
54+
/** @var ClassContext<static> $context */
55+
$context = ClassContext::getInstance(new ReflectionClass(static::class));
56+
57+
$value = static::normalizeValue($value, static::class, $context->normalizers);
5558

5659
if ($value === false) {
5760
throw DeserializeException::invalidValue();
5861
}
5962

60-
/** @var ClassContext<static> $context */
61-
$context = ClassContext::getInstance(new ReflectionClass(static::class));
62-
6363
$data = DeserializePipeline::hydrateFromArray()
6464
->sendThenReturn(new DeserializePipelinePassable(
6565
classContext: $context,

src/Concerns/CloneableData.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,16 @@ public function with(mixed ...$args): static
2222
throw DataCreationException::invalidParamsPassed(static::class);
2323
}
2424

25-
$value = static::normalizeValue($args, static::class)
26-
?: static::normalizeValue($args[0], static::class);
25+
/** @var ClassContext<static> $context */
26+
$context = ClassContext::getInstance(new ReflectionClass(static::class));
27+
28+
$value = static::normalizeValue($args, static::class, $context->normalizers)
29+
?: static::normalizeValue($args[0], static::class, $context->normalizers);
2730

2831
if ($value === false) {
2932
throw DataCreationException::invalidParamsPassed(static::class);
3033
}
3134

32-
/** @var ClassContext<static> $context */
33-
$context = ClassContext::getInstance(new ReflectionClass(static::class));
34-
3535
return $context->hasComputedProperties
3636
? $this->cloneInstanceWithConstructorCall($context, $value)
3737
: $this->cloneInstanceWithoutConstructorCall($context, $value);

src/Contexts/ClassContext.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22

33
namespace Nuxtifyts\PhpDto\Contexts;
44

5+
use Nuxtifyts\PhpDto\Attributes\Class\WithNormalizer;
56
use Nuxtifyts\PhpDto\Data;
67
use Nuxtifyts\PhpDto\Exceptions\DataCreationException;
78
use Nuxtifyts\PhpDto\Exceptions\UnsupportedTypeException;
9+
use Nuxtifyts\PhpDto\Normalizers\Normalizer;
10+
use ReflectionAttribute;
811
use ReflectionException;
912
use ReflectionParameter;
1013
use ReflectionClass;
@@ -30,6 +33,9 @@ class ClassContext
3033
/** @var list<string> List of param names */
3134
public readonly array $constructorParams;
3235

36+
/** @var array<array-key, class-string<Normalizer>> */
37+
private(set) array $normalizers = [];
38+
3339
/**
3440
* @param ReflectionClass<T> $reflection
3541
*
@@ -43,6 +49,7 @@ final private function __construct(
4349
static fn (ReflectionParameter $param) => $param->getName(),
4450
$this->reflection->getConstructor()?->getParameters() ?? [],
4551
);
52+
$this->syncClassAttributes();
4653
}
4754

4855
public bool $hasComputedProperties {
@@ -91,6 +98,17 @@ private static function getPropertyContexts(ReflectionClass $reflectionClass): a
9198
return $properties;
9299
}
93100

101+
private function syncClassAttributes(): void
102+
{
103+
foreach ($this->reflection->getAttributes(WithNormalizer::class) as $withNormalizerAttribute) {
104+
/** @var ReflectionAttribute<WithNormalizer> $withNormalizerAttribute */
105+
$this->normalizers = array_values([
106+
...$this->normalizers,
107+
...$withNormalizerAttribute->newInstance()->classStrings
108+
]);
109+
}
110+
}
111+
94112
/**
95113
* @throws ReflectionException
96114
*

src/Normalizers/Concerns/HasNormalizers.php

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,28 @@
33
namespace Nuxtifyts\PhpDto\Normalizers\Concerns;
44

55
use Nuxtifyts\PhpDto\Configuration\DataConfiguration;
6+
use Nuxtifyts\PhpDto\Contexts\ClassContext;
67
use Nuxtifyts\PhpDto\Data;
78
use Nuxtifyts\PhpDto\Exceptions\DataConfigurationException;
89
use Nuxtifyts\PhpDto\Normalizers\Normalizer;
10+
use ReflectionClass;
911

1012
trait HasNormalizers
1113
{
1214
/**
1315
* @param class-string<Data> $class
16+
* @param array<array-key, class-string<Normalizer>> $classNormalizers
1417
*
1518
* @return array<string, mixed>|false
1619
*
1720
* @throws DataConfigurationException
1821
*/
19-
protected static function normalizeValue(mixed $value, string $class): array|false
20-
{
21-
foreach (static::allNormalizer() as $normalizer) {
22+
protected static function normalizeValue(
23+
mixed $value,
24+
string $class,
25+
array $classNormalizers = []
26+
): array|false {
27+
foreach (static::allNormalizer($classNormalizers) as $normalizer) {
2228
$normalized = new $normalizer($value, $class)->normalize();
2329

2430
if ($normalized !== false) {
@@ -30,13 +36,16 @@ protected static function normalizeValue(mixed $value, string $class): array|fal
3036
}
3137

3238
/**
39+
* @param array<array-key, class-string<Normalizer>> $classNormalizers
40+
*
3341
* @return list<class-string<Normalizer>>
3442
*
3543
* @throws DataConfigurationException
3644
*/
37-
final protected static function allNormalizer(): array
45+
final protected static function allNormalizer(array $classNormalizers = []): array
3846
{
3947
return array_values(array_unique([
48+
...$classNormalizers,
4049
...static::normalizers(),
4150
...DataConfiguration::getInstance()->normalizers->baseNormalizers,
4251
]));
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace Nuxtifyts\PhpDto\Tests\Dummies;
4+
5+
use Nuxtifyts\PhpDto\Attributes\Class\WithNormalizer;
6+
use Nuxtifyts\PhpDto\Data;
7+
use Nuxtifyts\PhpDto\Tests\Dummies\Normalizers\DummyNormalizer;
8+
9+
#[WithNormalizer(DummyNormalizer::class)]
10+
final readonly class DummyWithNormalizerData extends Data
11+
{
12+
public function __construct() {
13+
}
14+
}

tests/Dummies/NonData/Human.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
namespace Nuxtifyts\PhpDto\Tests\Dummies\NonData;
4+
5+
class Human
6+
{
7+
public function __construct(
8+
public string $name,
9+
public string $surname
10+
) {
11+
}
12+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace Nuxtifyts\PhpDto\Tests\Dummies\Normalizers;
4+
5+
use Nuxtifyts\PhpDto\Tests\Dummies\NonData\Human;
6+
use Nuxtifyts\PhpDto\Normalizers\Normalizer;
7+
8+
final readonly class HumanToPersonNormalizer extends Normalizer
9+
{
10+
/**
11+
* @return array<string, mixed>|false
12+
*/
13+
public function normalize(): array|false
14+
{
15+
return $this->value instanceof Human
16+
? [
17+
'firstName' => $this->value->name,
18+
'lastName' => $this->value->surname
19+
]
20+
: false;
21+
}
22+
}

tests/Dummies/PersonData.php

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

33
namespace Nuxtifyts\PhpDto\Tests\Dummies;
44

5+
use Nuxtifyts\PhpDto\Tests\Dummies\Normalizers\DummyNormalizer;
6+
use Nuxtifyts\PhpDto\Tests\Dummies\Normalizers\HumanToPersonNormalizer;
7+
use Nuxtifyts\PhpDto\Attributes\Class\WithNormalizer;
58
use Nuxtifyts\PhpDto\Attributes\Property\Aliases;
69
use Nuxtifyts\PhpDto\Attributes\Property\Computed;
710
use Nuxtifyts\PhpDto\Data;
811

12+
#[WithNormalizer(DummyNormalizer::class)]
13+
#[WithNormalizer(HumanToPersonNormalizer::class)]
914
final readonly class PersonData extends Data
1015
{
1116
#[Computed]

0 commit comments

Comments
 (0)