diff --git a/composer.json b/composer.json index 87514ff3b..1d76859b2 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", "doctrine/dbal": "^4.0.0", "doctrine/migrations": "^3.3.2", - "patchlevel/hydrator": "^1.8.0", + "patchlevel/hydrator": "^2.0.x-dev", "patchlevel/worker": "^1.4.0", "psr/cache": "^2.0.0 || ^3.0.0", "psr/clock": "^1.0", diff --git a/composer.lock b/composer.lock index 46013a0c8..aef96caaa 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0c3c161d87cdce3d1923690220fd6394", + "content-hash": "9bf1b1cf234fc1b36cbb452e1bed8edb", "packages": [ { "name": "brick/math", @@ -416,16 +416,16 @@ }, { "name": "patchlevel/hydrator", - "version": "1.14.0", + "version": "2.0.x-dev", "source": { "type": "git", "url": "https://github.com/patchlevel/hydrator.git", - "reference": "cec72c5372e7014605be5a853f0f404715dd763a" + "reference": "163b37b7581cc77d0735508fb8be9b1dc8b3513d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/patchlevel/hydrator/zipball/cec72c5372e7014605be5a853f0f404715dd763a", - "reference": "cec72c5372e7014605be5a853f0f404715dd763a", + "url": "https://api.github.com/repos/patchlevel/hydrator/zipball/163b37b7581cc77d0735508fb8be9b1dc8b3513d", + "reference": "163b37b7581cc77d0735508fb8be9b1dc8b3513d", "shasum": "" }, "require": { @@ -433,17 +433,16 @@ "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", "psr/cache": "^2.0.0 || ^3.0.0", "psr/simple-cache": "^2.0.0 || ^3.0.0", - "symfony/event-dispatcher": "^5.4.29 || ^6.4.0 || ^7.0.0 || ^8.0.0", "symfony/type-info": "^7.3.0 || ^8.0.0" }, "require-dev": { - "infection/infection": "^0.32.0", + "infection/infection": "^0.32.4", "patchlevel/coding-standard": "^1.3.0", - "phpat/phpat": "^0.12.0", - "phpbench/phpbench": "^1.2.15", - "phpstan/phpstan": "^2.1.32", - "phpstan/phpstan-phpunit": "^2.0.8", - "phpunit/phpunit": "^11.5.17", + "phpat/phpat": "^0.12.2", + "phpbench/phpbench": "^1.4.3", + "phpstan/phpstan": "^2.1.39", + "phpstan/phpstan-phpunit": "^2.0.15", + "phpunit/phpunit": "^11.5.53", "symfony/var-dumper": "^5.4.29 || ^6.4.0 || ^7.0.0 || ^8.0.0" }, "type": "library", @@ -474,9 +473,9 @@ ], "support": { "issues": "https://github.com/patchlevel/hydrator/issues", - "source": "https://github.com/patchlevel/hydrator/tree/1.14.0" + "source": "https://github.com/patchlevel/hydrator/tree/2.0.x" }, - "time": "2026-02-11T10:32:51+00:00" + "time": "2026-02-24T14:37:40+00:00" }, { "name": "patchlevel/worker", @@ -3345,16 +3344,16 @@ }, { "name": "infection/infection", - "version": "0.32.4", + "version": "0.32.5", "source": { "type": "git", "url": "https://github.com/infection/infection.git", - "reference": "a2b0a3e47b56bd2f27ca13caecae47baa7e5abe8" + "reference": "932fc7aa7a03bdbe387e42f8c8bd17d9d347653e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/infection/infection/zipball/a2b0a3e47b56bd2f27ca13caecae47baa7e5abe8", - "reference": "a2b0a3e47b56bd2f27ca13caecae47baa7e5abe8", + "url": "https://api.github.com/repos/infection/infection/zipball/932fc7aa7a03bdbe387e42f8c8bd17d9d347653e", + "reference": "932fc7aa7a03bdbe387e42f8c8bd17d9d347653e", "shasum": "" }, "require": { @@ -3465,7 +3464,7 @@ ], "support": { "issues": "https://github.com/infection/infection/issues", - "source": "https://github.com/infection/infection/tree/0.32.4" + "source": "https://github.com/infection/infection/tree/0.32.5" }, "funding": [ { @@ -3477,7 +3476,7 @@ "type": "open_collective" } ], - "time": "2026-02-09T13:24:18+00:00" + "time": "2026-02-20T07:59:31+00:00" }, { "name": "infection/mutator", @@ -3534,16 +3533,16 @@ }, { "name": "justinrainbow/json-schema", - "version": "v6.7.1", + "version": "v6.7.2", "source": { "type": "git", "url": "https://github.com/jsonrainbow/json-schema.git", - "reference": "cd3137ab4ad45033230f530ab7d5618d583c17be" + "reference": "6fea66c7204683af437864e7c4e7abf383d14bc0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/cd3137ab4ad45033230f530ab7d5618d583c17be", - "reference": "cd3137ab4ad45033230f530ab7d5618d583c17be", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/6fea66c7204683af437864e7c4e7abf383d14bc0", + "reference": "6fea66c7204683af437864e7c4e7abf383d14bc0", "shasum": "" }, "require": { @@ -3603,9 +3602,9 @@ ], "support": { "issues": "https://github.com/jsonrainbow/json-schema/issues", - "source": "https://github.com/jsonrainbow/json-schema/tree/v6.7.1" + "source": "https://github.com/jsonrainbow/json-schema/tree/v6.7.2" }, - "time": "2026-02-13T16:16:54+00:00" + "time": "2026-02-15T15:06:22+00:00" }, { "name": "league/commonmark", @@ -3931,16 +3930,16 @@ }, { "name": "nette/schema", - "version": "v1.3.4", + "version": "v1.3.5", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "086497a2f34b82fede9b5a41cc8e131d087cd8f7" + "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/086497a2f34b82fede9b5a41cc8e131d087cd8f7", - "reference": "086497a2f34b82fede9b5a41cc8e131d087cd8f7", + "url": "https://api.github.com/repos/nette/schema/zipball/f0ab1a3cda782dbc5da270d28545236aa80c4002", + "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002", "shasum": "" }, "require": { @@ -3948,8 +3947,10 @@ "php": "8.1 - 8.5" }, "require-dev": { + "nette/phpstan-rules": "^1.0", "nette/tester": "^2.6", - "phpstan/phpstan": "^2.0@stable", + "phpstan/extension-installer": "^1.4@stable", + "phpstan/phpstan": "^2.1.39@stable", "tracy/tracy": "^2.8" }, "type": "library", @@ -3990,9 +3991,9 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.3.4" + "source": "https://github.com/nette/schema/tree/v1.3.5" }, - "time": "2026-02-08T02:54:00+00:00" + "time": "2026-02-23T03:47:12+00:00" }, { "name": "nette/utils", @@ -4453,16 +4454,16 @@ }, { "name": "phpat/phpat", - "version": "0.12.2", + "version": "0.12.3", "source": { "type": "git", "url": "https://github.com/carlosas/phpat.git", - "reference": "fe9caef4f8633a57c1d19643d37b58050b11806c" + "reference": "2412a8959254a076e751498cbba8cf29406e0cf4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/carlosas/phpat/zipball/fe9caef4f8633a57c1d19643d37b58050b11806c", - "reference": "fe9caef4f8633a57c1d19643d37b58050b11806c", + "url": "https://api.github.com/repos/carlosas/phpat/zipball/2412a8959254a076e751498cbba8cf29406e0cf4", + "reference": "2412a8959254a076e751498cbba8cf29406e0cf4", "shasum": "" }, "require": { @@ -4504,9 +4505,9 @@ "description": "PHP Architecture Tester", "support": { "issues": "https://github.com/carlosas/phpat/issues", - "source": "https://github.com/carlosas/phpat/tree/0.12.2" + "source": "https://github.com/carlosas/phpat/tree/0.12.3" }, - "time": "2026-01-27T11:41:37+00:00" + "time": "2026-02-20T11:15:22+00:00" }, { "name": "phpbench/container", @@ -4706,11 +4707,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.39", + "version": "2.1.40", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c6f73a2af4cbcd99c931d0fb8f08548cc0fa8224", - "reference": "c6f73a2af4cbcd99c931d0fb8f08548cc0fa8224", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9b2c7aeb83a75d8680ea5e7c9b7fca88052b766b", + "reference": "9b2c7aeb83a75d8680ea5e7c9b7fca88052b766b", "shasum": "" }, "require": { @@ -4755,7 +4756,7 @@ "type": "github" } ], - "time": "2026-02-11T14:48:56+00:00" + "time": "2026-02-23T15:04:35+00:00" }, { "name": "phpstan/phpstan-phpunit", @@ -5162,16 +5163,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.53", + "version": "11.5.55", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "a997a653a82845f1240d73ee73a8a4e97e4b0607" + "reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a997a653a82845f1240d73ee73a8a4e97e4b0607", - "reference": "a997a653a82845f1240d73ee73a8a4e97e4b0607", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/adc7262fccc12de2b30f12a8aa0b33775d814f00", + "reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00", "shasum": "" }, "require": { @@ -5244,7 +5245,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.53" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.55" }, "funding": [ { @@ -5268,7 +5269,7 @@ "type": "tidelift" } ], - "time": "2026-02-10T12:28:25+00:00" + "time": "2026-02-18T12:37:06+00:00" }, { "name": "sanmai/di-container", @@ -7496,16 +7497,16 @@ }, { "name": "thecodingmachine/safe", - "version": "v3.3.0", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/thecodingmachine/safe.git", - "reference": "2cdd579eeaa2e78e51c7509b50cc9fb89a956236" + "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/2cdd579eeaa2e78e51c7509b50cc9fb89a956236", - "reference": "2cdd579eeaa2e78e51c7509b50cc9fb89a956236", + "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/705683a25bacf0d4860c7dea4d7947bfd09eea19", + "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19", "shasum": "" }, "require": { @@ -7615,7 +7616,7 @@ "description": "PHP core functions that throw exceptions instead of returning FALSE on error", "support": { "issues": "https://github.com/thecodingmachine/safe/issues", - "source": "https://github.com/thecodingmachine/safe/tree/v3.3.0" + "source": "https://github.com/thecodingmachine/safe/tree/v3.4.0" }, "funding": [ { @@ -7626,12 +7627,16 @@ "url": "https://github.com/shish", "type": "github" }, + { + "url": "https://github.com/silasjoisten", + "type": "github" + }, { "url": "https://github.com/staabm", "type": "github" } ], - "time": "2025-05-14T06:15:44+00:00" + "time": "2026-02-04T18:08:13+00:00" }, { "name": "theseer/tokenizer", @@ -7685,16 +7690,16 @@ }, { "name": "webmozart/assert", - "version": "2.1.3", + "version": "2.1.5", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "6976757ba8dd70bf8cbaea0914ad84d8b51a9f46" + "reference": "79155f94852fa27e2f73b459f6503f5e87e2c188" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/6976757ba8dd70bf8cbaea0914ad84d8b51a9f46", - "reference": "6976757ba8dd70bf8cbaea0914ad84d8b51a9f46", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/79155f94852fa27e2f73b459f6503f5e87e2c188", + "reference": "79155f94852fa27e2f73b459f6503f5e87e2c188", "shasum": "" }, "require": { @@ -7741,9 +7746,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/2.1.3" + "source": "https://github.com/webmozarts/assert/tree/2.1.5" }, - "time": "2026-02-13T21:01:40+00:00" + "time": "2026-02-18T14:09:36+00:00" }, { "name": "webmozart/glob", @@ -7859,7 +7864,8 @@ "aliases": [], "minimum-stability": "stable", "stability-flags": { - "patchlevel/event-sourcing-phpstan-extension": 20 + "patchlevel/event-sourcing-phpstan-extension": 20, + "patchlevel/hydrator": 20 }, "prefer-stable": false, "prefer-lowest": false, diff --git a/docs/index.md b/docs/index.md index 11a4c9918..8ee436999 100644 --- a/docs/index.md +++ b/docs/index.md @@ -11,7 +11,7 @@ powered by the reliable Doctrine ecosystem and focused on developer experience. * Automatic [snapshot](snapshots.md)-system to boost your performance * [Split](split-stream.md) big aggregates into multiple streams * Versioned and managed lifecycle of [subscriptions](subscription.md) like projections and processors -* Safe usage of [Personal Data](personal-data.md) with crypto-shredding +* Safe usage of [Personal Data](sensitive-data.md) with crypto-shredding * Smooth [upcasting](upcasting.md) of old events * Simple setup with [scheme management](store.md) and [doctrine migration](store.md) * Built in [cli commands](cli.md) with [symfony](https://symfony.com/) diff --git a/docs/normalizer.md b/docs/normalizer.md index 4701a3704..65c9ab656 100644 --- a/docs/normalizer.md +++ b/docs/normalizer.md @@ -101,7 +101,7 @@ final class CreateHotel ``` :::note -If you have personal data, you can use [crypto-shredding](personal-data.md). + If you have personal data, you can use [crypto-shredding](sensitive_data.md). ::: ### Aggregate @@ -472,4 +472,4 @@ final class DTO * [How to define aggregates](aggregate.md) * [How to define events](events.md) * [How to snapshot aggregates](snapshots.md) -* [How to work with personal data](personal-data.md) +* [How to work with personal data](sensitive-data.md) diff --git a/docs/personal-data.md b/docs/sensitive-data.md similarity index 86% rename from docs/personal-data.md rename to docs/sensitive-data.md index cb95cc3a2..60fccb6be 100644 --- a/docs/personal-data.md +++ b/docs/sensitive-data.md @@ -1,4 +1,4 @@ -# Personal Data (GDPR) +# Sensitive Data According to GDPR, personal data must be able to be deleted upon request. But here we have the problem that our events are immutable and we cannot easily manipulate the event store. @@ -42,23 +42,22 @@ final class EmailChanged :::tip You can use the `DataSubjectId` in aggregates for snapshots too. -::: - -### PersonalData +::: +### SensitiveData -Next, you have to mark the properties that should be encrypted with the `#[PersonalData]` attribute. +Next, you have to mark the properties that should be encrypted with the `#[SensitiveData]` attribute. ```php use Patchlevel\EventSourcing\Identifier\Uuid; use Patchlevel\Hydrator\Attribute\DataSubjectId; -use Patchlevel\Hydrator\Attribute\PersonalData; +use Patchlevel\Hydrator\Attribute\SensitiveData; final class EmailChanged { public function __construct( #[DataSubjectId] public readonly Uuid $profileId, - #[PersonalData] + #[SensitiveData] public readonly string|null $email, ) { } @@ -66,7 +65,7 @@ final class EmailChanged ``` :::tip -You can use the `PersonalData` in aggregates for snapshots too. +You can use the `SensitiveData` in aggregates for snapshots too. ::: If the information could not be decrypted, then a fallback value will be used. @@ -74,16 +73,16 @@ The default fallback value is `null`. You can change this by setting the `fallback` parameter or using the `fallbackCallable` parameter. ```php -use Patchlevel\Hydrator\Attribute\PersonalData; +use Patchlevel\Hydrator\Attribute\SensitiveData; final class ProfileChanged { public function __construct( #[DataSubjectId] public readonly Uuid $profileId, - #[PersonalData(fallback: 'unknown')] + #[SensitiveData(fallback: 'unknown')] public readonly string $name, - #[PersonalData(fallbackCallable: [self::class, 'createAnonymousEmail'])] + #[SensitiveData(fallbackCallable: [self::class, 'createAnonymousEmail'])] public readonly string $email, ) { } @@ -147,10 +146,10 @@ Now we have to put the whole thing together in a Personal Data Payload Cryptogra ```php use Patchlevel\EventSourcing\Cryptography\Store\CipherKeyStore; -use Patchlevel\Hydrator\Cryptography\PersonalDataPayloadCryptographer; +use Patchlevel\Hydrator\Cryptography\SensitiveDataPayloadCryptographer; /** @var CipherKeyStore $cipherKeyStore */ -$cryptographer = PersonalDataPayloadCryptographer::createWithDefaultSettings($cipherKeyStore); +$cryptographer = SensitiveDataPayloadCryptographer::createWithDefaultSettings($cipherKeyStore); ``` :::tip @@ -163,9 +162,9 @@ The last step is to integrate the cryptographer into the event store. ```php use Patchlevel\EventSourcing\Serializer\DefaultEventSerializer; -use Patchlevel\Hydrator\Cryptography\PersonalDataPayloadCryptographer; +use Patchlevel\Hydrator\Cryptography\SensitiveDataPayloadCryptographer; -/** @var PersonalDataPayloadCryptographer $cryptographer */ +/** @var SensitiveDataPayloadCryptographer $cryptographer */ DefaultEventSerializer::createFromPaths( [__DIR__ . '/Events'], cryptographer: $cryptographer, @@ -182,9 +181,9 @@ And for the snapshot store. ```php use Patchlevel\EventSourcing\Snapshot\DefaultSnapshotStore; -use Patchlevel\Hydrator\Cryptography\PersonalDataPayloadCryptographer; +use Patchlevel\Hydrator\Cryptography\SensitiveDataPayloadCryptographer; -/** @var PersonalDataPayloadCryptographer $cryptographer */ +/** @var SensitiveDataPayloadCryptographer $cryptographer */ $snapshotStore = DefaultSnapshotStore::createDefault( [ /* adapters... */ @@ -212,7 +211,7 @@ use Patchlevel\EventSourcing\Message\Message; use Patchlevel\Hydrator\Cryptography\Store\CipherKeyStore; #[Processor('delete_personal_data')] -final class DeletePersonalDataProcessor +final class DeleteSensitiveDataProcessor { public function __construct( private readonly CipherKeyStore $cipherKeyStore, diff --git a/docs/snapshots.md b/docs/snapshots.md index a8caa83b6..96447e3fe 100644 --- a/docs/snapshots.md +++ b/docs/snapshots.md @@ -265,4 +265,4 @@ You still have to bring the aggregate up to date by loading the missing events f * [How to define aggregates](aggregate.md) * [How to store and load aggregates](repository.md) * [How to split streams](split-stream.md) -* [How to work with personal data](personal-data.md) +* [How to work with personal data](sensitive-data.md) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 842e4438c..9f375c640 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -7,13 +7,13 @@ parameters: path: src/Console/DoctrineHelper.php - - message: '#^Parameter \#1 \$key of class Patchlevel\\Hydrator\\Cryptography\\Cipher\\CipherKey constructor expects non\-empty\-string, string given\.$#' + message: '#^Parameter \#1 \$key of class Patchlevel\\Hydrator\\Extension\\Cryptography\\Cipher\\CipherKey constructor expects non\-empty\-string, string given\.$#' identifier: argument.type count: 1 path: src/Cryptography/DoctrineCipherKeyStore.php - - message: '#^Parameter \#3 \$iv of class Patchlevel\\Hydrator\\Cryptography\\Cipher\\CipherKey constructor expects non\-empty\-string, string given\.$#' + message: '#^Parameter \#3 \$iv of class Patchlevel\\Hydrator\\Extension\\Cryptography\\Cipher\\CipherKey constructor expects non\-empty\-string, string given\.$#' identifier: argument.type count: 1 path: src/Cryptography/DoctrineCipherKeyStore.php @@ -252,24 +252,6 @@ parameters: count: 1 path: tests/Integration/MicroAggregate/Profile.php - - - message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertArrayHasKey\(\) with 0 and array\{array\\} will always evaluate to true\.$#' - identifier: staticMethod.alreadyNarrowedType - count: 1 - path: tests/Integration/PersonalData/PersonalDataTest.php - - - - 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\.$#' - identifier: staticMethod.alreadyNarrowedType - count: 6 - path: tests/Integration/PersonalData/PersonalDataTest.php - - - - message: '#^Parameter \#2 \$haystack of static method PHPUnit\\Framework\\Assert\:\:assertStringNotContainsString\(\) expects string, mixed given\.$#' - identifier: argument.type - count: 1 - path: tests/Integration/PersonalData/PersonalDataTest.php - - message: '#^Property Patchlevel\\EventSourcing\\Tests\\Integration\\Store\\Profile\:\:\$id is never read, only written\.$#' identifier: property.onlyWritten diff --git a/src/Cryptography/DoctrineCipherKeyStore.php b/src/Cryptography/DoctrineCipherKeyStore.php index f70680046..49cc80e62 100644 --- a/src/Cryptography/DoctrineCipherKeyStore.php +++ b/src/Cryptography/DoctrineCipherKeyStore.php @@ -8,9 +8,9 @@ use Doctrine\DBAL\Schema\Schema; use Patchlevel\EventSourcing\Schema\DoctrineHelper; use Patchlevel\EventSourcing\Schema\DoctrineSchemaConfigurator; -use Patchlevel\Hydrator\Cryptography\Cipher\CipherKey; -use Patchlevel\Hydrator\Cryptography\Store\CipherKeyNotExists; -use Patchlevel\Hydrator\Cryptography\Store\CipherKeyStore; +use Patchlevel\Hydrator\Extension\Cryptography\Cipher\CipherKey; +use Patchlevel\Hydrator\Extension\Cryptography\Store\CipherKeyNotExists; +use Patchlevel\Hydrator\Extension\Cryptography\Store\CipherKeyStore; use function array_key_exists; use function base64_decode; diff --git a/src/Message/Serializer/DefaultHeadersSerializer.php b/src/Message/Serializer/DefaultHeadersSerializer.php index 02addd9d5..ca4035ff8 100644 --- a/src/Message/Serializer/DefaultHeadersSerializer.php +++ b/src/Message/Serializer/DefaultHeadersSerializer.php @@ -17,8 +17,8 @@ final class DefaultHeadersSerializer implements HeadersSerializer { public function __construct( private readonly MessageHeaderRegistry $messageHeaderRegistry, - private readonly Hydrator $hydrator, - private readonly Encoder $encoder, + private readonly Hydrator $hydrator = new MetadataHydrator(), + private readonly Encoder $encoder = new JsonEncoder(), ) { } @@ -61,20 +61,22 @@ public function deserialize(string $string, array $options = []): array } /** @param list $paths */ - public static function createFromPaths(array $paths): static - { + public static function createFromPaths( + array $paths, + Hydrator $hydrator = new MetadataHydrator(), + ): static { return new self( (new AttributeMessageHeaderRegistryFactory())->create($paths), - new MetadataHydrator(), + $hydrator, new JsonEncoder(), ); } - public static function createDefault(): static + public static function createDefault(Hydrator $hydrator = new MetadataHydrator()): static { return new self( MessageHeaderRegistry::createWithInternalHeaders(), - new MetadataHydrator(), + $hydrator, new JsonEncoder(), ); } diff --git a/src/Serializer/DefaultEventSerializer.php b/src/Serializer/DefaultEventSerializer.php index 260098936..a50178e20 100644 --- a/src/Serializer/DefaultEventSerializer.php +++ b/src/Serializer/DefaultEventSerializer.php @@ -8,19 +8,18 @@ use Patchlevel\EventSourcing\Metadata\Event\EventRegistry; use Patchlevel\EventSourcing\Serializer\Encoder\Encoder; use Patchlevel\EventSourcing\Serializer\Encoder\JsonEncoder; -use Patchlevel\EventSourcing\Serializer\Upcast\Upcast; -use Patchlevel\EventSourcing\Serializer\Upcast\Upcaster; -use Patchlevel\Hydrator\Cryptography\PayloadCryptographer; use Patchlevel\Hydrator\Hydrator; use Patchlevel\Hydrator\MetadataHydrator; final class DefaultEventSerializer implements EventSerializer { + public const CONTEXT_EVENT_NAME = 'event_name'; + public const CONTEXT_EVENT_CLASS = 'event_class'; + public function __construct( private EventRegistry $eventRegistry, private Hydrator $hydrator = new MetadataHydrator(), private Encoder $encoder = new JsonEncoder(), - private Upcaster|null $upcaster = null, ) { } @@ -28,7 +27,11 @@ public function __construct( public function serialize(object $event, array $options = []): SerializedEvent { $name = $this->eventRegistry->eventName($event::class); - $data = $this->hydrator->extract($event); + + $data = $this->hydrator->extract($event, [ + self::CONTEXT_EVENT_NAME => $name, + self::CONTEXT_EVENT_CLASS => $event::class, + ]); return new SerializedEvent( $name, @@ -40,30 +43,23 @@ public function serialize(object $event, array $options = []): SerializedEvent public function deserialize(SerializedEvent $data, array $options = []): object { $payload = $this->encoder->decode($data->payload, $options); + $class = $this->eventRegistry->eventClass($data->name); - $eventName = $data->name; - if ($this->upcaster) { - $upcast = ($this->upcaster)(new Upcast($data->name, $payload)); - $eventName = $upcast->eventName; - $payload = $upcast->payload; - } - - $class = $this->eventRegistry->eventClass($eventName); - - return $this->hydrator->hydrate($class, $payload); + return $this->hydrator->hydrate($class, $payload, [ + self::CONTEXT_EVENT_NAME => $data->name, + self::CONTEXT_EVENT_CLASS => $class, + ]); } /** @param list $paths */ public static function createFromPaths( array $paths, - Upcaster|null $upcaster = null, - PayloadCryptographer|null $cryptographer = null, + Hydrator $hydrator = new MetadataHydrator(), ): static { return new self( (new AttributeEventRegistryFactory())->create($paths), - new MetadataHydrator(cryptographer: $cryptographer), + $hydrator, new JsonEncoder(), - $upcaster, ); } } diff --git a/src/Serializer/Normalizer/IdNormalizer.php b/src/Serializer/Normalizer/IdNormalizer.php index d65d903b2..422354097 100644 --- a/src/Serializer/Normalizer/IdNormalizer.php +++ b/src/Serializer/Normalizer/IdNormalizer.php @@ -25,7 +25,7 @@ public function __construct( ) { } - public function normalize(mixed $value): string|null + public function normalize(mixed $value, array $context): string|null { if ($value === null) { return null; @@ -40,7 +40,7 @@ public function normalize(mixed $value): string|null return $value->toString(); } - public function denormalize(mixed $value): Identifier|null + public function denormalize(mixed $value, array $context): Identifier|null { if ($value === null) { return null; diff --git a/src/Serializer/Upcast/UpcastMiddleware.php b/src/Serializer/Upcast/UpcastMiddleware.php new file mode 100644 index 000000000..9a9e99653 --- /dev/null +++ b/src/Serializer/Upcast/UpcastMiddleware.php @@ -0,0 +1,42 @@ +className !== $eventClass) { + return $stack->next()->hydrate($metadata, $data, $context, $stack); + } + + assert(is_string($eventName)); + + $upcast = ($this->upcaster)(new Upcast($eventName, $data)); + + return $stack->next()->hydrate($metadata, $upcast->payload, $context, $stack); + } + + public function extract(ClassMetadata $metadata, object $object, array $context, Stack $stack): array + { + return $stack->next()->extract($metadata, $object, $context, $stack); + } +} diff --git a/src/Snapshot/DefaultSnapshotStore.php b/src/Snapshot/DefaultSnapshotStore.php index 5bab975f6..39b30672f 100644 --- a/src/Snapshot/DefaultSnapshotStore.php +++ b/src/Snapshot/DefaultSnapshotStore.php @@ -9,37 +9,20 @@ use Patchlevel\EventSourcing\Metadata\AggregateRoot\AggregateRootMetadataAwareMetadataFactory; use Patchlevel\EventSourcing\Metadata\AggregateRoot\AggregateRootMetadataFactory; use Patchlevel\EventSourcing\Snapshot\Adapter\SnapshotAdapter; -use Patchlevel\Hydrator\Cryptography\PayloadCryptographer; use Patchlevel\Hydrator\Hydrator; use Patchlevel\Hydrator\MetadataHydrator; use Throwable; use function array_key_exists; -use function is_array; use function sprintf; final class DefaultSnapshotStore implements SnapshotStore { - private AdapterRepository $adapterRepository; - - private Hydrator $hydrator; - - private AggregateRootMetadataFactory $metadataFactory; - - /** @param array|AdapterRepository $adapterRepository */ public function __construct( - array|AdapterRepository $adapterRepository, - Hydrator|null $hydrator = null, - AggregateRootMetadataFactory|null $metadataFactory = null, + private readonly AdapterRepository $adapterRepository, + private readonly Hydrator $hydrator = new MetadataHydrator(), + private readonly AggregateRootMetadataFactory $metadataFactory = new AggregateRootMetadataAwareMetadataFactory(), ) { - if (is_array($adapterRepository)) { - $this->adapterRepository = new ArrayAdapterRepository($adapterRepository); - } else { - $this->adapterRepository = $adapterRepository; - } - - $this->hydrator = $hydrator ?? new MetadataHydrator(); - $this->metadataFactory = $metadataFactory ?? new AggregateRootMetadataAwareMetadataFactory(); } public function save(AggregateRoot $aggregateRoot): void @@ -118,11 +101,11 @@ private function version(string $aggregateClass): string|null } /** @param array $snapshotAdapters */ - public static function createDefault(array $snapshotAdapters, PayloadCryptographer|null $cryptographer = null): self + public static function createDefault(array $snapshotAdapters, Hydrator $hydrator = new MetadataHydrator()): self { return new self( new ArrayAdapterRepository($snapshotAdapters), - new MetadataHydrator(cryptographer: $cryptographer), + $hydrator, ); } } diff --git a/tests/Benchmark/BasicImplementation/Events/EmailChanged.php b/tests/Benchmark/BasicImplementation/Events/EmailChanged.php index a7c3743cc..de0d6fc9a 100644 --- a/tests/Benchmark/BasicImplementation/Events/EmailChanged.php +++ b/tests/Benchmark/BasicImplementation/Events/EmailChanged.php @@ -6,8 +6,8 @@ use Patchlevel\EventSourcing\Attribute\Event; use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\ProfileId; -use Patchlevel\Hydrator\Attribute\DataSubjectId; -use Patchlevel\Hydrator\Attribute\PersonalData; +use Patchlevel\Hydrator\Extension\Cryptography\Attribute\DataSubjectId; +use Patchlevel\Hydrator\Extension\Cryptography\Attribute\SensitiveData; #[Event('profile.email_changed')] final class EmailChanged @@ -15,7 +15,7 @@ final class EmailChanged public function __construct( #[DataSubjectId] public ProfileId $profileId, - #[PersonalData] + #[SensitiveData] public string|null $email, ) { } diff --git a/tests/Benchmark/BasicImplementation/Events/ProfileCreated.php b/tests/Benchmark/BasicImplementation/Events/ProfileCreated.php index 07adcaeee..6be650f17 100644 --- a/tests/Benchmark/BasicImplementation/Events/ProfileCreated.php +++ b/tests/Benchmark/BasicImplementation/Events/ProfileCreated.php @@ -7,8 +7,8 @@ use Patchlevel\EventSourcing\Attribute\Event; use Patchlevel\EventSourcing\Attribute\EventTag; use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\ProfileId; -use Patchlevel\Hydrator\Attribute\DataSubjectId; -use Patchlevel\Hydrator\Attribute\PersonalData; +use Patchlevel\Hydrator\Extension\Cryptography\Attribute\DataSubjectId; +use Patchlevel\Hydrator\Extension\Cryptography\Attribute\SensitiveData; #[Event('profile.created')] final class ProfileCreated @@ -18,7 +18,7 @@ public function __construct( #[EventTag(prefix: 'profile')] public ProfileId $profileId, public string $name, - #[PersonalData] + #[SensitiveData] public string|null $email, ) { } diff --git a/tests/Benchmark/CommandToQueryBench.php b/tests/Benchmark/CommandToQueryBench.php index e6695a4e1..4fbacdcce 100644 --- a/tests/Benchmark/CommandToQueryBench.php +++ b/tests/Benchmark/CommandToQueryBench.php @@ -58,7 +58,7 @@ public function setUp(): void $aggregateRootRegistry, $store, null, - new DefaultSnapshotStore(['default' => new InMemorySnapshotAdapter()]), + DefaultSnapshotStore::createDefault(['default' => new InMemorySnapshotAdapter()]), ); $projectionConnection = DbalManager::createConnection(); diff --git a/tests/Benchmark/PersonalDataBench.php b/tests/Benchmark/PersonalDataBench.php index 0e7b93a92..1581d17e0 100644 --- a/tests/Benchmark/PersonalDataBench.php +++ b/tests/Benchmark/PersonalDataBench.php @@ -16,7 +16,9 @@ use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\Profile; use Patchlevel\EventSourcing\Tests\Benchmark\BasicImplementation\ProfileId; use Patchlevel\EventSourcing\Tests\DbalManager; -use Patchlevel\Hydrator\Cryptography\PersonalDataPayloadCryptographer; +use Patchlevel\Hydrator\Extension\Cryptography\BaseCryptographer; +use Patchlevel\Hydrator\Extension\Cryptography\CryptographyExtension; +use Patchlevel\Hydrator\HydratorBuilder; use PhpBench\Attributes as Bench; #[Bench\BeforeMethods('setUp')] @@ -34,15 +36,19 @@ public function setUp(): void $cipherKeyStore = new DoctrineCipherKeyStore($connection); - $cryptographer = PersonalDataPayloadCryptographer::createWithOpenssl( + $cryptographer = BaseCryptographer::createWithOpenssl( $cipherKeyStore, ); + $hydrator = (new HydratorBuilder()) + ->useExtension(new CryptographyExtension($cryptographer)) + ->build(); + $this->store = new StreamDoctrineDbalStore( $connection, DefaultEventSerializer::createFromPaths( [__DIR__ . '/BasicImplementation/Events'], - cryptographer: $cryptographer, + $hydrator, ), ); diff --git a/tests/Benchmark/SnapshotsBench.php b/tests/Benchmark/SnapshotsBench.php index 43d8ec223..ad1dc8ce9 100644 --- a/tests/Benchmark/SnapshotsBench.php +++ b/tests/Benchmark/SnapshotsBench.php @@ -41,7 +41,7 @@ public function setUp(): void $this->adapter = new InMemorySnapshotAdapter(); - $this->snapshotStore = new DefaultSnapshotStore(['default' => $this->adapter]); + $this->snapshotStore = DefaultSnapshotStore::createDefault(['default' => $this->adapter]); $this->repository = new DefaultRepository($this->store, Profile::metadata(), null, $this->snapshotStore); diff --git a/tests/Integration/BasicImplementation/BasicIntegrationTest.php b/tests/Integration/BasicImplementation/BasicIntegrationTest.php index 0cabaad6c..6614a8235 100644 --- a/tests/Integration/BasicImplementation/BasicIntegrationTest.php +++ b/tests/Integration/BasicImplementation/BasicIntegrationTest.php @@ -151,7 +151,7 @@ public function testSnapshot(): void new AggregateRootRegistry(['profile' => Profile::class]), $store, null, - new DefaultSnapshotStore(['default' => new InMemorySnapshotAdapter()]), + DefaultSnapshotStore::createDefault(['default' => new InMemorySnapshotAdapter()]), new FooMessageDecorator(), ), $engine, @@ -205,7 +205,7 @@ public function testTempProjection(): void new AggregateRootRegistry(['profile' => Profile::class]), $store, null, - new DefaultSnapshotStore(['default' => new InMemorySnapshotAdapter()]), + DefaultSnapshotStore::createDefault(['default' => new InMemorySnapshotAdapter()]), new FooMessageDecorator(), ); @@ -265,7 +265,7 @@ public function testCommandBus(): void new AggregateRootRegistry(['profile_with_commands' => ProfileWithCommands::class]), $store, null, - new DefaultSnapshotStore(['default' => new InMemorySnapshotAdapter()]), + DefaultSnapshotStore::createDefault(['default' => new InMemorySnapshotAdapter()]), new FooMessageDecorator(), ); @@ -343,7 +343,7 @@ public function testQueryBus(): void new AggregateRootRegistry(['profile_with_commands' => ProfileWithCommands::class]), $store, null, - new DefaultSnapshotStore(['default' => new InMemorySnapshotAdapter()]), + DefaultSnapshotStore::createDefault(['default' => new InMemorySnapshotAdapter()]), new FooMessageDecorator(), ); diff --git a/tests/Integration/MicroAggregate/MicroAggregateIntegrationTest.php b/tests/Integration/MicroAggregate/MicroAggregateIntegrationTest.php index 7fac99b8b..0e5542178 100644 --- a/tests/Integration/MicroAggregate/MicroAggregateIntegrationTest.php +++ b/tests/Integration/MicroAggregate/MicroAggregateIntegrationTest.php @@ -128,7 +128,7 @@ public function testSnapshot(): void ]), $store, null, - new DefaultSnapshotStore(['default' => new InMemorySnapshotAdapter()]), + DefaultSnapshotStore::createDefault(['default' => new InMemorySnapshotAdapter()]), ), $engine, ); diff --git a/tests/Integration/PersonalData/Events/NameChanged.php b/tests/Integration/PersonalData/Events/NameChanged.php deleted file mode 100644 index 22c18482d..000000000 --- a/tests/Integration/PersonalData/Events/NameChanged.php +++ /dev/null @@ -1,22 +0,0 @@ -connection); - $cryptographer = PersonalDataPayloadCryptographer::createWithOpenssl($cipherKeyStore); + + $hydrator = (new HydratorBuilder()) + ->useExtension(new CoreExtension()) + ->useExtension(new CryptographyExtension(BaseCryptographer::createWithOpenssl($cipherKeyStore))) + ->build(); $store = new StreamDoctrineDbalStore( $this->connection, - DefaultEventSerializer::createFromPaths([__DIR__ . '/Events'], cryptographer: $cryptographer), + DefaultEventSerializer::createFromPaths([__DIR__ . '/Events'], $hydrator), ); $manager = new DefaultRepositoryManager( @@ -73,7 +80,6 @@ public function testSuccessfulWithEvent(): void $profile = $repository->load($profileId); - self::assertInstanceOf(Profile::class, $profile); self::assertEquals($profileId, $profile->aggregateRootId()); self::assertSame(1, $profile->playhead()); self::assertSame('John', $profile->name()); @@ -81,17 +87,18 @@ public function testSuccessfulWithEvent(): void $result = $this->connection->fetchAllAssociative('SELECT * FROM event_store'); self::assertCount(1, $result); - self::assertArrayHasKey(0, $result); - - $row = $result[0]; - - self::assertStringNotContainsString('John', $row['event_payload']); + self::assertIsString($result[0]['event_payload']); + self::assertStringNotContainsString('John', $result[0]['event_payload']); } public function testRemoveKeyWithEvent(): void { $cipherKeyStore = new DoctrineCipherKeyStore($this->connection); - $cryptographer = PersonalDataPayloadCryptographer::createWithOpenssl($cipherKeyStore); + + $hydrator = (new HydratorBuilder()) + ->useExtension(new CoreExtension()) + ->useExtension(new CryptographyExtension(BaseCryptographer::createWithOpenssl($cipherKeyStore))) + ->build(); $subscriptionStore = new DoctrineSubscriptionStore( $this->connection, @@ -99,7 +106,7 @@ public function testRemoveKeyWithEvent(): void $store = new StreamDoctrineDbalStore( $this->connection, - DefaultEventSerializer::createFromPaths([__DIR__ . '/Events'], cryptographer: $cryptographer), + DefaultEventSerializer::createFromPaths([__DIR__ . '/Events'], $hydrator), ); $manager = new DefaultRepositoryManager( @@ -136,7 +143,6 @@ public function testRemoveKeyWithEvent(): void $profile = $repository->load($profileId); - self::assertInstanceOf(Profile::class, $profile); self::assertEquals($profileId, $profile->aggregateRootId()); self::assertSame(1, $profile->playhead()); self::assertSame('John', $profile->name()); @@ -147,7 +153,6 @@ public function testRemoveKeyWithEvent(): void $profile = $repository->load($profileId); - self::assertInstanceOf(Profile::class, $profile); self::assertEquals($profileId, $profile->aggregateRootId()); self::assertSame(2, $profile->playhead()); self::assertSame('unknown', $profile->name()); @@ -157,7 +162,6 @@ public function testRemoveKeyWithEvent(): void $profile = $repository->load($profileId); - self::assertInstanceOf(Profile::class, $profile); self::assertEquals($profileId, $profile->aggregateRootId()); self::assertSame(3, $profile->playhead()); self::assertSame('hallo', $profile->name()); @@ -166,7 +170,11 @@ public function testRemoveKeyWithEvent(): void public function testRemoveKeyWithEventAndSnapshot(): void { $cipherKeyStore = new DoctrineCipherKeyStore($this->connection); - $cryptographer = PersonalDataPayloadCryptographer::createWithOpenssl($cipherKeyStore); + + $hydrator = (new HydratorBuilder()) + ->useExtension(new CoreExtension()) + ->useExtension(new CryptographyExtension(BaseCryptographer::createWithOpenssl($cipherKeyStore))) + ->build(); $subscriptionStore = new DoctrineSubscriptionStore( $this->connection, @@ -174,7 +182,7 @@ public function testRemoveKeyWithEventAndSnapshot(): void $store = new StreamDoctrineDbalStore( $this->connection, - DefaultEventSerializer::createFromPaths([__DIR__ . '/Events'], cryptographer: $cryptographer), + DefaultEventSerializer::createFromPaths([__DIR__ . '/Events'], $hydrator), ); $snapshotAdapter = new InMemorySnapshotAdapter(); @@ -185,7 +193,7 @@ public function testRemoveKeyWithEventAndSnapshot(): void null, DefaultSnapshotStore::createDefault( ['default' => $snapshotAdapter], - $cryptographer, + $hydrator, ), ); @@ -219,16 +227,20 @@ public function testRemoveKeyWithEventAndSnapshot(): void $profile = $repository->load($profileId); - self::assertInstanceOf(Profile::class, $profile); self::assertEquals($profileId, $profile->aggregateRootId()); self::assertSame(2, $profile->playhead()); self::assertSame('John 2', $profile->name()); + $result = $this->connection->fetchAllAssociative('SELECT * FROM event_store'); + + self::assertCount(2, $result); + self::assertIsString($result[1]['event_payload']); + self::assertStringNotContainsString('John 2', $result[1]['event_payload']); + $cipherKeyStore->remove($profileId->toString()); $profile = $repository->load($profileId); - self::assertInstanceOf(Profile::class, $profile); self::assertEquals($profileId, $profile->aggregateRootId()); self::assertSame(2, $profile->playhead()); self::assertSame('unknown', $profile->name()); diff --git a/tests/Integration/Subscription/SubscriptionTest.php b/tests/Integration/Subscription/SubscriptionTest.php index 368a9aab5..2149e7743 100644 --- a/tests/Integration/Subscription/SubscriptionTest.php +++ b/tests/Integration/Subscription/SubscriptionTest.php @@ -45,6 +45,8 @@ use Patchlevel\EventSourcing\Tests\Integration\Subscription\Subscriber\ProfileProcessor; use Patchlevel\EventSourcing\Tests\Integration\Subscription\Subscriber\ProfileProjection; use Patchlevel\EventSourcing\Tests\Integration\Subscription\Subscriber\ProfileProjectionWithCleanup; +use Patchlevel\Hydrator\CoreExtension; +use Patchlevel\Hydrator\HydratorBuilder; use PHPUnit\Framework\Attributes\CoversNothing; use PHPUnit\Framework\TestCase; use RuntimeException; @@ -1360,7 +1362,10 @@ public function testCleanup(): void public function testLookup(): void { $eventRegistry = (new AttributeEventRegistryFactory())->create([__DIR__ . '/Events']); - $serializer = new DefaultEventSerializer($eventRegistry); + $serializer = new DefaultEventSerializer( + $eventRegistry, + (new HydratorBuilder())->useExtension(new CoreExtension())->build(), + ); $store = new StreamDoctrineDbalStore( $this->connection, diff --git a/tests/Unit/Fixture/EmailNormalizer.php b/tests/Unit/Fixture/EmailNormalizer.php index d13924557..74ac99166 100644 --- a/tests/Unit/Fixture/EmailNormalizer.php +++ b/tests/Unit/Fixture/EmailNormalizer.php @@ -14,7 +14,7 @@ #[Attribute(Attribute::TARGET_PROPERTY)] final class EmailNormalizer implements Normalizer { - public function normalize(mixed $value): string + public function normalize(mixed $value, array $context): string { if (!$value instanceof Email) { throw new InvalidArgumentException(); @@ -23,7 +23,7 @@ public function normalize(mixed $value): string return $value->toString(); } - public function denormalize(mixed $value): Email|null + public function denormalize(mixed $value, array $context): Email|null { if ($value === null) { return null; diff --git a/tests/Unit/Fixture/MessageNormalizer.php b/tests/Unit/Fixture/MessageNormalizer.php index d18220bcb..ef7d31869 100644 --- a/tests/Unit/Fixture/MessageNormalizer.php +++ b/tests/Unit/Fixture/MessageNormalizer.php @@ -14,7 +14,7 @@ final class MessageNormalizer implements Normalizer { /** @return array|null */ - public function normalize(mixed $value): array|null + public function normalize(mixed $value, array $context): array|null { if ($value === null) { return null; @@ -27,7 +27,7 @@ public function normalize(mixed $value): array|null return $value->toArray(); } - public function denormalize(mixed $value): Message|null + public function denormalize(mixed $value, array $context): Message|null { if ($value === null) { return null; diff --git a/tests/Unit/Serializer/DefaultEventSerializerTest.php b/tests/Unit/Serializer/DefaultEventSerializerTest.php index cba9905c6..bb0b83535 100644 --- a/tests/Unit/Serializer/DefaultEventSerializerTest.php +++ b/tests/Unit/Serializer/DefaultEventSerializerTest.php @@ -4,17 +4,11 @@ namespace Patchlevel\EventSourcing\Tests\Unit\Serializer; -use Patchlevel\EventSourcing\Metadata\Event\AttributeEventRegistryFactory; use Patchlevel\EventSourcing\Serializer\DefaultEventSerializer; -use Patchlevel\EventSourcing\Serializer\Encoder\JsonEncoder; use Patchlevel\EventSourcing\Serializer\SerializedEvent; -use Patchlevel\EventSourcing\Serializer\Upcast\Upcast; -use Patchlevel\EventSourcing\Serializer\Upcast\Upcaster; -use Patchlevel\EventSourcing\Serializer\Upcast\UpcasterChain; use Patchlevel\EventSourcing\Tests\Unit\Fixture\Email; use Patchlevel\EventSourcing\Tests\Unit\Fixture\ProfileCreated; use Patchlevel\EventSourcing\Tests\Unit\Fixture\ProfileId; -use Patchlevel\Hydrator\MetadataHydrator; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; @@ -57,85 +51,4 @@ public function testDeserialize(): void self::assertEquals($expected, $event); } - - public function testSerializeWithUpcasting(): void - { - $upcaster = new class implements Upcaster { - public function __invoke(Upcast $upcast): Upcast - { - if ($upcast->eventName !== 'profile_created_old') { - return $upcast; - } - - return new Upcast('profile_created', $upcast->payload + ['email' => 'info@patchlevel.de']); - } - }; - - $serializer = new DefaultEventSerializer( - (new AttributeEventRegistryFactory())->create([__DIR__ . '/../Fixture']), - new MetadataHydrator(), - new JsonEncoder(), - $upcaster, - ); - - $expected = new ProfileCreated( - ProfileId::fromString('1'), - Email::fromString('info@patchlevel.de'), - ); - - $event = $serializer->deserialize( - new SerializedEvent( - 'profile_created_old', - '{"profileId":"1"}', - ), - ); - - self::assertEquals($expected, $event); - } - - public function testSerializeWithUpcastingChain(): void - { - $upcasterOne = new class implements Upcaster { - public function __invoke(Upcast $upcast): Upcast - { - if ($upcast->eventName !== 'profile_created_very_old') { - return $upcast; - } - - return new Upcast('profile_created_old', ['profileId' => $upcast->payload['id'] ?? 'None']); - } - }; - - $upcasterTwo = new class implements Upcaster { - public function __invoke(Upcast $upcast): Upcast - { - if ($upcast->eventName !== 'profile_created_old') { - return $upcast; - } - - return new Upcast('profile_created', $upcast->payload + ['email' => 'info@patchlevel.de']); - } - }; - - $serializer = new DefaultEventSerializer( - (new AttributeEventRegistryFactory())->create([__DIR__ . '/../Fixture']), - new MetadataHydrator(), - new JsonEncoder(), - new UpcasterChain([$upcasterOne, $upcasterTwo]), - ); - - $expected = new ProfileCreated( - ProfileId::fromString('1'), - Email::fromString('info@patchlevel.de'), - ); - - $event = $serializer->deserialize( - new SerializedEvent( - 'profile_created_very_old', - '{"id":"1"}', - ), - ); - - self::assertEquals($expected, $event); - } } diff --git a/tests/Unit/Serializer/Normalizer/IdNormalizerTest.php b/tests/Unit/Serializer/Normalizer/IdNormalizerTest.php index 720976f7e..f7ae6e1f2 100644 --- a/tests/Unit/Serializer/Normalizer/IdNormalizerTest.php +++ b/tests/Unit/Serializer/Normalizer/IdNormalizerTest.php @@ -23,13 +23,13 @@ final class IdNormalizerTest extends TestCase public function testNormalizeWithNull(): void { $normalizer = new IdNormalizer(CustomId::class); - $this->assertEquals(null, $normalizer->normalize(null)); + $this->assertEquals(null, $normalizer->normalize(null, [])); } public function testDenormalizeWithNull(): void { $normalizer = new IdNormalizer(CustomId::class); - $this->assertEquals(null, $normalizer->denormalize(null)); + $this->assertEquals(null, $normalizer->denormalize(null, [])); } public function testNormalizeWithInvalidArgument(): void @@ -38,7 +38,7 @@ public function testNormalizeWithInvalidArgument(): void $this->expectExceptionMessage('type "Patchlevel\EventSourcing\Identifier\CustomId" was expected but "string" was passed.'); $normalizer = new IdNormalizer(CustomId::class); - $normalizer->normalize('foo'); + $normalizer->normalize('foo', []); } public function testDenormalizeWithInvalidArgument(): void @@ -46,19 +46,19 @@ public function testDenormalizeWithInvalidArgument(): void $this->expectException(InvalidUuidStringException::class); $normalizer = new IdNormalizer(Uuid::class); - $normalizer->denormalize('foo'); + $normalizer->denormalize('foo', []); } public function testNormalizeWithValue(): void { $normalizer = new IdNormalizer(CustomId::class); - $this->assertEquals('foo', $normalizer->normalize(new CustomId('foo'))); + $this->assertEquals('foo', $normalizer->normalize(new CustomId('foo'), [])); } public function testDenormalizeWithValue(): void { $normalizer = new IdNormalizer(CustomId::class); - $this->assertEquals(new CustomId('foo'), $normalizer->denormalize('foo')); + $this->assertEquals(new CustomId('foo'), $normalizer->denormalize('foo', [])); } public function testDenormalizeWithWrongValue(): void @@ -66,7 +66,7 @@ public function testDenormalizeWithWrongValue(): void $normalizer = new IdNormalizer(CustomId::class); $this->expectException(InvalidArgument::class); - $normalizer->denormalize(123); + $normalizer->denormalize(123, []); } public function testAutoDetect(): void diff --git a/tests/Unit/Snapshot/DefaultSnapshotStoreTest.php b/tests/Unit/Snapshot/DefaultSnapshotStoreTest.php index a97cc1267..c91292519 100644 --- a/tests/Unit/Snapshot/DefaultSnapshotStoreTest.php +++ b/tests/Unit/Snapshot/DefaultSnapshotStoreTest.php @@ -29,7 +29,7 @@ public function testSave(): void 'payload' => ['id' => '1', 'email' => 'info@patchlevel.de', 'messages' => [], '_playhead' => 2], ]); - $store = new DefaultSnapshotStore(['memory' => $adapter]); + $store = DefaultSnapshotStore::createDefault(['memory' => $adapter]); $aggregate = ProfileWithSnapshot::createProfile( ProfileId::fromString('1'), @@ -51,7 +51,7 @@ public function testLoad(): void ], ); - $store = new DefaultSnapshotStore(['memory' => $adapter]); + $store = DefaultSnapshotStore::createDefault(['memory' => $adapter]); $aggregate = $store->load(ProfileWithSnapshot::class, ProfileId::fromString('1')); @@ -69,7 +69,7 @@ public function testLoadNotFound(): void ->with('profile_with_snapshot-1') ->willThrowException(new RuntimeException()); - $store = new DefaultSnapshotStore(['memory' => $adapter]); + $store = DefaultSnapshotStore::createDefault(['memory' => $adapter]); $this->expectException(SnapshotNotFound::class); $store->load(ProfileWithSnapshot::class, ProfileId::fromString('1')); @@ -82,7 +82,7 @@ public function testLoadLegacySnapshots(): void $adapter = $this->createMock(SnapshotAdapter::class); $adapter->method('load')->with('profile_with_snapshot-1')->willReturn(['id' => '1', 'email' => 'info@patchlevel.de', 'messages' => [], '_playhead' => 2]); - $store = new DefaultSnapshotStore(['memory' => $adapter]); + $store = DefaultSnapshotStore::createDefault(['memory' => $adapter]); $store->load(ProfileWithSnapshot::class, ProfileId::fromString('1')); } @@ -99,7 +99,7 @@ public function testLoadExpiredSnapshot(): void ], ); - $store = new DefaultSnapshotStore(['memory' => $adapter]); + $store = DefaultSnapshotStore::createDefault(['memory' => $adapter]); $store->load(ProfileWithSnapshot::class, ProfileId::fromString('1')); } @@ -108,7 +108,7 @@ public function testAdapterIsMissing(): void { $this->expectException(AdapterNotFound::class); - $store = new DefaultSnapshotStore([]); + $store = DefaultSnapshotStore::createDefault([]); $store->load(ProfileWithSnapshot::class, ProfileId::fromString('1')); } @@ -116,14 +116,14 @@ public function testSnapshotConfigIsMissing(): void { $this->expectException(SnapshotNotConfigured::class); - $store = new DefaultSnapshotStore([], null); + $store = DefaultSnapshotStore::createDefault(['memory' => $this->createMock(SnapshotAdapter::class)]); $store->load(Profile::class, ProfileId::fromString('1')); } public function testGetAdapter(): void { $adapter = $this->createMock(SnapshotAdapter::class); - $store = new DefaultSnapshotStore(['memory' => $adapter]); + $store = DefaultSnapshotStore::createDefault(['memory' => $adapter]); self::assertSame($adapter, $store->adapter(ProfileWithSnapshot::class)); }