Skip to content

Commit 81cb479

Browse files
committed
wip
1 parent 4e95abf commit 81cb479

File tree

15 files changed

+296
-197
lines changed

15 files changed

+296
-197
lines changed

src/AutoMapper.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ public function mapCollection(iterable $collection, string $target, array $conte
131131
/**
132132
* @param ProviderInterface[] $providers
133133
* @param iterable<string|int, PropertyTransformerInterface> $propertyTransformers
134+
* @param iterable<string|int, object> $extraMapperServices
134135
*
135136
* @return self
136137
*/
@@ -143,6 +144,7 @@ public static function create(
143144
EventDispatcherInterface $eventDispatcher = new EventDispatcher(),
144145
iterable $providers = [],
145146
?ObjectManager $objectManager = null,
147+
iterable $extraMapperServices = [],
146148
): AutoMapperInterface {
147149
if (class_exists(AttributeLoader::class)) {
148150
$loaderClass = new AttributeLoader();
@@ -193,6 +195,14 @@ public static function create(
193195
}
194196
}
195197

198+
foreach ($extraMapperServices as $key => $mapperService) {
199+
if (\is_int($key)) {
200+
$key = $mapperService::class;
201+
}
202+
203+
$serviceLocator->set($key, $mapperService);
204+
}
205+
196206
$metadataRegistry = new MetadataRegistry($configuration);
197207
$classDiscriminatorResolver = new ClassDiscriminatorResolver($classDiscriminatorFromClassMetadata);
198208

src/Event/GenerateMapperEvent.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66

77
use AutoMapper\ConstructorStrategy;
88
use AutoMapper\Metadata\MapperMetadata;
9+
use Symfony\Contracts\EventDispatcher\Event;
910

1011
/**
1112
* @internal
1213
*/
13-
final class GenerateMapperEvent
14+
final class GenerateMapperEvent extends Event
1415
{
1516
/**
1617
* @param PropertyMetadataEvent[] $properties A list of properties to add to this mapping

src/EventListener/ObjectMapper/MapClassListener.php

Lines changed: 0 additions & 152 deletions
This file was deleted.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace AutoMapper\EventListener\ObjectMapper;
6+
7+
use AutoMapper\Event\GenerateMapperEvent;
8+
use AutoMapper\Event\PropertyMetadataEvent;
9+
use AutoMapper\Event\SourcePropertyMetadata;
10+
use AutoMapper\Event\TargetPropertyMetadata;
11+
use AutoMapper\Exception\BadMapDefinitionException;
12+
use AutoMapper\Transformer\CallableTransformer;
13+
use AutoMapper\Transformer\ExpressionLanguageTransformer;
14+
use AutoMapper\Transformer\ServiceLocatorTransformer;
15+
use AutoMapper\Transformer\TransformerInterface;
16+
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
17+
use Symfony\Component\ExpressionLanguage\SyntaxError;
18+
use Symfony\Component\ObjectMapper\Attribute\Map;
19+
use Symfony\Component\ObjectMapper\TransformCallableInterface;
20+
21+
abstract readonly class MapListener
22+
{
23+
public function __construct(
24+
private ExpressionLanguage $expressionLanguage,
25+
) {
26+
}
27+
28+
protected function getTransformerFromMapAttribute(string $class, Map $attribute, bool $fromSource = true): ?TransformerInterface
29+
{
30+
$transformer = null;
31+
32+
if ($attribute->transform !== null) {
33+
$callableName = null;
34+
$transformerCallable = $attribute->transform;
35+
36+
if ($transformerCallable instanceof \Closure) {
37+
// This is not supported because we cannot generate code from a closure
38+
// However this should never be possible since attributes does not allow to pass a closure
39+
// Let's keep this check for future proof
40+
throw new BadMapDefinitionException('Closure transformer is not supported.');
41+
}
42+
43+
if (\is_callable($transformerCallable, false, $callableName)) {
44+
$transformer = new CallableTransformer($callableName);
45+
} elseif (\is_string($transformerCallable) && method_exists($class, $transformerCallable)) {
46+
$reflMethod = new \ReflectionMethod($class, $transformerCallable);
47+
48+
if ($reflMethod->isStatic()) {
49+
$transformer = new CallableTransformer($class . '::' . $transformerCallable);
50+
} else {
51+
$transformer = new CallableTransformer($transformerCallable, $fromSource, !$fromSource);
52+
}
53+
} elseif (\is_string($transformerCallable) && class_exists($transformerCallable) && is_subclass_of($transformerCallable, TransformCallableInterface::class)) {
54+
$transformer = new ServiceLocatorTransformer($transformerCallable);
55+
} elseif (\is_string($transformerCallable)) {
56+
try {
57+
$expression = $this->expressionLanguage->compile($transformerCallable, ['value' => 'source', 'context']);
58+
} catch (SyntaxError $e) {
59+
throw new BadMapDefinitionException(\sprintf('Transformer "%s" targeted by %s transformer on class "%s" is not valid.', $transformerCallable, $attribute::class, $class), 0, $e);
60+
}
61+
62+
$transformer = new ExpressionLanguageTransformer($expression);
63+
} else {
64+
throw new BadMapDefinitionException(\sprintf('Callable "%s" targeted by %s transformer on class "%s" is not valid.', json_encode($transformerCallable), $attribute::class, $class));
65+
}
66+
}
67+
68+
return $transformer;
69+
}
70+
}

src/EventListener/ObjectMapper/MapPropertyListener.php

Lines changed: 0 additions & 9 deletions
This file was deleted.
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace AutoMapper\EventListener\ObjectMapper;
6+
7+
use AutoMapper\Event\GenerateMapperEvent;
8+
use AutoMapper\Event\PropertyMetadataEvent;
9+
use AutoMapper\Event\SourcePropertyMetadata;
10+
use AutoMapper\Event\TargetPropertyMetadata;
11+
use Symfony\Component\ObjectMapper\Attribute\Map;
12+
13+
final readonly class MapSourceListener extends MapListener
14+
{
15+
public function __invoke(GenerateMapperEvent $event): void
16+
{
17+
// only handle class to class mapping
18+
if (!$event->mapperMetadata->sourceReflectionClass) {
19+
return;
20+
}
21+
22+
$mapAttribute = null;
23+
$hasAnyMapAttribute = false;
24+
25+
foreach ($event->mapperMetadata->sourceReflectionClass->getAttributes(Map::class) as $sourceAttribute) {
26+
/** @var Map $attribute */
27+
$attribute = $sourceAttribute->newInstance();
28+
$hasAnyMapAttribute = true;
29+
30+
if (!$attribute->target || $attribute->target === $event->mapperMetadata->target) {
31+
$mapAttribute = $attribute;
32+
break;
33+
}
34+
}
35+
36+
// it means that there is at least one Map attribute but none match the current mapping
37+
if (!$mapAttribute && $hasAnyMapAttribute) {
38+
return;
39+
}
40+
41+
// get all properties
42+
$properties = [];
43+
44+
foreach ($event->mapperMetadata->sourceReflectionClass->getProperties() as $property) {
45+
foreach ($property->getAttributes(Map::class) as $propertyAttribute) {
46+
/** @var Map $attribute */
47+
$attribute = $propertyAttribute->newInstance();
48+
$propertyMetadata = new PropertyMetadataEvent(
49+
/*
50+
* public ?string $if = null,// @TODO
51+
*/
52+
$event->mapperMetadata,
53+
new SourcePropertyMetadata($property->getName()),
54+
new TargetPropertyMetadata($attribute->target ?? $property->getName()),
55+
transformer: $this->getTransformerFromMapAttribute($event->mapperMetadata->sourceReflectionClass->getName(), $attribute, true),
56+
);
57+
58+
$ifCallableName = null;
59+
60+
if ($attribute->if && is_callable($attribute->if, false, $ifCallableName)) {
61+
$propertyMetadata->if = $ifCallableName;
62+
} elseif (is_string($attribute->if)) {
63+
$propertyMetadata->if = $attribute->if;
64+
}
65+
66+
$properties[] = $propertyMetadata;
67+
}
68+
}
69+
70+
$event->properties = $properties;
71+
72+
if ($mapAttribute?->transform) {
73+
$callableName = null;
74+
75+
if (is_callable($mapAttribute->transform, false, $callableName)) {
76+
$event->provider = $callableName;
77+
}
78+
}
79+
80+
// Stop propagation if any Map attribute is found
81+
if ($hasAnyMapAttribute || count($properties) > 0 || $mapAttribute) {
82+
$event->stopPropagation();
83+
}
84+
}
85+
}

0 commit comments

Comments
 (0)