Skip to content

Commit

Permalink
Merge pull request #346 from patchlevel/simplify-normalizer-usage
Browse files Browse the repository at this point in the history
simplify normalizer usage
  • Loading branch information
DavidBadura committed Jan 12, 2023
2 parents a301ff2 + 2e4d7a1 commit da8c395
Show file tree
Hide file tree
Showing 45 changed files with 180 additions and 114 deletions.
23 changes: 23 additions & 0 deletions baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,18 @@
<code>Headers</code>
</MoreSpecificReturnType>
</file>
<file src="src/Metadata/AggregateRoot/AttributeAggregateRootMetadataFactory.php">
<DeprecatedClass occurrences="2">
<code>$attributeReflectionList[0]-&gt;newInstance()</code>
<code>Normalize::class</code>
</DeprecatedClass>
</file>
<file src="src/Metadata/Event/AttributeEventMetadataFactory.php">
<DeprecatedClass occurrences="2">
<code>$attributeReflectionList[0]-&gt;newInstance()</code>
<code>Normalize::class</code>
</DeprecatedClass>
</file>
<file src="src/Metadata/Projection/AttributeProjectionMetadataFactory.php">
<DeprecatedClass occurrences="3">
<code>array</code>
Expand Down Expand Up @@ -298,6 +310,12 @@
<code>ProfileProjection</code>
</DeprecatedInterface>
</file>
<file src="tests/Unit/Attribute/NormalizeTest.php">
<DeprecatedClass occurrences="2">
<code>new Normalize($normalizer)</code>
<code>new Normalize($normalizer, true)</code>
</DeprecatedClass>
</file>
<file src="tests/Unit/Console/Command/ProjectionCreateCommandTest.php">
<DeprecatedClass occurrences="2">
<code>$this-&gt;prophesize(ProjectionHandler::class)</code>
Expand Down Expand Up @@ -343,6 +361,11 @@
<code>$event</code>
</MissingParamType>
</file>
<file src="tests/Unit/Metadata/Event/AttributeEventMetadataFactoryTest.php">
<DeprecatedClass occurrences="1">
<code>Normalize(new EmailNormalizer())</code>
</DeprecatedClass>
</file>
<file src="tests/Unit/Metadata/Projection/AttributeProjectionMetadataFactoryTest.php">
<DeprecatedInterface occurrences="5">
<code>class implements Projection {</code>
Expand Down
3 changes: 1 addition & 2 deletions docs/pages/aggregate.md
Original file line number Diff line number Diff line change
Expand Up @@ -492,13 +492,12 @@ since we only expected a string before but now passed a `Name` value object.

```php
use Patchlevel\EventSourcing\Attribute\Event;
use Patchlevel\EventSourcing\Attribute\Normalize;

#[Event('profile.name_changed')]
final class NameChanged
{
public function __construct(
#[Normalize(new NameNormalizer())]
#[NameNormalizer]
public readonly Name $name
) {}
}
Expand Down
3 changes: 1 addition & 2 deletions docs/pages/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,14 @@ so that the library knows how to write this data to the database and load it aga

```php
use Patchlevel\EventSourcing\Attribute\Event;
use Patchlevel\EventSourcing\Attribute\Normalize;
use Patchlevel\EventSourcing\Serializer\Normalizer\DateTimeImmutableNormalizer;

#[Event('profile.name_changed')]
final class NameChanged
{
public function __construct(
public readonly string $name,
#[Normalize(new DateTimeImmutableNormalizer())]
#[DateTimeImmutableNormalizer]
public readonly DateTimeImmutable $changedAt
) {}
}
Expand Down
63 changes: 20 additions & 43 deletions docs/pages/normalizer.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,27 @@ how to write this data to the database and load it again.

## Usage

You have to set the normalizer to the properties using the normalize attribute.
You have to set the normalizer to the properties using the specific normalizer class.

```php
use Patchlevel\EventSourcing\Attribute\Normalize;
use Patchlevel\EventSourcing\Serializer\Normalizer\DateTimeImmutableNormalizer;

final class DTO
{
#[Normalize(new DateTimeImmutableNormalizer())]
#[DateTimeImmutableNormalizer]
public DateTimeImmutable $date;
}
```

The whole thing also works with property promotion.

```php
use Patchlevel\EventSourcing\Attribute\Normalize;
use Patchlevel\EventSourcing\Serializer\Normalizer\DateTimeImmutableNormalizer;

final class DTO
{
public function __construct(
#[Normalize(new DateTimeImmutableNormalizer())]
#[DateTimeImmutableNormalizer]
public readonly DateTimeImmutable $date
) {}
}
Expand All @@ -42,15 +40,14 @@ The whole thing is then loaded again from the DB and denormalized in the propert

```php
use Patchlevel\EventSourcing\Attribute\Event;
use Patchlevel\EventSourcing\Attribute\Normalize;
use Patchlevel\EventSourcing\Serializer\Normalizer\DateTimeImmutableNormalizer;

#[Event('hotel.create')]
final class CreateHotel
{
public function __construct(
public readonly string $name,
#[Normalize(new DateTimeImmutableNormalizer())]
#[DateTimeImmutableNormalizer]
public readonly DateTimeImmutable $createAt
) {}
}
Expand All @@ -64,7 +61,6 @@ Here you can determine how the aggregate is saved in the snapshot store at the e
```php
use Patchlevel\EventSourcing\Aggregate\AggregateRoot;
use Patchlevel\EventSourcing\Attribute\Aggregate;
use Patchlevel\EventSourcing\Attribute\Normalize;
use Patchlevel\EventSourcing\Attribute\Snapshot;
use Patchlevel\EventSourcing\Serializer\Normalizer\DateTimeImmutableNormalizer;

Expand All @@ -73,7 +69,7 @@ use Patchlevel\EventSourcing\Serializer\Normalizer\DateTimeImmutableNormalizer;
final class Hotel extends AggregateRoot
{
private string $name,
#[Normalize(new DateTimeImmutableNormalizer())]
#[DateTimeImmutableNormalizer]
private DateTimeImmutable $createAt

// ...
Expand All @@ -96,13 +92,12 @@ In order to use the `ArrayNormaliser`, you still have to specify which normalise
objects. Internally, it basically does an `array_map` and then runs the specified normalizer on each element.

```php
use Patchlevel\EventSourcing\Attribute\Normalize;
use Patchlevel\EventSourcing\Serializer\Normalizer\ArrayNormalizer;
use Patchlevel\EventSourcing\Serializer\Normalizer\DateTimeImmutableNormalizer;

final class DTO
{
#[Normalize(new ArrayNormalizer(new DateTimeImmutableNormalizer()))]
#[ArrayNormalizer(new DateTimeImmutableNormalizer())]
public array $dates;
}
```
Expand All @@ -111,32 +106,17 @@ final class DTO

The keys from the arrays are taken over here.

To save yourself this nesting, you can set the flag `list` on the `Normalize` attribute.
In the background, your defined normalizer is then wrapped with the `ArrayNormalizer`.

```php
use Patchlevel\EventSourcing\Attribute\Normalize;
use Patchlevel\EventSourcing\Serializer\Normalizer\DateTimeImmutableNormalizer;

final class DTO
{
#[Normalize(new DateTimeImmutableNormalizer(), list: true)]
public array $dates;
}
```

### DateTimeImmutable

With the `DateTimeImmutable` Normalizer, as the name suggests,
you can convert DateTimeImmutable objects to a String and back again.

```php
use Patchlevel\EventSourcing\Attribute\Normalize;
use Patchlevel\EventSourcing\Serializer\Normalizer\DateTimeImmutableNormalizer;

final class DTO
{
#[Normalize(new DateTimeImmutableNormalizer())]
#[DateTimeImmutableNormalizer]
public DateTimeImmutable $date;
}
```
Expand All @@ -145,12 +125,11 @@ You can also define the format. Either describe it yourself as a string or use o
The default is `DateTimeImmutable::ATOM`.

```php
use Patchlevel\EventSourcing\Attribute\Normalize;
use Patchlevel\EventSourcing\Serializer\Normalizer\DateTimeImmutableNormalizer;

final class DTO
{
#[Normalize(new DateTimeImmutableNormalizer(format: DateTimeImmutable::RFC3339_EXTENDED))]
#[DateTimeImmutableNormalizer(format: DateTimeImmutable::RFC3339_EXTENDED)]
public DateTimeImmutable $date;
}
```
Expand All @@ -164,25 +143,23 @@ final class DTO
The `DateTime` Normalizer works exactly like the DateTimeNormalizer. Only for DateTime objects.

```php
use Patchlevel\EventSourcing\Attribute\Normalize;
use Patchlevel\EventSourcing\Serializer\Normalizer\DateTimeNormalizer;

final class DTO
{
#[Normalize(new DateTimeNormalizer())]
#[DateTimeNormalizer]
public DateTime $date;
}
```

You can also specify the format here. The default is `DateTime::ATOM`.

```php
use Patchlevel\EventSourcing\Attribute\Normalize;
use Patchlevel\EventSourcing\Serializer\Normalizer\DateTimeNormalizer;

final class DTO
{
#[Normalize(new DateTimeNormalizer(format: DateTime::RFC3339_EXTENDED))]
#[DateTimeNormalizer(format: DateTime::RFC3339_EXTENDED)]
public DateTime $date;
}
```
Expand All @@ -201,11 +178,10 @@ final class DTO
To normalize a `DateTimeZone` one can use the `DateTimeZoneNormalizer`.

```php
use Patchlevel\EventSourcing\Attribute\Normalize;
use Patchlevel\EventSourcing\Serializer\Normalizer\DateTimeZoneNormalizer;

final class DTO {
#[Normalize(new DateTimeZoneNormalizer())]
#[DateTimeZoneNormalizer]
public DateTimeZone $timeZone;
}
```
Expand All @@ -216,11 +192,10 @@ 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\EventSourcing\Attribute\Normalize;
use Patchlevel\EventSourcing\Serializer\Normalizer\EnumNormalizer;

final class DTO {
#[Normalize(new EnumNormalizer(Status::class))]
#[EnumNormalizer(Status::class)]
public Status $status;
}
```
Expand Down Expand Up @@ -256,11 +231,12 @@ final class Name
For this we now need a custom normalizer.
This normalizer must implement the `Normalizer` interface.
You also need to implement a `normalize` and `denormalize` method.
The important thing is that the result of Normalize is serializable.
Finally, you have to allow the normalizer to be used as an attribute.

```php
use Patchlevel\EventSourcing\Serializer\Normalizer\Normalizer;

#[Attribute(Attribute::TARGET_PROPERTY)]
class NameNormalizer implements Normalizer
{
public function normalize(mixed $value): string
Expand All @@ -287,14 +263,16 @@ class NameNormalizer implements Normalizer
}
```

Now we can also use the normalizer directly by passing it to the Normalize attribute.
!!! warning

```php
use Patchlevel\EventSourcing\Attribute\Normalize;
The important thing is that the result of Normalize is serializable!

Now we can also use the normalizer directly.

```php
final class DTO
{
#[Normalize(new NameNormalizer())]
#[NameNormalizer]
public Name $name
}
```
Expand All @@ -303,7 +281,6 @@ final class DTO

Every normalizer, including the custom normalizer, can be used both for the events and for the snapshots.


## Normalized Name

By default, the property name is used to name the field in the normalized result.
Expand Down
4 changes: 4 additions & 0 deletions src/Attribute/Normalize.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
use Patchlevel\EventSourcing\Serializer\Normalizer\ArrayNormalizer;
use Patchlevel\EventSourcing\Serializer\Normalizer\Normalizer;

/**
* @deprecated use the specific normalizer as attribute.
* Custom normalizers need the "#[Attribute(Attribute::TARGET_PROPERTY)]" attribute.
*/
#[Attribute(Attribute::TARGET_PROPERTY)]
final class Normalize
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@
use Patchlevel\EventSourcing\Attribute\NormalizedName;
use Patchlevel\EventSourcing\Attribute\Snapshot;
use Patchlevel\EventSourcing\Attribute\SuppressMissingApply;
use Patchlevel\EventSourcing\Serializer\Normalizer\Normalizer;
use ReflectionAttribute;
use ReflectionClass;
use ReflectionIntersectionType;
use ReflectionMethod;
use ReflectionNamedType;
use ReflectionProperty;
use ReflectionUnionType;

use function array_key_exists;
Expand Down Expand Up @@ -232,22 +235,35 @@ private function getPropertyMetadataList(ReflectionClass $reflectionClass): arra
$fieldName = $attribute->name();
}

$attributeReflectionList = $reflectionProperty->getAttributes(Normalize::class);

$normalizer = null;

if ($attributeReflectionList !== []) {
$attribute = $attributeReflectionList[0]->newInstance();
$normalizer = $attribute->normalizer();
}

$properties[$reflectionProperty->getName()] = new AggregateRootPropertyMetadata(
$fieldName,
$reflectionProperty,
$normalizer
$this->getNormalizer($reflectionProperty)
);
}

return $properties;
}

private function getNormalizer(ReflectionProperty $reflectionProperty): ?Normalizer
{
$attributeReflectionList = $reflectionProperty->getAttributes(
Normalizer::class,
ReflectionAttribute::IS_INSTANCEOF
);

if ($attributeReflectionList !== []) {
return $attributeReflectionList[0]->newInstance();
}

$attributeReflectionList = $reflectionProperty->getAttributes(Normalize::class);

if ($attributeReflectionList !== []) {
$attribute = $attributeReflectionList[0]->newInstance();

return $attribute->normalizer();
}

return null;
}
}

0 comments on commit da8c395

Please sign in to comment.