Skip to content

Commit 2e50371

Browse files
committed
Added array properties support
1 parent eb3451a commit 2e50371

17 files changed

+658
-162
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
namespace Nuxtifyts\PhpDto\Concerns;
4+
5+
use Nuxtifyts\PhpDto\Contexts\PropertyContext;
6+
use Nuxtifyts\PhpDto\Exceptions\DeserializeException;
7+
use Nuxtifyts\PhpDto\Exceptions\SerializeException;
8+
use Nuxtifyts\PhpDto\Serializers\Serializer;
9+
10+
/**
11+
* @mixin Serializer
12+
*/
13+
trait SerializesArrayOfItems
14+
{
15+
/**
16+
* @return array<string, ?array<array-key, mixed>>
17+
*
18+
* @throws SerializeException
19+
*/
20+
public function serializeArrayOfItems(
21+
PropertyContext $property,
22+
object $object
23+
): array {
24+
$value = $property->getValue($object);
25+
26+
return [
27+
$property->propertyName => match(true) {
28+
is_null($value) && $property->isNullable => null,
29+
30+
is_array($value) => array_map(
31+
fn(mixed $item) => $this->serializeItem($item, $property, $object),
32+
$value
33+
),
34+
35+
default => throw new SerializeException('Could not serialize array of items')
36+
}
37+
];
38+
}
39+
40+
/**
41+
* @param array<string, mixed> $data
42+
*
43+
* @return ?array<array-key, mixed>
44+
*
45+
* @throws DeserializeException
46+
*/
47+
public function deserializeArrayOfItems(
48+
PropertyContext $property,
49+
array $data
50+
): ?array {
51+
return array_map(
52+
fn(mixed $item) => $this->deserializeItem($item, $property),
53+
$data
54+
);
55+
}
56+
}

src/Contexts/PropertyContext.php

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,6 @@ class PropertyContext
2323
*/
2424
private static array $_instances = [];
2525

26-
/**
27-
* @var list<Serializer>|null
28-
*/
29-
private ?array $_serializers = null;
30-
3126
/**
3227
* @throws UnsupportedTypeException
3328
*/
@@ -45,6 +40,17 @@ final private function __construct(
4540
get => $this->_reflectionProperty->getDeclaringClass()->getName();
4641
}
4742

43+
/** @var list<TypeContext<Type>> $arrayTypeContexts */
44+
public array $arrayTypeContexts {
45+
get => array_reduce(
46+
$this->typeContexts ?? [],
47+
static fn (array $typeContexts, TypeContext $context) => $context->type === Type::ARRAY
48+
? [...$typeContexts, ...$context->subTypeContexts]
49+
: [],
50+
[]
51+
);
52+
}
53+
4854
/**
4955
* @throws UnsupportedTypeException
5056
*/
@@ -59,16 +65,6 @@ private static function getKey(ReflectionProperty $property): string
5965
return $property->getDeclaringClass()->getName() . '@' . $property->getName();
6066
}
6167

62-
/**
63-
* @return list<Serializer>
64-
*
65-
* @throws UnknownTypeException
66-
*/
67-
public function serializers(): array
68-
{
69-
return $this->_serializers ??= $this->getSerializers($this);
70-
}
71-
7268
public function getValue(object $object): mixed
7369
{
7470
return $this->_reflectionProperty->getValue($object);
@@ -87,4 +83,28 @@ public function getFilteredTypeContexts(Type $type, Type ...$additionalTypes): a
8783
)
8884
);
8985
}
86+
87+
/**
88+
* @return list<TypeContext<Type>>
89+
*/
90+
public function getFilteredSubTypeContexts(Type $type, Type ...$additionalTypes): array
91+
{
92+
return array_values(
93+
array_filter(
94+
$this->arrayTypeContexts,
95+
static fn (TypeContext $typeContext) =>
96+
in_array($typeContext->type, [$type, ...$additionalTypes], true)
97+
)
98+
);
99+
}
100+
101+
/**
102+
* @return list<Serializer>
103+
*
104+
* @throws UnknownTypeException
105+
*/
106+
protected function resolveSerializers(): array
107+
{
108+
return $this->getSerializersFromPropertyContext($this);
109+
}
90110
}

src/Contexts/TypeContext.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
use DateTimeInterface;
66
use Nuxtifyts\PhpDto\Data;
77
use Nuxtifyts\PhpDto\Enums\Property\Type;
8+
use Nuxtifyts\PhpDto\Exceptions\UnknownTypeException;
89
use Nuxtifyts\PhpDto\Exceptions\UnsupportedTypeException;
10+
use Nuxtifyts\PhpDto\Serializers\Serializer;
11+
use Nuxtifyts\PhpDto\Support\Traits\HasSerializers;
912
use ReflectionClass;
1013
use ReflectionNamedType;
1114
use ReflectionProperty;
@@ -24,6 +27,9 @@
2427
class TypeContext
2528
{
2629
use TypeContext\ResolvesArraySubContexts;
30+
use HasSerializers {
31+
serializers as subTypeSerializers;
32+
}
2733

2834
/** @var array<string, ReflectionEnum<BackedEnum>> */
2935
private static array $_enumReflections = [];
@@ -202,4 +208,12 @@ private static function getPropertyStringTypes(ReflectionProperty $property): ar
202208
default => [],
203209
};
204210
}
211+
212+
/**
213+
* @return list<Serializer>
214+
*/
215+
protected function resolveSerializers(): array
216+
{
217+
return $this->getSerializersFromTypeContext($this);
218+
}
205219
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace Nuxtifyts\PhpDto\Contracts;
4+
5+
use Nuxtifyts\PhpDto\Contexts\PropertyContext;
6+
use Nuxtifyts\PhpDto\Exceptions\DeserializeException;
7+
use Nuxtifyts\PhpDto\Exceptions\SerializeException;
8+
9+
interface SerializesArrayOfItems
10+
{
11+
/**
12+
* @return array<string, ?array<array-key, mixed>>
13+
*
14+
* @throws SerializeException
15+
*/
16+
public function serializeArrayOfItems(
17+
PropertyContext $property,
18+
object $object
19+
): array;
20+
21+
/**
22+
* @param array<string, mixed> $data
23+
*
24+
* @return ?array<array-key, mixed>
25+
*
26+
* @throws DeserializeException
27+
*/
28+
public function deserializeArrayOfItems(
29+
PropertyContext $property,
30+
array $data
31+
): ?array;
32+
}

src/Serializers/ArraySerializer.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
namespace Nuxtifyts\PhpDto\Serializers;
4+
5+
use Nuxtifyts\PhpDto\Contexts\PropertyContext;
6+
use Nuxtifyts\PhpDto\Enums\Property\Type;
7+
use Nuxtifyts\PhpDto\Contracts\SerializesArrayOfItems as SerializesArrayOfItemsContract;
8+
use Nuxtifyts\PhpDto\Exceptions\DeserializeException;
9+
use Nuxtifyts\PhpDto\Exceptions\SerializeException;
10+
use Exception;
11+
12+
class ArraySerializer extends Serializer
13+
{
14+
public static function supportedTypes(): array
15+
{
16+
return [
17+
Type::ARRAY
18+
];
19+
}
20+
21+
/**
22+
* @return ?array<array-key, mixed>
23+
*/
24+
protected function serializeItem(mixed $item, PropertyContext $property, object $object): ?array
25+
{
26+
if ($item === null && $property->isNullable) {
27+
return null;
28+
}
29+
30+
if (is_array($item)) {
31+
foreach ($property->getFilteredTypeContexts(...self::supportedTypes()) as $typeContext) {
32+
try {
33+
foreach ($typeContext->subTypeSerializers() as $serializer) {
34+
try {
35+
if ($serializer instanceof SerializesArrayOfItemsContract) {
36+
$serializedValue = $serializer->serializeArrayOfItems($property, $object);
37+
38+
if (array_key_exists($property->propertyName, $serializedValue)) {
39+
return $serializedValue[$property->propertyName];
40+
}
41+
}
42+
} catch (Exception) {}
43+
}
44+
} catch (Exception) {}
45+
}
46+
}
47+
48+
throw new SerializeException('Could not serialize array of items');
49+
}
50+
51+
/**
52+
* @return ?array<array-key, mixed>
53+
*/
54+
protected function deserializeItem(mixed $item, PropertyContext $property): ?array
55+
{
56+
if (is_array($item)) {
57+
foreach ($property->getFilteredTypeContexts(...self::supportedTypes()) as $typeContext) {
58+
try {
59+
foreach ($typeContext->subTypeSerializers() as $serializer) {
60+
try {
61+
if ($serializer instanceof SerializesArrayOfItemsContract) {
62+
// @phpstan-ignore-next-line
63+
return $serializer->deserializeArrayOfItems($property, $item);
64+
}
65+
} catch (Exception) {}
66+
}
67+
} catch (Exception) {}
68+
}
69+
}
70+
71+
return is_null($item) && $property->isNullable
72+
? null
73+
: throw new DeserializeException('Property is not nullable');
74+
}
75+
}

src/Serializers/BackedEnumSerializer.php

Lines changed: 42 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,19 @@
22

33
namespace Nuxtifyts\PhpDto\Serializers;
44

5-
use ArrayAccess;
5+
use BackedEnum;
6+
use Exception;
67
use Nuxtifyts\PhpDto\Contexts\PropertyContext;
8+
use Nuxtifyts\PhpDto\Contracts\SerializesArrayOfItems as SerializesArrayOfItemsContract;
9+
use Nuxtifyts\PhpDto\Concerns\SerializesArrayOfItems;
710
use Nuxtifyts\PhpDto\Enums\Property\Type;
8-
use BackedEnum;
911
use Nuxtifyts\PhpDto\Exceptions\DeserializeException;
1012
use Nuxtifyts\PhpDto\Exceptions\SerializeException;
11-
use Exception;
1213

13-
class BackedEnumSerializer extends Serializer
14+
class BackedEnumSerializer extends Serializer implements SerializesArrayOfItemsContract
1415
{
15-
/**
16-
* @inheritDoc
17-
*/
16+
use SerializesArrayOfItems;
17+
1818
public static function supportedTypes(): array
1919
{
2020
return [
@@ -23,58 +23,57 @@ public static function supportedTypes(): array
2323
}
2424

2525
/**
26-
* @inheritDoc
26+
* @throws SerializeException
2727
*/
28-
public function serialize(PropertyContext $property, object $object): array
28+
protected function serializeItem(mixed $item, PropertyContext $property, object $object): string|int|null
2929
{
30-
$value = $property->getValue($object);
31-
32-
return [
33-
$property->propertyName => match (true) {
34-
$value instanceof BackedEnum => $value->value,
35-
$value === null && $property->isNullable => null,
36-
default => throw new SerializeException('Value is not a BackedEnum')
37-
}
38-
];
30+
return match(true) {
31+
is_null($item) && $property->isNullable => null,
32+
$item instanceof BackedEnum => $item->value,
33+
default => throw new SerializeException('Could not serialize array of BackedEnum items')
34+
};
3935
}
4036

4137
/**
42-
* @inheritDoc
38+
* @throws DeserializeException
4339
*/
44-
public function deserialize(PropertyContext $property, ArrayAccess|array $data): ?BackedEnum
40+
protected function deserializeItem(mixed $item, PropertyContext $property): ?BackedEnum
4541
{
46-
$value = $data[$property->propertyName] ?? null;
42+
if (is_null($item)) {
43+
return $property->isNullable
44+
? null
45+
: throw new DeserializeException('Property is not nullable');
46+
}
4747

48-
if (!is_string($value) && !is_integer($value) && !is_null($value)) {
48+
if (!is_string($item) && !is_integer($item)) {
4949
throw new DeserializeException('Value is not a string or integer');
5050
}
5151

52-
if ($value !== null) {
53-
foreach ($property->getFilteredTypeContexts(...self::supportedTypes()) as $typeContext) {
54-
try {
55-
if (!$typeContext->reflection?->implementsInterface(BackedEnum::class)) {
56-
continue;
57-
}
52+
$typeContexts = $property->getFilteredTypeContexts(...self::supportedTypes())
53+
?: $property->getFilteredSubTypeContexts(...self::supportedTypes());
5854

59-
$enumValue = call_user_func(
60-
// @phpstan-ignore-next-line
61-
[$typeContext->reflection->getName(), 'tryFrom'],
62-
$value
63-
);
64-
65-
if ($enumValue instanceof BackedEnum) {
66-
return $enumValue;
67-
}
68-
// @codeCoverageIgnoreStart
69-
} catch (Exception) {
55+
foreach ($typeContexts as $typeContext) {
56+
try {
57+
if (!$typeContext->reflection?->implementsInterface(BackedEnum::class)) {
7058
continue;
7159
}
72-
// @codeCoverageIgnoreEnd
60+
61+
$enumValue = call_user_func(
62+
// @phpstan-ignore-next-line
63+
[$typeContext->reflection->getName(), 'tryFrom'],
64+
$item
65+
);
66+
67+
if ($enumValue instanceof BackedEnum) {
68+
return $enumValue;
69+
}
70+
// @codeCoverageIgnoreStart
71+
} catch (Exception) {
72+
continue;
7373
}
74+
// @codeCoverageIgnoreEnd
7475
}
7576

76-
return $property->isNullable
77-
? null
78-
: throw new DeserializeException('Could not deserialize BackedEnum');
77+
throw new DeserializeException('Could not deserialize BackedEnum');
7978
}
8079
}

0 commit comments

Comments
 (0)