diff --git a/src/AutoMapper.php b/src/AutoMapper.php index f8c618d0..1913d71b 100644 --- a/src/AutoMapper.php +++ b/src/AutoMapper.php @@ -27,6 +27,8 @@ use Symfony\Component\Lock\LockFactory; use Symfony\Component\Lock\Store\FlockStore; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata; +use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping; +use Symfony\Component\Serializer\Mapping\ClassDiscriminatorResolverInterface; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; @@ -49,6 +51,7 @@ public function __construct( private readonly MetadataRegistry $metadataRegistry, private readonly ProviderRegistry $providerRegistry, private readonly ?ExpressionLanguageProvider $expressionLanguageProvider = null, + private readonly ?ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null, ) { } @@ -137,6 +140,7 @@ public function mapCollection(iterable $collection, string $target, array $conte * @param TransformerFactoryInterface[] $transformerFactories * @param ProviderInterface[] $providers * @param iterable $propertyTransformers + * @param iterable $discriminatorMappings * * @return self */ @@ -151,6 +155,7 @@ public static function create( iterable $providers = [], bool $removeDefaultProperties = false, ?ObjectManager $objectManager = null, + array $discriminatorMappings = [], ): AutoMapperInterface { if (\count($transformerFactories) > 0) { trigger_deprecation('jolicode/automapper', '9.0', 'The "$transformerFactories" property will be removed in version 10.0, AST transformer factories must be included within AutoMapper.', __METHOD__); @@ -172,11 +177,14 @@ public static function create( } $classMetadataFactory = null; - $classDiscriminatorFromClassMetadata = null; + $classDiscriminatorResolver = null; if (class_exists(ClassMetadataFactory::class) && $loaderClass !== null) { $classMetadataFactory = new ClassMetadataFactory($loaderClass); - $classDiscriminatorFromClassMetadata = new ClassDiscriminatorFromClassMetadata($classMetadataFactory); + $classDiscriminatorResolver = new ClassDiscriminatorResolver( + classDiscriminator: new ClassDiscriminatorFromClassMetadata($classMetadataFactory), + mappings: $discriminatorMappings, + ); } $providers = iterator_to_array($providers); @@ -188,7 +196,6 @@ public static function create( $customTransformerRegistry = new PropertyTransformerRegistry($propertyTransformers); $metadataRegistry = new MetadataRegistry($configuration); $providerRegistry = new ProviderRegistry($providers); - $classDiscriminatorResolver = new ClassDiscriminatorResolver($classDiscriminatorFromClassMetadata); $metadataFactory = MetadataFactory::create( $configuration, diff --git a/src/Generator/MapMethodStatementsGenerator.php b/src/Generator/MapMethodStatementsGenerator.php index 0b497e57..7441b4a8 100644 --- a/src/Generator/MapMethodStatementsGenerator.php +++ b/src/Generator/MapMethodStatementsGenerator.php @@ -53,7 +53,9 @@ public function getStatements(GeneratorMetadata $metadata): array $statements = [$this->ifSourceIsNullReturnNull($metadata)]; $statements = [...$statements, ...$this->handleCircularReference($metadata)]; - if ($this->createObjectStatementsGenerator->canUseTargetToPopulate($metadata)) { + $canUseTargetToPopulate = $this->createObjectStatementsGenerator->canUseTargetToPopulate($metadata); + + if ($canUseTargetToPopulate) { $statements = [...$statements, ...$this->initializeTargetToPopulate($metadata)]; $statements = [...$statements, ...$this->initializeTargetFromProvider($metadata)]; } @@ -98,7 +100,7 @@ public function getStatements(GeneratorMetadata $metadata): array } } - if (\count($duplicatedStatements) > 0 && \count($metadata->getPropertiesInConstructor())) { + if ($canUseTargetToPopulate && \count($duplicatedStatements) > 0 && \count($metadata->getPropertiesInConstructor())) { /** * We know that the last statement is an `if` statement (otherwise we can't add an `else` statement). * Without this logic, the addedDependencies would only be called when the target was set. If the target is diff --git a/src/Generator/Shared/ClassDiscriminatorResolver.php b/src/Generator/Shared/ClassDiscriminatorResolver.php index ea94779d..90646847 100644 --- a/src/Generator/Shared/ClassDiscriminatorResolver.php +++ b/src/Generator/Shared/ClassDiscriminatorResolver.php @@ -13,10 +13,14 @@ /** * @internal */ -final readonly class ClassDiscriminatorResolver +final readonly class ClassDiscriminatorResolver implements ClassDiscriminatorResolverInterface { + /** + * @param array $mappings + */ public function __construct( private ?ClassDiscriminatorResolverInterface $classDiscriminator = null, + private array $mappings = [], ) { } @@ -34,7 +38,7 @@ public function hasClassDiscriminator(GeneratorMetadata $metadata, bool $fromSou public function getDiscriminatorPropertyMetadata(GeneratorMetadata $metadata, bool $fromSource): ?PropertyMetadata { - $classDiscriminatorMapping = $this->classDiscriminator?->getMappingForClass($fromSource ? $metadata->mapperMetadata->source : $metadata->mapperMetadata->target); + $classDiscriminatorMapping = $this->getMappingForClass($fromSource ? $metadata->mapperMetadata->source : $metadata->mapperMetadata->target); if (!$classDiscriminatorMapping) { return null; @@ -54,7 +58,7 @@ public function getDiscriminatorPropertyMetadata(GeneratorMetadata $metadata, bo */ public function discriminatorMapperNames(GeneratorMetadata $metadata, bool $fromSource): array { - $classDiscriminatorMapping = $this->classDiscriminator?->getMappingForClass($fromSource ? $metadata->mapperMetadata->source : $metadata->mapperMetadata->target); + $classDiscriminatorMapping = $this->getMappingForClass($fromSource ? $metadata->mapperMetadata->source : $metadata->mapperMetadata->target); if (!$classDiscriminatorMapping) { return []; @@ -71,7 +75,7 @@ public function discriminatorMapperNames(GeneratorMetadata $metadata, bool $from */ public function discriminatorMapperNamesIndexedByTypeValue(GeneratorMetadata $metadata, bool $fromSource): array { - $classDiscriminatorMapping = $this->classDiscriminator?->getMappingForClass($fromSource ? $metadata->mapperMetadata->source : $metadata->mapperMetadata->target); + $classDiscriminatorMapping = $this->getMappingForClass($fromSource ? $metadata->mapperMetadata->source : $metadata->mapperMetadata->target); if (!$classDiscriminatorMapping) { return []; @@ -93,4 +97,37 @@ private function discriminatorNames(GeneratorMetadata $metadata, ClassDiscrimina $classDiscriminatorMapping->getTypesMapping() ); } + + public function getMappingForClass(string $class): null|ClassDiscriminatorMapping + { + if (\array_key_exists($class, $this->mappings)) { + return $this->mappings[$class]; + } + + return $this->classDiscriminator?->getMappingForClass($class); + } + + public function getMappingForMappedObject(object|string $object): null|ClassDiscriminatorMapping + { + foreach ($this->mappings as $baseClass => $mapping) { + if ($object instanceof $baseClass) { + return $mapping; + } + } + + return $this->classDiscriminator?->getMappingForMappedObject($object); + } + + public function getTypeForMappedObject(object|string $object): ?string + { + foreach ($this->mappings as $mapping) { + foreach ($mapping->getTypesMapping() as $type => $className) { + if ($object instanceof $className) { + return $type; + } + } + } + + return $this->classDiscriminator?->getTypeForMappedObject($object); + } } diff --git a/src/Metadata/MetadataFactory.php b/src/Metadata/MetadataFactory.php index bd09d55c..1db1eedc 100644 --- a/src/Metadata/MetadataFactory.php +++ b/src/Metadata/MetadataFactory.php @@ -375,7 +375,7 @@ public static function create( $eventDispatcher->addListener(PropertyMetadataEvent::class, new SerializerMaxDepthListener($classMetadataFactory)); $eventDispatcher->addListener(PropertyMetadataEvent::class, new SerializerGroupListener($classMetadataFactory)); $eventDispatcher->addListener(PropertyMetadataEvent::class, new SerializerIgnoreListener($classMetadataFactory)); - $eventDispatcher->addListener(GenerateMapperEvent::class, new ClassDiscriminatorListener(new ClassDiscriminatorFromClassMetadata($classMetadataFactory))); + $eventDispatcher->addListener(GenerateMapperEvent::class, new ClassDiscriminatorListener($classDiscriminatorResolver)); } elseif (null !== $nameConverter) { $eventDispatcher->addListener(PropertyMetadataEvent::class, new AdvancedNameConverterListener($nameConverter)); } diff --git a/tests/AutoMapperBuilder.php b/tests/AutoMapperBuilder.php index 85c7dc14..e983b9ae 100644 --- a/tests/AutoMapperBuilder.php +++ b/tests/AutoMapperBuilder.php @@ -31,6 +31,7 @@ public static function buildAutoMapper( EventDispatcherInterface $eventDispatcher = new EventDispatcher(), bool $removeDefaultProperties = false, ?ObjectManager $objectManager = null, + array $discriminatorMappings = [], ): AutoMapper { $skipCacheRemove = $_SERVER['SKIP_CACHE_REMOVE'] ?? false; @@ -57,6 +58,7 @@ classPrefix: $classPrefix, providers: $providers, removeDefaultProperties: $removeDefaultProperties, objectManager: $objectManager, + discriminatorMappings: $discriminatorMappings, ); } } diff --git a/tests/AutoMapperTest/DiscriminatorMapConfig/expected.to-array.data b/tests/AutoMapperTest/DiscriminatorMapConfig/expected.to-array.data new file mode 100644 index 00000000..34a75b80 --- /dev/null +++ b/tests/AutoMapperTest/DiscriminatorMapConfig/expected.to-array.data @@ -0,0 +1,6 @@ +[ + "myInterface" => [ + "name" => "my name" + "type" => "type_a" + ] +] diff --git a/tests/AutoMapperTest/DiscriminatorMapConfig/expected.to-class.data b/tests/AutoMapperTest/DiscriminatorMapConfig/expected.to-class.data new file mode 100644 index 00000000..9089786f --- /dev/null +++ b/tests/AutoMapperTest/DiscriminatorMapConfig/expected.to-class.data @@ -0,0 +1,5 @@ +AutoMapper\Tests\AutoMapperTest\DiscriminatorMapConfig\Something { + +myInterface: AutoMapper\Tests\AutoMapperTest\DiscriminatorMapConfig\TypeA { + +name: "my name" + } +} diff --git a/tests/AutoMapperTest/DiscriminatorMapConfig/map.php b/tests/AutoMapperTest/DiscriminatorMapConfig/map.php new file mode 100644 index 00000000..acbdd51e --- /dev/null +++ b/tests/AutoMapperTest/DiscriminatorMapConfig/map.php @@ -0,0 +1,62 @@ + new ClassDiscriminatorMapping( + typeProperty: 'type', + typesMapping: [ + 'type_a' => TypeA::class, + 'type_b' => TypeB::class, + ] + )] + ); + + $something = [ + 'myInterface' => [ + 'type' => 'type_a', + 'name' => 'my name', + ], + ]; + yield 'to-class' => $autoMapper->map($something, Something::class); + + $typeA = new TypeA('my name'); + $something = new Something($typeA); + + yield 'to-array' => $autoMapper->map($something, 'array'); +})();