Skip to content

Commit 73842e6

Browse files
committed
update
1 parent f9137a9 commit 73842e6

36 files changed

Lines changed: 479 additions & 534 deletions

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
2323
"doctrine/dbal": "^4.0.0",
2424
"doctrine/migrations": "^3.3.2",
25-
"patchlevel/hydrator": "^1.8.0",
25+
"patchlevel/hydrator": "^2.0.x-dev",
2626
"patchlevel/worker": "^1.4.0",
2727
"psr/cache": "^2.0.0 || ^3.0.0",
2828
"psr/clock": "^1.0",

composer.lock

Lines changed: 241 additions & 211 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ powered by the reliable Doctrine ecosystem and focused on developer experience.
1111
* Automatic [snapshot](snapshots.md)-system to boost your performance
1212
* [Split](split-stream.md) big aggregates into multiple streams
1313
* Versioned and managed lifecycle of [subscriptions](subscription.md) like projections and processors
14-
* Safe usage of [Personal Data](personal-data.md) with crypto-shredding
14+
* Safe usage of [Personal Data](sensitive-data.md) with crypto-shredding
1515
* Smooth [upcasting](upcasting.md) of old events
1616
* Simple setup with [scheme management](store.md) and [doctrine migration](store.md)
1717
* Built in [cli commands](cli.md) with [symfony](https://symfony.com/)

docs/normalizer.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ final class CreateHotel
101101
```
102102

103103
:::note
104-
If you have personal data, you can use [crypto-shredding](personal-data.md).
104+
If you have personal data, you can use [crypto-shredding](sensitive_data.md).
105105
:::
106106

107107
### Aggregate
@@ -472,4 +472,4 @@ final class DTO
472472
* [How to define aggregates](aggregate.md)
473473
* [How to define events](events.md)
474474
* [How to snapshot aggregates](snapshots.md)
475-
* [How to work with personal data](personal-data.md)
475+
* [How to work with personal data](sensitive-data.md)
Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Personal Data (GDPR)
1+
# Sensitive Data
22

33
According to GDPR, personal data must be able to be deleted upon request.
44
But here we have the problem that our events are immutable and we cannot easily manipulate the event store.
@@ -42,48 +42,47 @@ final class EmailChanged
4242

4343
:::tip
4444
You can use the `DataSubjectId` in aggregates for snapshots too.
45-
:::
46-
47-
### PersonalData
45+
:::
46+
### SensitiveData
4847

49-
Next, you have to mark the properties that should be encrypted with the `#[PersonalData]` attribute.
48+
Next, you have to mark the properties that should be encrypted with the `#[SensitiveData]` attribute.
5049

5150
```php
5251
use Patchlevel\EventSourcing\Identifier\Uuid;
5352
use Patchlevel\Hydrator\Attribute\DataSubjectId;
54-
use Patchlevel\Hydrator\Attribute\PersonalData;
53+
use Patchlevel\Hydrator\Attribute\SensitiveData;
5554

5655
final class EmailChanged
5756
{
5857
public function __construct(
5958
#[DataSubjectId]
6059
public readonly Uuid $profileId,
61-
#[PersonalData]
60+
#[SensitiveData]
6261
public readonly string|null $email,
6362
) {
6463
}
6564
}
6665
```
6766

6867
:::tip
69-
You can use the `PersonalData` in aggregates for snapshots too.
68+
You can use the `SensitiveData` in aggregates for snapshots too.
7069
:::
7170

7271
If the information could not be decrypted, then a fallback value will be used.
7372
The default fallback value is `null`.
7473
You can change this by setting the `fallback` parameter or using the `fallbackCallable` parameter.
7574

7675
```php
77-
use Patchlevel\Hydrator\Attribute\PersonalData;
76+
use Patchlevel\Hydrator\Attribute\SensitiveData;
7877

7978
final class ProfileChanged
8079
{
8180
public function __construct(
8281
#[DataSubjectId]
8382
public readonly Uuid $profileId,
84-
#[PersonalData(fallback: 'unknown')]
83+
#[SensitiveData(fallback: 'unknown')]
8584
public readonly string $name,
86-
#[PersonalData(fallbackCallable: [self::class, 'createAnonymousEmail'])]
85+
#[SensitiveData(fallbackCallable: [self::class, 'createAnonymousEmail'])]
8786
public readonly string $email,
8887
) {
8988
}
@@ -147,10 +146,10 @@ Now we have to put the whole thing together in a Personal Data Payload Cryptogra
147146

148147
```php
149148
use Patchlevel\EventSourcing\Cryptography\Store\CipherKeyStore;
150-
use Patchlevel\Hydrator\Cryptography\PersonalDataPayloadCryptographer;
149+
use Patchlevel\Hydrator\Cryptography\SensitiveDataPayloadCryptographer;
151150

152151
/** @var CipherKeyStore $cipherKeyStore */
153-
$cryptographer = PersonalDataPayloadCryptographer::createWithDefaultSettings($cipherKeyStore);
152+
$cryptographer = SensitiveDataPayloadCryptographer::createWithDefaultSettings($cipherKeyStore);
154153
```
155154

156155
:::tip
@@ -163,9 +162,9 @@ The last step is to integrate the cryptographer into the event store.
163162

164163
```php
165164
use Patchlevel\EventSourcing\Serializer\DefaultEventSerializer;
166-
use Patchlevel\Hydrator\Cryptography\PersonalDataPayloadCryptographer;
165+
use Patchlevel\Hydrator\Cryptography\SensitiveDataPayloadCryptographer;
167166

168-
/** @var PersonalDataPayloadCryptographer $cryptographer */
167+
/** @var SensitiveDataPayloadCryptographer $cryptographer */
169168
DefaultEventSerializer::createFromPaths(
170169
[__DIR__ . '/Events'],
171170
cryptographer: $cryptographer,
@@ -182,9 +181,9 @@ And for the snapshot store.
182181

183182
```php
184183
use Patchlevel\EventSourcing\Snapshot\DefaultSnapshotStore;
185-
use Patchlevel\Hydrator\Cryptography\PersonalDataPayloadCryptographer;
184+
use Patchlevel\Hydrator\Cryptography\SensitiveDataPayloadCryptographer;
186185

187-
/** @var PersonalDataPayloadCryptographer $cryptographer */
186+
/** @var SensitiveDataPayloadCryptographer $cryptographer */
188187
$snapshotStore = DefaultSnapshotStore::createDefault(
189188
[
190189
/* adapters... */
@@ -212,7 +211,7 @@ use Patchlevel\EventSourcing\Message\Message;
212211
use Patchlevel\Hydrator\Cryptography\Store\CipherKeyStore;
213212

214213
#[Processor('delete_personal_data')]
215-
final class DeletePersonalDataProcessor
214+
final class DeleteSensitiveDataProcessor
216215
{
217216
public function __construct(
218217
private readonly CipherKeyStore $cipherKeyStore,

docs/snapshots.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,4 +265,4 @@ You still have to bring the aggregate up to date by loading the missing events f
265265
* [How to define aggregates](aggregate.md)
266266
* [How to store and load aggregates](repository.md)
267267
* [How to split streams](split-stream.md)
268-
* [How to work with personal data](personal-data.md)
268+
* [How to work with personal data](sensitive-data.md)

phpstan-baseline.neon

Lines changed: 3 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
parameters:
22
ignoreErrors:
33
-
4-
message: '#^Cannot unset offset ''url'' on array\{application_name\?\: string, charset\?\: string, dbname\?\: string, defaultTableOptions\?\: array\<string, mixed\>, driver\?\: ''ibm_db2''\|''mysqli''\|''oci8''\|''pdo_mysql''\|''pdo_oci''\|''pdo_pgsql''\|''pdo_sqlite''\|''pdo_sqlsrv''\|''pgsql''\|''sqlite3''\|''sqlsrv'', driverClass\?\: class\-string\<Doctrine\\DBAL\\Driver\>, driverOptions\?\: array\<mixed\>, host\?\: string, \.\.\.\}\.$#'
4+
message: '#^Cannot unset offset ''url'' on array\{application_name\?\: string, charset\?\: string, defaultTableOptions\?\: array\<string, mixed\>, driver\?\: ''ibm_db2''\|''mysqli''\|''oci8''\|''pdo_mysql''\|''pdo_oci''\|''pdo_pgsql''\|''pdo_sqlite''\|''pdo_sqlsrv''\|''pgsql''\|''sqlite3''\|''sqlsrv'', driverClass\?\: class\-string\<Doctrine\\DBAL\\Driver\>, driverOptions\?\: array\<mixed\>, host\?\: string, keepReplica\?\: bool, \.\.\.\}\.$#'
55
identifier: unset.offset
66
count: 1
77
path: src/Console/DoctrineHelper.php
88

99
-
10-
message: '#^Parameter \#1 \$key of class Patchlevel\\Hydrator\\Cryptography\\Cipher\\CipherKey constructor expects non\-empty\-string, string given\.$#'
10+
message: '#^Parameter \#1 \$key of class Patchlevel\\Hydrator\\Extension\\Cryptography\\Cipher\\CipherKey constructor expects non\-empty\-string, string given\.$#'
1111
identifier: argument.type
1212
count: 1
1313
path: src/Cryptography/DoctrineCipherKeyStore.php
1414

1515
-
16-
message: '#^Parameter \#3 \$iv of class Patchlevel\\Hydrator\\Cryptography\\Cipher\\CipherKey constructor expects non\-empty\-string, string given\.$#'
16+
message: '#^Parameter \#3 \$iv of class Patchlevel\\Hydrator\\Extension\\Cryptography\\Cipher\\CipherKey constructor expects non\-empty\-string, string given\.$#'
1717
identifier: argument.type
1818
count: 1
1919
path: src/Cryptography/DoctrineCipherKeyStore.php
@@ -174,12 +174,6 @@ parameters:
174174
count: 1
175175
path: tests/Benchmark/BasicImplementation/Projection/ProfileProjector.php
176176

177-
-
178-
message: '#^Parameter \#1 \$messageLoader of class Patchlevel\\EventSourcing\\Subscription\\Engine\\DefaultSubscriptionEngine constructor expects Patchlevel\\EventSourcing\\Subscription\\Engine\\MessageLoader, Patchlevel\\EventSourcing\\Store\\StreamDoctrineDbalStore given\.$#'
179-
identifier: argument.type
180-
count: 1
181-
path: tests/Benchmark/CommandToQueryBench.php
182-
183177
-
184178
message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertInstanceOf\(\) with ''Patchlevel\\\\EventSourcing\\\\Tests\\\\Integration\\\\BankAccountSplitStream\\\\BankAccount'' and Patchlevel\\EventSourcing\\Tests\\Integration\\BankAccountSplitStream\\BankAccount will always evaluate to true\.$#'
185179
identifier: staticMethod.alreadyNarrowedType
@@ -228,30 +222,6 @@ parameters:
228222
count: 1
229223
path: tests/Integration/BasicImplementation/BasicIntegrationTest.php
230224

231-
-
232-
message: '#^Instantiated class Patchlevel\\EventSourcing\\Tests\\Integration\\BasicImplementation\\DoctrineDbalStore not found\.$#'
233-
identifier: class.notFound
234-
count: 1
235-
path: tests/Integration/BasicImplementation/BasicIntegrationTest.php
236-
237-
-
238-
message: '#^Parameter \#1 \$messageLoader of class Patchlevel\\EventSourcing\\Subscription\\Engine\\DefaultSubscriptionEngine constructor expects Patchlevel\\EventSourcing\\Subscription\\Engine\\MessageLoader, Patchlevel\\EventSourcing\\Tests\\Integration\\BasicImplementation\\DoctrineDbalStore given\.$#'
239-
identifier: argument.type
240-
count: 1
241-
path: tests/Integration/BasicImplementation/BasicIntegrationTest.php
242-
243-
-
244-
message: '#^Parameter \#2 \$schemaConfigurator of class Patchlevel\\EventSourcing\\Schema\\DoctrineSchemaDirector constructor expects Patchlevel\\EventSourcing\\Schema\\DoctrineSchemaConfigurator, Patchlevel\\EventSourcing\\Tests\\Integration\\BasicImplementation\\DoctrineDbalStore given\.$#'
245-
identifier: argument.type
246-
count: 1
247-
path: tests/Integration/BasicImplementation/BasicIntegrationTest.php
248-
249-
-
250-
message: '#^Parameter \#2 \$store of class Patchlevel\\EventSourcing\\Repository\\DefaultRepositoryManager constructor expects Patchlevel\\EventSourcing\\Store\\Store, Patchlevel\\EventSourcing\\Tests\\Integration\\BasicImplementation\\DoctrineDbalStore given\.$#'
251-
identifier: argument.type
252-
count: 1
253-
path: tests/Integration/BasicImplementation/BasicIntegrationTest.php
254-
255225
-
256226
message: '#^Cannot access offset ''name'' on array\<string, mixed\>\|false\.$#'
257227
identifier: offsetAccess.nonOffsetAccessible
@@ -276,24 +246,6 @@ parameters:
276246
count: 1
277247
path: tests/Integration/MicroAggregate/Profile.php
278248

279-
-
280-
message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertArrayHasKey\(\) with 0 and array\{array\<string, mixed\>\} will always evaluate to true\.$#'
281-
identifier: staticMethod.alreadyNarrowedType
282-
count: 1
283-
path: tests/Integration/PersonalData/PersonalDataTest.php
284-
285-
-
286-
message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertInstanceOf\(\) with ''Patchlevel\\\\EventSourcing\\\\Tests\\\\Integration\\\\PersonalData\\\\Profile'' and Patchlevel\\EventSourcing\\Tests\\Integration\\PersonalData\\Profile will always evaluate to true\.$#'
287-
identifier: staticMethod.alreadyNarrowedType
288-
count: 6
289-
path: tests/Integration/PersonalData/PersonalDataTest.php
290-
291-
-
292-
message: '#^Parameter \#2 \$haystack of static method PHPUnit\\Framework\\Assert\:\:assertStringNotContainsString\(\) expects string, mixed given\.$#'
293-
identifier: argument.type
294-
count: 1
295-
path: tests/Integration/PersonalData/PersonalDataTest.php
296-
297249
-
298250
message: '#^Property Patchlevel\\EventSourcing\\Tests\\Integration\\Store\\Profile\:\:\$id is never read, only written\.$#'
299251
identifier: property.onlyWritten

src/Cryptography/DoctrineCipherKeyStore.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
use Doctrine\DBAL\Schema\Schema;
99
use Patchlevel\EventSourcing\Schema\DoctrineHelper;
1010
use Patchlevel\EventSourcing\Schema\DoctrineSchemaConfigurator;
11-
use Patchlevel\Hydrator\Cryptography\Cipher\CipherKey;
12-
use Patchlevel\Hydrator\Cryptography\Store\CipherKeyNotExists;
13-
use Patchlevel\Hydrator\Cryptography\Store\CipherKeyStore;
1411

12+
use Patchlevel\Hydrator\Extension\Cryptography\Cipher\CipherKey;
13+
use Patchlevel\Hydrator\Extension\Cryptography\Store\CipherKeyNotExists;
14+
use Patchlevel\Hydrator\Extension\Cryptography\Store\CipherKeyStore;
1515
use function array_key_exists;
1616
use function base64_decode;
1717
use function base64_encode;

src/Message/Serializer/DefaultHeadersSerializer.php

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ final class DefaultHeadersSerializer implements HeadersSerializer
1717
{
1818
public function __construct(
1919
private readonly MessageHeaderRegistry $messageHeaderRegistry,
20-
private readonly Hydrator $hydrator,
21-
private readonly Encoder $encoder,
20+
private readonly Hydrator $hydrator = new MetadataHydrator(),
21+
private readonly Encoder $encoder = new JsonEncoder(),
2222
) {
2323
}
2424

@@ -61,20 +61,22 @@ public function deserialize(string $string, array $options = []): array
6161
}
6262

6363
/** @param list<string> $paths */
64-
public static function createFromPaths(array $paths): static
65-
{
64+
public static function createFromPaths(
65+
array $paths,
66+
Hydrator $hydrator = new MetadataHydrator(),
67+
): static {
6668
return new self(
6769
(new AttributeMessageHeaderRegistryFactory())->create($paths),
68-
new MetadataHydrator(),
70+
$hydrator,
6971
new JsonEncoder(),
7072
);
7173
}
7274

73-
public static function createDefault(): static
75+
public static function createDefault(Hydrator $hydrator = new MetadataHydrator()): static
7476
{
7577
return new self(
7678
MessageHeaderRegistry::createWithInternalHeaders(),
77-
new MetadataHydrator(),
79+
$hydrator,
7880
new JsonEncoder(),
7981
);
8082
}

src/Serializer/DefaultEventSerializer.php

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,30 @@
88
use Patchlevel\EventSourcing\Metadata\Event\EventRegistry;
99
use Patchlevel\EventSourcing\Serializer\Encoder\Encoder;
1010
use Patchlevel\EventSourcing\Serializer\Encoder\JsonEncoder;
11-
use Patchlevel\EventSourcing\Serializer\Upcast\Upcast;
12-
use Patchlevel\EventSourcing\Serializer\Upcast\Upcaster;
13-
use Patchlevel\Hydrator\Cryptography\PayloadCryptographer;
1411
use Patchlevel\Hydrator\Hydrator;
1512
use Patchlevel\Hydrator\MetadataHydrator;
1613

1714
final class DefaultEventSerializer implements EventSerializer
1815
{
16+
public const CONTEXT_EVENT_NAME = 'event_name';
17+
public const CONTEXT_EVENT_CLASS = 'event_class';
18+
1919
public function __construct(
2020
private EventRegistry $eventRegistry,
2121
private Hydrator $hydrator = new MetadataHydrator(),
2222
private Encoder $encoder = new JsonEncoder(),
23-
private Upcaster|null $upcaster = null,
2423
) {
2524
}
2625

2726
/** @param array<string, mixed> $options */
2827
public function serialize(object $event, array $options = []): SerializedEvent
2928
{
3029
$name = $this->eventRegistry->eventName($event::class);
31-
$data = $this->hydrator->extract($event);
30+
31+
$data = $this->hydrator->extract($event, [
32+
self::CONTEXT_EVENT_NAME => $name,
33+
self::CONTEXT_EVENT_CLASS => $event::class,
34+
]);
3235

3336
return new SerializedEvent(
3437
$name,
@@ -40,30 +43,23 @@ public function serialize(object $event, array $options = []): SerializedEvent
4043
public function deserialize(SerializedEvent $data, array $options = []): object
4144
{
4245
$payload = $this->encoder->decode($data->payload, $options);
46+
$class = $this->eventRegistry->eventClass($data->name);
4347

44-
$eventName = $data->name;
45-
if ($this->upcaster) {
46-
$upcast = ($this->upcaster)(new Upcast($data->name, $payload));
47-
$eventName = $upcast->eventName;
48-
$payload = $upcast->payload;
49-
}
50-
51-
$class = $this->eventRegistry->eventClass($eventName);
52-
53-
return $this->hydrator->hydrate($class, $payload);
48+
return $this->hydrator->hydrate($class, $payload, [
49+
self::CONTEXT_EVENT_NAME => $data->name,
50+
self::CONTEXT_EVENT_CLASS => $class,
51+
]);
5452
}
5553

5654
/** @param list<string> $paths */
5755
public static function createFromPaths(
5856
array $paths,
59-
Upcaster|null $upcaster = null,
60-
PayloadCryptographer|null $cryptographer = null,
57+
Hydrator $hydrator = new MetadataHydrator(),
6158
): static {
6259
return new self(
6360
(new AttributeEventRegistryFactory())->create($paths),
64-
new MetadataHydrator(cryptographer: $cryptographer),
61+
$hydrator,
6562
new JsonEncoder(),
66-
$upcaster,
6763
);
6864
}
6965
}

0 commit comments

Comments
 (0)