Skip to content

Commit

Permalink
Merge pull request #295 from neos/enumNormalization
Browse files Browse the repository at this point in the history
Add normalization capabilities for backed enums
  • Loading branch information
bwaidelich authored Feb 17, 2022
2 parents c679747 + 05a9a2a commit c6fd635
Show file tree
Hide file tree
Showing 11 changed files with 401 additions and 1 deletion.
9 changes: 8 additions & 1 deletion Classes/EventStore/EventNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down
85 changes: 85 additions & 0 deletions Tests/Unit/EventStore/EventNormalizerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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());
}
}
29 changes: 29 additions & 0 deletions Tests/Unit/EventStore/Fixture/ArrayValueObject.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);

namespace Neos\EventSourcing\Tests\Unit\EventStore\Fixture;

final class ArrayValueObject implements \JsonSerializable
{
private $value;

private function __construct(array $value)
{
$this->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;
}
}
12 changes: 12 additions & 0 deletions Tests/Unit/EventStore/Fixture/BackedEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);

namespace Neos\EventSourcing\Tests\Unit\EventStore\Fixture;

enum BackedEnum: string
{
case Hearts = 'H';
case Diamonds = 'D';
case Clubs = 'C';
case Spades = 'S';
}
29 changes: 29 additions & 0 deletions Tests/Unit/EventStore/Fixture/BooleanValueObject.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);

namespace Neos\EventSourcing\Tests\Unit\EventStore\Fixture;

final class BooleanValueObject implements \JsonSerializable
{
private $value;

private function __construct(bool $value)
{
$this->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;
}
}
25 changes: 25 additions & 0 deletions Tests/Unit/EventStore/Fixture/EventWithBackedEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);

namespace Neos\EventSourcing\Tests\Unit\EventStore\Fixture;

use Neos\EventSourcing\Event\DomainEventInterface;

final class EventWithBackedEnum implements DomainEventInterface
{
/**
* @var BackedEnum
*/
private $enum;

public function __construct(BackedEnum $enum)
{
$this->enum = $enum;
}

public function getEnum(): BackedEnum
{
return $this->enum;
}

}
33 changes: 33 additions & 0 deletions Tests/Unit/EventStore/Fixture/EventWithDateTime.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);

namespace Neos\EventSourcing\Tests\Unit\EventStore\Fixture;

use Neos\EventSourcing\Event\DomainEventInterface;

final class EventWithDateTime implements DomainEventInterface
{
/**
* @var \DateTimeInterface
*/
private $date;

public function __construct(\DateTimeInterface $date)
{
$this->date = $date;
}

/**
* @return \DateTimeInterface
*/
public function getDate(): \DateTimeInterface
{
return $this->date;
}

public function equals(self $other): bool
{
return $other->date->getTimestamp() === $this->date->getTimestamp();
}

}
93 changes: 93 additions & 0 deletions Tests/Unit/EventStore/Fixture/EventWithValueObjects.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php
declare(strict_types=1);

namespace Neos\EventSourcing\Tests\Unit\EventStore\Fixture;

use Neos\EventSourcing\Event\DomainEventInterface;

final class EventWithValueObjects implements DomainEventInterface
{
/**
* @var ArrayValueObject
*/
private $array;

/**
* @var StringValueObject
*/
private $string;

/**
* @var IntegerValueObject
*/
private $integer;

/**
* @var FloatValueObject
*/
private $float;

/**
* @var BooleanValueObject
*/
private $boolean;

public function __construct(ArrayValueObject $array, StringValueObject $string, IntegerValueObject $integer, FloatValueObject $float, BooleanValueObject $boolean)
{
$this->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);
}

}
29 changes: 29 additions & 0 deletions Tests/Unit/EventStore/Fixture/FloatValueObject.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);

namespace Neos\EventSourcing\Tests\Unit\EventStore\Fixture;

final class FloatValueObject implements \JsonSerializable
{
private $value;

private function __construct(float $value)
{
$this->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;
}
}
Loading

0 comments on commit c6fd635

Please sign in to comment.