diff --git a/Classes/EventStore/EventNormalizer.php b/Classes/EventStore/EventNormalizer.php index feebbda9..3c6458eb 100644 --- a/Classes/EventStore/EventNormalizer.php +++ b/Classes/EventStore/EventNormalizer.php @@ -18,6 +18,7 @@ use Neos\EventSourcing\EventStore\Normalizer\ValueObjectNormalizer; use Neos\Flow\Annotations as Flow; use Symfony\Component\Serializer\Exception\ExceptionInterface as SerializerException; +use Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer; use Symfony\Component\Serializer\Serializer; @@ -48,7 +49,13 @@ public function __construct(EventTypeResolverInterface $eventTypeResolver) $this->eventTypeResolver = $eventTypeResolver; // TODO: make normalizers configurable - $normalizers = [new DateTimeNormalizer(), new JsonSerializableNormalizer(), new ValueObjectNormalizer(), new ProxyAwareObjectNormalizer()]; + $normalizers = [ + new BackedEnumNormalizer(), + new DateTimeNormalizer(), + new JsonSerializableNormalizer(), + new ValueObjectNormalizer(), + new ProxyAwareObjectNormalizer() + ]; $this->serializer = new Serializer($normalizers); } diff --git a/Tests/Unit/EventStore/EventNormalizerTest.php b/Tests/Unit/EventStore/EventNormalizerTest.php index 58e85c30..4a66d7e4 100644 --- a/Tests/Unit/EventStore/EventNormalizerTest.php +++ b/Tests/Unit/EventStore/EventNormalizerTest.php @@ -2,9 +2,19 @@ declare(strict_types=1); namespace Neos\EventSourcing\Tests\Unit\EventStore; +use Neos\EventSourcing\Event\DomainEventInterface; use Neos\EventSourcing\Event\EventTypeResolver; use Neos\EventSourcing\Event\EventTypeResolverInterface; use Neos\EventSourcing\EventStore\EventNormalizer; +use Neos\EventSourcing\Tests\Unit\EventStore\Fixture\ArrayValueObject; +use Neos\EventSourcing\Tests\Unit\EventStore\Fixture\BackedEnum; +use Neos\EventSourcing\Tests\Unit\EventStore\Fixture\BooleanValueObject; +use Neos\EventSourcing\Tests\Unit\EventStore\Fixture\EventWithBackedEnum; +use Neos\EventSourcing\Tests\Unit\EventStore\Fixture\EventWithDateTime; +use Neos\EventSourcing\Tests\Unit\EventStore\Fixture\EventWithValueObjects; +use Neos\EventSourcing\Tests\Unit\EventStore\Fixture\FloatValueObject; +use Neos\EventSourcing\Tests\Unit\EventStore\Fixture\IntegerValueObject; +use Neos\EventSourcing\Tests\Unit\EventStore\Fixture\StringValueObject; use Neos\Flow\Tests\UnitTestCase; use PHPUnit\Framework\MockObject\MockObject; @@ -54,4 +64,79 @@ public function denormalizeConstructsArrayBasedEventWithCorrectPayload(): void $event = $this->eventNormalizer->denormalize($normalizedEvent, 'Some.Event:Type'); self::assertSame($mockData, $event->getData()); } + + public function normalizeDataProvider(): \generator + { + $dateTimeImmutable = new \DateTimeImmutable('1980-12-13'); + $dateTime = new \DateTime('1980-12-13 15:34:19'); + $jsonSerializable = new class implements \JsonSerializable { public function jsonSerialize(): array { return ['foo' => 'bar'];}}; + yield 'dateTimeImmutable' => ['value' => $dateTimeImmutable, 'expectedResult' => $dateTimeImmutable->format(\DateTimeInterface::RFC3339)]; + yield 'dateTime' => ['value' => $dateTime, 'expectedResult' => $dateTime->format(\DateTimeInterface::RFC3339)]; + yield 'jsonSerializable' => ['value' => $jsonSerializable, 'expectedResult' => ['foo' => 'bar']]; + } + + /** + * @test + * @dataProvider normalizeDataProvider + */ + public function normalizeTests($value, $expectedResult): void + { + $event = $this->getMockBuilder(DomainEventInterface::class)->addMethods(['getProperty'])->getMock(); + /** @noinspection MockingMethodsCorrectnessInspection */ + $event->method('getProperty')->willReturn($value); + $result = $this->eventNormalizer->normalize($event); + self::assertSame(['property' => $expectedResult], $result); + } + + public function denormalizeDataProvider(): \generator + { + $dateTimeImmutable = new \DateTimeImmutable('1980-12-13'); + $dateTime = new \DateTime('1980-12-13 15:34:19'); + $array = ['foo' => 'bar', 'Bar' => ['nested' => 'foos']]; + $string = 'Some string with späcial characterß'; + $integer = 42; + $float = 42.987; + $boolean = true; + yield 'dateTimeImmutable' => ['data' => ['date' => $dateTimeImmutable->format(\DateTimeInterface::RFC3339)], 'expectedResult' => new EventWithDateTime($dateTimeImmutable)]; + yield 'dateTime' => ['data' => ['date' => $dateTime->format(\DateTimeInterface::RFC3339)], 'expectedResult' => new EventWithDateTime($dateTime)]; + yield 'valueObjects' => ['data' => compact('array', 'string', 'integer', 'float', 'boolean'), 'expectedResult' => new EventWithValueObjects(ArrayValueObject::fromArray($array), StringValueObject::fromString($string), IntegerValueObject::fromInteger($integer), FloatValueObject::fromFloat($float), BooleanValueObject::fromBoolean($boolean))]; + } + + /** + * @test + * @dataProvider denormalizeDataProvider + */ + public function denormalizeTests(array $data, object $expectedResult): void + { + $this->mockEventTypeResolver->method('getEventClassNameByType')->with('Some.Event:Type')->willReturn(get_class($expectedResult)); + $result = $this->eventNormalizer->denormalize($data, 'Some.Event:Type'); + self::assertObjectEquals($expectedResult, $result); + } + + /** + * @test + */ + public function normalizeSupportsBackedEnums(): void + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Backed enums are only available with PHP 8.1+'); + } + $event = new EventWithBackedEnum(BackedEnum::Hearts); + $result = $this->eventNormalizer->normalize($event); + self::assertSame(['enum' => 'H'], $result); + } + + /** + * @test + */ + public function denormalizeSupportsBackedEnums(): void + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('Backed enums are only available with PHP 8.1+'); + } + $this->mockEventTypeResolver->method('getEventClassNameByType')->with('Some.Event:Type')->willReturn(EventWithBackedEnum::class); + /** @var EventWithBackedEnum $event */ + $event = $this->eventNormalizer->denormalize(['enum' => 'C'], 'Some.Event:Type'); + self::assertSame(BackedEnum::Clubs, $event->getEnum()); + } } diff --git a/Tests/Unit/EventStore/Fixture/ArrayValueObject.php b/Tests/Unit/EventStore/Fixture/ArrayValueObject.php new file mode 100644 index 00000000..92b14a71 --- /dev/null +++ b/Tests/Unit/EventStore/Fixture/ArrayValueObject.php @@ -0,0 +1,29 @@ +value = $value; + } + + public static function fromArray(array $value): self + { + return new self($value); + } + + public function equals(self $other): bool + { + return $other->value === $this->value; + } + + public function jsonSerialize(): array + { + return $this->value; + } +} diff --git a/Tests/Unit/EventStore/Fixture/BackedEnum.php b/Tests/Unit/EventStore/Fixture/BackedEnum.php new file mode 100644 index 00000000..f1475f53 --- /dev/null +++ b/Tests/Unit/EventStore/Fixture/BackedEnum.php @@ -0,0 +1,12 @@ +value = $value; + } + + public static function fromBoolean(bool $value): self + { + return new self($value); + } + + public function equals(self $other): bool + { + return $other->value === $this->value; + } + + public function jsonSerialize(): bool + { + return $this->value; + } +} diff --git a/Tests/Unit/EventStore/Fixture/EventWithBackedEnum.php b/Tests/Unit/EventStore/Fixture/EventWithBackedEnum.php new file mode 100644 index 00000000..8de8b8a9 --- /dev/null +++ b/Tests/Unit/EventStore/Fixture/EventWithBackedEnum.php @@ -0,0 +1,25 @@ +enum = $enum; + } + + public function getEnum(): BackedEnum + { + return $this->enum; + } + +} diff --git a/Tests/Unit/EventStore/Fixture/EventWithDateTime.php b/Tests/Unit/EventStore/Fixture/EventWithDateTime.php new file mode 100644 index 00000000..86231265 --- /dev/null +++ b/Tests/Unit/EventStore/Fixture/EventWithDateTime.php @@ -0,0 +1,33 @@ +date = $date; + } + + /** + * @return \DateTimeInterface + */ + public function getDate(): \DateTimeInterface + { + return $this->date; + } + + public function equals(self $other): bool + { + return $other->date->getTimestamp() === $this->date->getTimestamp(); + } + +} diff --git a/Tests/Unit/EventStore/Fixture/EventWithValueObjects.php b/Tests/Unit/EventStore/Fixture/EventWithValueObjects.php new file mode 100644 index 00000000..64d38a73 --- /dev/null +++ b/Tests/Unit/EventStore/Fixture/EventWithValueObjects.php @@ -0,0 +1,93 @@ +array = $array; + $this->string = $string; + $this->integer = $integer; + $this->float = $float; + $this->boolean = $boolean; + } + + /** + * @return ArrayValueObject + */ + public function getArray(): ArrayValueObject + { + return $this->array; + } + + /** + * @return StringValueObject + */ + public function getString(): StringValueObject + { + return $this->string; + } + + /** + * @return IntegerValueObject + */ + public function getInteger(): IntegerValueObject + { + return $this->integer; + } + + /** + * @return FloatValueObject + */ + public function getFloat(): FloatValueObject + { + return $this->float; + } + + /** + * @return BooleanValueObject + */ + public function getBoolean(): BooleanValueObject + { + return $this->boolean; + } + + public function equals(self $other): bool + { + return $other->array->equals($this->array) + && $other->string->equals($this->string) + && $other->integer->equals($this->integer) + && $other->float->equals($this->float) + && $other->boolean->equals($this->boolean); + } + +} diff --git a/Tests/Unit/EventStore/Fixture/FloatValueObject.php b/Tests/Unit/EventStore/Fixture/FloatValueObject.php new file mode 100644 index 00000000..d7dd3cbf --- /dev/null +++ b/Tests/Unit/EventStore/Fixture/FloatValueObject.php @@ -0,0 +1,29 @@ +value = $value; + } + + public static function fromFloat(float $value): self + { + return new self($value); + } + + public function equals(self $other): bool + { + return $other->value === $this->value; + } + + public function jsonSerialize(): float + { + return $this->value; + } +} diff --git a/Tests/Unit/EventStore/Fixture/IntegerValueObject.php b/Tests/Unit/EventStore/Fixture/IntegerValueObject.php new file mode 100644 index 00000000..3311f069 --- /dev/null +++ b/Tests/Unit/EventStore/Fixture/IntegerValueObject.php @@ -0,0 +1,29 @@ +value = $value; + } + + public static function fromInteger(int $value): self + { + return new self($value); + } + + public function equals(self $other): bool + { + return $other->value === $this->value; + } + + public function jsonSerialize(): int + { + return $this->value; + } +} diff --git a/Tests/Unit/EventStore/Fixture/StringValueObject.php b/Tests/Unit/EventStore/Fixture/StringValueObject.php new file mode 100644 index 00000000..de62c0c9 --- /dev/null +++ b/Tests/Unit/EventStore/Fixture/StringValueObject.php @@ -0,0 +1,29 @@ +value = $value; + } + + public static function fromString(string $value): self + { + return new self($value); + } + + public function equals(self $other): bool + { + return $other->value === $this->value; + } + + public function jsonSerialize(): string + { + return $this->value; + } +}