From db0bd3b3d40d8e39d4808f705294c4d4da7189a2 Mon Sep 17 00:00:00 2001 From: David Badura Date: Sun, 25 Feb 2024 17:43:29 +0100 Subject: [PATCH 1/2] upgrade hydrator & use new auto detect feature --- composer.json | 2 +- composer.lock | 76 +++++++++---------- docs/pages/aggregate.md | 2 +- docs/pages/aggregate_id.md | 6 +- docs/pages/events.md | 2 +- docs/pages/getting_started.md | 2 +- docs/pages/normalizer.md | 50 +++++++++++- docs/pages/split_stream.md | 6 +- src/Serializer/Normalizer/IdNormalizer.php | 38 ++++++++-- .../BasicImplementation/Aggregate/Profile.php | 2 +- .../Events/ProfileCreated.php | 2 +- .../BasicImplementation/Events/Reborn.php | 2 +- .../Events/BalanceAdded.php | 4 +- .../Events/BankAccountCreated.php | 4 +- .../Events/MonthPassed.php | 4 +- .../Normalizer/AccountIdNormalizer.php | 38 ---------- .../BasicImplementation/Aggregate/Profile.php | 4 +- .../Events/ProfileCreated.php | 4 +- .../Normalizer/ProfileIdNormalizer.php | 38 ---------- .../Integration/Outbox/Aggregate/Profile.php | 4 +- .../Outbox/Events/ProfileCreated.php | 4 +- .../Outbox/Normalizer/ProfileIdNormalizer.php | 38 ---------- .../Projectionist/Aggregate/Profile.php | 4 +- .../Projectionist/Events/ProfileCreated.php | 4 +- .../Normalizer/ProfileIdNormalizer.php | 38 ---------- .../Store/Events/ProfileCreated.php | 4 +- .../Store/Normalizer/ProfileIdNormalizer.php | 38 ---------- tests/Integration/Store/Profile.php | 4 +- tests/Unit/Fixture/ProfileCreated.php | 3 +- tests/Unit/Fixture/ProfileIdNormalizer.php | 38 ---------- tests/Unit/Fixture/ProfileVisited.php | 3 +- tests/Unit/Fixture/ProfileWithSnapshot.php | 3 +- .../Normalizer/IdNormalizerTest.php | 47 ++++++++++++ 33 files changed, 209 insertions(+), 309 deletions(-) delete mode 100644 tests/Integration/BankAccountSplitStream/Normalizer/AccountIdNormalizer.php delete mode 100644 tests/Integration/BasicImplementation/Normalizer/ProfileIdNormalizer.php delete mode 100644 tests/Integration/Outbox/Normalizer/ProfileIdNormalizer.php delete mode 100644 tests/Integration/Projectionist/Normalizer/ProfileIdNormalizer.php delete mode 100644 tests/Integration/Store/Normalizer/ProfileIdNormalizer.php delete mode 100644 tests/Unit/Fixture/ProfileIdNormalizer.php diff --git a/composer.json b/composer.json index 9c388154..55192fee 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "php": "~8.1.0 || ~8.2.0 || ~8.3.0", "doctrine/dbal": "^3.8.1|^4.0.0", "doctrine/migrations": "^3.3.2", - "patchlevel/hydrator": "^1.1.0", + "patchlevel/hydrator": "^1.2.0", "patchlevel/worker": "^1.1.1", "psr/cache": "^2.0.0|^3.0.0", "psr/clock": "^1.0", diff --git a/composer.lock b/composer.lock index 98522e43..51668911 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": "06b66664b0ad72a735e5a54f0722966e", + "content-hash": "0c745538b704294d92dfee9c2b9d6323", "packages": [ { "name": "brick/math", @@ -411,16 +411,16 @@ }, { "name": "patchlevel/hydrator", - "version": "1.1.0", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/patchlevel/hydrator.git", - "reference": "6e545a9ba41062932525f005916120603f7795e7" + "reference": "c9f1166513c55c9e659925dcbdfc693ac57ecae4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/patchlevel/hydrator/zipball/6e545a9ba41062932525f005916120603f7795e7", - "reference": "6e545a9ba41062932525f005916120603f7795e7", + "url": "https://api.github.com/repos/patchlevel/hydrator/zipball/c9f1166513c55c9e659925dcbdfc693ac57ecae4", + "reference": "c9f1166513c55c9e659925dcbdfc693ac57ecae4", "shasum": "" }, "require": { @@ -469,9 +469,9 @@ ], "support": { "issues": "https://github.com/patchlevel/hydrator/issues", - "source": "https://github.com/patchlevel/hydrator/tree/1.1.0" + "source": "https://github.com/patchlevel/hydrator/tree/1.2.0" }, - "time": "2023-12-12T13:47:23+00:00" + "time": "2024-02-26T08:32:12+00:00" }, { "name": "patchlevel/worker", @@ -3072,22 +3072,22 @@ }, { "name": "doctrine/orm", - "version": "3.0.0", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/doctrine/orm.git", - "reference": "5b8b5f28f535e1f03b54dcfb0427407ed92f5b72" + "reference": "2a250b5814de192a23438c0a43e15da7e77890a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/5b8b5f28f535e1f03b54dcfb0427407ed92f5b72", - "reference": "5b8b5f28f535e1f03b54dcfb0427407ed92f5b72", + "url": "https://api.github.com/repos/doctrine/orm/zipball/2a250b5814de192a23438c0a43e15da7e77890a7", + "reference": "2a250b5814de192a23438c0a43e15da7e77890a7", "shasum": "" }, "require": { "composer-runtime-api": "^2", "doctrine/collections": "^2.1", - "doctrine/dbal": "^3.6 || ^4", + "doctrine/dbal": "^3.8.2 || ^4", "doctrine/deprecations": "^0.5.3 || ^1", "doctrine/event-manager": "^1.2 || ^2", "doctrine/inflector": "^1.4 || ^2.0", @@ -3154,9 +3154,9 @@ ], "support": { "issues": "https://github.com/doctrine/orm/issues", - "source": "https://github.com/doctrine/orm/tree/3.0.0" + "source": "https://github.com/doctrine/orm/tree/3.0.1" }, - "time": "2024-02-03T16:50:09+00:00" + "time": "2024-02-22T12:23:53+00:00" }, { "name": "doctrine/persistence", @@ -4575,21 +4575,21 @@ }, { "name": "phpdocumentor/type-resolver", - "version": "1.8.1", + "version": "1.8.2", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "bc3dc91a5e9b14aa06d1d9e90647c5c5a2cc5353" + "reference": "153ae662783729388a584b4361f2545e4d841e3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/bc3dc91a5e9b14aa06d1d9e90647c5c5a2cc5353", - "reference": "bc3dc91a5e9b14aa06d1d9e90647c5c5a2cc5353", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c", + "reference": "153ae662783729388a584b4361f2545e4d841e3c", "shasum": "" }, "require": { "doctrine/deprecations": "^1.0", - "php": "^7.4 || ^8.0", + "php": "^7.3 || ^8.0", "phpdocumentor/reflection-common": "^2.0", "phpstan/phpdoc-parser": "^1.13" }, @@ -4627,9 +4627,9 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.1" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.2" }, - "time": "2024-01-18T19:15:27+00:00" + "time": "2024-02-23T11:10:43+00:00" }, { "name": "phpspec/prophecy", @@ -4754,16 +4754,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.25.0", + "version": "1.26.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "bd84b629c8de41aa2ae82c067c955e06f1b00240" + "reference": "231e3186624c03d7e7c890ec662b81e6b0405227" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/bd84b629c8de41aa2ae82c067c955e06f1b00240", - "reference": "bd84b629c8de41aa2ae82c067c955e06f1b00240", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/231e3186624c03d7e7c890ec662b81e6b0405227", + "reference": "231e3186624c03d7e7c890ec662b81e6b0405227", "shasum": "" }, "require": { @@ -4795,9 +4795,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.25.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.26.0" }, - "time": "2024-01-04T17:06:16+00:00" + "time": "2024-02-23T16:05:55+00:00" }, { "name": "phpstan/phpstan", @@ -5184,16 +5184,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.10", + "version": "10.5.11", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "50b8e314b6d0dd06521dc31d1abffa73f25f850c" + "reference": "0d968f6323deb3dbfeba5bfd4929b9415eb7a9a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/50b8e314b6d0dd06521dc31d1abffa73f25f850c", - "reference": "50b8e314b6d0dd06521dc31d1abffa73f25f850c", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0d968f6323deb3dbfeba5bfd4929b9415eb7a9a4", + "reference": "0d968f6323deb3dbfeba5bfd4929b9415eb7a9a4", "shasum": "" }, "require": { @@ -5265,7 +5265,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.10" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.11" }, "funding": [ { @@ -5281,7 +5281,7 @@ "type": "tidelift" } ], - "time": "2024-02-04T09:07:51+00:00" + "time": "2024-02-25T14:05:00+00:00" }, { "name": "psalm/plugin-phpunit", @@ -7559,16 +7559,16 @@ }, { "name": "vimeo/psalm", - "version": "5.22.1", + "version": "5.22.2", "source": { "type": "git", "url": "https://github.com/vimeo/psalm.git", - "reference": "e9dad66e11274315dac27e08349c628c7d6a1a43" + "reference": "d768d914152dbbf3486c36398802f74e80cfde48" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/e9dad66e11274315dac27e08349c628c7d6a1a43", - "reference": "e9dad66e11274315dac27e08349c628c7d6a1a43", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/d768d914152dbbf3486c36398802f74e80cfde48", + "reference": "d768d914152dbbf3486c36398802f74e80cfde48", "shasum": "" }, "require": { @@ -7665,7 +7665,7 @@ "issues": "https://github.com/vimeo/psalm/issues", "source": "https://github.com/vimeo/psalm" }, - "time": "2024-02-15T22:52:31+00:00" + "time": "2024-02-22T23:39:07+00:00" }, { "name": "webmozart/assert", diff --git a/docs/pages/aggregate.md b/docs/pages/aggregate.md index 76021100..ec4a74f4 100644 --- a/docs/pages/aggregate.md +++ b/docs/pages/aggregate.md @@ -105,7 +105,7 @@ use Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer; final class ProfileRegistered { public function __construct( - #[IdNormalizer(Uuid::class)] + #[IdNormalizer] public readonly Uuid $profileId, public readonly string $name ) {} diff --git a/docs/pages/aggregate_id.md b/docs/pages/aggregate_id.md index dc021907..0f3fb76c 100644 --- a/docs/pages/aggregate_id.md +++ b/docs/pages/aggregate_id.md @@ -26,7 +26,7 @@ use Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer; final class Profile extends BasicAggregateRoot { #[Id] - #[IdNormalizer(Uuid::class)] + #[IdNormalizer] private Uuid $id; } ``` @@ -68,7 +68,7 @@ use Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer; final class Profile extends BasicAggregateRoot { #[Id] - #[IdNormalizer(CustomId::class)] + #[IdNormalizer] private CustomId $id; } ``` @@ -127,7 +127,7 @@ use Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer; final class Profile extends BasicAggregateRoot { #[Id] - #[IdNormalizer(ProfileId::class)] + #[IdNormalizer] private ProfileId $id; } ``` diff --git a/docs/pages/events.md b/docs/pages/events.md index da2682cc..24c6d90a 100644 --- a/docs/pages/events.md +++ b/docs/pages/events.md @@ -73,7 +73,7 @@ use Patchlevel\Hydrator\Normalizer\IdNormalizer; final class ProfileCreated { public function __construct( - #[IdNormalizer(Uuid::class)] + #[IdNormalizer] public readonly Uuid $id, #[NameNormalizer] public readonly Name $name, diff --git a/docs/pages/getting_started.md b/docs/pages/getting_started.md index 4108aaa4..fa30276f 100644 --- a/docs/pages/getting_started.md +++ b/docs/pages/getting_started.md @@ -17,7 +17,7 @@ use Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer; final class HotelCreated { public function __construct( - #[IdNormalizer(Uuid::class)] + #[IdNormalizer] public readonly Uuid $hotelId, public readonly string $hotelName ) { diff --git a/docs/pages/normalizer.md b/docs/pages/normalizer.md index 2cd8b5e0..6c6eff3b 100644 --- a/docs/pages/normalizer.md +++ b/docs/pages/normalizer.md @@ -189,7 +189,17 @@ final class DTO { ### Enum Backed enums can also be normalized. -For this, the enum FQCN must also be pass so that the `EnumNormalizer` knows which enum it is. + +```php +use Patchlevel\Hydrator\Normalizer\EnumNormalizer; + +final class DTO { + #[EnumNormalizer] + public Status $status; +} +``` + +You can also specify the enum class. ```php use Patchlevel\Hydrator\Normalizer\EnumNormalizer; @@ -203,7 +213,18 @@ final class DTO { ### Id If you have your own AggregateRootId, you can use the `IdNormalizer`. -the `IdNormalizer` needs the FQCN of the AggregateRootId as a parameter. + +```php +use Patchlevel\EventSourcing\Aggregate\Uuid; +use Patchlevel\Hydrator\Normalizer\IdNormalizer; + +final class DTO { + #[IdNormalizer] + public Uuid $id; +} +``` + +Optional you can also define the type of the id. ```php use Patchlevel\EventSourcing\Aggregate\Uuid; @@ -215,6 +236,31 @@ final class DTO { } ``` +### Object + +If you have a complex object that you want to normalize, you can use the `ObjectNormalizer`. +Internally, it uses the `Hydrator` to normalize and denormalize the object. + +```php +use Patchlevel\Hydrator\Normalizer\ObjectNormalizer; + +final class DTO { + #[ObjectNormalizer] + public ComplexObject $object; +} +``` + +Optional you can also define the type of the object. + +```php +use Patchlevel\Hydrator\Normalizer\ObjectNormalizer; + +final class DTO { + #[ObjectNormalizer(ComplexObject::class)] + public object $object; +} +``` + ## Custom Normalizer Since we only offer normalizers for PHP native things, diff --git a/docs/pages/split_stream.md b/docs/pages/split_stream.md index eab13635..cdc5f749 100644 --- a/docs/pages/split_stream.md +++ b/docs/pages/split_stream.md @@ -17,12 +17,16 @@ loaded anymore for building the aggregate. This means that all needed data has t should trigger the event split. ```php +use Patchlevel\EventSourcing\Attribute\Event; +use Patchlevel\EventSourcing\Attribute\SplitStream; +use Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer; + #[Event('bank_account.month_passed')] #[SplitStream] final class MonthPassed { public function __construct( - #[Normalize(new AccountIdNormalizer())] + #[IdNormalizer] public AccountId $accountId, public string $name, public int $balanceInCents, diff --git a/src/Serializer/Normalizer/IdNormalizer.php b/src/Serializer/Normalizer/IdNormalizer.php index 9c911c33..5a59c9ad 100644 --- a/src/Serializer/Normalizer/IdNormalizer.php +++ b/src/Serializer/Normalizer/IdNormalizer.php @@ -7,16 +7,20 @@ use Attribute; use Patchlevel\EventSourcing\Aggregate\AggregateRootId; use Patchlevel\Hydrator\Normalizer\InvalidArgument; +use Patchlevel\Hydrator\Normalizer\InvalidType; use Patchlevel\Hydrator\Normalizer\Normalizer; +use Patchlevel\Hydrator\Normalizer\ReflectionTypeAwareNormalizer; +use Patchlevel\Hydrator\Normalizer\ReflectionTypeUtil; +use ReflectionType; use function is_string; #[Attribute(Attribute::TARGET_PROPERTY)] -final class IdNormalizer implements Normalizer +final class IdNormalizer implements Normalizer, ReflectionTypeAwareNormalizer { public function __construct( - /** @var class-string */ - private readonly string $aggregateIdClass, + /** @var class-string|null */ + private string|null $aggregateIdClass = null, ) { } @@ -26,8 +30,10 @@ public function normalize(mixed $value): string|null return null; } + $class = $this->aggregateIdClass(); + if (!$value instanceof AggregateRootId) { - throw InvalidArgument::withWrongType($this->aggregateIdClass, $value); + throw InvalidArgument::withWrongType($class, $value); } return $value->toString(); @@ -43,8 +49,30 @@ public function denormalize(mixed $value): AggregateRootId|null throw InvalidArgument::withWrongType('string', $value); } - $class = $this->aggregateIdClass; + $class = $this->aggregateIdClass(); return $class::fromString($value); } + + public function handleReflectionType(ReflectionType|null $reflectionType): void + { + if ($this->aggregateIdClass !== null || $reflectionType === null) { + return; + } + + $this->aggregateIdClass = ReflectionTypeUtil::classStringInstanceOf( + $reflectionType, + AggregateRootId::class, + ); + } + + /** @return class-string */ + public function aggregateIdClass(): string + { + if ($this->aggregateIdClass === null) { + throw InvalidType::missingType(); + } + + return $this->aggregateIdClass; + } } diff --git a/tests/Benchmark/BasicImplementation/Aggregate/Profile.php b/tests/Benchmark/BasicImplementation/Aggregate/Profile.php index 5f994fdd..4fcf7327 100644 --- a/tests/Benchmark/BasicImplementation/Aggregate/Profile.php +++ b/tests/Benchmark/BasicImplementation/Aggregate/Profile.php @@ -20,7 +20,7 @@ final class Profile extends BasicAggregateRoot { #[Id] - #[IdNormalizer(ProfileId::class)] + #[IdNormalizer] private ProfileId $id; private string $name; diff --git a/tests/Benchmark/BasicImplementation/Events/ProfileCreated.php b/tests/Benchmark/BasicImplementation/Events/ProfileCreated.php index c8335915..c031b445 100644 --- a/tests/Benchmark/BasicImplementation/Events/ProfileCreated.php +++ b/tests/Benchmark/BasicImplementation/Events/ProfileCreated.php @@ -12,7 +12,7 @@ final class ProfileCreated { public function __construct( - #[IdNormalizer(ProfileId::class)] + #[IdNormalizer] public ProfileId $profileId, public string $name, ) { diff --git a/tests/Benchmark/BasicImplementation/Events/Reborn.php b/tests/Benchmark/BasicImplementation/Events/Reborn.php index 16f9dc9d..78c2a9a5 100644 --- a/tests/Benchmark/BasicImplementation/Events/Reborn.php +++ b/tests/Benchmark/BasicImplementation/Events/Reborn.php @@ -14,7 +14,7 @@ final class Reborn { public function __construct( - #[IdNormalizer(ProfileId::class)] + #[IdNormalizer] public ProfileId $profileId, public string $name, ) { diff --git a/tests/Integration/BankAccountSplitStream/Events/BalanceAdded.php b/tests/Integration/BankAccountSplitStream/Events/BalanceAdded.php index 13753e9f..f166b053 100644 --- a/tests/Integration/BankAccountSplitStream/Events/BalanceAdded.php +++ b/tests/Integration/BankAccountSplitStream/Events/BalanceAdded.php @@ -5,14 +5,14 @@ namespace Patchlevel\EventSourcing\Tests\Integration\BankAccountSplitStream\Events; use Patchlevel\EventSourcing\Attribute\Event; +use Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer; use Patchlevel\EventSourcing\Tests\Integration\BankAccountSplitStream\AccountId; -use Patchlevel\EventSourcing\Tests\Integration\BankAccountSplitStream\Normalizer\AccountIdNormalizer; #[Event('bank_account.balance_added')] final class BalanceAdded { public function __construct( - #[AccountIdNormalizer] + #[IdNormalizer] public AccountId $accountId, /** @var positive-int */ public int $balanceInCents, diff --git a/tests/Integration/BankAccountSplitStream/Events/BankAccountCreated.php b/tests/Integration/BankAccountSplitStream/Events/BankAccountCreated.php index 889e881a..651c2947 100644 --- a/tests/Integration/BankAccountSplitStream/Events/BankAccountCreated.php +++ b/tests/Integration/BankAccountSplitStream/Events/BankAccountCreated.php @@ -5,14 +5,14 @@ namespace Patchlevel\EventSourcing\Tests\Integration\BankAccountSplitStream\Events; use Patchlevel\EventSourcing\Attribute\Event; +use Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer; use Patchlevel\EventSourcing\Tests\Integration\BankAccountSplitStream\AccountId; -use Patchlevel\EventSourcing\Tests\Integration\BankAccountSplitStream\Normalizer\AccountIdNormalizer; #[Event('bank_account.created')] final class BankAccountCreated { public function __construct( - #[AccountIdNormalizer] + #[IdNormalizer] public AccountId $accountId, public string $name, ) { diff --git a/tests/Integration/BankAccountSplitStream/Events/MonthPassed.php b/tests/Integration/BankAccountSplitStream/Events/MonthPassed.php index ffe77b06..be04547f 100644 --- a/tests/Integration/BankAccountSplitStream/Events/MonthPassed.php +++ b/tests/Integration/BankAccountSplitStream/Events/MonthPassed.php @@ -6,15 +6,15 @@ use Patchlevel\EventSourcing\Attribute\Event; use Patchlevel\EventSourcing\Attribute\SplitStream; +use Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer; use Patchlevel\EventSourcing\Tests\Integration\BankAccountSplitStream\AccountId; -use Patchlevel\EventSourcing\Tests\Integration\BankAccountSplitStream\Normalizer\AccountIdNormalizer; #[Event('bank_account.month_passed')] #[SplitStream] final class MonthPassed { public function __construct( - #[AccountIdNormalizer] + #[IdNormalizer] public AccountId $accountId, public string $name, public int $balanceInCents, diff --git a/tests/Integration/BankAccountSplitStream/Normalizer/AccountIdNormalizer.php b/tests/Integration/BankAccountSplitStream/Normalizer/AccountIdNormalizer.php deleted file mode 100644 index 9c6daa98..00000000 --- a/tests/Integration/BankAccountSplitStream/Normalizer/AccountIdNormalizer.php +++ /dev/null @@ -1,38 +0,0 @@ -toString(); - } - - public function denormalize(mixed $value): AccountId|null - { - if ($value === null) { - return null; - } - - if (!is_string($value)) { - throw new InvalidArgumentException(); - } - - return AccountId::fromString($value); - } -} diff --git a/tests/Integration/BasicImplementation/Aggregate/Profile.php b/tests/Integration/BasicImplementation/Aggregate/Profile.php index 34b90b38..4b8063a2 100644 --- a/tests/Integration/BasicImplementation/Aggregate/Profile.php +++ b/tests/Integration/BasicImplementation/Aggregate/Profile.php @@ -9,8 +9,8 @@ use Patchlevel\EventSourcing\Attribute\Apply; use Patchlevel\EventSourcing\Attribute\Id; use Patchlevel\EventSourcing\Attribute\Snapshot; +use Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer; use Patchlevel\EventSourcing\Tests\Integration\BasicImplementation\Events\ProfileCreated; -use Patchlevel\EventSourcing\Tests\Integration\BasicImplementation\Normalizer\ProfileIdNormalizer; use Patchlevel\EventSourcing\Tests\Integration\BasicImplementation\ProfileId; #[Aggregate('profile')] @@ -18,7 +18,7 @@ final class Profile extends BasicAggregateRoot { #[Id] - #[ProfileIdNormalizer] + #[IdNormalizer] private ProfileId $id; private string $name; diff --git a/tests/Integration/BasicImplementation/Events/ProfileCreated.php b/tests/Integration/BasicImplementation/Events/ProfileCreated.php index 6def2156..6ce7ef6d 100644 --- a/tests/Integration/BasicImplementation/Events/ProfileCreated.php +++ b/tests/Integration/BasicImplementation/Events/ProfileCreated.php @@ -5,14 +5,14 @@ namespace Patchlevel\EventSourcing\Tests\Integration\BasicImplementation\Events; use Patchlevel\EventSourcing\Attribute\Event; -use Patchlevel\EventSourcing\Tests\Integration\BasicImplementation\Normalizer\ProfileIdNormalizer; +use Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer; use Patchlevel\EventSourcing\Tests\Integration\BasicImplementation\ProfileId; #[Event('profile.created')] final class ProfileCreated { public function __construct( - #[ProfileIdNormalizer] + #[IdNormalizer] public ProfileId $profileId, public string $name, ) { diff --git a/tests/Integration/BasicImplementation/Normalizer/ProfileIdNormalizer.php b/tests/Integration/BasicImplementation/Normalizer/ProfileIdNormalizer.php deleted file mode 100644 index 28fcd9e5..00000000 --- a/tests/Integration/BasicImplementation/Normalizer/ProfileIdNormalizer.php +++ /dev/null @@ -1,38 +0,0 @@ -toString(); - } - - public function denormalize(mixed $value): ProfileId|null - { - if ($value === null) { - return null; - } - - if (!is_string($value)) { - throw new InvalidArgumentException(); - } - - return ProfileId::fromString($value); - } -} diff --git a/tests/Integration/Outbox/Aggregate/Profile.php b/tests/Integration/Outbox/Aggregate/Profile.php index 8ed0ab53..0c48f70a 100644 --- a/tests/Integration/Outbox/Aggregate/Profile.php +++ b/tests/Integration/Outbox/Aggregate/Profile.php @@ -9,8 +9,8 @@ use Patchlevel\EventSourcing\Attribute\Apply; use Patchlevel\EventSourcing\Attribute\Id; use Patchlevel\EventSourcing\Attribute\Snapshot; +use Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer; use Patchlevel\EventSourcing\Tests\Integration\Outbox\Events\ProfileCreated; -use Patchlevel\EventSourcing\Tests\Integration\Outbox\Normalizer\ProfileIdNormalizer; use Patchlevel\EventSourcing\Tests\Integration\Outbox\ProfileId; #[Aggregate('profile')] @@ -18,7 +18,7 @@ final class Profile extends BasicAggregateRoot { #[Id] - #[ProfileIdNormalizer] + #[IdNormalizer] private ProfileId $id; private string $name; diff --git a/tests/Integration/Outbox/Events/ProfileCreated.php b/tests/Integration/Outbox/Events/ProfileCreated.php index 27cf670b..597b006e 100644 --- a/tests/Integration/Outbox/Events/ProfileCreated.php +++ b/tests/Integration/Outbox/Events/ProfileCreated.php @@ -5,14 +5,14 @@ namespace Patchlevel\EventSourcing\Tests\Integration\Outbox\Events; use Patchlevel\EventSourcing\Attribute\Event; -use Patchlevel\EventSourcing\Tests\Integration\Outbox\Normalizer\ProfileIdNormalizer; +use Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer; use Patchlevel\EventSourcing\Tests\Integration\Outbox\ProfileId; #[Event('profile.created')] final class ProfileCreated { public function __construct( - #[ProfileIdNormalizer] + #[IdNormalizer] public ProfileId $profileId, public string $name, ) { diff --git a/tests/Integration/Outbox/Normalizer/ProfileIdNormalizer.php b/tests/Integration/Outbox/Normalizer/ProfileIdNormalizer.php deleted file mode 100644 index d44ae288..00000000 --- a/tests/Integration/Outbox/Normalizer/ProfileIdNormalizer.php +++ /dev/null @@ -1,38 +0,0 @@ -toString(); - } - - public function denormalize(mixed $value): ProfileId|null - { - if ($value === null) { - return null; - } - - if (!is_string($value)) { - throw new InvalidArgumentException(); - } - - return ProfileId::fromString($value); - } -} diff --git a/tests/Integration/Projectionist/Aggregate/Profile.php b/tests/Integration/Projectionist/Aggregate/Profile.php index 95cc98b2..0d827bad 100644 --- a/tests/Integration/Projectionist/Aggregate/Profile.php +++ b/tests/Integration/Projectionist/Aggregate/Profile.php @@ -8,15 +8,15 @@ use Patchlevel\EventSourcing\Attribute\Aggregate; use Patchlevel\EventSourcing\Attribute\Apply; use Patchlevel\EventSourcing\Attribute\Id; +use Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer; use Patchlevel\EventSourcing\Tests\Integration\Projectionist\Events\ProfileCreated; -use Patchlevel\EventSourcing\Tests\Integration\Projectionist\Normalizer\ProfileIdNormalizer; use Patchlevel\EventSourcing\Tests\Integration\Projectionist\ProfileId; #[Aggregate('profile')] final class Profile extends BasicAggregateRoot { #[Id] - #[ProfileIdNormalizer] + #[IdNormalizer] private ProfileId $id; private string $name; diff --git a/tests/Integration/Projectionist/Events/ProfileCreated.php b/tests/Integration/Projectionist/Events/ProfileCreated.php index 7626f32e..73c2f922 100644 --- a/tests/Integration/Projectionist/Events/ProfileCreated.php +++ b/tests/Integration/Projectionist/Events/ProfileCreated.php @@ -5,14 +5,14 @@ namespace Patchlevel\EventSourcing\Tests\Integration\Projectionist\Events; use Patchlevel\EventSourcing\Attribute\Event; -use Patchlevel\EventSourcing\Tests\Integration\Projectionist\Normalizer\ProfileIdNormalizer; +use Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer; use Patchlevel\EventSourcing\Tests\Integration\Projectionist\ProfileId; #[Event('profile.created')] final class ProfileCreated { public function __construct( - #[ProfileIdNormalizer] + #[IdNormalizer] public ProfileId $profileId, public string $name, ) { diff --git a/tests/Integration/Projectionist/Normalizer/ProfileIdNormalizer.php b/tests/Integration/Projectionist/Normalizer/ProfileIdNormalizer.php deleted file mode 100644 index 6641d127..00000000 --- a/tests/Integration/Projectionist/Normalizer/ProfileIdNormalizer.php +++ /dev/null @@ -1,38 +0,0 @@ -toString(); - } - - public function denormalize(mixed $value): ProfileId|null - { - if ($value === null) { - return null; - } - - if (!is_string($value)) { - throw new InvalidArgumentException(); - } - - return ProfileId::fromString($value); - } -} diff --git a/tests/Integration/Store/Events/ProfileCreated.php b/tests/Integration/Store/Events/ProfileCreated.php index 20035d1b..4304c487 100644 --- a/tests/Integration/Store/Events/ProfileCreated.php +++ b/tests/Integration/Store/Events/ProfileCreated.php @@ -5,14 +5,14 @@ namespace Patchlevel\EventSourcing\Tests\Integration\Store\Events; use Patchlevel\EventSourcing\Attribute\Event; -use Patchlevel\EventSourcing\Tests\Integration\Store\Normalizer\ProfileIdNormalizer; +use Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer; use Patchlevel\EventSourcing\Tests\Integration\Store\ProfileId; #[Event('profile.created')] final class ProfileCreated { public function __construct( - #[ProfileIdNormalizer] + #[IdNormalizer] public ProfileId $profileId, public string $name, ) { diff --git a/tests/Integration/Store/Normalizer/ProfileIdNormalizer.php b/tests/Integration/Store/Normalizer/ProfileIdNormalizer.php deleted file mode 100644 index 87fa0124..00000000 --- a/tests/Integration/Store/Normalizer/ProfileIdNormalizer.php +++ /dev/null @@ -1,38 +0,0 @@ -toString(); - } - - public function denormalize(mixed $value): ProfileId|null - { - if ($value === null) { - return null; - } - - if (!is_string($value)) { - throw new InvalidArgumentException(); - } - - return ProfileId::fromString($value); - } -} diff --git a/tests/Integration/Store/Profile.php b/tests/Integration/Store/Profile.php index f78412d0..38952065 100644 --- a/tests/Integration/Store/Profile.php +++ b/tests/Integration/Store/Profile.php @@ -8,15 +8,15 @@ use Patchlevel\EventSourcing\Attribute\Aggregate; use Patchlevel\EventSourcing\Attribute\Apply; use Patchlevel\EventSourcing\Attribute\Id; +use Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer; use Patchlevel\EventSourcing\Tests\Integration\Projectionist\Events\ProfileCreated; -use Patchlevel\EventSourcing\Tests\Integration\Projectionist\Normalizer\ProfileIdNormalizer; use Patchlevel\EventSourcing\Tests\Integration\Projectionist\ProfileId; #[Aggregate('profile')] final class Profile extends BasicAggregateRoot { #[Id] - #[ProfileIdNormalizer] + #[IdNormalizer] private ProfileId $id; private string $name; diff --git a/tests/Unit/Fixture/ProfileCreated.php b/tests/Unit/Fixture/ProfileCreated.php index 7caba0d2..5df3db48 100644 --- a/tests/Unit/Fixture/ProfileCreated.php +++ b/tests/Unit/Fixture/ProfileCreated.php @@ -5,12 +5,13 @@ namespace Patchlevel\EventSourcing\Tests\Unit\Fixture; use Patchlevel\EventSourcing\Attribute\Event; +use Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer; #[Event('profile_created')] final class ProfileCreated { public function __construct( - #[ProfileIdNormalizer] + #[IdNormalizer] public ProfileId $profileId, #[EmailNormalizer] public Email $email, diff --git a/tests/Unit/Fixture/ProfileIdNormalizer.php b/tests/Unit/Fixture/ProfileIdNormalizer.php deleted file mode 100644 index f240a3d0..00000000 --- a/tests/Unit/Fixture/ProfileIdNormalizer.php +++ /dev/null @@ -1,38 +0,0 @@ -toString(); - } - - public function denormalize(mixed $value): ProfileId|null - { - if ($value === null) { - return null; - } - - if (!is_string($value)) { - throw new InvalidArgument(); - } - - return ProfileId::fromString($value); - } -} diff --git a/tests/Unit/Fixture/ProfileVisited.php b/tests/Unit/Fixture/ProfileVisited.php index a4d5a2a1..9ec34b7b 100644 --- a/tests/Unit/Fixture/ProfileVisited.php +++ b/tests/Unit/Fixture/ProfileVisited.php @@ -5,12 +5,13 @@ namespace Patchlevel\EventSourcing\Tests\Unit\Fixture; use Patchlevel\EventSourcing\Attribute\Event; +use Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer; #[Event('profile_visited')] final class ProfileVisited { public function __construct( - #[ProfileIdNormalizer] + #[IdNormalizer] public ProfileId $visitorId, ) { } diff --git a/tests/Unit/Fixture/ProfileWithSnapshot.php b/tests/Unit/Fixture/ProfileWithSnapshot.php index 3c0f65e5..d215c5f6 100644 --- a/tests/Unit/Fixture/ProfileWithSnapshot.php +++ b/tests/Unit/Fixture/ProfileWithSnapshot.php @@ -10,6 +10,7 @@ use Patchlevel\EventSourcing\Attribute\Id; use Patchlevel\EventSourcing\Attribute\Snapshot; use Patchlevel\EventSourcing\Attribute\SuppressMissingApply; +use Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer; use Patchlevel\Hydrator\Normalizer\ArrayNormalizer; #[Aggregate('profile_with_snapshot')] @@ -17,8 +18,8 @@ #[SuppressMissingApply([ProfileVisited::class])] final class ProfileWithSnapshot extends BasicAggregateRoot { - #[ProfileIdNormalizer] #[Id] + #[IdNormalizer] private ProfileId $id; #[EmailNormalizer] private Email $email; diff --git a/tests/Unit/Serializer/Normalizer/IdNormalizerTest.php b/tests/Unit/Serializer/Normalizer/IdNormalizerTest.php index 6f21c10c..ef8b1adb 100644 --- a/tests/Unit/Serializer/Normalizer/IdNormalizerTest.php +++ b/tests/Unit/Serializer/Normalizer/IdNormalizerTest.php @@ -8,10 +8,16 @@ use Patchlevel\EventSourcing\Aggregate\CustomId; use Patchlevel\EventSourcing\Aggregate\Uuid; use Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer; +use Patchlevel\EventSourcing\Tests\Unit\Fixture\ProfileCreated; +use Patchlevel\EventSourcing\Tests\Unit\Fixture\ProfileId; use Patchlevel\Hydrator\Normalizer\InvalidArgument; +use Patchlevel\Hydrator\Normalizer\InvalidType; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; use Ramsey\Uuid\Exception\InvalidUuidStringException; +use ReflectionClass; +use ReflectionType; +use RuntimeException; /** @covers \Patchlevel\EventSourcing\Serializer\Normalizer\IdNormalizer */ #[Attribute(Attribute::TARGET_PROPERTY)] @@ -67,4 +73,45 @@ public function testDenormalizeWithWrongValue(): void $this->expectException(InvalidArgument::class); $normalizer->denormalize(123); } + + public function testAutoDetect(): void + { + $normalizer = new IdNormalizer(); + $normalizer->handleReflectionType($this->reflectionType(ProfileCreated::class, 'profileId')); + + self::assertEquals(ProfileId::class, $normalizer->aggregateIdClass()); + } + + public function testAutoDetectMissingType(): void + { + $this->expectException(InvalidType::class); + + $normalizer = new IdNormalizer(); + $normalizer->aggregateIdClass(); + } + + public function testAutoDetectMissingTypeBecauseNull(): void + { + $this->expectException(InvalidType::class); + + $normalizer = new IdNormalizer(); + $normalizer->handleReflectionType(null); + + $normalizer->aggregateIdClass(); + } + + /** @param class-string $class */ + private function reflectionType(string $class, string $property): ReflectionType + { + $reflection = new ReflectionClass($class); + $property = $reflection->getProperty($property); + + $type = $property->getType(); + + if (!$type instanceof ReflectionType) { + throw new RuntimeException('no type'); + } + + return $type; + } } From 80b2b8c2ff3b967f95d2829dd618a21126555033 Mon Sep 17 00:00:00 2001 From: David Badura Date: Mon, 26 Feb 2024 10:13:54 +0100 Subject: [PATCH 2/2] update docs --- docs/pages/normalizer.md | 50 +++++++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/docs/pages/normalizer.md b/docs/pages/normalizer.md index 6c6eff3b..5b86c591 100644 --- a/docs/pages/normalizer.md +++ b/docs/pages/normalizer.md @@ -5,6 +5,11 @@ For example DateTime, enums or value objects. You can do that too. However, you must define a normalizer for this so that the library knows how to write this data to the database and load it again. +!!! note + + The underlying system called hydrator exists as a library. + You can find out more details [here](https://github.com/patchlevel/hydrator). + ## Usage You have to set the normalizer to the properties using the specific normalizer class. @@ -19,7 +24,7 @@ final class DTO } ``` -The whole thing also works with property promotion. +The whole thing also works with property promotion and readonly properties. ```php use Patchlevel\Hydrator\Normalizer\DateTimeImmutableNormalizer; @@ -180,7 +185,8 @@ To normalize a `DateTimeZone` one can use the `DateTimeZoneNormalizer`. ```php use Patchlevel\Hydrator\Normalizer\DateTimeZoneNormalizer; -final class DTO { +final class DTO +{ #[DateTimeZoneNormalizer] public DateTimeZone $timeZone; } @@ -193,7 +199,8 @@ Backed enums can also be normalized. ```php use Patchlevel\Hydrator\Normalizer\EnumNormalizer; -final class DTO { +final class DTO +{ #[EnumNormalizer] public Status $status; } @@ -204,7 +211,8 @@ You can also specify the enum class. ```php use Patchlevel\Hydrator\Normalizer\EnumNormalizer; -final class DTO { +final class DTO +{ #[EnumNormalizer(Status::class)] public Status $status; } @@ -218,7 +226,8 @@ If you have your own AggregateRootId, you can use the `IdNormalizer`. use Patchlevel\EventSourcing\Aggregate\Uuid; use Patchlevel\Hydrator\Normalizer\IdNormalizer; -final class DTO { +final class DTO +{ #[IdNormalizer] public Uuid $id; } @@ -230,7 +239,8 @@ Optional you can also define the type of the id. use Patchlevel\EventSourcing\Aggregate\Uuid; use Patchlevel\Hydrator\Normalizer\IdNormalizer; -final class DTO { +final class DTO +{ #[IdNormalizer(Uuid::class)] public Uuid $id; } @@ -244,7 +254,8 @@ Internally, it uses the `Hydrator` to normalize and denormalize the object. ```php use Patchlevel\Hydrator\Normalizer\ObjectNormalizer; -final class DTO { +final class DTO +{ #[ObjectNormalizer] public ComplexObject $object; } @@ -255,7 +266,8 @@ Optional you can also define the type of the object. ```php use Patchlevel\Hydrator\Normalizer\ObjectNormalizer; -final class DTO { +final class DTO +{ #[ObjectNormalizer(ComplexObject::class)] public object $object; } @@ -375,3 +387,25 @@ The whole thing looks like this NormalizedName also works for snapshots. But since a snapshot is just a cache, you can also just invalidate it, if you have backwards compatibility break in the property name + +## Ignore + +You can also ignore properties with the `Ignore` attribute. + +```php +use Patchlevel\Hydrator\Attribute\Ignore; + +final class DTO +{ + #[Ignore] + public string $name +} +``` + +## Learn more + +* [How to use the Hydrator](https://github.com/patchlevel/hydrator) +* [How to define aggregates](aggregate.md) +* [How to snapshot aggregates](snapshots.md) +* [How to create own aggregate id](aggregate_id.md) +* [How to define events](events.md) \ No newline at end of file