Skip to content

Commit

Permalink
feat(attribute): allow multiple source/target, allow overriding attri…
Browse files Browse the repository at this point in the history
…bute with priority system (#117)

This make those attributes consistent with the way of the `#[Mapper]`
attribute works

I think the priority system is better than throwing an exception, and
would use case that were not possible before
  • Loading branch information
joelwurtz committed May 5, 2024
2 parents 48a0415 + fe334e5 commit 1b23ffc
Show file tree
Hide file tree
Showing 8 changed files with 70 additions and 32 deletions.
31 changes: 29 additions & 2 deletions docs/mapping/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,32 @@ class EntityDto
}
```

> [!WARNING]
> If multiple `#[MapTo]` and/or `#[MapFrom]` attributes target the same property an exception will be thrown.
You can also pass an array to the `target` or `source` argument to specify configuration for multiple targets or sources.
```php
class EntityDto
{
#[MapFrom(source: Entity::class, property: 'title')]
#[MapFrom(source: 'array', property: 'name')]
public string $name;

#[MapFrom(source: [Entity::class, 'array'], property: 'bar')]
public string $foo;
}
```

In case there is multiple attributes that match the same target (not source), you can use the `priority` argument
to specify which one should be used first. The default priority is `0`.

```php
class Entity
{
#[MapTo(ignore: true)]
public string $title;
}

class EntityDto
{
#[MapFrom(source: Entity::class, ignore: false, priority: 10)]
public string $title;
}
```
17 changes: 9 additions & 8 deletions src/Attribute/MapFrom.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,23 @@
final readonly class MapFrom
{
/**
* @param class-string|'array'|null $source The specific source class name or array. If null this attribute will be used for all source classes.
* @param string|null $property The source property name. If null, the target property name will be used.
* @param int|null $maxDepth The maximum depth of the mapping. If null, the default max depth will be used.
* @param string|callable(mixed $value, object $object): mixed|null $transformer A transformer id or a callable that transform the value during mapping
* @param bool|null $ignore if true, the property will be ignored during mapping
* @param string|null $if The condition to map the property, using the expression language
* @param string[]|null $groups The groups to map the property
* @param class-string<object>|'array'|array<class-string<object>|'array'>|null $source The specific source class name or array. If null this attribute will be used for all source classes.
* @param string|null $property The source property name. If null, the target property name will be used.
* @param int|null $maxDepth The maximum depth of the mapping. If null, the default max depth will be used.
* @param string|callable(mixed $value, object $object): mixed|null $transformer A transformer id or a callable that transform the value during mapping
* @param bool|null $ignore if true, the property will be ignored during mapping
* @param string|null $if The condition to map the property, using the expression language
* @param string[]|null $groups The groups to map the property
*/
public function __construct(
public ?string $source = null,
public string|array|null $source = null,
public ?string $property = null,
public ?int $maxDepth = null,
public mixed $transformer = null,
public ?bool $ignore = null,
public ?string $if = null,
public ?array $groups = null,
public int $priority = 0,
) {
}
}
17 changes: 9 additions & 8 deletions src/Attribute/MapTo.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,23 @@
final readonly class MapTo
{
/**
* @param class-string|'array'|null $target The specific target class name or array. If null this attribute will be used for all target classes.
* @param string|null $property The target property name. If null, the source property name will be used.
* @param int|null $maxDepth The maximum depth of the mapping. If null, the default max depth will be used.
* @param string|callable(mixed $value, object $object): mixed|null $transformer A transformer id or a callable that transform the value during mapping
* @param bool|null $ignore if true, the property will be ignored during mapping
* @param string|null $if The condition to map the property, using the expression language
* @param string[]|null $groups The groups to map the property
* @param class-string<object>|'array'|array<class-string<object>|'array'>|null $target The specific target class name or array. If null this attribute will be used for all target classes.
* @param string|null $property The target property name. If null, the source property name will be used.
* @param int|null $maxDepth The maximum depth of the mapping. If null, the default max depth will be used.
* @param string|callable(mixed $value, object $object): mixed|null $transformer A transformer id or a callable that transform the value during mapping
* @param bool|null $ignore if true, the property will be ignored during mapping
* @param string|null $if The condition to map the property, using the expression language
* @param string[]|null $groups The groups to map the property
*/
public function __construct(
public ?string $target = null,
public string|array|null $target = null,
public ?string $property = null,
public ?int $maxDepth = null,
public mixed $transformer = null,
public ?bool $ignore = null,
public ?string $if = null,
public ?array $groups = null,
public int $priority = 0,
) {
}
}
1 change: 1 addition & 0 deletions src/Event/PropertyMetadataEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public function __construct(
public ?string $if = null,
public ?array $groups = null,
public ?bool $disableGroupsCheck = null,
public int $priority = 0,
) {
}
}
9 changes: 6 additions & 3 deletions src/EventListener/MapFromListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ public function __invoke(GenerateMapperEvent $event): void

private function addPropertyFromTarget(GenerateMapperEvent $event, MapFrom $mapFrom, string $property): void
{
if ($mapFrom->source !== null && $event->mapperMetadata->source !== $mapFrom->source) {
$sources = null === $mapFrom->source ? null : (\is_array($mapFrom->source) ? $mapFrom->source : [$mapFrom->source]);

if ($sources !== null && !\in_array($event->mapperMetadata->source, $sources, true)) {
return;
}

Expand All @@ -79,10 +81,11 @@ private function addPropertyFromTarget(GenerateMapperEvent $event, MapFrom $mapF
ignoreReason: $mapFrom->ignore === true ? 'Property is ignored by MapFrom Attribute on Target' : null,
if: $mapFrom->if,
groups: $mapFrom->groups,
priority: $mapFrom->priority,
);

if (\array_key_exists($property->target->property, $event->properties)) {
throw new BadMapDefinitionException(sprintf('There is already a MapTo or MapFrom attribute with target "%s" in class "%s" or class "%s".', $property->target->property, $event->mapperMetadata->source, $event->mapperMetadata->target));
if (\array_key_exists($property->target->property, $event->properties) && $event->properties[$property->target->property]->priority >= $property->priority) {
return;
}

$event->properties[$property->target->property] = $property;
Expand Down
9 changes: 6 additions & 3 deletions src/EventListener/MapToListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ public function __invoke(GenerateMapperEvent $event): void

private function addPropertyFromSource(GenerateMapperEvent $event, MapTo $mapTo, string $property): void
{
if ($mapTo->target !== null && $event->mapperMetadata->target !== $mapTo->target) {
$targets = null === $mapTo->target ? null : (\is_array($mapTo->target) ? $mapTo->target : [$mapTo->target]);

if ($targets !== null && !\in_array($event->mapperMetadata->target, $targets, true)) {
return;
}

Expand All @@ -80,10 +82,11 @@ private function addPropertyFromSource(GenerateMapperEvent $event, MapTo $mapTo,
ignoreReason: $mapTo->ignore === true ? 'Property is ignored by MapTo Attribute on Source' : null,
if: $mapTo->if,
groups: $mapTo->groups,
priority: $mapTo->priority,
);

if (\array_key_exists($property->target->property, $event->properties)) {
throw new BadMapDefinitionException(sprintf('There is already a MapTo attribute with target "%s" in class "%s".', $property->target->property, $event->mapperMetadata->source));
if (\array_key_exists($property->target->property, $event->properties) && $event->properties[$property->target->property]->priority >= $property->priority) {
return;
}

$event->properties[$property->target->property] = $property;
Expand Down
12 changes: 7 additions & 5 deletions tests/AutoMapperMapToTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
use AutoMapper\Exception\BadMapDefinitionException;
use AutoMapper\MapperContext;
use AutoMapper\Symfony\ExpressionLanguageProvider;
use AutoMapper\Tests\Fixtures\MapTo\BadMapTo;
use AutoMapper\Tests\Fixtures\MapTo\BadMapToTransformer;
use AutoMapper\Tests\Fixtures\MapTo\Bar;
use AutoMapper\Tests\Fixtures\MapTo\FooMapTo;
use AutoMapper\Tests\Fixtures\MapTo\PriorityMapTo;
use AutoMapper\Tests\Fixtures\Transformer\CustomTransformer\FooDependency;
use AutoMapper\Tests\Fixtures\Transformer\CustomTransformer\TransformerWithDependency;
use Symfony\Component\DependencyInjection\ServiceLocator;
Expand Down Expand Up @@ -114,12 +114,14 @@ public function testMapFromArray()
$this->assertSame('', $bar->getB());
}

public function testBadDefinitionOnSameTargetProperty()
public function testPriority()
{
$foo = new BadMapTo('foo');
$foo = new PriorityMapTo('foo');

$this->expectException(BadMapDefinitionException::class);
$this->autoMapper->map($foo, 'array');
$result = $this->autoMapper->map($foo, 'array');

self::assertArrayHasKey('foo', $result);
self::assertSame('foo', $result['foo']);
}

public function testBadDefinitionOnTransformer()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@

use AutoMapper\Attribute\MapTo;

class BadMapTo
class PriorityMapTo
{
public function __construct(
#[MapTo('array', ignore: true)]
#[MapTo('array', ignore: false)]
#[MapTo('array', ignore: true, priority: 0)]
#[MapTo('array', ignore: false, priority: 10)]
public string $foo
) {
}
Expand Down

0 comments on commit 1b23ffc

Please sign in to comment.