diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index e7ecebe2..6e3fe1a1 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -72,12 +72,6 @@ parameters: count: 1 path: src/Metadata/ClassMetadata.php - - - message: '#^Dead catch \- Patchlevel\\Hydrator\\CircularReference is never thrown in the try block\.$#' - identifier: catch.neverThrown - count: 1 - path: src/MetadataHydrator.php - - message: '#^Property Patchlevel\\Hydrator\\Normalizer\\EnumNormalizer\:\:\$enum \(class\-string\\|null\) does not accept string\.$#' identifier: assign.propertyType @@ -90,12 +84,24 @@ parameters: count: 1 path: src/Normalizer/ObjectMapNormalizer.php + - + message: '#^Parameter \#2 \$data of method Patchlevel\\Hydrator\\HydratorWithContext\:\:hydrate\(\) expects array\, array given\.$#' + identifier: argument.type + count: 1 + path: src/Normalizer/ObjectMapNormalizer.php + - message: '#^Parameter \#2 \$data of method Patchlevel\\Hydrator\\Hydrator\:\:hydrate\(\) expects array\, array\ given\.$#' identifier: argument.type count: 1 path: src/Normalizer/ObjectNormalizer.php + - + message: '#^Parameter \#2 \$data of method Patchlevel\\Hydrator\\HydratorWithContext\:\:hydrate\(\) expects array\, array\ given\.$#' + identifier: argument.type + count: 1 + path: src/Normalizer/ObjectNormalizer.php + - message: '#^Property Patchlevel\\Hydrator\\Normalizer\\ObjectNormalizer\:\:\$className \(class\-string\|null\) does not accept string\.$#' identifier: assign.propertyType diff --git a/src/HydratorWithContext.php b/src/HydratorWithContext.php new file mode 100644 index 00000000..d4936b77 --- /dev/null +++ b/src/HydratorWithContext.php @@ -0,0 +1,28 @@ + $class + * @param array $data + * @param array $context + * + * @return T + * + * @throws ClassNotSupported if the class is not supported or not found. + * + * @template T of object + */ + public function hydrate(string $class, array $data, array $context = []): object; + + /** + * @param array $context + * + * @return array + */ + public function extract(object $object, array $context = []): array; +} diff --git a/src/MetadataHydrator.php b/src/MetadataHydrator.php index e1a37a7f..20fb5e73 100644 --- a/src/MetadataHydrator.php +++ b/src/MetadataHydrator.php @@ -16,6 +16,7 @@ use Patchlevel\Hydrator\Metadata\ClassNotFound; use Patchlevel\Hydrator\Metadata\MetadataFactory; use Patchlevel\Hydrator\Normalizer\HydratorAwareNormalizer; +use Patchlevel\Hydrator\Normalizer\NormalizerWithContext; use ReflectionClass; use ReflectionParameter; use Symfony\Component\EventDispatcher\EventDispatcher; @@ -30,7 +31,7 @@ use const PHP_VERSION_ID; -final class MetadataHydrator implements Hydrator +final class MetadataHydrator implements HydratorWithContext { /** @var array */ private array $stack = []; @@ -57,12 +58,13 @@ public function __construct( /** * @param class-string $class * @param array $data + * @param array $context * * @return T * * @template T of object */ - public function hydrate(string $class, array $data): object + public function hydrate(string $class, array $data, array $context = []): object { try { $metadata = $this->metadataFactory->metadata($class); @@ -71,18 +73,18 @@ public function hydrate(string $class, array $data): object } if (PHP_VERSION_ID < 80400) { - return $this->doHydrate($metadata, $data); + return $this->doHydrate($metadata, $data, $context); } $lazy = $metadata->lazy() ?? $this->defaultLazy; if (!$lazy) { - return $this->doHydrate($metadata, $data); + return $this->doHydrate($metadata, $data, $context); } return (new ReflectionClass($class))->newLazyProxy( - function () use ($metadata, $data): object { - return $this->doHydrate($metadata, $data); + function () use ($metadata, $data, $context): object { + return $this->doHydrate($metadata, $data, $context); }, ); } @@ -90,12 +92,13 @@ function () use ($metadata, $data): object { /** * @param ClassMetadata $metadata * @param array $data + * @param array $context * * @return T * * @template T of object */ - private function doHydrate(ClassMetadata $metadata, array $data): object + private function doHydrate(ClassMetadata $metadata, array $data, array $context = []): object { if ($this->eventDispatcher) { $data = $this->eventDispatcher->dispatch(new PreHydrate($data, $metadata))->data; @@ -138,7 +141,11 @@ private function doHydrate(ClassMetadata $metadata, array $data): object try { /** @psalm-suppress MixedAssignment */ - $value = $normalizer->denormalize($value); + if ($normalizer instanceof NormalizerWithContext) { + $value = $normalizer->denormalize($value, $context); + } else { + $value = $normalizer->denormalize($value); + } } catch (Throwable $e) { throw new DenormalizationFailure( $metadata->className(), @@ -167,8 +174,12 @@ private function doHydrate(ClassMetadata $metadata, array $data): object return $object; } - /** @return array */ - public function extract(object $object): array + /** + * @param array $context + * + * @return array + */ + public function extract(object $object, array $context = []): array { $objectId = spl_object_id($object); @@ -202,11 +213,18 @@ public function extract(object $object): array } try { - /** @psalm-suppress MixedAssignment */ - $value = $normalizer->normalize($value); - } catch (CircularReference $e) { - throw $e; + if ($normalizer instanceof NormalizerWithContext) { + /** @psalm-suppress MixedAssignment */ + $value = $normalizer->normalize($value, $context); + } else { + /** @psalm-suppress MixedAssignment */ + $value = $normalizer->normalize($value); + } } catch (Throwable $e) { + if ($e instanceof CircularReference) { + throw $e; + } + throw new NormalizationFailure( $object::class, $propertyMetadata->propertyName(), diff --git a/src/Normalizer/ArrayNormalizer.php b/src/Normalizer/ArrayNormalizer.php index b410f174..b61d9b29 100644 --- a/src/Normalizer/ArrayNormalizer.php +++ b/src/Normalizer/ArrayNormalizer.php @@ -13,15 +13,19 @@ use function is_array; #[Attribute(Attribute::TARGET_PROPERTY)] -final readonly class ArrayNormalizer implements Normalizer, TypeAwareNormalizer, HydratorAwareNormalizer +final readonly class ArrayNormalizer implements NormalizerWithContext, TypeAwareNormalizer, HydratorAwareNormalizer { public function __construct( private Normalizer $normalizer, ) { } - /** @return array|null */ - public function normalize(mixed $value): array|null + /** + * @param array $context + * + * @return array|null + */ + public function normalize(mixed $value, array $context = []): array|null { if ($value === null) { return null; @@ -31,15 +35,25 @@ public function normalize(mixed $value): array|null throw InvalidArgument::withWrongType('array|null', $value); } - foreach ($value as &$item) { - $item = $this->normalizer->normalize($item); + if ($this->normalizer instanceof NormalizerWithContext) { + foreach ($value as &$item) { + $item = $this->normalizer->normalize($item, $context); + } + } else { + foreach ($value as &$item) { + $item = $this->normalizer->normalize($item); + } } return $value; } - /** @return array|null */ - public function denormalize(mixed $value): array|null + /** + * @param array $context + * + * @return array|null + */ + public function denormalize(mixed $value, array $context = []): array|null { if ($value === null) { return null; @@ -49,8 +63,14 @@ public function denormalize(mixed $value): array|null throw InvalidArgument::withWrongType('array|null', $value); } - foreach ($value as &$item) { - $item = $this->normalizer->denormalize($item); + if ($this->normalizer instanceof NormalizerWithContext) { + foreach ($value as &$item) { + $item = $this->normalizer->denormalize($item, $context); + } + } else { + foreach ($value as &$item) { + $item = $this->normalizer->denormalize($item); + } } return $value; diff --git a/src/Normalizer/ArrayShapeNormalizer.php b/src/Normalizer/ArrayShapeNormalizer.php index 3bae3d42..b86692f7 100644 --- a/src/Normalizer/ArrayShapeNormalizer.php +++ b/src/Normalizer/ArrayShapeNormalizer.php @@ -13,7 +13,7 @@ use function is_array; #[Attribute(Attribute::TARGET_PROPERTY)] -final readonly class ArrayShapeNormalizer implements Normalizer, TypeAwareNormalizer, HydratorAwareNormalizer +final readonly class ArrayShapeNormalizer implements NormalizerWithContext, TypeAwareNormalizer, HydratorAwareNormalizer { /** @param array $normalizerMap */ public function __construct( @@ -21,8 +21,12 @@ public function __construct( ) { } - /** @return array|null */ - public function normalize(mixed $value): array|null + /** + * @param array $context + * + * @return array|null + */ + public function normalize(mixed $value, array $context = []): array|null { if ($value === null) { return null; @@ -39,14 +43,22 @@ public function normalize(mixed $value): array|null continue; } - $result[$field] = $normalizer->normalize($value[$field]); + if ($normalizer instanceof NormalizerWithContext) { + $result[$field] = $normalizer->normalize($value[$field], $context); + } else { + $result[$field] = $normalizer->normalize($value[$field]); + } } return $result; } - /** @return array|null */ - public function denormalize(mixed $value): array|null + /** + * @param array $context + * + * @return array|null + */ + public function denormalize(mixed $value, array $context = []): array|null { if ($value === null) { return null; @@ -63,7 +75,11 @@ public function denormalize(mixed $value): array|null continue; } - $result[$field] = $normalizer->denormalize($value[$field]); + if ($normalizer instanceof NormalizerWithContext) { + $result[$field] = $normalizer->denormalize($value[$field], $context); + } else { + $result[$field] = $normalizer->denormalize($value[$field]); + } } return $result; diff --git a/src/Normalizer/NormalizerWithContext.php b/src/Normalizer/NormalizerWithContext.php new file mode 100644 index 00000000..7a976e76 --- /dev/null +++ b/src/Normalizer/NormalizerWithContext.php @@ -0,0 +1,22 @@ + $context + * + * @throws InvalidArgument + */ + public function normalize(mixed $value, array $context = []): mixed; + + /** + * @param array $context + * + * @throws InvalidArgument + */ + public function denormalize(mixed $value, array $context = []): mixed; +} diff --git a/src/Normalizer/ObjectMapNormalizer.php b/src/Normalizer/ObjectMapNormalizer.php index 83da1b45..8bc76308 100644 --- a/src/Normalizer/ObjectMapNormalizer.php +++ b/src/Normalizer/ObjectMapNormalizer.php @@ -6,6 +6,7 @@ use Attribute; use Patchlevel\Hydrator\Hydrator; +use Patchlevel\Hydrator\HydratorWithContext; use function array_flip; use function array_key_exists; @@ -17,7 +18,7 @@ use function sprintf; #[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_CLASS)] -final class ObjectMapNormalizer implements Normalizer, HydratorAwareNormalizer +final class ObjectMapNormalizer implements NormalizerWithContext, HydratorAwareNormalizer { private Hydrator|null $hydrator = null; @@ -37,7 +38,8 @@ public function setHydrator(Hydrator $hydrator): void $this->hydrator = $hydrator; } - public function normalize(mixed $value): mixed + /** @param array $context */ + public function normalize(mixed $value, array $context = []): mixed { if (!$this->hydrator) { throw new MissingHydrator(); @@ -61,13 +63,19 @@ public function normalize(mixed $value): mixed ); } - $data = $this->hydrator->extract($value); + if ($this->hydrator instanceof HydratorWithContext) { + $data = $this->hydrator->extract($value, $context); + } else { + $data = $this->hydrator->extract($value); + } + $data[$this->typeFieldName] = $this->classToTypeMap[$value::class]; return $data; } - public function denormalize(mixed $value): mixed + /** @param array $context */ + public function denormalize(mixed $value, array $context = []): mixed { if (!$this->hydrator) { throw new MissingHydrator(); @@ -98,6 +106,10 @@ public function denormalize(mixed $value): mixed $className = $this->typeToClassMap[$type]; unset($value[$this->typeFieldName]); + if ($this->hydrator instanceof HydratorWithContext) { + return $this->hydrator->hydrate($className, $value, $context); + } + return $this->hydrator->hydrate($className, $value); } diff --git a/src/Normalizer/ObjectNormalizer.php b/src/Normalizer/ObjectNormalizer.php index 4977ec93..2aca4d7b 100644 --- a/src/Normalizer/ObjectNormalizer.php +++ b/src/Normalizer/ObjectNormalizer.php @@ -6,6 +6,7 @@ use Attribute; use Patchlevel\Hydrator\Hydrator; +use Patchlevel\Hydrator\HydratorWithContext; use ReflectionType; use Symfony\Component\TypeInfo\Type; use Symfony\Component\TypeInfo\Type\GenericType; @@ -16,7 +17,7 @@ use function is_array; #[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_CLASS)] -final class ObjectNormalizer implements Normalizer, ReflectionTypeAwareNormalizer, TypeAwareNormalizer, HydratorAwareNormalizer +final class ObjectNormalizer implements NormalizerWithContext, ReflectionTypeAwareNormalizer, TypeAwareNormalizer, HydratorAwareNormalizer { private Hydrator|null $hydrator = null; @@ -26,8 +27,12 @@ public function __construct( ) { } - /** @return array|null */ - public function normalize(mixed $value): array|null + /** + * @param array $context + * + * @return array|null + */ + public function normalize(mixed $value, array $context = []): array|null { if (!$this->hydrator) { throw new MissingHydrator(); @@ -43,10 +48,15 @@ public function normalize(mixed $value): array|null throw InvalidArgument::withWrongType($className . '|null', $value); } + if ($this->hydrator instanceof HydratorWithContext) { + return $this->hydrator->extract($value, $context); + } + return $this->hydrator->extract($value); } - public function denormalize(mixed $value): object|null + /** @param array $context */ + public function denormalize(mixed $value, array $context = []): object|null { if (!$this->hydrator) { throw new MissingHydrator(); @@ -62,6 +72,10 @@ public function denormalize(mixed $value): object|null $className = $this->getClassName(); + if ($this->hydrator instanceof HydratorWithContext) { + return $this->hydrator->hydrate($className, $value, $context); + } + return $this->hydrator->hydrate($className, $value); } diff --git a/tests/Unit/Fixture/ContextAwareDto.php b/tests/Unit/Fixture/ContextAwareDto.php new file mode 100644 index 00000000..1aae379a --- /dev/null +++ b/tests/Unit/Fixture/ContextAwareDto.php @@ -0,0 +1,14 @@ + $context */ + public function normalize(mixed $value, array $context = []): mixed + { + if (!is_string($value)) { + throw InvalidArgument::withWrongType('string', $value); + } + + $prefix = isset($context['prefix']) && is_string($context['prefix']) + ? $context['prefix'] + : ''; + + return $prefix . $value; + } + + /** @param array $context */ + public function denormalize(mixed $value, array $context = []): mixed + { + if (!is_string($value)) { + throw InvalidArgument::withWrongType('string', $value); + } + + $suffix = isset($context['suffix']) && is_string($context['suffix']) + ? $context['suffix'] + : ''; + + return $value . $suffix; + } +} diff --git a/tests/Unit/MetadataHydratorTest.php b/tests/Unit/MetadataHydratorTest.php index 0a2ce9d5..0054bb7d 100644 --- a/tests/Unit/MetadataHydratorTest.php +++ b/tests/Unit/MetadataHydratorTest.php @@ -22,6 +22,7 @@ use Patchlevel\Hydrator\Tests\Unit\Fixture\Circle1Dto; use Patchlevel\Hydrator\Tests\Unit\Fixture\Circle2Dto; use Patchlevel\Hydrator\Tests\Unit\Fixture\Circle3Dto; +use Patchlevel\Hydrator\Tests\Unit\Fixture\ContextAwareDto; use Patchlevel\Hydrator\Tests\Unit\Fixture\DefaultDto; use Patchlevel\Hydrator\Tests\Unit\Fixture\DtoWithHooks; use Patchlevel\Hydrator\Tests\Unit\Fixture\Email; @@ -100,6 +101,15 @@ public function testExtractWithHydratorAwareNormalizer(): void ); } + public function testExtractPassesContextToNormalizer(): void + { + $dto = new ContextAwareDto('value'); + + $data = $this->hydrator->extract($dto, ['prefix' => 'ctx-']); + + self::assertSame(['value' => 'ctx-value'], $data); + } + public function testExtractCircularReference(): void { $this->expectException(CircularReference::class); @@ -187,6 +197,17 @@ public function testHydrate(): void self::assertEquals($expected, $event); } + public function testHydratePassesContextToNormalizer(): void + { + $event = $this->hydrator->hydrate( + ContextAwareDto::class, + ['value' => 'value'], + ['suffix' => '-ctx'], + ); + + self::assertSame('value-ctx', $event->value); + } + public function testHydrateUnknownClass(): void { $this->expectException(ClassNotSupported::class); diff --git a/tests/Unit/Normalizer/ArrayNormalizerTest.php b/tests/Unit/Normalizer/ArrayNormalizerTest.php index fdc8c714..a617bfe3 100644 --- a/tests/Unit/Normalizer/ArrayNormalizerTest.php +++ b/tests/Unit/Normalizer/ArrayNormalizerTest.php @@ -10,6 +10,7 @@ use Patchlevel\Hydrator\Normalizer\HydratorAwareNormalizer; use Patchlevel\Hydrator\Normalizer\InvalidArgument; use Patchlevel\Hydrator\Normalizer\Normalizer; +use Patchlevel\Hydrator\Normalizer\NormalizerWithContext; use PHPUnit\Framework\TestCase; #[Attribute(Attribute::TARGET_PROPERTY)] @@ -89,6 +90,68 @@ public function denormalize(mixed $value): int $this->assertEquals([1, 2], $normalizer->denormalize(['1', '2'])); } + public function testNormalizePassesContextToInnerNormalizer(): void + { + $context = ['key' => 'value']; + + $innerNormalizer = new class implements NormalizerWithContext { + /** @var array> */ + public array $contexts = []; + + /** @param array $context */ + public function normalize(mixed $value, array $context = []): mixed + { + $this->contexts[] = $context; + + return $value; + } + + /** @param array $context */ + public function denormalize(mixed $value, array $context = []): mixed + { + $this->contexts[] = $context; + + return $value; + } + }; + + $normalizer = new ArrayNormalizer($innerNormalizer); + $normalizer->normalize(['a', 'b'], $context); + + $this->assertSame([$context, $context], $innerNormalizer->contexts); + } + + public function testDenormalizePassesContextToInnerNormalizer(): void + { + $context = ['key' => 'value']; + + $innerNormalizer = new class implements NormalizerWithContext { + /** @var array> */ + public array $contexts = []; + + /** @param array $context */ + public function normalize(mixed $value, array $context = []): mixed + { + $this->contexts[] = $context; + + return $value; + } + + /** @param array $context */ + public function denormalize(mixed $value, array $context = []): mixed + { + $this->contexts[] = $context; + + return $value; + } + }; + + $normalizer = new ArrayNormalizer($innerNormalizer); + $normalizer->denormalize(['a', 'b'], $context); + + $this->assertSame([$context, $context], $innerNormalizer->contexts); + } + public function testPassHydrator(): void { $hydrator = $this->createMock(Hydrator::class); diff --git a/tests/Unit/Normalizer/ArrayShapeNormalizerTest.php b/tests/Unit/Normalizer/ArrayShapeNormalizerTest.php index 45fdfcc0..fc2369b3 100644 --- a/tests/Unit/Normalizer/ArrayShapeNormalizerTest.php +++ b/tests/Unit/Normalizer/ArrayShapeNormalizerTest.php @@ -10,6 +10,7 @@ use Patchlevel\Hydrator\Normalizer\HydratorAwareNormalizer; use Patchlevel\Hydrator\Normalizer\InvalidArgument; use Patchlevel\Hydrator\Normalizer\Normalizer; +use Patchlevel\Hydrator\Normalizer\NormalizerWithContext; use PHPUnit\Framework\TestCase; #[Attribute(Attribute::TARGET_PROPERTY)] @@ -89,6 +90,68 @@ public function denormalize(mixed $value): int $this->assertEquals(['foo' => 1], $normalizer->denormalize(['foo' => '1'])); } + public function testNormalizePassesContextToInnerNormalizer(): void + { + $context = ['key' => 'value']; + + $innerNormalizer = new class implements NormalizerWithContext { + /** @var array> */ + public array $contexts = []; + + /** @param array $context */ + public function normalize(mixed $value, array $context = []): mixed + { + $this->contexts[] = $context; + + return $value; + } + + /** @param array $context */ + public function denormalize(mixed $value, array $context = []): mixed + { + $this->contexts[] = $context; + + return $value; + } + }; + + $normalizer = new ArrayShapeNormalizer(['foo' => $innerNormalizer, 'bar' => $innerNormalizer]); + $normalizer->normalize(['foo' => 'a', 'bar' => 'b'], $context); + + $this->assertSame([$context, $context], $innerNormalizer->contexts); + } + + public function testDenormalizePassesContextToInnerNormalizer(): void + { + $context = ['key' => 'value']; + + $innerNormalizer = new class implements NormalizerWithContext { + /** @var array> */ + public array $contexts = []; + + /** @param array $context */ + public function normalize(mixed $value, array $context = []): mixed + { + $this->contexts[] = $context; + + return $value; + } + + /** @param array $context */ + public function denormalize(mixed $value, array $context = []): mixed + { + $this->contexts[] = $context; + + return $value; + } + }; + + $normalizer = new ArrayShapeNormalizer(['foo' => $innerNormalizer, 'bar' => $innerNormalizer]); + $normalizer->denormalize(['foo' => 'a', 'bar' => 'b'], $context); + + $this->assertSame([$context, $context], $innerNormalizer->contexts); + } + public function testPassHydrator(): void { $hydrator = $this->createMock(Hydrator::class); diff --git a/tests/Unit/Normalizer/ObjectMapNormalizerTest.php b/tests/Unit/Normalizer/ObjectMapNormalizerTest.php index 1b8b0549..237e418f 100644 --- a/tests/Unit/Normalizer/ObjectMapNormalizerTest.php +++ b/tests/Unit/Normalizer/ObjectMapNormalizerTest.php @@ -5,6 +5,7 @@ namespace Patchlevel\Hydrator\Tests\Unit\Normalizer; use Patchlevel\Hydrator\Hydrator; +use Patchlevel\Hydrator\HydratorWithContext; use Patchlevel\Hydrator\Normalizer\InvalidArgument; use Patchlevel\Hydrator\Normalizer\MissingHydrator; use Patchlevel\Hydrator\Normalizer\ObjectMapNormalizer; @@ -128,6 +129,56 @@ public function testDenormalizeWithValue(): void ); } + public function testNormalizePassesContextToHydrator(): void + { + $context = ['key' => 'value']; + $hydrator = $this->createMock(HydratorWithContext::class); + + $event = new ProfileCreated( + ProfileId::fromString('1'), + Email::fromString('info@patchlevel.de'), + ); + + $hydrator + ->expects($this->once()) + ->method('extract') + ->with($event, $context) + ->willReturn(['profileId' => '1', 'email' => 'info@patchlevel.de']); + + $normalizer = new ObjectMapNormalizer([ProfileCreated::class => 'created']); + $normalizer->setHydrator($hydrator); + + self::assertEquals( + ['profileId' => '1', 'email' => 'info@patchlevel.de', '_type' => 'created'], + $normalizer->normalize($event, $context), + ); + } + + public function testDenormalizePassesContextToHydrator(): void + { + $context = ['key' => 'value']; + $hydrator = $this->createMock(HydratorWithContext::class); + + $expected = new ProfileCreated( + ProfileId::fromString('1'), + Email::fromString('info@patchlevel.de'), + ); + + $hydrator + ->expects($this->once()) + ->method('hydrate') + ->with(ProfileCreated::class, ['profileId' => '1', 'email' => 'info@patchlevel.de'], $context) + ->willReturn($expected); + + $normalizer = new ObjectMapNormalizer([ProfileCreated::class => 'created']); + $normalizer->setHydrator($hydrator); + + $this->assertEquals( + $expected, + $normalizer->denormalize(['profileId' => '1', 'email' => 'info@patchlevel.de', '_type' => 'created'], $context), + ); + } + public function testSerialize(): void { $hydrator = $this->createStub(Hydrator::class); diff --git a/tests/Unit/Normalizer/ObjectNormalizerTest.php b/tests/Unit/Normalizer/ObjectNormalizerTest.php index 3b894e13..2f53a153 100644 --- a/tests/Unit/Normalizer/ObjectNormalizerTest.php +++ b/tests/Unit/Normalizer/ObjectNormalizerTest.php @@ -6,6 +6,7 @@ use Attribute; use Patchlevel\Hydrator\Hydrator; +use Patchlevel\Hydrator\HydratorWithContext; use Patchlevel\Hydrator\Normalizer\InvalidArgument; use Patchlevel\Hydrator\Normalizer\InvalidType; use Patchlevel\Hydrator\Normalizer\MissingHydrator; @@ -133,6 +134,53 @@ public function testDenormalizeWithValue(): void ); } + public function testNormalizePassesContextToHydrator(): void + { + $context = ['key' => 'value']; + $hydrator = $this->createMock(HydratorWithContext::class); + + $event = new ProfileCreated( + ProfileId::fromString('1'), + Email::fromString('info@patchlevel.de'), + ); + + $hydrator->expects($this->once())->method('extract')->with($event, $context) + ->willReturn(['profileId' => '1', 'email' => 'info@patchlevel.de']); + + $normalizer = new ObjectNormalizer(ProfileCreated::class); + $normalizer->setHydrator($hydrator); + + self::assertEquals( + ['profileId' => '1', 'email' => 'info@patchlevel.de'], + $normalizer->normalize($event, $context), + ); + } + + public function testDenormalizePassesContextToHydrator(): void + { + $context = ['key' => 'value']; + $hydrator = $this->createMock(HydratorWithContext::class); + + $expected = new ProfileCreated( + ProfileId::fromString('1'), + Email::fromString('info@patchlevel.de'), + ); + + $hydrator->expects($this->once())->method('hydrate')->with( + ProfileCreated::class, + ['profileId' => '1', 'email' => 'info@patchlevel.de'], + $context, + )->willReturn($expected); + + $normalizer = new ObjectNormalizer(ProfileCreated::class); + $normalizer->setHydrator($hydrator); + + $this->assertEquals( + $expected, + $normalizer->denormalize(['profileId' => '1', 'email' => 'info@patchlevel.de'], $context), + ); + } + public function testAutoDetect(): void { $hydrator = $this->createMock(Hydrator::class);