Skip to content

Commit eadca46

Browse files
authored
Merge pull request #319 from jolicode/feat/discriminator
feat(discriminator): add discriminator def, support class to class discriminator
2 parents f1f7ef0 + 3736dbd commit eadca46

16 files changed

Lines changed: 264 additions & 138 deletions

File tree

src/Attribute/Mapper.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace AutoMapper\Attribute;
66

77
use AutoMapper\ConstructorStrategy;
8+
use AutoMapper\Metadata\Discriminator;
89

910
/**
1011
* Configures a mapper.
@@ -27,6 +28,7 @@ public function __construct(
2728
public int $priority = 0,
2829
public ?string $dateTimeFormat = null,
2930
public ?bool $allowExtraProperties = null,
31+
public ?Discriminator $discriminator = null,
3032
) {
3133
}
3234
}

src/AutoMapper.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
2626
use Symfony\Component\Lock\LockFactory;
2727
use Symfony\Component\Lock\Store\FlockStore;
28-
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata;
2928
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
3029
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
3130
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
@@ -157,11 +156,9 @@ public static function create(
157156
}
158157

159158
$classMetadataFactory = null;
160-
$classDiscriminatorFromClassMetadata = null;
161159

162160
if (class_exists(ClassMetadataFactory::class) && $loaderClass !== null) {
163161
$classMetadataFactory = new ClassMetadataFactory($loaderClass);
164-
$classDiscriminatorFromClassMetadata = new ClassDiscriminatorFromClassMetadata($classMetadataFactory);
165162
}
166163

167164
$providers = iterator_to_array($providers);
@@ -194,7 +191,7 @@ public static function create(
194191
}
195192

196193
$metadataRegistry = new MetadataRegistry($configuration);
197-
$classDiscriminatorResolver = new ClassDiscriminatorResolver($classDiscriminatorFromClassMetadata);
194+
$classDiscriminatorResolver = new ClassDiscriminatorResolver();
198195

199196
$metadataFactory = MetadataFactory::create(
200197
$configuration,

src/Event/GenerateMapperEvent.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace AutoMapper\Event;
66

77
use AutoMapper\ConstructorStrategy;
8+
use AutoMapper\Metadata\Discriminator;
89
use AutoMapper\Metadata\MapperMetadata;
910

1011
/**
@@ -24,6 +25,8 @@ public function __construct(
2425
public ?bool $allowReadOnlyTargetToPopulate = null,
2526
public ?bool $strictTypes = null,
2627
public ?bool $allowExtraProperties = null,
28+
public ?Discriminator $sourceDiscriminator = null,
29+
public ?Discriminator $targetDiscriminator = null,
2730
) {
2831
}
2932
}

src/EventListener/MapperListener.php

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public function __construct()
1818

1919
public function __invoke(GenerateMapperEvent $event): void
2020
{
21-
/** @var Mapper[] $mappers */
21+
/** @var array{0: Mapper, 1: bool}[] $mappers */
2222
$mappers = [];
2323

2424
if ($event->mapperMetadata->sourceReflectionClass) {
@@ -27,15 +27,15 @@ public function __invoke(GenerateMapperEvent $event): void
2727
$mapper = $attribute->newInstance();
2828

2929
if ($mapper->target === null) {
30-
$mappers[] = $mapper;
30+
$mappers[] = [$mapper, true];
3131
}
3232

3333
if (\is_string($mapper->target) && $mapper->target === $event->mapperMetadata->target) {
34-
$mappers[] = $mapper;
34+
$mappers[] = [$mapper, true];
3535
}
3636

3737
if (\is_array($mapper->target) && \in_array($event->mapperMetadata->target, $mapper->target, true)) {
38-
$mappers[] = $mapper;
38+
$mappers[] = [$mapper, true];
3939
}
4040
}
4141
}
@@ -46,34 +46,41 @@ public function __invoke(GenerateMapperEvent $event): void
4646
$mapper = $attribute->newInstance();
4747

4848
if ($mapper->source === null) {
49-
$mappers[] = $mapper;
49+
$mappers[] = [$mapper, false];
5050
}
5151

5252
if (\is_string($mapper->source) && $mapper->source === $event->mapperMetadata->source) {
53-
$mappers[] = $mapper;
53+
$mappers[] = [$mapper, false];
5454
}
5555

5656
if (\is_array($mapper->source) && \in_array($event->mapperMetadata->source, $mapper->source, true)) {
57-
$mappers[] = $mapper;
57+
$mappers[] = [$mapper, false];
5858
}
5959
}
6060
}
61-
6261
if (0 === \count($mappers)) {
6362
return;
6463
}
6564

6665
// sort by priority
67-
usort($mappers, fn (Mapper $a, Mapper $b) => $a->priority <=> $b->priority);
66+
usort($mappers, fn (array $a, array $b) => $a[0]->priority <=> $b[0]->priority);
6867

6968
// get mapper with highest priority
70-
$mapper = $mappers[0];
69+
[$mapper, $fromSource] = $mappers[0];
7170

7271
$event->checkAttributes ??= $mapper->checkAttributes;
7372
$event->constructorStrategy ??= $mapper->constructorStrategy;
7473
$event->allowReadOnlyTargetToPopulate ??= $mapper->allowReadOnlyTargetToPopulate;
7574
$event->strictTypes ??= $mapper->strictTypes;
7675
$event->allowExtraProperties ??= $mapper->allowExtraProperties;
7776
$event->mapperMetadata->dateTimeFormat = $mapper->dateTimeFormat;
77+
78+
if ($mapper->discriminator) {
79+
if ($fromSource) {
80+
$event->sourceDiscriminator = $mapper->discriminator;
81+
} else {
82+
$event->targetDiscriminator = $mapper->discriminator;
83+
}
84+
}
7885
}
7986
}

src/EventListener/Symfony/ClassDiscriminatorListener.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use AutoMapper\Event\PropertyMetadataEvent;
99
use AutoMapper\Event\SourcePropertyMetadata;
1010
use AutoMapper\Event\TargetPropertyMetadata;
11+
use AutoMapper\Metadata\Discriminator;
1112
use AutoMapper\Transformer\FixedValueTransformer;
1213
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping;
1314
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorResolverInterface;
@@ -37,6 +38,14 @@ public function __invoke(GenerateMapperEvent $event): void
3738
}
3839
}
3940

41+
if (null === $sourceType) {
42+
// It means the source is a parent class or interface of the mapped types
43+
$event->sourceDiscriminator = new Discriminator(
44+
mapping: $classDiscriminatorMappingSource->getTypesMapping(),
45+
propertyName: $classDiscriminatorMappingSource->getTypeProperty(),
46+
);
47+
}
48+
4049
$property = $classDiscriminatorMappingSource->getTypeProperty();
4150
$sourceProperty = new SourcePropertyMetadata($property);
4251
$targetProperty = new TargetPropertyMetadata($property);
@@ -50,6 +59,23 @@ public function __invoke(GenerateMapperEvent $event): void
5059
}
5160

5261
if ($classDiscriminatorMappingTarget) {
62+
$targetType = null;
63+
64+
foreach ($classDiscriminatorMappingTarget->getTypesMapping() as $type => $class) {
65+
if ($class === $event->mapperMetadata->target) {
66+
$targetType = $type;
67+
break;
68+
}
69+
}
70+
71+
if (null === $targetType) {
72+
// It means the target is a parent class or interface of the mapped types
73+
$event->targetDiscriminator = new Discriminator(
74+
mapping: $classDiscriminatorMappingTarget->getTypesMapping(),
75+
propertyName: $classDiscriminatorMappingTarget->getTypeProperty(),
76+
);
77+
}
78+
5379
$property = $classDiscriminatorMappingTarget->getTypeProperty();
5480
$sourceProperty = new SourcePropertyMetadata($property);
5581
$targetProperty = new TargetPropertyMetadata($property);

src/Generator/MapMethodStatementsGenerator.php

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -132,26 +132,32 @@ public function getStatements(GeneratorMetadata $metadata, array $duplicatedStat
132132
* }
133133
*/
134134
$lastStatement = $statements[array_key_last($statements)];
135-
\assert($lastStatement instanceof Stmt\If_);
136-
$lastStatement->stmts = [
137-
...$lastStatement->stmts,
138-
...$addedDependenciesStatements,
139-
];
140-
/*
141-
* Generate else statements when the result is already an object, which means it has already been created,
142-
* so we need to execute the statements that need to be executed before the constructor since the constructor has already been called
143-
*
144-
* ```php
145-
* if (null !== $result) {
146-
* .. // create object statements
147-
* } else {
148-
* // remap property from the constructor in case object already exists so we do not loose information
149-
* $source->propertyName = $this->extractCallbacks['propertyName']($source);
150-
* ...
151-
* }
152-
* ```
153-
*/
154-
$statements[] = new Stmt\Else_(array_merge($addedDependenciesStatements, $duplicatedStatements));
135+
136+
if ($lastStatement instanceof Stmt\If_) {
137+
$lastStatement->stmts = [
138+
...$lastStatement->stmts,
139+
...$addedDependenciesStatements,
140+
];
141+
142+
$statements[] = new Stmt\Else_(array_merge($addedDependenciesStatements, $duplicatedStatements));
143+
} else {
144+
$statements = [...$statements, ...$addedDependenciesStatements, ...$duplicatedStatements];
145+
}
146+
147+
/*
148+
* Generate else statements when the result is already an object, which means it has already been created,
149+
* so we need to execute the statements that need to be executed before the constructor since the constructor has already been called
150+
*
151+
* ```php
152+
* if (null !== $result) {
153+
* .. // create object statements
154+
* } else {
155+
* // remap property from the constructor in case object already exists so we do not loose information
156+
* $source->propertyName = $this->extractCallbacks['propertyName']($source);
157+
* ...
158+
* }
159+
* ```
160+
*/
155161
} else {
156162
$statements = [...$statements, ...$addedDependenciesStatements];
157163
}

src/Generator/Shared/ClassDiscriminatorResolver.php

Lines changed: 34 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -6,91 +6,70 @@
66

77
use AutoMapper\Metadata\GeneratorMetadata;
88
use AutoMapper\Metadata\PropertyMetadata;
9-
use AutoMapper\Transformer\TransformerInterface;
10-
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping;
11-
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorResolverInterface;
9+
use AutoMapper\Transformer\MapperDependency;
1210

1311
/**
1412
* @internal
1513
*/
1614
final readonly class ClassDiscriminatorResolver
1715
{
18-
public function __construct(
19-
private ?ClassDiscriminatorResolverInterface $classDiscriminator = null,
20-
) {
21-
}
22-
2316
public function hasClassDiscriminator(GeneratorMetadata $metadata, bool $fromSource): bool
2417
{
25-
if (!($fromSource ? $metadata->isSourceUserDefined() : $metadata->isTargetUserDefined())
26-
|| !($propertyMetadata = $this->getDiscriminatorPropertyMetadata($metadata, $fromSource))
27-
|| !$propertyMetadata->transformer instanceof TransformerInterface
28-
) {
29-
return false;
18+
if ($fromSource) {
19+
return $metadata->sourceDiscriminator !== null;
3020
}
3121

32-
return true;
22+
return $metadata->targetDiscriminator !== null;
3323
}
3424

3525
public function getDiscriminatorPropertyMetadata(GeneratorMetadata $metadata, bool $fromSource): ?PropertyMetadata
3626
{
37-
$classDiscriminatorMapping = $this->classDiscriminator?->getMappingForClass($fromSource ? $metadata->mapperMetadata->source : $metadata->mapperMetadata->target);
27+
$discriminator = $fromSource ? $metadata->sourceDiscriminator : $metadata->targetDiscriminator;
3828

39-
if (!$classDiscriminatorMapping) {
29+
if (!$discriminator) {
4030
return null;
4131
}
4232

43-
foreach ($metadata->propertiesMetadata as $propertyMetadata) {
44-
if (($fromSource ? $propertyMetadata->source->property : $propertyMetadata->target->property) === $classDiscriminatorMapping->getTypeProperty()) {
45-
return $propertyMetadata;
46-
}
47-
}
48-
49-
return null;
50-
}
51-
52-
/**
53-
* @return array<class-string<object>, string>
54-
*/
55-
public function discriminatorMapperNames(GeneratorMetadata $metadata, bool $fromSource): array
56-
{
57-
$classDiscriminatorMapping = $this->classDiscriminator?->getMappingForClass($fromSource ? $metadata->mapperMetadata->source : $metadata->mapperMetadata->target);
58-
59-
if (!$classDiscriminatorMapping) {
60-
return [];
33+
if ($discriminator->propertyName === null) {
34+
return null;
6135
}
6236

63-
return array_combine(
64-
array_values($classDiscriminatorMapping->getTypesMapping()),
65-
$this->discriminatorNames($metadata, $classDiscriminatorMapping, $fromSource)
37+
return array_find($metadata->propertiesMetadata,
38+
fn ($propertyMetadata,
39+
) => ($fromSource ? $propertyMetadata->source->property : $propertyMetadata->target->property) === $discriminator->propertyName
6640
);
6741
}
6842

6943
/**
70-
* @return array<string, string>
44+
* @return list<MapperDependency>
7145
*/
72-
public function discriminatorMapperNamesIndexedByTypeValue(GeneratorMetadata $metadata, bool $fromSource): array
46+
public function getMappersList(GeneratorMetadata $metadata, bool $fromSource): array
7347
{
74-
$classDiscriminatorMapping = $this->classDiscriminator?->getMappingForClass($fromSource ? $metadata->mapperMetadata->source : $metadata->mapperMetadata->target);
48+
$discriminator = $fromSource ? $metadata->sourceDiscriminator : $metadata->targetDiscriminator;
7549

76-
if (!$classDiscriminatorMapping) {
50+
if (!$discriminator) {
7751
return [];
7852
}
7953

80-
return array_combine(
81-
array_keys($classDiscriminatorMapping->getTypesMapping()),
82-
$this->discriminatorNames($metadata, $classDiscriminatorMapping, $fromSource)
83-
);
84-
}
54+
$classList = array_values($discriminator->mapping);
55+
$typeList = array_keys($discriminator->mapping);
56+
$targetClassList = $discriminator->propertyName === null ? $typeList : array_fill(0, \count($classList), $fromSource ? $metadata->mapperMetadata->target : $metadata->mapperMetadata->source);
57+
$mappers = [];
58+
59+
foreach ($classList as $index => $className) {
60+
/** @var class-string $sourceClass */
61+
$sourceClass = $fromSource ? $className : $targetClassList[$index];
62+
/** @var class-string $targetClass */
63+
$targetClass = $fromSource ? $targetClassList[$index] : $className;
64+
65+
$mappers[] = new MapperDependency(
66+
name: "Discriminator_Mapper_{$sourceClass}_{$targetClass}",
67+
source: $sourceClass,
68+
target: $targetClass,
69+
type: $typeList[$index],
70+
);
71+
}
8572

86-
/**
87-
* @return list<string>
88-
*/
89-
private function discriminatorNames(GeneratorMetadata $metadata, ClassDiscriminatorMapping $classDiscriminatorMapping, bool $fromSource): array
90-
{
91-
return array_map(
92-
static fn (string $typeTarget) => $fromSource ? "Discriminator_Mapper_{$typeTarget}_{$metadata->mapperMetadata->target}" : "Discriminator_Mapper_{$metadata->mapperMetadata->source}_{$typeTarget}",
93-
$classDiscriminatorMapping->getTypesMapping()
94-
);
73+
return $mappers;
9574
}
9675
}

0 commit comments

Comments
 (0)