Skip to content

Commit

Permalink
Merge pull request #253 from patchlevel/normalizer
Browse files Browse the repository at this point in the history
Add native normalizer
  • Loading branch information
DavidBadura authored May 10, 2022
2 parents 36b1592 + 6a312ea commit 9b237b6
Show file tree
Hide file tree
Showing 38 changed files with 925 additions and 67 deletions.
5 changes: 5 additions & 0 deletions baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@
<code>$store</code>
</MissingConstructor>
</file>
<file src="tests/Unit/Fixture/MessageNormalizer.php">
<MixedArgumentTypeCoercion occurrences="1">
<code>$value</code>
</MixedArgumentTypeCoercion>
</file>
<file src="tests/Unit/Fixture/ProfileWithBrokenApplyNoType.php">
<MissingParamType occurrences="1">
<code>$event</code>
Expand Down
2 changes: 1 addition & 1 deletion docs/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ You also need to implement a `normalize` and `denormalize` method.
The important thing is that the result of Normalize is serializable.

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

class NameNormalizer implements Normalizer
{
Expand Down
10 changes: 10 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ parameters:
count: 1
path: src/EventBus/Message.php

-
message: "#^Method Patchlevel\\\\EventSourcing\\\\Serializer\\\\Normalizer\\\\ArrayNormalizer\\:\\:denormalize\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
path: src/Serializer/Normalizer/ArrayNormalizer.php

-
message: "#^Method Patchlevel\\\\EventSourcing\\\\Serializer\\\\Normalizer\\\\ArrayNormalizer\\:\\:normalize\\(\\) return type has no value type specified in iterable type array\\.$#"
count: 1
path: src/Serializer/Normalizer/ArrayNormalizer.php

-
message: "#^While loop condition is always true\\.$#"
count: 1
Expand Down
11 changes: 8 additions & 3 deletions src/Attribute/Normalize.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
namespace Patchlevel\EventSourcing\Attribute;

use Attribute;
use Patchlevel\EventSourcing\Serializer\Hydrator\Normalizer;
use Patchlevel\EventSourcing\Serializer\Normalizer\ArrayNormalizer;
use Patchlevel\EventSourcing\Serializer\Normalizer\Normalizer;

use function is_string;

Expand All @@ -17,10 +18,14 @@ final class Normalize
/**
* @param Normalizer|class-string<Normalizer> $normalizer
*/
public function __construct(Normalizer|string $normalizer)
public function __construct(Normalizer|string $normalizer, bool $list = false)
{
if (is_string($normalizer)) {
$this->normalizer = new $normalizer();
$normalizer = new $normalizer();
}

if ($list) {
$this->normalizer = new ArrayNormalizer($normalizer);

return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Patchlevel\EventSourcing\Metadata\AggregateRoot;

use Patchlevel\EventSourcing\Serializer\Hydrator\Normalizer;
use Patchlevel\EventSourcing\Serializer\Normalizer\Normalizer;
use ReflectionProperty;

final class AggregateRootPropertyMetadata
Expand Down
2 changes: 1 addition & 1 deletion src/Metadata/Event/EventPropertyMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Patchlevel\EventSourcing\Metadata\Event;

use Patchlevel\EventSourcing\Serializer\Hydrator\Normalizer;
use Patchlevel\EventSourcing\Serializer\Normalizer\Normalizer;
use ReflectionProperty;

final class EventPropertyMetadata
Expand Down
30 changes: 30 additions & 0 deletions src/Serializer/Hydrator/DenormalizationFailure.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcing\Serializer\Hydrator;

use Throwable;

use function sprintf;

final class DenormalizationFailure extends HydratorException
{
/**
* @param class-string $class
* @param class-string $normalizer
*/
public function __construct(string $class, string $property, string $normalizer, Throwable $e)
{
parent::__construct(
sprintf(
'denormalization for the property "%s" in the class "%s" with the normalizer "%s" failed.',
$property,
$class,
$normalizer
),
0,
$e
);
}
}
11 changes: 11 additions & 0 deletions src/Serializer/Hydrator/HydratorException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcing\Serializer\Hydrator;

use Patchlevel\EventSourcing\Serializer\SerializeException;

abstract class HydratorException extends SerializeException
{
}
35 changes: 29 additions & 6 deletions src/Serializer/Hydrator/MetadataAggregateRootHydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Patchlevel\EventSourcing\Aggregate\AggregateRoot;
use ReflectionClass;
use ReflectionProperty;
use Throwable;
use TypeError;

use function array_key_exists;
Expand Down Expand Up @@ -40,14 +41,27 @@ public function hydrate(string $class, array $data): AggregateRoot
$value = $data[$propertyMetadata->fieldName] ?? null;

if ($propertyMetadata->normalizer) {
/** @psalm-suppress MixedAssignment */
$value = $propertyMetadata->normalizer->denormalize($value);
try {
/** @psalm-suppress MixedAssignment */
$value = $propertyMetadata->normalizer->denormalize($value);
} catch (Throwable $e) {
throw new DenormalizationFailure(
$class,
$propertyMetadata->reflection->getName(),
$propertyMetadata->normalizer::class,
$e
);
}
}

try {
$propertyMetadata->reflection->setValue($aggregateRoot, $value);
} catch (TypeError $error) {
throw new TypeMismatch($error->getMessage(), 0, $error);
} catch (TypeError $e) {
throw new TypeMismatch(
$class,
$propertyMetadata->reflection->getName(),
$e
);
}
}

Expand All @@ -74,8 +88,17 @@ public function extract(AggregateRoot $aggregateRoot): array
$value = $propertyMetadata->reflection->getValue($aggregateRoot);

if ($propertyMetadata->normalizer) {
/** @psalm-suppress MixedAssignment */
$value = $propertyMetadata->normalizer->normalize($value);
try {
/** @psalm-suppress MixedAssignment */
$value = $propertyMetadata->normalizer->normalize($value);
} catch (Throwable $e) {
throw new NormalizationFailure(
$aggregateRoot::class,
$propertyMetadata->reflection->getName(),
$propertyMetadata->normalizer::class,
$e
);
}
}

/** @psalm-suppress MixedAssignment */
Expand Down
35 changes: 29 additions & 6 deletions src/Serializer/Hydrator/MetadataEventHydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Patchlevel\EventSourcing\Metadata\Event\EventMetadataFactory;
use ReflectionClass;
use Throwable;
use TypeError;

use function array_key_exists;
Expand Down Expand Up @@ -41,14 +42,27 @@ public function hydrate(string $class, array $data): object
$value = $data[$propertyMetadata->fieldName] ?? null;

if ($propertyMetadata->normalizer) {
/** @psalm-suppress MixedAssignment */
$value = $propertyMetadata->normalizer->denormalize($value);
try {
/** @psalm-suppress MixedAssignment */
$value = $propertyMetadata->normalizer->denormalize($value);
} catch (Throwable $e) {
throw new DenormalizationFailure(
$class,
$propertyMetadata->reflection->getName(),
$propertyMetadata->normalizer::class,
$e
);
}
}

try {
$propertyMetadata->reflection->setValue($object, $value);
} catch (TypeError $error) {
throw new TypeMismatch($error->getMessage(), 0, $error);
} catch (TypeError $e) {
throw new TypeMismatch(
$class,
$propertyMetadata->reflection->getName(),
$e
);
}
}

Expand All @@ -69,8 +83,17 @@ public function extract(object $object): array
$value = $propertyMetadata->reflection->getValue($object);

if ($propertyMetadata->normalizer) {
/** @psalm-suppress MixedAssignment */
$value = $propertyMetadata->normalizer->normalize($value);
try {
/** @psalm-suppress MixedAssignment */
$value = $propertyMetadata->normalizer->normalize($value);
} catch (Throwable $e) {
throw new NormalizationFailure(
$object::class,
$propertyMetadata->reflection->getName(),
$propertyMetadata->normalizer::class,
$e
);
}
}

/** @psalm-suppress MixedAssignment */
Expand Down
30 changes: 30 additions & 0 deletions src/Serializer/Hydrator/NormalizationFailure.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcing\Serializer\Hydrator;

use Throwable;

use function sprintf;

final class NormalizationFailure extends HydratorException
{
/**
* @param class-string $class
* @param class-string $normalizer
*/
public function __construct(string $class, string $property, string $normalizer, Throwable $e)
{
parent::__construct(
sprintf(
'normalization for the property "%s" in the class "%s" with the normalizer "%s" failed.',
$property,
$class,
$normalizer
),
0,
$e
);
}
}
21 changes: 19 additions & 2 deletions src/Serializer/Hydrator/TypeMismatch.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,25 @@

namespace Patchlevel\EventSourcing\Serializer\Hydrator;

use Patchlevel\EventSourcing\Serializer\SerializeException;
use TypeError;

final class TypeMismatch extends SerializeException
use function sprintf;

final class TypeMismatch extends HydratorException
{
/**
* @param class-string $class
*/
public function __construct(string $class, string $property, ?TypeError $previous = null)
{
parent::__construct(
sprintf(
'The value could not be set because the expected type of the property "%s" in class "%s" does not match.',
$property,
$class
),
0,
$previous
);
}
}
41 changes: 41 additions & 0 deletions src/Serializer/Normalizer/ArrayNormalizer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcing\Serializer\Normalizer;

use function array_map;
use function is_array;

class ArrayNormalizer implements Normalizer
{
public function __construct(private readonly Normalizer $normalizer)
{
}

public function normalize(mixed $value): ?array
{
if ($value === null) {
return null;
}

if (!is_array($value)) {
throw new InvalidArgument();
}

return array_map(fn (mixed $value): mixed => $this->normalizer->normalize($value), $value);
}

public function denormalize(mixed $value): ?array
{
if ($value === null) {
return null;
}

if (!is_array($value)) {
throw new InvalidArgument();
}

return array_map(fn (mixed $value): mixed => $this->normalizer->denormalize($value), $value);
}
}
49 changes: 49 additions & 0 deletions src/Serializer/Normalizer/DateTimeImmutableNormalizer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcing\Serializer\Normalizer;

use DateTimeImmutable;

use function is_string;

class DateTimeImmutableNormalizer implements Normalizer
{
public function __construct(
private readonly string $format = DateTimeImmutable::ATOM
) {
}

public function normalize(mixed $value): ?string
{
if ($value === null) {
return null;
}

if (!$value instanceof DateTimeImmutable) {
throw new InvalidArgument();
}

return $value->format($this->format);
}

public function denormalize(mixed $value): ?DateTimeImmutable
{
if ($value === null) {
return null;
}

if (!is_string($value)) {
throw new InvalidArgument();
}

$date = DateTimeImmutable::createFromFormat($this->format, $value);

if ($date === false) {
throw new InvalidArgument();
}

return $date;
}
}
Loading

0 comments on commit 9b237b6

Please sign in to comment.