Skip to content

Commit db92598

Browse files
authored
Merge pull request #98 from patchlevel/array-shapes
add support for array shapes
2 parents dc33c9d + 6b3cb99 commit db92598

File tree

10 files changed

+804
-283
lines changed

10 files changed

+804
-283
lines changed

README.md

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,10 @@ For this purpose, normalizers of this order are determined:
137137

138138
1) Does the class property have a normalizer as an attribute? Use this.
139139
2) The data type of the property is determined.
140-
1) If it is a collection, use the ArrayNormalizer (recursive).
141-
2) If it is an object, then look for a normalizer as attribute on the class or interfaces and use this.
142-
3) If it is an object, then guess the normalizer based on the object. Fallback to the object normalizer.
140+
1) If it is an array shape, use the ArrayShapeNormalizer (recursive).
141+
2) If it is a collection, use the ArrayNormalizer (recursive).
142+
3) If it is an object, then look for a normalizer as attribute on the class or interfaces and use this.
143+
4) If it is an object, then guess the normalizer based on the object. Fallback to the object normalizer.
143144

144145
The normalizer is only determined once because it is cached in the metadata.
145146
Below you will find the list of all normalizers and how to set them manually or explicitly.
@@ -155,14 +156,44 @@ use Patchlevel\Hydrator\Normalizer\DateTimeImmutableNormalizer;
155156

156157
final class DTO
157158
{
158-
#[ArrayNormalizer(new DateTimeImmutableNormalizer())]
159+
/**
160+
* @var list<DateTimeImmutable>
161+
*/
162+
#[ArrayNormalizer]
159163
public array $dates;
164+
165+
#[ArrayNormalizer(new DateTimeImmutableNormalizer())]
166+
public array $explicitDates;
160167
}
161168
```
162169

163170
> [!NOTE]
164171
> The keys from the arrays are taken over here.
165172
173+
#### ArrayShape
174+
175+
If you have an array with a specific shape, you can use the `ArrayShapeNormalizer`.
176+
177+
```php
178+
use Patchlevel\Hydrator\Normalizer\ArrayShapeNormalizer;
179+
use Patchlevel\Hydrator\Normalizer\DateTimeImmutableNormalizer;
180+
181+
final class DTO
182+
{
183+
/**
184+
* @var array{
185+
* date: DateTimeImmutable,
186+
* otherField: string
187+
* }
188+
*/
189+
#[ArrayShapeNormalizer]
190+
public array $meta;
191+
192+
#[ArrayShapeNormalizer(['date' => new DateTimeImmutableNormalizer()])]
193+
public array $explicitMeta;
194+
}
195+
```
196+
166197
#### DateTimeImmutable
167198

168199
With the `DateTimeImmutable` Normalizer, as the name suggests,

baseline.xml

Lines changed: 133 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<files psalm-version="6.9.1@81c8a77c0793d450fee40265cfe68891df11d505">
2+
<files psalm-version="6.12.0@cf420941d061a57050b6c468ef2c778faf40aee2">
3+
<file src="src/Attribute/PersonalData.php">
4+
<MixedPropertyTypeCoercion>
5+
<code><![CDATA[$fallbackCallable]]></code>
6+
</MixedPropertyTypeCoercion>
7+
</file>
38
<file src="src/Cryptography/Cipher/OpensslCipherKeyFactory.php">
49
<ArgumentTypeCoercion>
510
<code><![CDATA[openssl_random_pseudo_bytes($this->ivLength)]]></code>
@@ -25,27 +30,106 @@
2530
</MixedAssignment>
2631
</file>
2732
<file src="src/Metadata/AttributeMetadataFactory.php">
28-
<MixedAssignment>
29-
<code><![CDATA[$personalDataFallback]]></code>
30-
</MixedAssignment>
33+
<InvalidReturnStatement>
34+
<code><![CDATA[[false, null]]]></code>
35+
</InvalidReturnStatement>
36+
<InvalidReturnType>
37+
<code><![CDATA[array{bool, mixed, (callable(string, mixed):mixed)|null}]]></code>
38+
</InvalidReturnType>
39+
<PossiblyNullReference>
40+
<code><![CDATA[guess]]></code>
41+
</PossiblyNullReference>
3142
</file>
3243
<file src="src/Metadata/ClassMetadata.php">
3344
<InvalidPropertyAssignmentValue>
3445
<code><![CDATA[new ReflectionClass($data['className'])]]></code>
3546
</InvalidPropertyAssignmentValue>
3647
</file>
48+
<file src="src/Metadata/PropertyMetadata.php">
49+
<RiskyTruthyFalsyComparison>
50+
<code><![CDATA[$this->personalDataFallbackCallable]]></code>
51+
</RiskyTruthyFalsyComparison>
52+
</file>
53+
<file src="src/MetadataHydrator.php">
54+
<InvalidOperand>
55+
<code><![CDATA[$guessers]]></code>
56+
</InvalidOperand>
57+
<PossiblyInvalidArgument>
58+
<code><![CDATA[[
59+
...$guessers,
60+
$guesser,
61+
]]]></code>
62+
</PossiblyInvalidArgument>
63+
</file>
64+
<file src="src/Normalizer/ArrayShapeNormalizer.php">
65+
<MixedAssignment>
66+
<code><![CDATA[$result[$field]]]></code>
67+
<code><![CDATA[$result[$field]]]></code>
68+
</MixedAssignment>
69+
</file>
70+
<file src="src/Normalizer/EnumNormalizer.php">
71+
<DeprecatedClass>
72+
<code><![CDATA[ReflectionTypeUtil::classStringInstanceOf($reflectionType, BackedEnum::class)]]></code>
73+
</DeprecatedClass>
74+
<DeprecatedInterface>
75+
<code><![CDATA[EnumNormalizer]]></code>
76+
</DeprecatedInterface>
77+
</file>
3778
<file src="src/Normalizer/ObjectNormalizer.php">
79+
<DeprecatedClass>
80+
<code><![CDATA[ReflectionTypeUtil::classString($reflectionType)]]></code>
81+
</DeprecatedClass>
82+
<DeprecatedInterface>
83+
<code><![CDATA[ObjectNormalizer]]></code>
84+
</DeprecatedInterface>
3885
<MixedArgument>
3986
<code><![CDATA[$value]]></code>
4087
</MixedArgument>
4188
<MixedArgumentTypeCoercion>
4289
<code><![CDATA[$value]]></code>
4390
</MixedArgumentTypeCoercion>
4491
</file>
92+
<file src="tests/Benchmark/tideways.php">
93+
<ClassMustBeFinal>
94+
<code><![CDATA[CompiledMetadataHydrator]]></code>
95+
</ClassMustBeFinal>
96+
<MixedMethodCall>
97+
<code><![CDATA[normalize]]></code>
98+
<code><![CDATA[normalize]]></code>
99+
</MixedMethodCall>
100+
<MixedReturnTypeCoercion>
101+
<code><![CDATA[array]]></code>
102+
<code><![CDATA[match (true) {
103+
$object instanceof ProfileCreated => $this->extractProfileCreated($object),
104+
$object instanceof Skill => $this->extractSkill($object),
105+
default => throw new InvalidArgumentException('Unknown object type'),
106+
}]]></code>
107+
</MixedReturnTypeCoercion>
108+
</file>
45109
<file src="tests/Unit/Cryptography/Cipher/OpensslCipherTest.php">
46110
<MixedAssignment>
47111
<code><![CDATA[$return]]></code>
48112
</MixedAssignment>
113+
<MixedReturnStatement>
114+
<code><![CDATA[Generator]]></code>
115+
</MixedReturnStatement>
116+
</file>
117+
<file src="tests/Unit/Cryptography/CryptographySubscriberTest.php">
118+
<InvalidArgument>
119+
<code><![CDATA[$metadata]]></code>
120+
<code><![CDATA[$metadata]]></code>
121+
</InvalidArgument>
122+
</file>
123+
<file src="tests/Unit/Fixture/IdNormalizer.php">
124+
<DeprecatedClass>
125+
<code><![CDATA[ReflectionTypeUtil::classStringInstanceOf(
126+
$reflectionType,
127+
Id::class,
128+
)]]></code>
129+
</DeprecatedClass>
130+
<DeprecatedInterface>
131+
<code><![CDATA[IdNormalizer]]></code>
132+
</DeprecatedInterface>
49133
</file>
50134
<file src="tests/Unit/Metadata/AttributeMetadataFactoryTest.php">
51135
<ArgumentTypeCoercion>
@@ -59,8 +143,53 @@
59143
<ArgumentTypeCoercion>
60144
<code><![CDATA['Unknown']]></code>
61145
</ArgumentTypeCoercion>
146+
<InvalidArgument>
147+
<code><![CDATA[$metadataFactory->metadata(ProfileCreated::class)]]></code>
148+
<code><![CDATA[$metadataFactory->metadata(ProfileCreated::class)]]></code>
149+
<code><![CDATA[$metadataFactory->metadata(ProfileCreated::class)]]></code>
150+
<code><![CDATA[$metadataFactory->metadata(ProfileCreated::class)]]></code>
151+
</InvalidArgument>
62152
<UndefinedClass>
63153
<code><![CDATA['Unknown']]></code>
64154
</UndefinedClass>
65155
</file>
156+
<file src="tests/Unit/Normalizer/ArrayNormalizerTest.php">
157+
<InvalidArgument>
158+
<code><![CDATA[$normalizer]]></code>
159+
</InvalidArgument>
160+
</file>
161+
<file src="tests/Unit/Normalizer/ArrayShapeNormalizerTest.php">
162+
<InvalidArgument>
163+
<code><![CDATA[['foo' => $normalizer]]]></code>
164+
</InvalidArgument>
165+
</file>
166+
<file src="tests/Unit/Normalizer/ReflectionTypeUtilTest.php">
167+
<DeprecatedClass>
168+
<code><![CDATA[ReflectionTypeUtil::classString($this->reflectionType($object, 'notAObject'))]]></code>
169+
<code><![CDATA[ReflectionTypeUtil::classString($this->reflectionType($object, 'object'))]]></code>
170+
<code><![CDATA[ReflectionTypeUtil::classString($this->reflectionType($object, 'objectNullable'))]]></code>
171+
<code><![CDATA[ReflectionTypeUtil::classString($this->reflectionType($object, 'objectUnionNullable'))]]></code>
172+
<code><![CDATA[ReflectionTypeUtil::classStringInstanceOf(
173+
$this->reflectionType($object, 'object'),
174+
ProfileCreated::class,
175+
)]]></code>
176+
<code><![CDATA[ReflectionTypeUtil::classStringInstanceOf(
177+
$this->reflectionType($object, 'objectNullable'),
178+
ProfileCreated::class,
179+
)]]></code>
180+
<code><![CDATA[ReflectionTypeUtil::classStringInstanceOf(
181+
$this->reflectionType($object, 'objectUnionNullable'),
182+
ProfileCreated::class,
183+
)]]></code>
184+
<code><![CDATA[ReflectionTypeUtil::classStringInstanceOf(
185+
$this->reflectionType($object, 'object'),
186+
ChildDto::class,
187+
)]]></code>
188+
<code><![CDATA[ReflectionTypeUtil::type($this->reflectionType($object, 'intersection'))]]></code>
189+
<code><![CDATA[ReflectionTypeUtil::type($this->reflectionType($object, 'nullableString'))]]></code>
190+
<code><![CDATA[ReflectionTypeUtil::type($this->reflectionType($object, 'string'))]]></code>
191+
<code><![CDATA[ReflectionTypeUtil::type($this->reflectionType($object, 'union'))]]></code>
192+
<code><![CDATA[ReflectionTypeUtil::type($this->reflectionType($object, 'unionNullableString'))]]></code>
193+
</DeprecatedClass>
194+
</file>
66195
</files>

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"psr/cache": "^2.0.0 || ^3.0.0",
2525
"psr/simple-cache": "^2.0.0 || ^3.0.0",
2626
"symfony/event-dispatcher": "^5.4.29 || ^6.4.0 || ^7.0.0",
27-
"symfony/type-info": "^7.2.4"
27+
"symfony/type-info": "^7.3.0"
2828
},
2929
"require-dev": {
3030
"infection/infection": "^0.29.10",

0 commit comments

Comments
 (0)