Skip to content

Commit

Permalink
Merge pull request #12 from EventSaucePHP/feature/resolve-nested-prop…
Browse files Browse the repository at this point in the history
…erties

[FEATURE] Added ability to define nested input
  • Loading branch information
frankdejonge authored Apr 28, 2022
2 parents 86cc979 + 855440a commit 2e626d8
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 15 deletions.
18 changes: 18 additions & 0 deletions src/Fixtures/ClassWithPropertyMappedFromNestedKey.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace EventSauce\ObjectHydrator\Fixtures;

use EventSauce\ObjectHydrator\MapFrom;

#[ExampleData(['nested' => ['name' => 'Frank']])]
class ClassWithPropertyMappedFromNestedKey
{
public function __construct(
#[MapFrom('nested.name', separator: '.')]
public string $name
)
{
}
}
14 changes: 8 additions & 6 deletions src/MapFrom.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,23 @@
namespace EventSauce\ObjectHydrator;

use Attribute;
use function explode;
use function is_string;

#[Attribute(Attribute::TARGET_PARAMETER)]
#[Attribute(Attribute::TARGET_PARAMETER | Attribute::TARGET_PROPERTY)]
final class MapFrom
{
/** @var array<string, string> */
/** @var array<string, array<string>> */
public array $keys = [];

public function __construct(string|array $keyOrMap)
public function __construct(string|array $keyOrMap, public ?string $separator = null)
{
if (is_string($keyOrMap)) {
$this->keys[$keyOrMap] = $keyOrMap;
$this->keys[$keyOrMap] = $this->separator ? explode($this->separator, $keyOrMap) : [$keyOrMap];
} else {
foreach ($keyOrMap as $index => $key) {
$this->keys[is_string($index) ? $index : $key] = $key;
foreach ($keyOrMap as $index => $toKey) {
$fromKey = is_string($index) ? $index : $toKey;
$this->keys[$toKey] = $this->separator ? explode($this->separator, $fromKey) : [$fromKey];
}
}
}
Expand Down
15 changes: 12 additions & 3 deletions src/ObjectHydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,19 @@ public function hydrateObject(string $className, array $payload): object
foreach ($classDefinition->propertyDefinitions as $definition) {
$value = [];

foreach ($definition->keys as $from => $to) {
if (array_key_exists($from, $payload)) {
$value[$to] = $payload[$from];
foreach ($definition->keys as $to => $from) {
$p = $payload;

foreach ($from as $fromSegment) {
if ( ! is_array($p) || ! array_key_exists($fromSegment, $p)) {
goto next_property;
}
$p = $p[$fromSegment];
}

$value[$to] = $p;

next_property:
}

if ($value === []) {
Expand Down
12 changes: 8 additions & 4 deletions src/ObjectHydratorDumper.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

namespace EventSauce\ObjectHydrator;

use function array_keys;
use function array_pop;
use function array_values;
use function count;
use function explode;
use function implode;
Expand Down Expand Up @@ -86,7 +86,8 @@ private function dumpClassHydrator(string $className, ClassDefinition $classDefi
$property = $definition->property;

if (count($keys) === 1) {
$from = array_keys($keys)[0];
$from = array_values($keys)[0];
$from = implode('\'][\'', $from);
$body .= <<<CODE
\$value = \$payload['$from'] ?? null;
Expand All @@ -99,10 +100,13 @@ private function dumpClassHydrator(string $className, ClassDefinition $classDefi
} else {
$collectKeys = '';

foreach ($keys as $from => $to) {
foreach ($keys as $to => $from) {
$from = implode('\'][\'', $from);
$collectKeys .= <<<CODE
if (array_key_exists('$from', \$payload)) {
\$to = \$payload['$from'] ?? null;
if (\$to !== null) {
\$value['$to'] = \$payload['$from'];
}
Expand Down
27 changes: 27 additions & 0 deletions src/ObjectHydratorTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use EventSauce\ObjectHydrator\Fixtures\ClassWithMappedStringProperty;
use EventSauce\ObjectHydrator\Fixtures\ClassWithNotCastedDateTimeInput;
use EventSauce\ObjectHydrator\Fixtures\ClassWithPropertyCasting;
use EventSauce\ObjectHydrator\Fixtures\ClassWithPropertyMappedFromNestedKey;
use EventSauce\ObjectHydrator\Fixtures\ClassWithPropertyThatUsesListCasting;
use EventSauce\ObjectHydrator\Fixtures\ClassWithPropertyThatUsesListCastingToClasses;
use EventSauce\ObjectHydrator\Fixtures\ClassWithStaticConstructor;
Expand All @@ -39,6 +40,32 @@ public function properties_can_be_mapped_from_a_specific_key(): void
self::assertEquals('Frank', $object->name);
}

/**
* @test
*/
public function mapping_a_nested_key(): void
{
$hydrator = $this->createObjectHydrator();

/** @var ClassWithPropertyMappedFromNestedKey $object */
$object = $hydrator->hydrateObject(ClassWithPropertyMappedFromNestedKey::class, ['nested' => ['name' => 'Frank']]);

self::assertInstanceOf(ClassWithPropertyMappedFromNestedKey::class, $object);
self::assertEquals('Frank', $object->name);
}

/**
* @test
*/
public function trying_to_map_a_nested_key_from_shallow_input(): void
{
$hydrator = $this->createObjectHydrator();

$this->expectExceptionObject(UnableToHydrateObject::dueToError(ClassWithPropertyMappedFromNestedKey::class));

$hydrator->hydrateObject(ClassWithPropertyMappedFromNestedKey::class, ['nested' => 'Frank']);
}

/**
* @test
*/
Expand Down
2 changes: 1 addition & 1 deletion src/PropertyDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
final class PropertyDefinition
{
public function __construct(
/** @var array<string, string> */
/** @var array<string, array<string>> */
public array $keys,
public string $property,
public array $propertyCasters,
Expand Down
2 changes: 1 addition & 1 deletion src/ReflectionDefinitionProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public function provideDefinition(string $className): ClassDefinition
$firstTypeName = $parameterType->firstTypeName();
$definition = [
'property' => $paramName,
'keys' => [$paramName => $paramName],
'keys' => [$paramName => [$paramName]],
'enum' => $parameterType->isEnum(),
];

Expand Down

0 comments on commit 2e626d8

Please sign in to comment.