Skip to content

Commit bf35dc3

Browse files
authored
Merge pull request #81 from patchlevel/1.8.x-merge-up-into-2.0.x_2VzHvpSA
Merge release 1.8.0 into 2.0.x
2 parents 517c94b + 395d939 commit bf35dc3

23 files changed

+991
-232
lines changed

README.md

Lines changed: 85 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -406,22 +406,83 @@ readonly class Dto
406406
}
407407
```
408408

409+
### Events
410+
411+
Another way to intervene in the extract and hydrate process is through events.
412+
There are two events: `PostExtract` and `PreHydrate`.
413+
For this functionality we use the [symfony/event-dispatcher](https://symfony.com/doc/current/components/event_dispatcher.html).
414+
415+
```php
416+
use Patchlevel\Hydrator\Cryptography\PersonalDataPayloadCryptographer;
417+
use Patchlevel\Hydrator\Cryptography\Store\CipherKeyStore;
418+
use Patchlevel\Hydrator\Metadata\Event\EventMetadataFactory;
419+
use Patchlevel\Hydrator\MetadataHydrator;
420+
use Symfony\Component\EventDispatcher\EventDispatcher;
421+
use Patchlevel\Hydrator\Event\PostExtract;
422+
use Patchlevel\Hydrator\Event\PreHydrate;
423+
424+
$eventDispatcher = new EventDispatcher();
425+
426+
$eventDispatcher->addListener(
427+
PostExtract::class,
428+
static function (PostExtract $event): void {
429+
// do something
430+
}
431+
);
432+
433+
$eventDispatcher->addListener(
434+
PreHydrate::class,
435+
static function (PreHydrate $event): void {
436+
// do something
437+
}
438+
);
439+
440+
$hydrator = new MetadataHydrator(eventDispatcher: $eventDispatcher);
441+
```
442+
409443
### Cryptography
410444

411445
The library also offers the possibility to encrypt and decrypt personal data.
446+
For this purpose, a key is created for each subject ID, which is used to encrypt the personal data.
447+
448+
#### DataSubjectId
449+
450+
First we need to define what the subject id is.
451+
452+
```php
453+
use Patchlevel\Hydrator\Attribute\DataSubjectId;
454+
455+
final class EmailChanged
456+
{
457+
public function __construct(
458+
#[DataSubjectId]
459+
public readonly string $profileId,
460+
) {
461+
}
462+
}
463+
```
464+
465+
> [!WARNING]
466+
> The `DataSubjectId` must be a string. You can use a normalizer to convert it to a string.
467+
> The Subject ID cannot be personal data.
412468
413469
#### PersonalData
414470

415-
First of all, we have to mark the fields that contain personal data.
416-
For our example, we use events, but you can do the same with aggregates.
471+
Next, we need to specify which fields we want to encrypt.
417472

418473
```php
474+
use Patchlevel\Hydrator\Attribute\DataSubjectId;
419475
use Patchlevel\Hydrator\Attribute\PersonalData;
420476

421477
final class DTO
422478
{
423-
#[PersonalData]
424-
public readonly string|null $email;
479+
public function __construct(
480+
#[DataSubjectId]
481+
public readonly string $profileId,
482+
#[PersonalData]
483+
public readonly string|null $email,
484+
) {
485+
}
425486
}
426487
```
427488

@@ -436,43 +497,40 @@ use Patchlevel\Hydrator\Attribute\PersonalData;
436497
final class DTO
437498
{
438499
public function __construct(
500+
#[DataSubjectId]
501+
public readonly string $profileId,
439502
#[PersonalData(fallback: 'unknown')]
440-
public readonly string $email,
503+
public readonly string $name,
441504
) {
442505
}
443506
}
444507
```
445508

446-
> [!DANGER]
447-
> You have to deal with this case in your business logic such as aggregates and subscriptions.
448-
449-
> [!WARNING]
450-
> You need to define a subject ID to use the personal data attribute.
451-
452-
#### DataSubjectId
453-
454-
In order for the correct key to be used, a subject ID must be defined.
455-
Without Subject Id, no personal data can be encrypted or decrypted.
509+
You can also use a callable as a fallback.
456510

457511
```php
458512
use Patchlevel\Hydrator\Attribute\DataSubjectId;
459513
use Patchlevel\Hydrator\Attribute\PersonalData;
460514

461-
final class EmailChanged
515+
final class ProfileCreated
462516
{
463517
public function __construct(
464518
#[DataSubjectId]
465-
public readonly string $personId,
466-
#[PersonalData(fallback: 'unknown')]
467-
public readonly string|null $email,
519+
public readonly string $profileId,
520+
#[PersonalData(fallback: 'deleted profile')]
521+
public readonly string $name,
522+
#[PersonalData(fallbackCallable: [self::class, 'anonymizedEmail'])]
523+
public readonly string $email,
468524
) {
469525
}
526+
527+
public static function anonymizedEmail(string $subjectId): string
528+
{
529+
return sprintf('%s@anno.com', $subjectId);
530+
}
470531
}
471532
```
472533

473-
> [!WARNING]
474-
> A subject ID can not be a personal data.
475-
476534
#### Configure Cryptography
477535

478536
Here we show you how to configure the cryptography.
@@ -484,10 +542,14 @@ use Patchlevel\Hydrator\Metadata\Event\EventMetadataFactory;
484542
use Patchlevel\Hydrator\MetadataHydrator;
485543

486544
$cipherKeyStore = new InMemoryCipherKeyStore();
487-
$cryptographer = PersonalDataPayloadCryptographer::createWithOpenssl($cipherKeyStore);
545+
$cryptographer = PersonalDataPayloadCryptographer::createWithDefaultSettings($cipherKeyStore);
488546
$hydrator = new MetadataHydrator(cryptographer: $cryptographer);
489547
```
490548

549+
> [!WARNING]
550+
> We recommend to use the `useEncryptedFieldName` option to recognize encrypted fields.
551+
> This allows data to be encrypted later without big troubles.
552+
491553
#### Cipher Key Store
492554

493555
The keys must be stored somewhere. For testing purposes, we offer an in-memory implementation.

baseline.xml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<files psalm-version="5.26.1@d747f6500b38ac4f7dfc5edbcae6e4b637d7add0">
2+
<files psalm-version="6.9.1@81c8a77c0793d450fee40265cfe68891df11d505">
33
<file src="src/Cryptography/Cipher/OpensslCipherKeyFactory.php">
44
<ArgumentTypeCoercion>
55
<code><![CDATA[openssl_random_pseudo_bytes($this->ivLength)]]></code>
@@ -14,12 +14,14 @@
1414
</file>
1515
<file src="src/Cryptography/PersonalDataPayloadCryptographer.php">
1616
<MixedArgument>
17-
<code><![CDATA[$data[$propertyMetadata->fieldName()]]]></code>
17+
<code><![CDATA[$rawData]]></code>
1818
</MixedArgument>
1919
<MixedAssignment>
2020
<code><![CDATA[$data[$propertyMetadata->fieldName()]]]></code>
2121
<code><![CDATA[$data[$propertyMetadata->fieldName()]]]></code>
2222
<code><![CDATA[$data[$propertyMetadata->fieldName()]]]></code>
23+
<code><![CDATA[$rawData]]></code>
24+
<code><![CDATA[$rawData]]></code>
2325
</MixedAssignment>
2426
</file>
2527
<file src="src/Metadata/AttributeMetadataFactory.php">

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"require": {
2222
"php": "~8.2.0 || ~8.3.0 || ~8.4.0",
2323
"ext-openssl": "*",
24+
"symfony/event-dispatcher": "^5.4.29|^6.4.0|^7.0.0",
2425
"symfony/type-info": "^7.2.4"
2526
},
2627
"require-dev": {

0 commit comments

Comments
 (0)