Skip to content

Commit 381c58d

Browse files
authored
feat(mapper): allow multiple fields in #[MapFrom] (#944)
1 parent 2479148 commit 381c58d

File tree

7 files changed

+65
-6
lines changed

7 files changed

+65
-6
lines changed

src/Tempest/Mapper/src/MapFrom.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@
99
#[Attribute(Attribute::TARGET_PROPERTY)]
1010
final readonly class MapFrom
1111
{
12+
/** @var array<string> */
13+
public array $names;
14+
1215
public function __construct(
13-
public string $name,
16+
string ...$names,
1417
) {
18+
$this->names = $names;
1519
}
1620
}

src/Tempest/Mapper/src/Mappers/ArrayToObjectMapper.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public function map(mixed $from, mixed $to): object
5858
continue;
5959
}
6060

61-
$propertyName = $this->resolvePropertyName($property);
61+
$propertyName = $this->resolvePropertyName($property, $from);
6262

6363
if (! array_key_exists($propertyName, $from)) {
6464
$isStrictProperty = $isStrictClass || $property->hasAttribute(Strict::class);
@@ -118,12 +118,15 @@ public function map(mixed $from, mixed $to): object
118118
return $object;
119119
}
120120

121-
private function resolvePropertyName(PropertyReflector $property): string
121+
/**
122+
* @param array<mixed> $from
123+
*/
124+
private function resolvePropertyName(PropertyReflector $property, array $from): string
122125
{
123126
$mapFrom = $property->getAttribute(MapFrom::class);
124127

125128
if ($mapFrom !== null) {
126-
return $mapFrom->name;
129+
return arr($from)->keys()->intersect($mapFrom->names)->first() ?? $property->getName();
127130
}
128131

129132
return $property->getName();

src/Tempest/Mapper/src/ObjectFactory.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ final class ObjectFactory
2828
public function __construct(
2929
private readonly MapperConfig $config,
3030
private readonly Container $container,
31-
) {}
31+
) {
32+
}
3233

3334
/**
3435
* @template T of object

src/Tempest/Reflection/src/PropertyReflector.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ public function isPublic(): bool
8686
return $this->reflectionProperty->isPublic();
8787
}
8888

89+
public function isReadonly(): bool
90+
{
91+
return $this->reflectionProperty->isReadOnly();
92+
}
93+
8994
public function getIterableType(): ?TypeReflector
9095
{
9196
$doc = $this->reflectionProperty->getDocComment();

src/Tempest/Support/src/ArrayHelper.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,7 @@ public function equals(array|self $other): bool
489489
*
490490
* @param null|Closure(TValue $value, TKey $key): bool $filter
491491
*
492-
* @return TValue
492+
* @return TValue|null
493493
*/
494494
public function first(?Closure $filter = null): mixed
495495
{
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Tempest\Integration\Mapper\Fixtures;
6+
7+
use Tempest\Mapper\MapFrom;
8+
9+
final class ObjectWithMultipleMapFrom
10+
{
11+
public function __construct(
12+
#[MapFrom('name', 'first_name')]
13+
public readonly string $fullName,
14+
) {
15+
}
16+
}

tests/Integration/Mapper/MapperTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use Tests\Tempest\Integration\Mapper\Fixtures\ObjectWithMapToAttribute;
2222
use Tests\Tempest\Integration\Mapper\Fixtures\ObjectWithMapToCollisions;
2323
use Tests\Tempest\Integration\Mapper\Fixtures\ObjectWithMapToCollisionsJsonSerializable;
24+
use Tests\Tempest\Integration\Mapper\Fixtures\ObjectWithMultipleMapFrom;
2425
use Tests\Tempest\Integration\Mapper\Fixtures\ObjectWithStrictOnClass;
2526
use Tests\Tempest\Integration\Mapper\Fixtures\ObjectWithStrictProperty;
2627
use Tests\Tempest\Integration\Mapper\Fixtures\Person;
@@ -258,4 +259,33 @@ enum: EnumToCast::FOO,
258259
],
259260
);
260261
}
262+
263+
public function test_multiple_map_from_source(): void
264+
{
265+
$object = map(['name' => 'Guillaume'])->to(ObjectWithMultipleMapFrom::class);
266+
$this->assertSame('Guillaume', $object->fullName);
267+
268+
$object = map(['first_name' => 'Guillaume'])->to(ObjectWithMultipleMapFrom::class);
269+
$this->assertSame('Guillaume', $object->fullName);
270+
}
271+
272+
public function test_multiple_map_from_take_first_occurence(): void
273+
{
274+
$data = [
275+
'name' => 'Guillaume',
276+
'first_name' => 'John',
277+
];
278+
279+
$object = map($data)->to(ObjectWithMultipleMapFrom::class);
280+
$this->assertSame('Guillaume', $object->fullName);
281+
}
282+
283+
public function test_multiple_map_from_fallback_to_property_name(): void
284+
{
285+
$object = map([
286+
'fullName' => 'Guillaume',
287+
])->to(ObjectWithMapFromAttribute::class);
288+
289+
$this->assertSame('Guillaume', $object->fullName);
290+
}
261291
}

0 commit comments

Comments
 (0)