diff --git a/src/AutoMapper/.gitignore b/src/AutoMapper/.gitignore
index 0a2e709d2f..7a3ced3a67 100644
--- a/src/AutoMapper/.gitignore
+++ b/src/AutoMapper/.gitignore
@@ -4,3 +4,4 @@ composer.lock
Tests/generated/*
!Tests/generated/.gitkeep
Tests/fixtures/*/generated
+Bundle/Tests/Resources/var
diff --git a/src/AutoMapper/AbstractAutoMapper.php b/src/AutoMapper/AbstractAutoMapper.php
index 7bb2b74fdf..755a1eac0c 100644
--- a/src/AutoMapper/AbstractAutoMapper.php
+++ b/src/AutoMapper/AbstractAutoMapper.php
@@ -93,7 +93,7 @@ public function getConfiguration(string $source, string $target): ?MapperConfigu
return null;
}
- $this->register($this->mapperConfigurationFactory->create($source, $target));
+ $this->register($this->mapperConfigurationFactory->create($this, $source, $target));
}
return $this->configurations[$source][$target];
diff --git a/src/AutoMapper/AutoMapperRegisterInterface.php b/src/AutoMapper/AutoMapperRegisterInterface.php
index 1a61e9aff9..a72fea44bb 100644
--- a/src/AutoMapper/AutoMapperRegisterInterface.php
+++ b/src/AutoMapper/AutoMapperRegisterInterface.php
@@ -5,4 +5,6 @@
interface AutoMapperRegisterInterface
{
public function register(MapperConfigurationInterface $configuration): void;
+
+ public function getConfiguration(string $source, string $target): ?MapperConfigurationInterface;
}
diff --git a/src/AutoMapper/Bundle/DependencyInjection/JaneAutoMapperExtension.php b/src/AutoMapper/Bundle/DependencyInjection/JaneAutoMapperExtension.php
index fa5d752064..33f5fc1eb8 100644
--- a/src/AutoMapper/Bundle/DependencyInjection/JaneAutoMapperExtension.php
+++ b/src/AutoMapper/Bundle/DependencyInjection/JaneAutoMapperExtension.php
@@ -64,6 +64,7 @@ private function createMapperConfigurationDefinition(ContainerBuilder $container
$serviceName = 'Mapping_' . $config['source'] . '_' . $config['target'];
$definition = $container->register($serviceName, MapperConfiguration::class);
$definition->setFactory([new Reference(MapperConfigurationFactory::class), 'create']);
+ $definition->addArgument(new Reference(AutoMapper::class));
$definition->addArgument($config['source']);
$definition->addArgument($config['target']);
$definition->addTag('jane_auto_mapper.mapper_configuration');
diff --git a/src/AutoMapper/Bundle/Resources/config/services.xml b/src/AutoMapper/Bundle/Resources/config/services.xml
index 7af8b00dbf..4c573f664c 100644
--- a/src/AutoMapper/Bundle/Resources/config/services.xml
+++ b/src/AutoMapper/Bundle/Resources/config/services.xml
@@ -34,7 +34,7 @@
-
+ Symfony_Mapper_
diff --git a/src/AutoMapper/Bundle/Tests/Resources/app/AppKernel.php b/src/AutoMapper/Bundle/Tests/Resources/app/AppKernel.php
index dca81e92ab..8295577f42 100644
--- a/src/AutoMapper/Bundle/Tests/Resources/app/AppKernel.php
+++ b/src/AutoMapper/Bundle/Tests/Resources/app/AppKernel.php
@@ -49,6 +49,11 @@ public function indexAction()
{
return new Response();
}
+
+ public function getProjectDir()
+ {
+ return __DIR__ . '/..';
+ }
}
class UserConfigurationPass implements ConfigurationPassInterface
diff --git a/src/AutoMapper/Compiler/Compiler.php b/src/AutoMapper/Compiler/Compiler.php
index 974c69afac..3c9d17ad37 100644
--- a/src/AutoMapper/Compiler/Compiler.php
+++ b/src/AutoMapper/Compiler/Compiler.php
@@ -40,6 +40,7 @@ public function compile(MapperConfigurationInterface $mapperConfiguration)
$constructStatements = [];
$injectMapperStatements = [];
$addedDependencies = [];
+ $canHaveCircularDependency = $mapperConfiguration->canHaveCircularDependency() && $mapperConfiguration->getSource() !== 'array';
$statements = [
new Stmt\If_(new Expr\BinaryOp\Identical(new Expr\ConstFetch(new Name('null')), $sourceInput), [
@@ -47,7 +48,7 @@ public function compile(MapperConfigurationInterface $mapperConfiguration)
]),
];
- if ($mapperConfiguration->getSource() !== 'array') {
+ if ($canHaveCircularDependency) {
$statements[] = new Stmt\Expression(new Expr\Assign($hashVariable, new Expr\BinaryOp\Concat(new Expr\FuncCall(new Name('spl_object_hash'), [
new Arg($sourceInput),
]),
@@ -96,7 +97,7 @@ public function compile(MapperConfigurationInterface $mapperConfiguration)
}
if (\count($addedDependencies) > 0) {
- if ($mapperConfiguration->getSource() !== 'array') {
+ if ($canHaveCircularDependency) {
$statements[] = new Stmt\Expression(new Expr\Assign(
$contextVariable,
new Expr\MethodCall($contextVariable, 'withReference', [
diff --git a/src/AutoMapper/MapperConfiguration.php b/src/AutoMapper/MapperConfiguration.php
index e67ad71a31..5a61f4208e 100644
--- a/src/AutoMapper/MapperConfiguration.php
+++ b/src/AutoMapper/MapperConfiguration.php
@@ -13,9 +13,12 @@ class MapperConfiguration extends AbstractMapperConfiguration
private $customMapping = [];
- public function __construct(PropertiesMappingExtractorInterface $mappingExtractor, string $source, string $target, string $classPrefix = 'Mapper_')
+ private $autoMapperRegister;
+
+ public function __construct(AutoMapperRegisterInterface $autoMapperRegister, PropertiesMappingExtractorInterface $mappingExtractor, string $source, string $target, string $classPrefix = 'Mapper_')
{
$this->mappingExtractor = $mappingExtractor;
+ $this->autoMapperRegister = $autoMapperRegister;
parent::__construct($source, $target, $classPrefix);
}
@@ -103,4 +106,36 @@ public function isTargetCloneable(): bool
return $reflection->isCloneable() && !$reflection->hasMethod('__clone');
}
+
+ public function canHaveCircularDependency(): bool
+ {
+ $checked = [];
+
+ return $this->checkCircularMapperConfiguration($this, $checked);
+ }
+
+ protected function checkCircularMapperConfiguration(MapperConfigurationInterface $configuration, &$checked)
+ {
+ foreach ($configuration->getPropertiesMapping() as $propertyMapping) {
+ foreach ($propertyMapping->getTransformer()->getDependencies() as $dependency) {
+ if (isset($checked[$dependency->getName()])) {
+ continue;
+ }
+
+ $checked[$dependency->getName()] = true;
+
+ if ($dependency->getSource() === $this->getSource() && $dependency->getTarget() === $this->getTarget()) {
+ return true;
+ }
+
+ $subConfiguration = $this->autoMapperRegister->getConfiguration($dependency->getSource(), $dependency->getTarget());
+
+ if (null !== $subConfiguration && true === $this->checkCircularMapperConfiguration($subConfiguration, $checked)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
}
diff --git a/src/AutoMapper/MapperConfigurationFactory.php b/src/AutoMapper/MapperConfigurationFactory.php
index 4cd67aeb12..04e750d366 100644
--- a/src/AutoMapper/MapperConfigurationFactory.php
+++ b/src/AutoMapper/MapperConfigurationFactory.php
@@ -25,7 +25,7 @@ public function __construct(
$this->classPrefix = $classPrefix;
}
- public function create($source, $target): MapperConfigurationInterface
+ public function create(AutoMapperRegisterInterface $autoMapperRegister, $source, $target): MapperConfiguration
{
$extractor = $this->sourceTargetPropertiesMappingExtractor;
@@ -37,6 +37,6 @@ public function create($source, $target): MapperConfigurationInterface
$extractor = $this->fromSourcePropertiesMappingExtractor;
}
- return new MapperConfiguration($extractor, $source, $target, $this->classPrefix);
+ return new MapperConfiguration($autoMapperRegister, $extractor, $source, $target, $this->classPrefix);
}
}
diff --git a/src/AutoMapper/MapperConfigurationInterface.php b/src/AutoMapper/MapperConfigurationInterface.php
index 1a369b0e21..f10fa1eda3 100644
--- a/src/AutoMapper/MapperConfigurationInterface.php
+++ b/src/AutoMapper/MapperConfigurationInterface.php
@@ -28,4 +28,6 @@ public function isConstructorAllowed(): bool;
public function isTargetCloneable(): bool;
public function getDateTimeFormat(): string;
+
+ public function canHaveCircularDependency(): bool;
}