Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(attribute): allow multiple source/target, allow overriding attribute with priority system #117

Merged
merged 1 commit into from
May 5, 2024
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
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
Loading