Skip to content

Commit 5e7efa7

Browse files
authored
Merge pull request #101 from patchlevel/1.11.x-merge-up-into-2.0.x_iBvxPuYj
Merge release 1.11.0 into 2.0.x
2 parents 1665fae + b8f09d2 commit 5e7efa7

26 files changed

Lines changed: 1152 additions & 302 deletions

.github/workflows/backward-compatibility-check.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
fetch-depth: 0
2828

2929
- name: "Install PHP"
30-
uses: "shivammathur/setup-php@2.33.0"
30+
uses: "shivammathur/setup-php@2.34.0"
3131
with:
3232
coverage: "pcov"
3333
php-version: "${{ matrix.php-version }}"

.github/workflows/benchmark.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222

2323
steps:
2424
- name: "Install PHP"
25-
uses: "shivammathur/setup-php@2.33.0"
25+
uses: "shivammathur/setup-php@2.34.0"
2626
with:
2727
coverage: "pcov"
2828
php-version: "${{ matrix.php-version }}"

.github/workflows/coding-standard.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
uses: actions/checkout@v4
3030

3131
- name: "Install PHP"
32-
uses: "shivammathur/setup-php@2.33.0"
32+
uses: "shivammathur/setup-php@2.34.0"
3333
with:
3434
coverage: "pcov"
3535
php-version: "${{ matrix.php-version }}"

.github/workflows/mutation-tests-diff.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
fetch-depth: 0
2828

2929
- name: "Install PHP"
30-
uses: "shivammathur/setup-php@2.33.0"
30+
uses: "shivammathur/setup-php@2.34.0"
3131
with:
3232
coverage: "pcov"
3333
php-version: "${{ matrix.php-version }}"

.github/workflows/mutation-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
uses: actions/checkout@v4
3030

3131
- name: "Install PHP"
32-
uses: "shivammathur/setup-php@2.33.0"
32+
uses: "shivammathur/setup-php@2.34.0"
3333
with:
3434
coverage: "pcov"
3535
php-version: "${{ matrix.php-version }}"

.github/workflows/phpstan.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
uses: actions/checkout@v4
3030

3131
- name: "Install PHP"
32-
uses: "shivammathur/setup-php@2.33.0"
32+
uses: "shivammathur/setup-php@2.34.0"
3333
with:
3434
coverage: "pcov"
3535
php-version: "${{ matrix.php-version }}"

.github/workflows/psalm.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
uses: actions/checkout@v4
3030

3131
- name: "Install PHP"
32-
uses: "shivammathur/setup-php@2.33.0"
32+
uses: "shivammathur/setup-php@2.34.0"
3333
with:
3434
coverage: "pcov"
3535
php-version: "${{ matrix.php-version }}"

.github/workflows/unit.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ jobs:
3838
uses: actions/checkout@v4
3939

4040
- name: "Install PHP"
41-
uses: "shivammathur/setup-php@2.33.0"
41+
uses: "shivammathur/setup-php@2.34.0"
4242
with:
4343
coverage: "pcov"
4444
php-version: "${{ matrix.php-version }}"

README.md

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ The library is a core component of [patchlevel/event-sourcing](ttps://github.com
1212
where it powers the storage and retrieval of thousands of objects.
1313

1414
Hydration is handled through normalizers, especially for complex data types.
15-
The system can automatically determine the appropriate normalizer based on the data type and PHPStan/Psalm annotations.
15+
The system can automatically determine the appropriate normalizer based on the data type and annotations.
1616

1717
In most cases, no manual configuration is needed.
1818
And if customization is required, it can be done easily using attributes.
@@ -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,
@@ -442,6 +473,29 @@ readonly class ProfileCreated
442473
}
443474
```
444475

476+
### Lazy
477+
478+
Since PHP 8.4, it's been possible to lazy-hydrate objects.
479+
That is, the actual hydration process occurs when the object is accessed.
480+
You can define for each class whether you want it to be lazy by using the `Lazy` attribute.
481+
482+
```php
483+
use Patchlevel\Hydrator\Attribute\Lazy;
484+
485+
#[Lazy]
486+
readonly class ProfileCreated
487+
{
488+
public function __construct(
489+
public string $id,
490+
public string $name,
491+
) {
492+
}
493+
}
494+
```
495+
496+
> [!NOTE]
497+
> If you are using a PHP version older than 8.4, the attribute will be ignored.
498+
445499
### Hooks
446500

447501
Sometimes you need to do something before extract or after hydrate process.
@@ -592,6 +646,10 @@ final class ProfileCreated
592646
}
593647
```
594648

649+
> [!TIP]
650+
> Cryptography is very expensive in terms of performance,
651+
> you can combine it with lazy to improve performance and only decrypt when you actually access the object.
652+
595653
#### Configure Cryptography
596654

597655
Here we show you how to configure the cryptography.

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>

0 commit comments

Comments
 (0)