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
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"psr/container": "^1.1 || ^2.0"
},
"require-dev": {
"phpunit/phpunit": "~9.5.0",
"phpunit/phpunit": "^10",
"illuminate/container": "^10.0"
},
"autoload": {
Expand Down
3 changes: 2 additions & 1 deletion src/Annotation/ConcreteResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ abstract class ConcreteResolver

/**
* @param array $data
* @param array $all
*
* @return string|null
*/
abstract public function concreteFor(array $data): ?string;
abstract public function concreteFor(array $data, array $all): ?string;

/**
* @return array
Expand Down
48 changes: 28 additions & 20 deletions src/Hydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public function __construct(?ContainerInterface $container = null)
*
* @param class-string<T>|T $object
* @param array|object $data
* @param array $additional
*
* @throws Exception\UnsupportedPropertyTypeException
* If one of the object properties contains an unsupported type.
Expand All @@ -77,15 +78,15 @@ public function __construct(?ContainerInterface $container = null)
*
* @template T of object
*/
public function hydrate(string|object $object, array|object $data): object
public function hydrate(string|object $object, array|object $data, array $additional = []): object
{
if (is_object($data)) {
$data = get_object_vars($data);
}

$object = $this->initializeObject($object, $data);

$object = $this->initializeObject($object, $data, $additional);
$class = new ReflectionClass($object);
$keys = [];
foreach ($class->getProperties() as $property) {
// statical properties cannot be hydrated...
if ($property->isStatic()) {
Expand Down Expand Up @@ -147,14 +148,16 @@ public function hydrate(string|object $object, array|object $data): object
$data[$key] = $mutator->apply($data[$key]);
}

$this->hydrateProperty($object, $class, $property, $propertyType, $data[$key]);
unset($data[$key]);
$this->hydrateProperty($object, $class, $property, $propertyType, $data[$key], $data);
$keys[] = $key;
}

$data = array_diff_key($data, array_flip($keys));

// if the object has a __set method, we will use it to hydrate the remaining data
if (!empty($data) && $class->hasMethod('__set')) {
foreach ($data as $key => $value) {
$object->$key = $value;
$object->__set($key, $value);
}
}

Expand Down Expand Up @@ -215,6 +218,8 @@ public function getConcreteResolverFor(string|object $object): ?ConcreteResolver
* Initializes the given object.
*
* @param class-string<T>|T $object
* @param array|object $data
* @param array $additional
*
* @throws ContainerExceptionInterface
* If the object cannot be initialized.
Expand All @@ -224,7 +229,7 @@ public function getConcreteResolverFor(string|object $object): ?ConcreteResolver
*
* @template T
*/
private function initializeObject(string|object $object, array|object $data): object
private function initializeObject(string|object $object, array|object $data, array $additional = []): object
{
if (is_object($object)) {
return $object;
Expand Down Expand Up @@ -257,7 +262,7 @@ private function initializeObject(string|object $object, array|object $data): ob
$data = get_object_vars($data);
}

return $this->initializeObject($attribute->concreteFor($data), $data);
return $this->initializeObject($attribute->concreteFor($data, $additional), $data);
}

// if we have a container, get the instance through it
Expand Down Expand Up @@ -336,7 +341,8 @@ private function hydrateProperty(
ReflectionClass $class,
ReflectionProperty $property,
ReflectionNamedType $type,
mixed $value
mixed $value,
array $additional = []
): void {
$propertyType = $type->getName();

Expand All @@ -357,7 +363,7 @@ private function hydrateProperty(

'string' === $propertyType => $this->propertyString($object, $class, $property, $type, $value),

'array' === $propertyType => $this->propertyArray($object, $class, $property, $type, $value),
'array' === $propertyType => $this->propertyArray($object, $class, $property, $type, $value, $additional),

'object' === $propertyType => $this->propertyObject($object, $class, $property, $type, $value),

Expand All @@ -382,7 +388,7 @@ private function hydrateProperty(
BackedEnum::class
) => $this->propertyBackedEnum($object, $class, $property, $type, $value),

class_exists($propertyType) => $this->propertyFromInstance($object, $class, $property, $type, $value),
class_exists($propertyType) => $this->propertyFromInstance($object, $class, $property, $type, $value, $additional),

default => throw new Exception\UnsupportedPropertyTypeException(sprintf(
'The %s.%s property contains an unsupported type %s.',
Expand Down Expand Up @@ -593,7 +599,8 @@ private function propertyArray(
ReflectionClass $class,
ReflectionProperty $property,
ReflectionNamedType $type,
mixed $value
mixed $value,
array $additional = []
): void {
if (is_object($value)) {
$value = get_object_vars($value);
Expand All @@ -609,7 +616,7 @@ private function propertyArray(

$arrayType = $this->getAttributeInstance($property, ArrayType::class);
if ($arrayType !== null) {
$value = $this->hydrateObjectsInArray($value, $arrayType->class, $arrayType->depth);
$value = $this->hydrateObjectsInArray($value, $arrayType->class, $arrayType->depth, $additional);
}

$property->setValue($object, $value);
Expand All @@ -624,20 +631,20 @@ private function propertyArray(
*
* @return array
*/
private function hydrateObjectsInArray(array $array, string $class, int $depth): array
private function hydrateObjectsInArray(array $array, string $class, int $depth, array $additional = []): array
{
if ($depth > 1) {
return array_map(function ($child) use ($class, $depth) {
return $this->hydrateObjectsInArray($child, $class, --$depth);
return array_map(function ($child) use ($class, $depth, $additional) {
return $this->hydrateObjectsInArray($child, $class, --$depth, $additional);
}, $array);
}

return array_map(function ($object) use ($class) {
return array_map(function ($object) use ($class, $additional) {
if (is_subclass_of($class, BackedEnum::class)) {
return $class::tryFrom($object) ?? $object;
}

return $this->hydrate($class, $object);
return $this->hydrate($class, $object, $additional);
}, $array);
}

Expand Down Expand Up @@ -838,7 +845,8 @@ private function propertyFromInstance(
ReflectionClass $class,
ReflectionProperty $property,
ReflectionNamedType $type,
mixed $value
mixed $value,
array $additional = []
): void {
if (!is_array($value) && !is_object($value)) {
throw new Exception\InvalidValueException($property, sprintf(
Expand All @@ -848,6 +856,6 @@ private function propertyFromInstance(
));
}

$property->setValue($object, $this->hydrate($type->getName(), $value));
$property->setValue($object, $this->hydrate($type->getName(), $value, $additional));
}
}
1 change: 1 addition & 0 deletions tests/Fixtures/ObjectWithAbstract.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@
final class ObjectWithAbstract
{
public Apple $value;
public string $name = 'Apple';
}
2 changes: 1 addition & 1 deletion tests/Fixtures/Resolver/AppleResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class AppleResolver extends ConcreteResolver
'sauce' => AppleSauce::class,
];

public function concreteFor(array $data): ?string
public function concreteFor(array $data, array $all): ?string
{
return $this->concretes[$data['type']] ?? null;
}
Expand Down
18 changes: 18 additions & 0 deletions tests/HydratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,24 @@ public function testHydrateAbstractProperty(): void
$this->assertSame('brandy', $o->value->category);
}

public function testHydrateAbstractPropertyWithAdditional(): void
{
$o = (new Hydrator())->hydrate(new ObjectWithAbstract(), [
'name' => 'notApple',
'value' => [
'type' => 'jack',
'sweetness' => null,
'category' => 'brandy',
],
]);

$this->assertInstanceOf(ObjectWithAbstract::class, $o);
$this->assertInstanceOf(AppleJack::class, $o->value);
$this->assertSame('jack', $o->value->type);
$this->assertSame('brandy', $o->value->category);
$this->assertSame('notApple', $o->name);
}

public function testHydrateArrayAbstractProperty(): void
{
$o = (new Hydrator())->hydrate(new ObjectWithArrayOfAbstracts(), [
Expand Down