Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -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\<BackedEnum\>\|null\) does not accept string\.$#'
identifier: assign.propertyType
Expand All @@ -90,12 +84,24 @@ parameters:
count: 1
path: src/Normalizer/ObjectMapNormalizer.php

-
message: '#^Parameter \#2 \$data of method Patchlevel\\Hydrator\\HydratorWithContext\:\:hydrate\(\) expects array\<string, mixed\>, array given\.$#'
identifier: argument.type
count: 1
path: src/Normalizer/ObjectMapNormalizer.php

-
message: '#^Parameter \#2 \$data of method Patchlevel\\Hydrator\\Hydrator\:\:hydrate\(\) expects array\<string, mixed\>, array\<mixed, mixed\> given\.$#'
identifier: argument.type
count: 1
path: src/Normalizer/ObjectNormalizer.php

-
message: '#^Parameter \#2 \$data of method Patchlevel\\Hydrator\\HydratorWithContext\:\:hydrate\(\) expects array\<string, mixed\>, array\<mixed, mixed\> 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
Expand Down
28 changes: 28 additions & 0 deletions src/HydratorWithContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Patchlevel\Hydrator;

interface HydratorWithContext extends Hydrator
{
/**
* @param class-string<T> $class
* @param array<string, mixed> $data
* @param array<string, mixed> $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<string, mixed> $context
*
* @return array<string, mixed>
*/
public function extract(object $object, array $context = []): array;
}
46 changes: 32 additions & 14 deletions src/MetadataHydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -30,7 +31,7 @@

use const PHP_VERSION_ID;

final class MetadataHydrator implements Hydrator
final class MetadataHydrator implements HydratorWithContext
{
/** @var array<int, class-string> */
private array $stack = [];
Expand All @@ -57,12 +58,13 @@ public function __construct(
/**
* @param class-string<T> $class
* @param array<string, mixed> $data
* @param array<string, mixed> $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);
Expand All @@ -71,31 +73,32 @@ 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);
},
);
}

/**
* @param ClassMetadata<T> $metadata
* @param array<string, mixed> $data
* @param array<string, mixed> $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;
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -167,8 +174,12 @@ private function doHydrate(ClassMetadata $metadata, array $data): object
return $object;
}

/** @return array<string, mixed> */
public function extract(object $object): array
/**
* @param array<string, mixed> $context
*
* @return array<string, mixed>
*/
public function extract(object $object, array $context = []): array
{
$objectId = spl_object_id($object);

Expand Down Expand Up @@ -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(),
Expand Down
38 changes: 29 additions & 9 deletions src/Normalizer/ArrayNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<array-key, mixed>|null */
public function normalize(mixed $value): array|null
/**
* @param array<string, mixed> $context
*
* @return array<array-key, mixed>|null
*/
public function normalize(mixed $value, array $context = []): array|null
{
if ($value === null) {
return null;
Expand All @@ -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<array-key, mixed>|null */
public function denormalize(mixed $value): array|null
/**
* @param array<string, mixed> $context
*
* @return array<array-key, mixed>|null
*/
public function denormalize(mixed $value, array $context = []): array|null
{
if ($value === null) {
return null;
Expand All @@ -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;
Expand Down
30 changes: 23 additions & 7 deletions src/Normalizer/ArrayShapeNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,20 @@
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<array-key, Normalizer> $normalizerMap */
public function __construct(
private array $normalizerMap,
) {
}

/** @return array<array-key, mixed>|null */
public function normalize(mixed $value): array|null
/**
* @param array<string, mixed> $context
*
* @return array<array-key, mixed>|null
*/
public function normalize(mixed $value, array $context = []): array|null
{
if ($value === null) {
return null;
Expand All @@ -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<array-key, mixed>|null */
public function denormalize(mixed $value): array|null
/**
* @param array<string, mixed> $context
*
* @return array<array-key, mixed>|null
*/
public function denormalize(mixed $value, array $context = []): array|null
{
if ($value === null) {
return null;
Expand All @@ -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;
Expand Down
22 changes: 22 additions & 0 deletions src/Normalizer/NormalizerWithContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace Patchlevel\Hydrator\Normalizer;

interface NormalizerWithContext extends Normalizer
{
/**
* @param array<string, mixed> $context
*
* @throws InvalidArgument
*/
public function normalize(mixed $value, array $context = []): mixed;

/**
* @param array<string, mixed> $context
*
* @throws InvalidArgument
*/
public function denormalize(mixed $value, array $context = []): mixed;
}
Loading
Loading