Skip to content
Draft
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
13 changes: 10 additions & 3 deletions src/AutoMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
) {
}

Expand Down Expand Up @@ -137,6 +140,7 @@ public function mapCollection(iterable $collection, string $target, array $conte
* @param TransformerFactoryInterface[] $transformerFactories
* @param ProviderInterface[] $providers
* @param iterable<string, PropertyTransformerInterface> $propertyTransformers
* @param iterable<string, ClassDiscriminatorMapping> $discriminatorMappings
*
* @return self
*/
Expand All @@ -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__);
Expand All @@ -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);
Expand All @@ -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,
Expand Down
6 changes: 4 additions & 2 deletions src/Generator/MapMethodStatementsGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)];
}
Expand Down Expand Up @@ -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
Expand Down
45 changes: 41 additions & 4 deletions src/Generator/Shared/ClassDiscriminatorResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@
/**
* @internal
*/
final readonly class ClassDiscriminatorResolver
final readonly class ClassDiscriminatorResolver implements ClassDiscriminatorResolverInterface
{
/**
* @param array<class-string, ClassDiscriminatorMapping> $mappings
*/
public function __construct(
private ?ClassDiscriminatorResolverInterface $classDiscriminator = null,
private array $mappings = [],
) {
}

Expand All @@ -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;
Expand All @@ -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 [];
Expand All @@ -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 [];
Expand All @@ -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);
}
}
2 changes: 1 addition & 1 deletion src/Metadata/MetadataFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down
2 changes: 2 additions & 0 deletions tests/AutoMapperBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -57,6 +58,7 @@ classPrefix: $classPrefix,
providers: $providers,
removeDefaultProperties: $removeDefaultProperties,
objectManager: $objectManager,
discriminatorMappings: $discriminatorMappings,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[
"myInterface" => [
"name" => "my name"
"type" => "type_a"
]
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
AutoMapper\Tests\AutoMapperTest\DiscriminatorMapConfig\Something {
+myInterface: AutoMapper\Tests\AutoMapperTest\DiscriminatorMapConfig\TypeA {
+name: "my name"
}
}
62 changes: 62 additions & 0 deletions tests/AutoMapperTest/DiscriminatorMapConfig/map.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

declare(strict_types=1);

namespace AutoMapper\Tests\AutoMapperTest\DiscriminatorMapConfig;

use AutoMapper\Tests\AutoMapperBuilder;
use Symfony\Component\Serializer\Attribute\DiscriminatorMap;
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping;

interface MyInterface
{
}

class Something
{
public function __construct(
public MyInterface $myInterface,
) {
}
}

class TypeA implements MyInterface
{
public function __construct(
public string $name,
) {
}
}

class TypeB implements MyInterface
{
public function __construct(
public string $age,
) {
}
}

return (function () {
$autoMapper = AutoMapperBuilder::buildAutoMapper(
discriminatorMappings: [MyInterface::class => 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');
})();