Skip to content

Commit eb3451a

Browse files
committed
Added attributes for array of: scalar type, backed enums, date times and data
1 parent dc9c14f commit eb3451a

17 files changed

+855
-19
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
namespace Nuxtifyts\PhpDto\Attributes\PropertyAttributes;
4+
5+
use Attribute;
6+
use BackedEnum;
7+
use InvalidArgumentException;
8+
use ReflectionEnum;
9+
10+
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
11+
class ArrayOfBackedEnums
12+
{
13+
/** @var array<string, ReflectionEnum<BackedEnum>> */
14+
private static array $_enumReflections = [];
15+
16+
/** @var list<class-string<BackedEnum>> $enums */
17+
private(set) array $enums;
18+
19+
/** @var array<string, ReflectionEnum<BackedEnum>> */
20+
private(set) array $resolvedBackedEnumReflections = [];
21+
22+
/**
23+
* @param class-string<BackedEnum>|list<class-string<BackedEnum>> $enums
24+
*/
25+
public function __construct(
26+
string|array $enums
27+
) {
28+
$enumArr = is_array($enums) ? $enums : [$enums];
29+
30+
if (empty($enumArr)) {
31+
throw new InvalidArgumentException(
32+
'BackedEnumArray must have at least one enum'
33+
);
34+
} else {
35+
foreach ($enumArr as $enum) {
36+
if (!enum_exists($enum)) {
37+
throw new InvalidArgumentException(
38+
'Invalid enum passed to BackedEnumArray: ' . $enum
39+
);
40+
}
41+
42+
/**
43+
* @var ReflectionEnum<BackedEnum> $reflectionEnum
44+
*/
45+
$reflectionEnum = self::$_enumReflections[$enum] ??= new ReflectionEnum($enum);
46+
47+
if (!$reflectionEnum->isBacked()) {
48+
throw new InvalidArgumentException(
49+
'Non-backed enum passed to BackedEnumArray: ' . $enum
50+
);
51+
}
52+
53+
$this->resolvedBackedEnumReflections[$enum] = $reflectionEnum;
54+
}
55+
}
56+
57+
$this->enums = is_array($enums) ? $enums : [$enums];
58+
}
59+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
namespace Nuxtifyts\PhpDto\Attributes\PropertyAttributes;
4+
5+
use Attribute;
6+
use ReflectionClass;
7+
use Nuxtifyts\PhpDto\Data;
8+
use InvalidArgumentException;
9+
use Exception;
10+
use Nuxtifyts\PhpDto\Contracts\BaseData as BaseDataContract;
11+
12+
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
13+
class ArrayOfData
14+
{
15+
/** @var array<string, ReflectionClass<Data>> */
16+
private static array $_dataReflections = [];
17+
18+
/** @var list<class-string<Data>> */
19+
private(set) array $dataClasses;
20+
21+
/** @var array<string, ReflectionClass<Data>> */
22+
private(set) array $resolvedDataReflections = [];
23+
24+
/**
25+
* @param class-string<Data>|list<class-string<Data>> $dataClasses
26+
*/
27+
public function __construct(
28+
string|array $dataClasses
29+
) {
30+
try {
31+
$dataArr = is_array($dataClasses) ? $dataClasses : [$dataClasses];
32+
33+
if (empty($dataArr)) {
34+
throw new InvalidArgumentException(
35+
'ArrayOfData must have at least one Data class'
36+
);
37+
} else {
38+
foreach ($dataArr as $data) {
39+
if (!class_exists($data)) {
40+
throw new InvalidArgumentException(
41+
'Invalid Data class passed to ArrayOfData: ' . $data
42+
);
43+
}
44+
45+
/**
46+
* @var ReflectionClass<Data> $reflectionData
47+
*/
48+
$reflectionData = self::$_dataReflections[$data] ??= new ReflectionClass($data);
49+
50+
if (!$reflectionData->implementsInterface(BaseDataContract::class)) {
51+
throw new InvalidArgumentException(
52+
'Non-Data class passed to ArrayOfData: ' . $data
53+
);
54+
}
55+
56+
$this->resolvedDataReflections[$data] = $reflectionData;
57+
}
58+
}
59+
60+
$this->dataClasses = is_array($dataClasses) ? $dataClasses : [$dataClasses];
61+
} catch (Exception) {
62+
throw new InvalidArgumentException(
63+
'Invalid Data class passed to ArrayOfData'
64+
);
65+
}
66+
}
67+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
namespace Nuxtifyts\PhpDto\Attributes\PropertyAttributes;
4+
5+
use Attribute;
6+
use DateTime;
7+
use DateTimeImmutable;
8+
use ReflectionClass;
9+
use InvalidArgumentException;
10+
use DateTimeInterface;
11+
use Exception;
12+
13+
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
14+
class ArrayOfDateTimes
15+
{
16+
/** @var array<string, ReflectionClass<DateTime|DateTimeImmutable>> */
17+
private static array $_dateTimeReflections = [];
18+
19+
/** @var list<class-string<DateTime|DateTimeImmutable>> */
20+
private(set) array $dateTimes;
21+
22+
/** @var array<string, ReflectionClass<DateTime|DateTimeImmutable>> */
23+
private(set) array $resolvedDateTimeReflections = [];
24+
25+
/**
26+
* @param class-string<DateTime|DateTimeImmutable>|list<class-string<DateTime|DateTimeImmutable>> $dateTimes
27+
*/
28+
public function __construct(
29+
string|array $dateTimes
30+
) {
31+
try {
32+
$dateTimeArr = is_array($dateTimes) ? $dateTimes : [$dateTimes];
33+
34+
if (empty($dateTimeArr)) {
35+
throw new InvalidArgumentException(
36+
'ArrayOfDateTimes must have at least one DateTime or DateTimeImmutable'
37+
);
38+
} else {
39+
foreach ($dateTimeArr as $dateTime) {
40+
if (!class_exists($dateTime) && !interface_exists($dateTime)) {
41+
throw new InvalidArgumentException(
42+
'Invalid DateTime or DateTimeImmutable passed to ArrayOfDateTimes: ' . $dateTime
43+
);
44+
}
45+
46+
/**
47+
* @var ReflectionClass<DateTime|DateTimeImmutable> $reflectionDateTime
48+
*/
49+
$reflectionDateTime = self::$_dateTimeReflections[$dateTime] ??= new ReflectionClass($dateTime);
50+
51+
if (!$reflectionDateTime->implementsInterface(DateTimeInterface::class)) {
52+
throw new InvalidArgumentException(
53+
'Non-DateTime or DateTimeImmutable passed to ArrayOfDateTimes: ' . $dateTime
54+
);
55+
}
56+
57+
$this->resolvedDateTimeReflections[$dateTime] = $reflectionDateTime;
58+
}
59+
}
60+
61+
$this->dateTimes = is_array($dateTimes) ? $dateTimes : [$dateTimes];
62+
} catch(Exception) {
63+
throw new InvalidArgumentException(
64+
'Invalid DateTime or DateTimeImmutable passed to ArrayOfDateTimes'
65+
);
66+
}
67+
}
68+
}
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\Attributes\PropertyAttributes;
4+
5+
use Attribute;
6+
use Nuxtifyts\PhpDto\Enums\Property\Type;
7+
use InvalidArgumentException;
8+
9+
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
10+
class ArrayOfScalarTypes
11+
{
12+
/** @var list<Type> $types */
13+
private(set) array $types;
14+
15+
/**
16+
* @param Type|list<Type> $types
17+
*/
18+
public function __construct(
19+
Type|array $types = Type::SCALAR_TYPES
20+
) {
21+
$typesArr = is_array($types) ? $types : [$types];
22+
23+
if (
24+
$invalidTypes = array_diff(
25+
array_column($typesArr, 'value'),
26+
array_column(Type::SCALAR_TYPES, 'value'),
27+
)
28+
) {
29+
throw new InvalidArgumentException(
30+
'Invalid type passed to ScalarTypeArray: ' . implode(', ', $invalidTypes)
31+
);
32+
}
33+
34+
$this->types = $typesArr;
35+
}
36+
}

src/Contexts/ClassContext.php

Lines changed: 7 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\Exceptions\UnsupportedTypeException;
56
use ReflectionClass;
67
use ReflectionException;
78

@@ -25,6 +26,8 @@ class ClassContext
2526

2627
/**
2728
* @param ReflectionClass<T> $_reflectionClass
29+
*
30+
* @throws UnsupportedTypeException
2831
*/
2932
final private function __construct(
3033
protected readonly ReflectionClass $_reflectionClass
@@ -39,6 +42,8 @@ final private function __construct(
3942

4043
/**
4144
* @param ReflectionClass<T> $reflectionClass
45+
*
46+
* @throws UnsupportedTypeException
4247
*/
4348
final public static function getInstance(ReflectionClass $reflectionClass): static
4449
{
@@ -58,6 +63,8 @@ private static function getKey(ReflectionClass $reflectionClass): string
5863
* @param ReflectionClass<T> $reflectionClass
5964
*
6065
* @return array<string, PropertyContext>
66+
*
67+
* @throws UnsupportedTypeException
6168
*/
6269
private static function getPropertyContexts(ReflectionClass $reflectionClass): array
6370
{

src/Contexts/PropertyContext.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Nuxtifyts\PhpDto\Enums\Property\Type;
66
use Nuxtifyts\PhpDto\Exceptions\UnknownTypeException;
7+
use Nuxtifyts\PhpDto\Exceptions\UnsupportedTypeException;
78
use Nuxtifyts\PhpDto\Serializers\Serializer;
89
use Nuxtifyts\PhpDto\Support\Traits\HasSerializers;
910
use Nuxtifyts\PhpDto\Support\Traits\HasTypes;
@@ -27,6 +28,9 @@ class PropertyContext
2728
*/
2829
private ?array $_serializers = null;
2930

31+
/**
32+
* @throws UnsupportedTypeException
33+
*/
3034
final private function __construct(
3135
protected readonly ReflectionProperty $_reflectionProperty
3236
) {
@@ -41,6 +45,9 @@ final private function __construct(
4145
get => $this->_reflectionProperty->getDeclaringClass()->getName();
4246
}
4347

48+
/**
49+
* @throws UnsupportedTypeException
50+
*/
4451
final public static function getInstance(ReflectionProperty $property): static
4552
{
4653
return self::$_instances[self::getKey($property)]

src/Contexts/TypeContext.php

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use DateTimeInterface;
66
use Nuxtifyts\PhpDto\Data;
77
use Nuxtifyts\PhpDto\Enums\Property\Type;
8+
use Nuxtifyts\PhpDto\Exceptions\UnsupportedTypeException;
89
use ReflectionClass;
910
use ReflectionNamedType;
1011
use ReflectionProperty;
@@ -22,6 +23,8 @@
2223
*/
2324
class TypeContext
2425
{
26+
use TypeContext\ResolvesArraySubContexts;
27+
2528
/** @var array<string, ReflectionEnum<BackedEnum>> */
2629
private static array $_enumReflections = [];
2730

@@ -31,17 +34,29 @@ class TypeContext
3134
/** @var array<string, ReflectionClass<Data>> */
3235
private static array $_dataReflections = [];
3336

37+
/** @var list<Type> $arrayElementTypes */
38+
public array $arrayElementTypes {
39+
get => array_map(
40+
static fn (TypeContext $context) => $context->type,
41+
$this->subTypeContexts ?? []
42+
);
43+
}
44+
3445
/**
3546
* @param ?ReflectionClass<object> $reflection
47+
* @param ?list<static<T>> $subTypeContexts
3648
*/
37-
final private function __construct(
49+
final protected function __construct(
3850
public readonly Type $type,
3951
public readonly ?ReflectionClass $reflection = null,
52+
public readonly ?array $subTypeContexts = null
4053
) {
4154
}
4255

4356
/**
4457
* @return list<static<T>>
58+
*
59+
* @throws UnsupportedTypeException
4560
*/
4661
public static function getInstances(ReflectionProperty $property): array
4762
{
@@ -65,16 +80,31 @@ public static function getInstances(ReflectionProperty $property): array
6580
case $type === 'null':
6681
break;
6782
case ($reflectionEnum = self::resolvesReflectionEnum($type)):
68-
$instances[] = new static(Type::BACKED_ENUM, $reflectionEnum);
83+
$instances[] = new static(
84+
Type::BACKED_ENUM,
85+
reflection: $reflectionEnum
86+
);
6987
break;
7088
case ($reflectionDateTime = self::resolvesDateTime($type)):
71-
$instances[] = new static(Type::DATETIME, $reflectionDateTime);
89+
$instances[] = new static(
90+
Type::DATETIME,
91+
reflection: $reflectionDateTime
92+
);
7293
break;
7394
case ($reflectionData = self::resolvesData($type)):
74-
$instances[] = new static(Type::DATA, $reflectionData);
95+
$instances[] = new static(
96+
Type::DATA,
97+
reflection: $reflectionData
98+
);
99+
break;
100+
case $type === 'array':
101+
$instances[] = new static(
102+
Type::ARRAY,
103+
subTypeContexts: self::resolveSubContextsForArray($property)
104+
);
75105
break;
76106
default:
77-
$instances[] = new static(Type::MIXED);
107+
throw UnsupportedTypeException::from($type);
78108
}
79109
}
80110

0 commit comments

Comments
 (0)