Skip to content

Commit 31a1e01

Browse files
committed
feat(type): allow to define source / target property type in attribute with a string
1 parent baf0cb2 commit 31a1e01

File tree

11 files changed

+61
-11
lines changed

11 files changed

+61
-11
lines changed

docs/bundle/expression-language.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ The `env` function allow you to access the environment variables.
1111
```php
1212
class Entity
1313
{
14-
#[MapTo('array', if: "env('FEATURE_ENABLED')")]
14+
#[MapTo(target: 'array', if: "env('FEATURE_ENABLED')")]
1515
public string $name;
1616
}
1717
```

docs/bundle/migrate.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ For example, if you have a custom normalizer that add a `virtualProperty` the no
8080
to do the same thing.
8181

8282
```php
83-
#[MapTo('array', property: 'virtualProperty', transformer: MyTransformer::class)]
83+
#[MapTo(target: 'array', property: 'virtualProperty', transformer: MyTransformer::class)]
8484
class App\Entity\MyEntity
8585
{
8686
// ...

docs/mapping/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ a `source` and a `target`.
1212
- [Groups](groups.md)
1313
- [Transformer](transformer.md)
1414
- [Provider](provider.md)
15+
- [Type](type.md)
1516
- [Mapping inheritance](inheritance.md)
1617
- [Identifier: mapping existing objects](identifier.md)
1718
- [DateTime format](date-time.md)

docs/mapping/type.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Property Type
2+
3+
When mapping properties, AutoMapper uses the property type to determine how to map the value from the source to the target.
4+
5+
It works well when both source and target properties are object, but when mapping to or from a generic data structure
6+
like an array or `\stdClass`, the property type is not available.
7+
8+
In this case the type is, by default, transformed to a native PHP type (`int`, `float`, `string`, `bool`, `array`, `object`, `null`).
9+
10+
You can override this behavior by specifying the `sourcePropertyType` or `targetPropertyType` argument in the
11+
`#[MapTo]` or `#[MapFrom]` attributes.
12+
13+
```php
14+
class Entity
15+
{
16+
#[MapTo(target: 'array', targetPropertyType: 'int']
17+
public string $number;
18+
}
19+
```
20+
21+
In this example, when mapping to an array, the `number` property will be converted to an `int` instead of a `string`.
22+
23+
This can also be useful when mapping to an object type with an union type, but you want to force a specific type during the mapping.
24+
25+
```php
26+
class EntityDto
27+
{
28+
#[MapTo(target: Entity:class, sourcePropertyType: 'int']
29+
private int|float $value;
30+
}
31+
```
32+
33+
In this example we consider, that the source property is always an `int`, so AutoMapper will never consider
34+
the `float` type during the mapping.

src/Attribute/MapFrom.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
* @param string|null $dateTimeFormat The date-time format to use when transforming this property
2424
* @param bool|null $extractTypesFromGetter If true, the types will be extracted from the getter method
2525
* @param bool|null $identifier If true, the property will be used as an identifier
26+
* @param Type|string|null $sourcePropertyType Override the source property type, where this property is mapped from
27+
* @param Type|string|null $targetPropertyType Override the target property type, which in this case is the property type where the attribute is defined
2628
*/
2729
public function __construct(
2830
public string|array|null $source = null,
@@ -36,8 +38,8 @@ public function __construct(
3638
public ?string $dateTimeFormat = null,
3739
public ?bool $extractTypesFromGetter = null,
3840
public ?bool $identifier = null,
39-
public ?Type $sourceType = null,
40-
public ?Type $targetType = null,
41+
public null|string|Type $sourcePropertyType = null,
42+
public null|string|Type $targetPropertyType = null,
4143
) {
4244
}
4345
}

src/Attribute/MapTo.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
* @param string|null $dateTimeFormat The date-time format to use when transforming this property
2424
* @param bool|null $extractTypesFromGetter If true, the types will be extracted from the getter method
2525
* @param bool|null $identifier If true, the property will be used as an identifier
26+
* @param Type|string|null $sourcePropertyType Override the source property type, which in this case is the property type where the attribute is defined
27+
* @param Type|string|null $targetPropertyType Override the target property type where this property is mapped to
2628
*/
2729
public function __construct(
2830
public string|array|null $target = null,
@@ -36,8 +38,8 @@ public function __construct(
3638
public ?string $dateTimeFormat = null,
3739
public ?bool $extractTypesFromGetter = null,
3840
public ?bool $identifier = null,
39-
public ?Type $sourceType = null,
40-
public ?Type $targetType = null,
41+
public null|string|Type $sourcePropertyType = null,
42+
public null|string|Type $targetPropertyType = null,
4143
) {
4244
}
4345
}

src/EventListener/MapFromListener.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,11 @@ private function addPropertyFromTarget(GenerateMapperEvent $event, MapFrom $mapF
6868
return;
6969
}
7070

71-
$sourceProperty = new SourcePropertyMetadata($mapFrom->property ?? $property, type: $mapFrom->sourceType);
72-
$targetProperty = new TargetPropertyMetadata($property, type: $mapFrom->targetType);
71+
$sourcePropertyType = is_string($mapFrom->sourcePropertyType) ? $this->stringTypeResolver->resolve($mapFrom->sourcePropertyType) : $mapFrom->sourcePropertyType;
72+
$targetPropertyType = is_string($mapFrom->targetPropertyType) ? $this->stringTypeResolver->resolve($mapFrom->targetPropertyType) : $mapFrom->targetPropertyType;
73+
74+
$sourceProperty = new SourcePropertyMetadata($mapFrom->property ?? $property, type: $sourcePropertyType);
75+
$targetProperty = new TargetPropertyMetadata($property, type: $targetPropertyType);
7376

7477
$propertyMetadata = new PropertyMetadataEvent(
7578
mapperMetadata: $event->mapperMetadata,

src/EventListener/MapListener.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
1919
use Symfony\Component\String\Inflector\EnglishInflector;
2020
use Symfony\Component\String\Inflector\InflectorInterface;
21+
use Symfony\Component\TypeInfo\TypeResolver\StringTypeResolver;
2122

2223
/**
2324
* @internal
@@ -28,6 +29,7 @@ public function __construct(
2829
private PropertyTransformerRegistry $propertyTransformerRegistry,
2930
private ExpressionLanguage $expressionLanguage,
3031
private InflectorInterface $inflector = new EnglishInflector(),
32+
protected StringTypeResolver $stringTypeResolver = new StringTypeResolver(),
3133
) {
3234
}
3335

src/EventListener/MapToListener.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,11 @@ private function addPropertyFromSource(GenerateMapperEvent $event, MapTo $mapTo,
6969
return;
7070
}
7171

72-
$sourceProperty = new SourcePropertyMetadata($property, type: $mapTo->sourceType);
73-
$targetProperty = new TargetPropertyMetadata($mapTo->property ?? $property, type: $mapTo->targetType);
72+
$sourcePropertyType = is_string($mapTo->sourcePropertyType) ? $this->stringTypeResolver->resolve($mapTo->sourcePropertyType) : $mapTo->sourcePropertyType;
73+
$targetPropertyType = is_string($mapTo->targetPropertyType) ? $this->stringTypeResolver->resolve($mapTo->targetPropertyType) : $mapTo->targetPropertyType;
74+
75+
$sourceProperty = new SourcePropertyMetadata($property, type: $sourcePropertyType);
76+
$targetProperty = new TargetPropertyMetadata($mapTo->property ?? $property, type: $targetPropertyType);
7477

7578
$propertyMetadata = new PropertyMetadataEvent(
7679
mapperMetadata: $event->mapperMetadata,

tests/AutoMapperMapToTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public function testMapToArray()
6161
$this->assertSame('transformed', $bar['transformFromExpressionLanguage']);
6262
$this->assertSame('bar', $bar['transformWithExpressionFunction']);
6363
$this->assertSame(0, $bar['fooInt']);
64+
$this->assertSame(0.0, $bar['fooFloat']);
6465

6566
$foo = new FooMapTo('bar');
6667
$bar = $this->autoMapper->map($foo, 'array');
@@ -80,6 +81,7 @@ public function testMapToArray()
8081
$this->assertSame('bar', $bar['transformFromCustomTransformerService']);
8182
$this->assertSame('not transformed', $bar['transformFromExpressionLanguage']);
8283
$this->assertSame(0, $bar['fooInt']);
84+
$this->assertSame(0.0, $bar['fooFloat']);
8385
}
8486

8587
public function testMapToArrayGroups()

0 commit comments

Comments
 (0)