Skip to content

Commit c6fd635

Browse files
authored
Merge pull request #295 from neos/enumNormalization
Add normalization capabilities for backed enums
2 parents c679747 + 05a9a2a commit c6fd635

11 files changed

+401
-1
lines changed

Classes/EventStore/EventNormalizer.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Neos\EventSourcing\EventStore\Normalizer\ValueObjectNormalizer;
1919
use Neos\Flow\Annotations as Flow;
2020
use Symfony\Component\Serializer\Exception\ExceptionInterface as SerializerException;
21+
use Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer;
2122
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
2223
use Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer;
2324
use Symfony\Component\Serializer\Serializer;
@@ -48,7 +49,13 @@ public function __construct(EventTypeResolverInterface $eventTypeResolver)
4849
$this->eventTypeResolver = $eventTypeResolver;
4950

5051
// TODO: make normalizers configurable
51-
$normalizers = [new DateTimeNormalizer(), new JsonSerializableNormalizer(), new ValueObjectNormalizer(), new ProxyAwareObjectNormalizer()];
52+
$normalizers = [
53+
new BackedEnumNormalizer(),
54+
new DateTimeNormalizer(),
55+
new JsonSerializableNormalizer(),
56+
new ValueObjectNormalizer(),
57+
new ProxyAwareObjectNormalizer()
58+
];
5259
$this->serializer = new Serializer($normalizers);
5360
}
5461

Tests/Unit/EventStore/EventNormalizerTest.php

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,19 @@
22
declare(strict_types=1);
33
namespace Neos\EventSourcing\Tests\Unit\EventStore;
44

5+
use Neos\EventSourcing\Event\DomainEventInterface;
56
use Neos\EventSourcing\Event\EventTypeResolver;
67
use Neos\EventSourcing\Event\EventTypeResolverInterface;
78
use Neos\EventSourcing\EventStore\EventNormalizer;
9+
use Neos\EventSourcing\Tests\Unit\EventStore\Fixture\ArrayValueObject;
10+
use Neos\EventSourcing\Tests\Unit\EventStore\Fixture\BackedEnum;
11+
use Neos\EventSourcing\Tests\Unit\EventStore\Fixture\BooleanValueObject;
12+
use Neos\EventSourcing\Tests\Unit\EventStore\Fixture\EventWithBackedEnum;
13+
use Neos\EventSourcing\Tests\Unit\EventStore\Fixture\EventWithDateTime;
14+
use Neos\EventSourcing\Tests\Unit\EventStore\Fixture\EventWithValueObjects;
15+
use Neos\EventSourcing\Tests\Unit\EventStore\Fixture\FloatValueObject;
16+
use Neos\EventSourcing\Tests\Unit\EventStore\Fixture\IntegerValueObject;
17+
use Neos\EventSourcing\Tests\Unit\EventStore\Fixture\StringValueObject;
818
use Neos\Flow\Tests\UnitTestCase;
919
use PHPUnit\Framework\MockObject\MockObject;
1020

@@ -54,4 +64,79 @@ public function denormalizeConstructsArrayBasedEventWithCorrectPayload(): void
5464
$event = $this->eventNormalizer->denormalize($normalizedEvent, 'Some.Event:Type');
5565
self::assertSame($mockData, $event->getData());
5666
}
67+
68+
public function normalizeDataProvider(): \generator
69+
{
70+
$dateTimeImmutable = new \DateTimeImmutable('1980-12-13');
71+
$dateTime = new \DateTime('1980-12-13 15:34:19');
72+
$jsonSerializable = new class implements \JsonSerializable { public function jsonSerialize(): array { return ['foo' => 'bar'];}};
73+
yield 'dateTimeImmutable' => ['value' => $dateTimeImmutable, 'expectedResult' => $dateTimeImmutable->format(\DateTimeInterface::RFC3339)];
74+
yield 'dateTime' => ['value' => $dateTime, 'expectedResult' => $dateTime->format(\DateTimeInterface::RFC3339)];
75+
yield 'jsonSerializable' => ['value' => $jsonSerializable, 'expectedResult' => ['foo' => 'bar']];
76+
}
77+
78+
/**
79+
* @test
80+
* @dataProvider normalizeDataProvider
81+
*/
82+
public function normalizeTests($value, $expectedResult): void
83+
{
84+
$event = $this->getMockBuilder(DomainEventInterface::class)->addMethods(['getProperty'])->getMock();
85+
/** @noinspection MockingMethodsCorrectnessInspection */
86+
$event->method('getProperty')->willReturn($value);
87+
$result = $this->eventNormalizer->normalize($event);
88+
self::assertSame(['property' => $expectedResult], $result);
89+
}
90+
91+
public function denormalizeDataProvider(): \generator
92+
{
93+
$dateTimeImmutable = new \DateTimeImmutable('1980-12-13');
94+
$dateTime = new \DateTime('1980-12-13 15:34:19');
95+
$array = ['foo' => 'bar', 'Bar' => ['nested' => 'foos']];
96+
$string = 'Some string with späcial characterß';
97+
$integer = 42;
98+
$float = 42.987;
99+
$boolean = true;
100+
yield 'dateTimeImmutable' => ['data' => ['date' => $dateTimeImmutable->format(\DateTimeInterface::RFC3339)], 'expectedResult' => new EventWithDateTime($dateTimeImmutable)];
101+
yield 'dateTime' => ['data' => ['date' => $dateTime->format(\DateTimeInterface::RFC3339)], 'expectedResult' => new EventWithDateTime($dateTime)];
102+
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))];
103+
}
104+
105+
/**
106+
* @test
107+
* @dataProvider denormalizeDataProvider
108+
*/
109+
public function denormalizeTests(array $data, object $expectedResult): void
110+
{
111+
$this->mockEventTypeResolver->method('getEventClassNameByType')->with('Some.Event:Type')->willReturn(get_class($expectedResult));
112+
$result = $this->eventNormalizer->denormalize($data, 'Some.Event:Type');
113+
self::assertObjectEquals($expectedResult, $result);
114+
}
115+
116+
/**
117+
* @test
118+
*/
119+
public function normalizeSupportsBackedEnums(): void
120+
{
121+
if (PHP_VERSION_ID < 80100) {
122+
$this->markTestSkipped('Backed enums are only available with PHP 8.1+');
123+
}
124+
$event = new EventWithBackedEnum(BackedEnum::Hearts);
125+
$result = $this->eventNormalizer->normalize($event);
126+
self::assertSame(['enum' => 'H'], $result);
127+
}
128+
129+
/**
130+
* @test
131+
*/
132+
public function denormalizeSupportsBackedEnums(): void
133+
{
134+
if (PHP_VERSION_ID < 80100) {
135+
$this->markTestSkipped('Backed enums are only available with PHP 8.1+');
136+
}
137+
$this->mockEventTypeResolver->method('getEventClassNameByType')->with('Some.Event:Type')->willReturn(EventWithBackedEnum::class);
138+
/** @var EventWithBackedEnum $event */
139+
$event = $this->eventNormalizer->denormalize(['enum' => 'C'], 'Some.Event:Type');
140+
self::assertSame(BackedEnum::Clubs, $event->getEnum());
141+
}
57142
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Neos\EventSourcing\Tests\Unit\EventStore\Fixture;
5+
6+
final class ArrayValueObject implements \JsonSerializable
7+
{
8+
private $value;
9+
10+
private function __construct(array $value)
11+
{
12+
$this->value = $value;
13+
}
14+
15+
public static function fromArray(array $value): self
16+
{
17+
return new self($value);
18+
}
19+
20+
public function equals(self $other): bool
21+
{
22+
return $other->value === $this->value;
23+
}
24+
25+
public function jsonSerialize(): array
26+
{
27+
return $this->value;
28+
}
29+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Neos\EventSourcing\Tests\Unit\EventStore\Fixture;
5+
6+
enum BackedEnum: string
7+
{
8+
case Hearts = 'H';
9+
case Diamonds = 'D';
10+
case Clubs = 'C';
11+
case Spades = 'S';
12+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Neos\EventSourcing\Tests\Unit\EventStore\Fixture;
5+
6+
final class BooleanValueObject implements \JsonSerializable
7+
{
8+
private $value;
9+
10+
private function __construct(bool $value)
11+
{
12+
$this->value = $value;
13+
}
14+
15+
public static function fromBoolean(bool $value): self
16+
{
17+
return new self($value);
18+
}
19+
20+
public function equals(self $other): bool
21+
{
22+
return $other->value === $this->value;
23+
}
24+
25+
public function jsonSerialize(): bool
26+
{
27+
return $this->value;
28+
}
29+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Neos\EventSourcing\Tests\Unit\EventStore\Fixture;
5+
6+
use Neos\EventSourcing\Event\DomainEventInterface;
7+
8+
final class EventWithBackedEnum implements DomainEventInterface
9+
{
10+
/**
11+
* @var BackedEnum
12+
*/
13+
private $enum;
14+
15+
public function __construct(BackedEnum $enum)
16+
{
17+
$this->enum = $enum;
18+
}
19+
20+
public function getEnum(): BackedEnum
21+
{
22+
return $this->enum;
23+
}
24+
25+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Neos\EventSourcing\Tests\Unit\EventStore\Fixture;
5+
6+
use Neos\EventSourcing\Event\DomainEventInterface;
7+
8+
final class EventWithDateTime implements DomainEventInterface
9+
{
10+
/**
11+
* @var \DateTimeInterface
12+
*/
13+
private $date;
14+
15+
public function __construct(\DateTimeInterface $date)
16+
{
17+
$this->date = $date;
18+
}
19+
20+
/**
21+
* @return \DateTimeInterface
22+
*/
23+
public function getDate(): \DateTimeInterface
24+
{
25+
return $this->date;
26+
}
27+
28+
public function equals(self $other): bool
29+
{
30+
return $other->date->getTimestamp() === $this->date->getTimestamp();
31+
}
32+
33+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Neos\EventSourcing\Tests\Unit\EventStore\Fixture;
5+
6+
use Neos\EventSourcing\Event\DomainEventInterface;
7+
8+
final class EventWithValueObjects implements DomainEventInterface
9+
{
10+
/**
11+
* @var ArrayValueObject
12+
*/
13+
private $array;
14+
15+
/**
16+
* @var StringValueObject
17+
*/
18+
private $string;
19+
20+
/**
21+
* @var IntegerValueObject
22+
*/
23+
private $integer;
24+
25+
/**
26+
* @var FloatValueObject
27+
*/
28+
private $float;
29+
30+
/**
31+
* @var BooleanValueObject
32+
*/
33+
private $boolean;
34+
35+
public function __construct(ArrayValueObject $array, StringValueObject $string, IntegerValueObject $integer, FloatValueObject $float, BooleanValueObject $boolean)
36+
{
37+
$this->array = $array;
38+
$this->string = $string;
39+
$this->integer = $integer;
40+
$this->float = $float;
41+
$this->boolean = $boolean;
42+
}
43+
44+
/**
45+
* @return ArrayValueObject
46+
*/
47+
public function getArray(): ArrayValueObject
48+
{
49+
return $this->array;
50+
}
51+
52+
/**
53+
* @return StringValueObject
54+
*/
55+
public function getString(): StringValueObject
56+
{
57+
return $this->string;
58+
}
59+
60+
/**
61+
* @return IntegerValueObject
62+
*/
63+
public function getInteger(): IntegerValueObject
64+
{
65+
return $this->integer;
66+
}
67+
68+
/**
69+
* @return FloatValueObject
70+
*/
71+
public function getFloat(): FloatValueObject
72+
{
73+
return $this->float;
74+
}
75+
76+
/**
77+
* @return BooleanValueObject
78+
*/
79+
public function getBoolean(): BooleanValueObject
80+
{
81+
return $this->boolean;
82+
}
83+
84+
public function equals(self $other): bool
85+
{
86+
return $other->array->equals($this->array)
87+
&& $other->string->equals($this->string)
88+
&& $other->integer->equals($this->integer)
89+
&& $other->float->equals($this->float)
90+
&& $other->boolean->equals($this->boolean);
91+
}
92+
93+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Neos\EventSourcing\Tests\Unit\EventStore\Fixture;
5+
6+
final class FloatValueObject implements \JsonSerializable
7+
{
8+
private $value;
9+
10+
private function __construct(float $value)
11+
{
12+
$this->value = $value;
13+
}
14+
15+
public static function fromFloat(float $value): self
16+
{
17+
return new self($value);
18+
}
19+
20+
public function equals(self $other): bool
21+
{
22+
return $other->value === $this->value;
23+
}
24+
25+
public function jsonSerialize(): float
26+
{
27+
return $this->value;
28+
}
29+
}

0 commit comments

Comments
 (0)