diff --git a/src/AutoMapper.php b/src/AutoMapper.php index 752b55e6..edf23682 100644 --- a/src/AutoMapper.php +++ b/src/AutoMapper.php @@ -10,6 +10,7 @@ use AutoMapper\Loader\ClassLoaderInterface; use AutoMapper\Loader\EvalLoader; use AutoMapper\Loader\FileLoader; +use AutoMapper\Metadata\MetadataFactory; use AutoMapper\Metadata\MetadataRegistry; use AutoMapper\Symfony\ExpressionLanguageProvider; use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerInterface; @@ -61,7 +62,7 @@ public function __construct( */ public function getMapper(string $source, string $target): MapperInterface { - $metadata = $this->metadataRegistry->getMapperMetadata($source, $target); + $metadata = $this->metadataRegistry->get($source, $target); $className = $metadata->className; if (\array_key_exists($className, $this->mapperRegistry)) { @@ -153,7 +154,17 @@ public static function create( } $customTransformerRegistry = new PropertyTransformerRegistry($propertyTransformers); - $metadataRegistry = MetadataRegistry::create($configuration, $customTransformerRegistry, $transformerFactories, $classMetadataFactory, $nameConverter, $expressionLanguage, $eventDispatcher); + $metadataRegistry = new MetadataRegistry($configuration); + $metadataFactory = MetadataFactory::create( + $configuration, + $customTransformerRegistry, + $metadataRegistry, + $transformerFactories, + $classMetadataFactory, + $nameConverter, + $expressionLanguage, + $eventDispatcher + ); $mapperGenerator = new MapperGenerator( new ClassDiscriminatorResolver($classDiscriminatorFromClassMetadata), @@ -162,9 +173,9 @@ public static function create( ); if (null === $cacheDirectory) { - $loader = new EvalLoader($mapperGenerator, $metadataRegistry); + $loader = new EvalLoader($mapperGenerator, $metadataFactory); } else { - $loader = new FileLoader($mapperGenerator, $metadataRegistry, $cacheDirectory); + $loader = new FileLoader($mapperGenerator, $metadataFactory, $cacheDirectory); } return new self($loader, $customTransformerRegistry, $metadataRegistry, $expressionLanguageProvider); diff --git a/src/Loader/ClassLoaderInterface.php b/src/Loader/ClassLoaderInterface.php index 1318726d..d21a8075 100644 --- a/src/Loader/ClassLoaderInterface.php +++ b/src/Loader/ClassLoaderInterface.php @@ -5,6 +5,7 @@ namespace AutoMapper\Loader; use AutoMapper\Metadata\MapperMetadata; +use AutoMapper\Metadata\MetadataRegistry; /** * Loads (require) a mapping given metadata. @@ -16,4 +17,6 @@ interface ClassLoaderInterface { public function loadClass(MapperMetadata $mapperMetadata): void; + + public function buildMappers(MetadataRegistry $registry): bool; } diff --git a/src/Loader/EvalLoader.php b/src/Loader/EvalLoader.php index 96ea5fb4..2b3c048d 100644 --- a/src/Loader/EvalLoader.php +++ b/src/Loader/EvalLoader.php @@ -6,6 +6,7 @@ use AutoMapper\Generator\MapperGenerator; use AutoMapper\Metadata\MapperMetadata; +use AutoMapper\Metadata\MetadataFactory; use AutoMapper\Metadata\MetadataRegistry; use PhpParser\PrettyPrinter\Standard; use PhpParser\PrettyPrinterAbstract; @@ -23,15 +24,20 @@ public function __construct( private MapperGenerator $generator, - private MetadataRegistry $metadataRegistry, + private MetadataFactory $metadataFactory, ) { $this->printer = new Standard(); } public function loadClass(MapperMetadata $mapperMetadata): void { - $class = $this->generator->generate($this->metadataRegistry->getGeneratorMetadata($mapperMetadata->source, $mapperMetadata->target)); + $class = $this->generator->generate($this->metadataFactory->getGeneratorMetadata($mapperMetadata->source, $mapperMetadata->target)); eval($this->printer->prettyPrint([$class])); } + + public function buildMappers(MetadataRegistry $registry): bool + { + return false; + } } diff --git a/src/Loader/FileLoader.php b/src/Loader/FileLoader.php index a25a1570..5944a367 100644 --- a/src/Loader/FileLoader.php +++ b/src/Loader/FileLoader.php @@ -6,6 +6,7 @@ use AutoMapper\Generator\MapperGenerator; use AutoMapper\Metadata\MapperMetadata; +use AutoMapper\Metadata\MetadataFactory; use AutoMapper\Metadata\MetadataRegistry; use PhpParser\PrettyPrinter\Standard; use PhpParser\PrettyPrinterAbstract; @@ -26,7 +27,7 @@ final class FileLoader implements ClassLoaderInterface public function __construct( private readonly MapperGenerator $generator, - private readonly MetadataRegistry $metadataRegistry, + private readonly MetadataFactory $metadataFactory, private readonly string $directory, private readonly bool $hotReload = true, ) { @@ -58,6 +59,15 @@ public function loadClass(MapperMetadata $mapperMetadata): void require $classPath; } + public function buildMappers(MetadataRegistry $registry): bool + { + foreach ($registry as $metadata) { + $this->saveMapper($metadata); + } + + return true; + } + /** * @return string The generated class name */ @@ -66,7 +76,7 @@ public function saveMapper(MapperMetadata $mapperMetadata): string $className = $mapperMetadata->className; $classPath = $this->directory . \DIRECTORY_SEPARATOR . $className . '.php'; - $generatorMetadata = $this->metadataRegistry->getGeneratorMetadata($mapperMetadata->source, $mapperMetadata->target); + $generatorMetadata = $this->metadataFactory->getGeneratorMetadata($mapperMetadata->source, $mapperMetadata->target); $classCode = $this->printer->prettyPrint([$this->generator->generate($generatorMetadata)]); $this->write($classPath, "> */ + private array $generatorMetadata = []; + + public function __construct( + private readonly Configuration $configuration, + private readonly SourceTargetMappingExtractor $sourceTargetPropertiesMappingExtractor, + private readonly FromSourceMappingExtractor $fromSourcePropertiesMappingExtractor, + private readonly FromTargetMappingExtractor $fromTargetPropertiesMappingExtractor, + private readonly TransformerFactoryInterface $transformerFactory, + private readonly EventDispatcherInterface $eventDispatcher, + public readonly MetadataRegistry $metadataRegistry, + ) { + } + + /** + * @param class-string|'array' $source + * @param class-string|'array' $target + * + * @internal + */ + public function getGeneratorMetadata(string $source, string $target): GeneratorMetadata + { + if (!isset($this->generatorMetadata[$source][$target])) { + $metadata = $this->createGeneratorMetadata($this->metadataRegistry->get($source, $target)); + $this->generatorMetadata[$source][$target] = $metadata; + + // Add dependencies to the mapper + foreach ($metadata->propertiesMetadata as $propertyMapping) { + if ($propertyMapping->transformer instanceof DependentTransformerInterface) { + foreach ($propertyMapping->transformer->getDependencies() as $mapperDependency) { + $dependencyMetadata = $this->getGeneratorMetadata($mapperDependency->source, $mapperDependency->target); + + $metadata->addDependency(new Dependency($mapperDependency, $dependencyMetadata)); + } + } + } + } + + return $this->generatorMetadata[$source][$target]; + } + + public function resolveAllMetadata(MetadataRegistry $metadataRegistry): void + { + $localGeneratorRegistry = []; + $remainingMetadata = iterator_to_array($metadataRegistry); + + while (!empty($remainingMetadata)) { + $mapperMetadata = array_shift($remainingMetadata); + + if (isset($localGeneratorRegistry[$mapperMetadata->source][$mapperMetadata->target])) { + continue; + } + + $generatorMetadata = $this->createGeneratorMetadata($mapperMetadata); + $localGeneratorRegistry[$mapperMetadata->source][$mapperMetadata->target] = $generatorMetadata; + + foreach ($generatorMetadata->propertiesMetadata as $propertyMetadata) { + if ($propertyMetadata->transformer instanceof DependentTransformerInterface) { + foreach ($propertyMetadata->transformer->getDependencies() as $mapperDependency) { + $remainingMetadata[] = $metadataRegistry->get($mapperDependency->source, $mapperDependency->target); + } + } + } + } + } + + private function createGeneratorMetadata(MapperMetadata $mapperMetadata): GeneratorMetadata + { + $extractor = $this->sourceTargetPropertiesMappingExtractor; + + if ('array' === $mapperMetadata->source || 'stdClass' === $mapperMetadata->source) { + $extractor = $this->fromTargetPropertiesMappingExtractor; + } + + if ('array' === $mapperMetadata->target || 'stdClass' === $mapperMetadata->target) { + $extractor = $this->fromSourcePropertiesMappingExtractor; + } + + $propertyEvents = []; + + $mapperEvent = new GenerateMapperEvent($mapperMetadata); + $this->eventDispatcher->dispatch($mapperEvent); + + // First get properties from the source + foreach ($extractor->getProperties($mapperMetadata->source) as $property) { + $propertyEvent = new PropertyMetadataEvent($mapperMetadata, new SourcePropertyMetadataEvent($property), new TargetPropertyMetadataEvent($property)); + + $this->eventDispatcher->dispatch($propertyEvent); + + $propertyEvents[$propertyEvent->target->name] = $propertyEvent; + } + + foreach ($extractor->getProperties($mapperMetadata->target) as $property) { + if (isset($propertyEvents[$property])) { + continue; + } + + $propertyEvent = new PropertyMetadataEvent($mapperMetadata, new SourcePropertyMetadataEvent($property), new TargetPropertyMetadataEvent($property)); + + $this->eventDispatcher->dispatch($propertyEvent); + + $propertyEvents[$propertyEvent->target->name] = $propertyEvent; + } + + foreach ($mapperEvent->properties as $propertyEvent) { + $this->eventDispatcher->dispatch($propertyEvent); + + $propertyEvents[$propertyEvent->target->name] = $propertyEvent; + } + + $propertiesMapping = []; + + foreach ($propertyEvents as $propertyMappedEvent) { + // Create the source property metadata + if ($propertyMappedEvent->source->accessor === null) { + $propertyMappedEvent->source->accessor = $extractor->getReadAccessor($mapperMetadata->source, $mapperMetadata->target, $propertyMappedEvent->source->name); + } + + if ($propertyMappedEvent->source->checkExists === null) { + $propertyMappedEvent->source->checkExists = $extractor->getCheckExists($mapperMetadata->source, $propertyMappedEvent->source->name); + } + + if ($propertyMappedEvent->source->extractGroupsIfNull && $propertyMappedEvent->source->groups === null) { + $propertyMappedEvent->source->groups = $extractor->getGroups($mapperMetadata->source, $propertyMappedEvent->source->name); + } + + if ($propertyMappedEvent->source->dateTimeFormat === null) { + $propertyMappedEvent->source->dateTimeFormat = $extractor->getDateTimeFormat($mapperMetadata->source, $propertyMappedEvent->source->name); + } + + // Create the target property metadata + if ($propertyMappedEvent->target->writeMutator === null) { + $propertyMappedEvent->target->writeMutator = $extractor->getWriteMutator($mapperMetadata->source, $mapperMetadata->target, $propertyMappedEvent->target->name, [ + 'enable_constructor_extraction' => false, + ]); + } + + if ($propertyMappedEvent->target->writeMutatorConstructor === null) { + $propertyMappedEvent->target->writeMutatorConstructor = $extractor->getWriteMutator($mapperMetadata->source, $mapperMetadata->target, $propertyMappedEvent->target->name, [ + 'enable_constructor_extraction' => true, + ]); + } + + if ($propertyMappedEvent->target->extractGroupsIfNull && $propertyMappedEvent->target->groups === null) { + $propertyMappedEvent->target->groups = $extractor->getGroups($mapperMetadata->target, $propertyMappedEvent->target->name); + } + + if ($propertyMappedEvent->target->dateTimeFormat === null) { + $propertyMappedEvent->target->dateTimeFormat = $extractor->getDateTimeFormat($mapperMetadata->target, $propertyMappedEvent->target->name); + } + + $sourcePropertyMetadata = SourcePropertyMetadata::fromEvent($propertyMappedEvent->source); + $targetPropertyMetadata = TargetPropertyMetadata::fromEvent($propertyMappedEvent->target); + + if (null === $propertyMappedEvent->types) { + $propertyMappedEvent->types = $extractor->getTypes($mapperMetadata->source, $sourcePropertyMetadata, $mapperMetadata->target, $targetPropertyMetadata); + } + + if (null === $propertyMappedEvent->transformer) { + $transformer = $this->transformerFactory->getTransformer($propertyMappedEvent->types, $sourcePropertyMetadata, $targetPropertyMetadata, $mapperMetadata); + + if (null === $transformer) { + continue; + } + + $propertyMappedEvent->transformer = $transformer; + } + + $propertiesMapping[] = new PropertyMetadata( + $sourcePropertyMetadata, + $targetPropertyMetadata, + $propertyMappedEvent->types, + $propertyMappedEvent->transformer, + $propertyMappedEvent->ignored ?? false, + $propertyMappedEvent->maxDepth, + $propertyMappedEvent->if, + $propertyMappedEvent->groups, + $propertyMappedEvent->disableGroupsCheck, + ); + } + + return new GeneratorMetadata($mapperMetadata, $propertiesMapping, $this->configuration->attributeChecking, $this->configuration->allowConstructor); + } + + /** + * @param TransformerFactoryInterface[] $transformerFactories + */ + public static function create( + Configuration $configuration, + PropertyTransformerRegistry $customTransformerRegistry, + MetadataRegistry $metadataRegistry, + array $transformerFactories = [], + ClassMetadataFactory $classMetadataFactory = null, + AdvancedNameConverterInterface $nameConverter = null, + ExpressionLanguage $expressionLanguage = new ExpressionLanguage(), + EventDispatcherInterface $eventDispatcher = new EventDispatcher(), + ): self { + // Create property info extractors + $flags = ReflectionExtractor::ALLOW_PUBLIC; + + if ($configuration->mapPrivateProperties) { + $flags |= ReflectionExtractor::ALLOW_PRIVATE | ReflectionExtractor::ALLOW_PROTECTED; + } + + $reflectionExtractor = new ReflectionExtractor(accessFlags: $flags); + $phpStanExtractor = new PhpStanExtractor(); + + if (null !== $nameConverter) { + $eventDispatcher->addListener(PropertyMetadataEvent::class, new AdvancedNameConverterListener($nameConverter)); + } + + if (null !== $classMetadataFactory) { + $eventDispatcher->addListener(PropertyMetadataEvent::class, new SerializerMaxDepthListener($classMetadataFactory)); + $eventDispatcher->addListener(PropertyMetadataEvent::class, new SerializerGroupListener($classMetadataFactory)); + $eventDispatcher->addListener(PropertyMetadataEvent::class, new SerializerIgnoreListener($classMetadataFactory)); + } + + $eventDispatcher->addListener(PropertyMetadataEvent::class, new MapToContextListener($reflectionExtractor)); + $eventDispatcher->addListener(GenerateMapperEvent::class, new MapToListener($customTransformerRegistry, $expressionLanguage)); + $eventDispatcher->addListener(GenerateMapperEvent::class, new MapFromListener($customTransformerRegistry, $expressionLanguage)); + + $propertyInfoExtractor = new PropertyInfoExtractor( + listExtractors: [$reflectionExtractor], + typeExtractors: [new ReadWriteTypeExtractor(), $phpStanExtractor, $reflectionExtractor], + accessExtractors: [$reflectionExtractor] + ); + + // Create transformer factories + $factories = [ + new MultipleTransformerFactory(), + new NullableTransformerFactory(), + new UniqueTypeTransformerFactory(), + new DateTimeTransformerFactory(), + new BuiltinTransformerFactory(), + new ArrayTransformerFactory(), + new ObjectTransformerFactory(), + new EnumTransformerFactory(), + new PropertyTransformerFactory($customTransformerRegistry), + ]; + + if (class_exists(AbstractUid::class)) { + $factories[] = new SymfonyUidTransformerFactory(); + } + + foreach ($transformerFactories as $transformerFactory) { + $factories[] = $transformerFactory; + } + + $transformerFactory = new ChainTransformerFactory($factories); + + $sourceTargetMappingExtractor = new SourceTargetMappingExtractor( + $configuration, + $propertyInfoExtractor, + $reflectionExtractor, + $reflectionExtractor, + ); + + $fromTargetMappingExtractor = new FromTargetMappingExtractor( + $configuration, + $propertyInfoExtractor, + $reflectionExtractor, + $reflectionExtractor, + ); + + $fromSourceMappingExtractor = new FromSourceMappingExtractor( + $configuration, + $propertyInfoExtractor, + $reflectionExtractor, + $reflectionExtractor, + ); + + return new self( + $configuration, + $sourceTargetMappingExtractor, + $fromSourceMappingExtractor, + $fromTargetMappingExtractor, + $transformerFactory, + $eventDispatcher, + $metadataRegistry, + ); + } +} diff --git a/src/Metadata/MetadataRegistry.php b/src/Metadata/MetadataRegistry.php index 2db5ae63..704922ec 100644 --- a/src/Metadata/MetadataRegistry.php +++ b/src/Metadata/MetadataRegistry.php @@ -5,323 +5,66 @@ namespace AutoMapper\Metadata; use AutoMapper\Configuration; -use AutoMapper\Event\GenerateMapperEvent; -use AutoMapper\Event\PropertyMetadataEvent; -use AutoMapper\Event\SourcePropertyMetadata as SourcePropertyMetadataEvent; -use AutoMapper\Event\TargetPropertyMetadata as TargetPropertyMetadataEvent; -use AutoMapper\EventListener\MapFromListener; -use AutoMapper\EventListener\MapToContextListener; -use AutoMapper\EventListener\MapToListener; -use AutoMapper\EventListener\Symfony\AdvancedNameConverterListener; -use AutoMapper\EventListener\Symfony\SerializerGroupListener; -use AutoMapper\EventListener\Symfony\SerializerIgnoreListener; -use AutoMapper\EventListener\Symfony\SerializerMaxDepthListener; -use AutoMapper\Extractor\FromSourceMappingExtractor; -use AutoMapper\Extractor\FromTargetMappingExtractor; -use AutoMapper\Extractor\ReadWriteTypeExtractor; -use AutoMapper\Extractor\SourceTargetMappingExtractor; -use AutoMapper\Transformer\ArrayTransformerFactory; -use AutoMapper\Transformer\BuiltinTransformerFactory; -use AutoMapper\Transformer\ChainTransformerFactory; -use AutoMapper\Transformer\DateTimeTransformerFactory; -use AutoMapper\Transformer\DependentTransformerInterface; -use AutoMapper\Transformer\EnumTransformerFactory; -use AutoMapper\Transformer\MultipleTransformerFactory; -use AutoMapper\Transformer\NullableTransformerFactory; -use AutoMapper\Transformer\ObjectTransformerFactory; -use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerFactory; -use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerRegistry; -use AutoMapper\Transformer\SymfonyUidTransformerFactory; -use AutoMapper\Transformer\TransformerFactoryInterface; -use AutoMapper\Transformer\UniqueTypeTransformerFactory; -use Symfony\Component\EventDispatcher\EventDispatcher; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Symfony\Component\ExpressionLanguage\ExpressionLanguage; -use Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor; -use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; -use Symfony\Component\PropertyInfo\PropertyInfoExtractor; -use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; -use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; -use Symfony\Component\Uid\AbstractUid; /** * @internal + * + * @implements \IteratorAggregate */ -final class MetadataRegistry +class MetadataRegistry implements \IteratorAggregate, \Countable { /** @var array> */ - private array $mapperMetadata = []; - - /** @var array> */ - private array $generatorMetadata = []; + private array $registry = []; public function __construct( private readonly Configuration $configuration, - private readonly SourceTargetMappingExtractor $sourceTargetPropertiesMappingExtractor, - private readonly FromSourceMappingExtractor $fromSourcePropertiesMappingExtractor, - private readonly FromTargetMappingExtractor $fromTargetPropertiesMappingExtractor, - private readonly TransformerFactoryInterface $transformerFactory, - private readonly EventDispatcherInterface $eventDispatcher, + ?self $subRegistry = null, ) { + if (null !== $subRegistry) { + $this->registry = $subRegistry->registry; + } } /** * @param class-string|'array' $source * @param class-string|'array' $target - * - * @internal */ - public function getMapperMetadata(string $source, string $target): MapperMetadata + public function get(string $source, string $target): MapperMetadata { $source = $this->getRealClassName($source); $target = $this->getRealClassName($target); - if (!isset($this->mapperMetadata[$source][$target])) { - $this->mapperMetadata[$source][$target] = new MapperMetadata($source, $target, $this->configuration->classPrefix); - } - - return $this->mapperMetadata[$source][$target]; + return $this->registry[$source][$target] ??= new MapperMetadata($source, $target, $this->configuration->classPrefix); } /** * @param class-string|'array' $source * @param class-string|'array' $target - * - * @internal */ - public function getGeneratorMetadata(string $source, string $target): GeneratorMetadata + public function register(string $source, string $target): void { - if (!isset($this->generatorMetadata[$source][$target])) { - $metadata = $this->createGeneratorMetadata($this->getMapperMetadata($source, $target)); - $this->generatorMetadata[$source][$target] = $metadata; - - // Add dependencies to the mapper - foreach ($metadata->propertiesMetadata as $propertyMapping) { - if ($propertyMapping->transformer instanceof DependentTransformerInterface) { - foreach ($propertyMapping->transformer->getDependencies() as $mapperDependency) { - $dependencyMetadata = $this->getGeneratorMetadata($mapperDependency->source, $mapperDependency->target); - - $metadata->addDependency(new Dependency($mapperDependency, $dependencyMetadata)); - } - } - } - } - - return $this->generatorMetadata[$source][$target]; - } - - private function createGeneratorMetadata(MapperMetadata $mapperMetadata): GeneratorMetadata - { - $extractor = $this->sourceTargetPropertiesMappingExtractor; - - if ('array' === $mapperMetadata->source || 'stdClass' === $mapperMetadata->source) { - $extractor = $this->fromTargetPropertiesMappingExtractor; - } - - if ('array' === $mapperMetadata->target || 'stdClass' === $mapperMetadata->target) { - $extractor = $this->fromSourcePropertiesMappingExtractor; - } - - $propertyEvents = []; - - $mapperEvent = new GenerateMapperEvent($mapperMetadata); - $this->eventDispatcher->dispatch($mapperEvent); - - // First get properties from the source - foreach ($extractor->getProperties($mapperMetadata->source) as $property) { - $propertyEvent = new PropertyMetadataEvent($mapperMetadata, new SourcePropertyMetadataEvent($property), new TargetPropertyMetadataEvent($property)); - - $this->eventDispatcher->dispatch($propertyEvent); - - $propertyEvents[$propertyEvent->target->name] = $propertyEvent; - } - - foreach ($extractor->getProperties($mapperMetadata->target) as $property) { - if (isset($propertyEvents[$property])) { - continue; - } - - $propertyEvent = new PropertyMetadataEvent($mapperMetadata, new SourcePropertyMetadataEvent($property), new TargetPropertyMetadataEvent($property)); - - $this->eventDispatcher->dispatch($propertyEvent); - - $propertyEvents[$propertyEvent->target->name] = $propertyEvent; - } - - foreach ($mapperEvent->properties as $propertyEvent) { - $this->eventDispatcher->dispatch($propertyEvent); - - $propertyEvents[$propertyEvent->target->name] = $propertyEvent; - } - - $propertiesMapping = []; - - foreach ($propertyEvents as $propertyMappedEvent) { - // Create the source property metadata - if ($propertyMappedEvent->source->accessor === null) { - $propertyMappedEvent->source->accessor = $extractor->getReadAccessor($mapperMetadata->source, $mapperMetadata->target, $propertyMappedEvent->source->name); - } - - if ($propertyMappedEvent->source->checkExists === null) { - $propertyMappedEvent->source->checkExists = $extractor->getCheckExists($mapperMetadata->source, $propertyMappedEvent->source->name); - } - - if ($propertyMappedEvent->source->extractGroupsIfNull && $propertyMappedEvent->source->groups === null) { - $propertyMappedEvent->source->groups = $extractor->getGroups($mapperMetadata->source, $propertyMappedEvent->source->name); - } - - if ($propertyMappedEvent->source->dateTimeFormat === null) { - $propertyMappedEvent->source->dateTimeFormat = $extractor->getDateTimeFormat($mapperMetadata->source, $propertyMappedEvent->source->name); - } - - // Create the target property metadata - if ($propertyMappedEvent->target->writeMutator === null) { - $propertyMappedEvent->target->writeMutator = $extractor->getWriteMutator($mapperMetadata->source, $mapperMetadata->target, $propertyMappedEvent->target->name, [ - 'enable_constructor_extraction' => false, - ]); - } - - if ($propertyMappedEvent->target->writeMutatorConstructor === null) { - $propertyMappedEvent->target->writeMutatorConstructor = $extractor->getWriteMutator($mapperMetadata->source, $mapperMetadata->target, $propertyMappedEvent->target->name, [ - 'enable_constructor_extraction' => true, - ]); - } - - if ($propertyMappedEvent->target->extractGroupsIfNull && $propertyMappedEvent->target->groups === null) { - $propertyMappedEvent->target->groups = $extractor->getGroups($mapperMetadata->target, $propertyMappedEvent->target->name); - } - - if ($propertyMappedEvent->target->dateTimeFormat === null) { - $propertyMappedEvent->target->dateTimeFormat = $extractor->getDateTimeFormat($mapperMetadata->target, $propertyMappedEvent->target->name); - } - - $sourcePropertyMetadata = SourcePropertyMetadata::fromEvent($propertyMappedEvent->source); - $targetPropertyMetadata = TargetPropertyMetadata::fromEvent($propertyMappedEvent->target); - - if (null === $propertyMappedEvent->types) { - $propertyMappedEvent->types = $extractor->getTypes($mapperMetadata->source, $sourcePropertyMetadata, $mapperMetadata->target, $targetPropertyMetadata); - } - - if (null === $propertyMappedEvent->transformer) { - $transformer = $this->transformerFactory->getTransformer($propertyMappedEvent->types, $sourcePropertyMetadata, $targetPropertyMetadata, $mapperMetadata); - - if (null === $transformer) { - continue; - } - - $propertyMappedEvent->transformer = $transformer; - } - - $propertiesMapping[] = new PropertyMetadata( - $sourcePropertyMetadata, - $targetPropertyMetadata, - $propertyMappedEvent->types, - $propertyMappedEvent->transformer, - $propertyMappedEvent->ignored ?? false, - $propertyMappedEvent->maxDepth, - $propertyMappedEvent->if, - $propertyMappedEvent->groups, - $propertyMappedEvent->disableGroupsCheck, - ); - } - - return new GeneratorMetadata($mapperMetadata, $propertiesMapping, $this->configuration->attributeChecking, $this->configuration->allowConstructor); + $this->get($source, $target); } /** - * @param TransformerFactoryInterface[] $transformerFactories + * @param class-string|'array' $source + * @param class-string|'array' $target */ - public static function create( - Configuration $configuration, - PropertyTransformerRegistry $customTransformerRegistry, - array $transformerFactories = [], - ClassMetadataFactory $classMetadataFactory = null, - AdvancedNameConverterInterface $nameConverter = null, - ExpressionLanguage $expressionLanguage = new ExpressionLanguage(), - EventDispatcherInterface $eventDispatcher = new EventDispatcher(), - ): self { - // Create property info extractors - $flags = ReflectionExtractor::ALLOW_PUBLIC; - - if ($configuration->mapPrivateProperties) { - $flags |= ReflectionExtractor::ALLOW_PRIVATE | ReflectionExtractor::ALLOW_PROTECTED; - } - - $reflectionExtractor = new ReflectionExtractor(accessFlags: $flags); - $phpStanExtractor = new PhpStanExtractor(); - - if (null !== $nameConverter) { - $eventDispatcher->addListener(PropertyMetadataEvent::class, new AdvancedNameConverterListener($nameConverter)); - } - - if (null !== $classMetadataFactory) { - $eventDispatcher->addListener(PropertyMetadataEvent::class, new SerializerMaxDepthListener($classMetadataFactory)); - $eventDispatcher->addListener(PropertyMetadataEvent::class, new SerializerGroupListener($classMetadataFactory)); - $eventDispatcher->addListener(PropertyMetadataEvent::class, new SerializerIgnoreListener($classMetadataFactory)); - } - - $eventDispatcher->addListener(PropertyMetadataEvent::class, new MapToContextListener($reflectionExtractor)); - $eventDispatcher->addListener(GenerateMapperEvent::class, new MapToListener($customTransformerRegistry, $expressionLanguage)); - $eventDispatcher->addListener(GenerateMapperEvent::class, new MapFromListener($customTransformerRegistry, $expressionLanguage)); - - $propertyInfoExtractor = new PropertyInfoExtractor( - listExtractors: [$reflectionExtractor], - typeExtractors: [new ReadWriteTypeExtractor(), $phpStanExtractor, $reflectionExtractor], - accessExtractors: [$reflectionExtractor] - ); - - // Create transformer factories - $factories = [ - new MultipleTransformerFactory(), - new NullableTransformerFactory(), - new UniqueTypeTransformerFactory(), - new DateTimeTransformerFactory(), - new BuiltinTransformerFactory(), - new ArrayTransformerFactory(), - new ObjectTransformerFactory(), - new EnumTransformerFactory(), - new PropertyTransformerFactory($customTransformerRegistry), - ]; + public function has(string $source, string $target): bool + { + $source = $this->getRealClassName($source); + $target = $this->getRealClassName($target); - if (class_exists(AbstractUid::class)) { - $factories[] = new SymfonyUidTransformerFactory(); - } + return isset($this->registry[$source][$target]); + } - foreach ($transformerFactories as $transformerFactory) { - $factories[] = $transformerFactory; + public function getIterator(): \Traversable + { + foreach ($this->registry as $targets) { + foreach ($targets as $metadata) { + yield $metadata; + } } - - $transformerFactory = new ChainTransformerFactory($factories); - - $sourceTargetMappingExtractor = new SourceTargetMappingExtractor( - $configuration, - $propertyInfoExtractor, - $reflectionExtractor, - $reflectionExtractor, - ); - - $fromTargetMappingExtractor = new FromTargetMappingExtractor( - $configuration, - $propertyInfoExtractor, - $reflectionExtractor, - $reflectionExtractor, - ); - - $fromSourceMappingExtractor = new FromSourceMappingExtractor( - $configuration, - $propertyInfoExtractor, - $reflectionExtractor, - $reflectionExtractor, - ); - - return new self( - $configuration, - $sourceTargetMappingExtractor, - $fromSourceMappingExtractor, - $fromTargetMappingExtractor, - $transformerFactory, - $eventDispatcher, - ); } /** @@ -354,4 +97,9 @@ private function getRealClassName(string $className): string strrpos($className, '\\') - ($positionPm + 8) ); } + + public function count(): int + { + return \count($this->registry, \COUNT_RECURSIVE); + } } diff --git a/src/Normalizer/AutoMapperNormalizer.php b/src/Normalizer/AutoMapperNormalizer.php index 5ac56b14..44da118e 100644 --- a/src/Normalizer/AutoMapperNormalizer.php +++ b/src/Normalizer/AutoMapperNormalizer.php @@ -6,6 +6,7 @@ use AutoMapper\AutoMapperInterface; use AutoMapper\MapperContext; +use AutoMapper\Metadata\MetadataRegistry; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; @@ -32,6 +33,7 @@ public function __construct( private AutoMapperInterface $autoMapper, + private ?MetadataRegistry $onlyMetadataRegistry = null, ) { } @@ -73,7 +75,11 @@ public function supportsNormalization(mixed $data, string $format = null, array return false; } - return true; + if ($this->onlyMetadataRegistry === null) { + return true; + } + + return $this->onlyMetadataRegistry->has($data::class, 'array'); } /** @@ -81,12 +87,40 @@ public function supportsNormalization(mixed $data, string $format = null, array */ public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool { - return class_exists($type); + if (!class_exists($type)) { + return false; + } + + if ($this->onlyMetadataRegistry === null) { + return true; + } + + return $this->onlyMetadataRegistry->has('array', $type); } public function getSupportedTypes(?string $format): array { - return ['object' => true]; + if ($this->onlyMetadataRegistry === null) { + return ['object' => true]; + } + + $types = []; + + foreach ($this->onlyMetadataRegistry as $metadata) { + if ($metadata->source === 'array') { + $hasTarget = $this->onlyMetadataRegistry->has($metadata->target, 'array'); + + // Only cache when both source and target exist in the registry + $types[$metadata->target] = $hasTarget; + } elseif ($metadata->target === 'array') { + $hasSource = $this->onlyMetadataRegistry->has($metadata->target, 'array'); + + // Only cache when both source and target exist in the registry + $types[$metadata->source] = $hasSource; + } + } + + return $types; } /** diff --git a/src/Symfony/Bundle/CacheWarmup/CacheWarmer.php b/src/Symfony/Bundle/CacheWarmup/CacheWarmer.php index 07494d32..e7cd8751 100644 --- a/src/Symfony/Bundle/CacheWarmup/CacheWarmer.php +++ b/src/Symfony/Bundle/CacheWarmup/CacheWarmer.php @@ -4,7 +4,9 @@ namespace AutoMapper\Symfony\Bundle\CacheWarmup; -use AutoMapper\AutoMapperRegistryInterface; +use AutoMapper\Loader\ClassLoaderInterface; +use AutoMapper\Metadata\MetadataFactory; +use AutoMapper\Metadata\MetadataRegistry; use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; /** @@ -12,44 +14,38 @@ */ final class CacheWarmer implements CacheWarmerInterface { - /** @param iterable $cacheWarmerLoaders */ public function __construct( - private readonly AutoMapperRegistryInterface $autoMapperRegistry, - private readonly iterable $cacheWarmerLoaders, + private readonly MetadataRegistry $mapping, + private readonly MetadataFactory $metadataFactory, + private readonly ClassLoaderInterface $classLoader, private readonly string $autoMapperCacheDirectory ) { } public function isOptional(): bool { - return false; + return true; } public function warmUp(string $cacheDir, string $buildDir = null): array { - foreach ($this->cacheWarmerLoaders as $cacheWarmerLoader) { - foreach ($cacheWarmerLoader->loadCacheWarmupData() as $cacheWarmupData) { - $mapper = $this->autoMapperRegistry->getMapper($cacheWarmupData->getSource(), $cacheWarmupData->getTarget()); - } - } + // load all mappers + $mapping = clone $this->mapping; + $this->metadataFactory->resolveAllMetadata($mapping); - // preloaded files must be in cache directory - if (!str_starts_with($this->autoMapperCacheDirectory, $cacheDir)) { + if (\count($mapping) === 0) { return []; } - $registryFile = sprintf('%s/registry.php', $this->autoMapperCacheDirectory); - if (!file_exists($registryFile)) { + if (!$this->classLoader->buildMappers($mapping)) { return []; } - $mappers = array_keys(require $registryFile); - return array_map( - function ($mapper) { - return sprintf('%s/%s.php', $this->autoMapperCacheDirectory, $mapper); + function ($mapperMetadata) { + return sprintf('%s/%s.php', $this->autoMapperCacheDirectory, $mapperMetadata->className); }, - $mappers + iterator_to_array($mapping), ); } } diff --git a/src/Symfony/Bundle/CacheWarmup/CacheWarmerLoaderInterface.php b/src/Symfony/Bundle/CacheWarmup/CacheWarmerLoaderInterface.php deleted file mode 100644 index 12e8d161..00000000 --- a/src/Symfony/Bundle/CacheWarmup/CacheWarmerLoaderInterface.php +++ /dev/null @@ -1,13 +0,0 @@ - - */ - public function loadCacheWarmupData(): iterable; -} diff --git a/src/Symfony/Bundle/CacheWarmup/CacheWarmupData.php b/src/Symfony/Bundle/CacheWarmup/CacheWarmupData.php deleted file mode 100644 index f99c7a62..00000000 --- a/src/Symfony/Bundle/CacheWarmup/CacheWarmupData.php +++ /dev/null @@ -1,54 +0,0 @@ -|'array' $source - * @param class-string|'array' $target - */ - public function __construct( - private string $source, - private string $target, - ) { - if (!$this->isValid($source) || !$this->isValid($target)) { - throw CacheWarmupDataException::sourceOrTargetDoesNoExist($source, $target); - } - - if ($target === $source) { - throw CacheWarmupDataException::sourceAndTargetAreEquals($source); - } - } - - /** - * @param array{source: class-string|'array', target: class-string|'array'} $array - */ - public static function fromArray(array $array): self - { - return new self($array['source'], $array['target']); - } - - /** - * @return class-string|'array' - */ - public function getSource(): string - { - return $this->source; - } - - /** - * @return class-string|'array' - */ - public function getTarget(): string - { - return $this->target; - } - - private function isValid(string $arrayOrClass): bool - { - return $arrayOrClass === 'array' || class_exists($arrayOrClass); - } -} diff --git a/src/Symfony/Bundle/CacheWarmup/CacheWarmupDataException.php b/src/Symfony/Bundle/CacheWarmup/CacheWarmupDataException.php deleted file mode 100644 index 4089b4c8..00000000 --- a/src/Symfony/Bundle/CacheWarmup/CacheWarmupDataException.php +++ /dev/null @@ -1,28 +0,0 @@ -|'array', target: class-string|'array'}> $mappersToGenerateOnWarmup - */ - public function __construct( - private array $mappersToGenerateOnWarmup - ) { - } - - public function loadCacheWarmupData(): iterable - { - foreach ($this->mappersToGenerateOnWarmup as $mapperToGenerate) { - yield CacheWarmupData::fromArray($mapperToGenerate); - } - } -} diff --git a/src/Symfony/Bundle/DependencyInjection/AutoMapperExtension.php b/src/Symfony/Bundle/DependencyInjection/AutoMapperExtension.php index 0fb94749..2a07f0b1 100644 --- a/src/Symfony/Bundle/DependencyInjection/AutoMapperExtension.php +++ b/src/Symfony/Bundle/DependencyInjection/AutoMapperExtension.php @@ -10,8 +10,8 @@ use AutoMapper\Loader\ClassLoaderInterface; use AutoMapper\Loader\EvalLoader; use AutoMapper\Loader\FileLoader; -use AutoMapper\Symfony\Bundle\CacheWarmup\CacheWarmerLoaderInterface; -use AutoMapper\Symfony\Bundle\CacheWarmup\ConfigurationCacheWarmerLoader; +use AutoMapper\Normalizer\AutoMapperNormalizer; +use AutoMapper\Symfony\Bundle\CacheWarmup\CacheWarmer; use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerInterface; use AutoMapper\Transformer\SymfonyUidTransformerFactory; use Symfony\Component\Config\Definition\ConfigurationInterface; @@ -88,12 +88,21 @@ public function load(array $configs, ContainerBuilder $container): void $loader->load('event_serializer.php'); } - if ($config['normalizer']) { + if ($config['normalizer']['enabled']) { if (!interface_exists(NormalizerInterface::class)) { throw new \LogicException('The "symfony/serializer" component is required to use the "normalizer" feature.'); } $loader->load('normalizer.php'); + + $normalizerDefinition = $container + ->getDefinition(AutoMapperNormalizer::class) + ->addTag('automapper.normalizer', ['priority' => $config['normalizer']['priority']]) + ; + + if ($config['normalizer']['only_registered_mapping']) { + $normalizerDefinition->setArgument('$onlyMetadataRegistry', new Reference('automapper.config_mapping_registry')); + } } if (null !== $config['name_converter']) { @@ -105,10 +114,15 @@ public function load(array $configs, ContainerBuilder $container): void $container->setParameter('automapper.cache_dir', $config['cache_dir']); - $container->registerForAutoconfiguration(CacheWarmerLoaderInterface::class)->addTag('automapper.cache_warmer_loader'); - $container - ->getDefinition(ConfigurationCacheWarmerLoader::class) - ->replaceArgument(0, $config['warmup']); + $configMappingRegistry = $container->getDefinition('automapper.config_mapping_registry'); + + foreach ($config['mapping']['mappers'] as $mapper) { + $configMappingRegistry->addMethodCall('register', [$mapper['source'], $mapper['target']]); + } + + if ($container->getParameter('kernel.environment') === 'test') { + $container->getDefinition(CacheWarmer::class)->setPublic(true); + } } public function getAlias(): string diff --git a/src/Symfony/Bundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/DependencyInjection/Configuration.php index 8f7a9d63..bafa333b 100644 --- a/src/Symfony/Bundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/DependencyInjection/Configuration.php @@ -28,20 +28,35 @@ public function getConfigTreeBuilder(): TreeBuilder ->booleanNode('auto_register')->defaultTrue()->end() ->booleanNode('map_private_properties')->defaultTrue()->end() ->booleanNode('allow_readonly_target_to_populate')->defaultFalse()->end() - ->booleanNode('normalizer')->defaultFalse()->end() + ->arrayNode('normalizer') + ->children() + ->booleanNode('enabled')->defaultFalse()->end() + ->booleanNode('only_registered_mapping')->defaultFalse()->end() + ->integerNode('priority')->defaultValue(1000)->end() + ->end() + ->addDefaultsIfNotSet() + ->end() ->booleanNode('serializer')->defaultValue(interface_exists(SerializerInterface::class))->end() ->scalarNode('name_converter')->defaultNull()->end() ->scalarNode('cache_dir')->defaultValue('%kernel.cache_dir%/automapper')->end() ->scalarNode('date_time_format')->defaultValue(\DateTimeInterface::RFC3339)->end() ->booleanNode('hot_reload')->defaultValue('%kernel.debug%')->end() ->booleanNode('map_private_properties')->defaultFalse()->end() - ->arrayNode('warmup') - ->arrayPrototype() - ->children() - ->scalarNode('source')->defaultValue('array')->end() - ->scalarNode('target')->defaultValue('array')->end() + ->arrayNode('mapping') + ->children() + ->arrayNode('paths') + ->scalarPrototype()->end() + ->end() + ->arrayNode('mappers') + ->arrayPrototype() + ->children() + ->scalarNode('source')->defaultValue('array')->end() + ->scalarNode('target')->defaultValue('array')->end() + ->end() + ->end() ->end() ->end() + ->addDefaultsIfNotSet() ->end() ->end() ; diff --git a/src/Symfony/Bundle/config/automapper.php b/src/Symfony/Bundle/config/automapper.php index 02111a41..f46bfc8f 100644 --- a/src/Symfony/Bundle/config/automapper.php +++ b/src/Symfony/Bundle/config/automapper.php @@ -12,6 +12,7 @@ use AutoMapper\Loader\ClassLoaderInterface; use AutoMapper\Loader\EvalLoader; use AutoMapper\Loader\FileLoader; +use AutoMapper\Metadata\MetadataFactory; use AutoMapper\Metadata\MetadataRegistry; use AutoMapper\Symfony\ExpressionLanguageProvider; use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerRegistry; @@ -31,13 +32,13 @@ ->set(EvalLoader::class) ->args([ service(MapperGenerator::class), - service(MetadataRegistry::class), + service(MetadataFactory::class), ]) ->set(FileLoader::class) ->args([ service(MapperGenerator::class), - service(MetadataRegistry::class), + service(MetadataFactory::class), '%kernel.cache_dir%/automapper', true, ]) diff --git a/src/Symfony/Bundle/config/metadata.php b/src/Symfony/Bundle/config/metadata.php index 40058b90..21755652 100644 --- a/src/Symfony/Bundle/config/metadata.php +++ b/src/Symfony/Bundle/config/metadata.php @@ -8,6 +8,7 @@ use AutoMapper\Extractor\FromSourceMappingExtractor; use AutoMapper\Extractor\FromTargetMappingExtractor; use AutoMapper\Extractor\SourceTargetMappingExtractor; +use AutoMapper\Metadata\MetadataFactory; use AutoMapper\Metadata\MetadataRegistry; use AutoMapper\Transformer\TransformerFactoryInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -39,6 +40,11 @@ ]) ->set(MetadataRegistry::class) + ->args([ + service(Configuration::class), + ]) + + ->set(MetadataFactory::class) ->args([ service(Configuration::class), service(SourceTargetMappingExtractor::class), @@ -46,6 +52,7 @@ service(FromTargetMappingExtractor::class), service(TransformerFactoryInterface::class), service(EventDispatcherInterface::class), + service(MetadataRegistry::class), ]) ; }; diff --git a/src/Symfony/Bundle/config/normalizer.php b/src/Symfony/Bundle/config/normalizer.php index f9d22186..3dde3ee4 100644 --- a/src/Symfony/Bundle/config/normalizer.php +++ b/src/Symfony/Bundle/config/normalizer.php @@ -11,6 +11,5 @@ $container->services() ->set(AutoMapperNormalizer::class) ->args([service(AutoMapperInterface::class)]) - ->tag('serializer.normalizer', ['priority' => 1000]) ; }; diff --git a/src/Symfony/Bundle/config/symfony.php b/src/Symfony/Bundle/config/symfony.php index 2d4f3500..679a74e9 100644 --- a/src/Symfony/Bundle/config/symfony.php +++ b/src/Symfony/Bundle/config/symfony.php @@ -4,24 +4,27 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; -use AutoMapper\AutoMapperRegistryInterface; +use AutoMapper\Configuration; +use AutoMapper\Loader\ClassLoaderInterface; +use AutoMapper\Metadata\MetadataFactory; +use AutoMapper\Metadata\MetadataRegistry; use AutoMapper\Symfony\Bundle\CacheWarmup\CacheWarmer; -use AutoMapper\Symfony\Bundle\CacheWarmup\ConfigurationCacheWarmerLoader; return static function (ContainerConfigurator $container) { $container->services() ->set(CacheWarmer::class) ->args([ - service(AutoMapperRegistryInterface::class), - tagged_iterator('automapper.cache_warmer_loader'), + service('automapper.config_mapping_registry'), + service(MetadataFactory::class), + service(ClassLoaderInterface::class), '%automapper.cache_dir%', ]) ->tag('kernel.cache_warmer') - ->set(ConfigurationCacheWarmerLoader::class) + ->set('automapper.config_mapping_registry') + ->class(MetadataRegistry::class) ->args([ - [], // mappers list from config + service(Configuration::class), ]) - ->tag('automapper.cache_warmer_loader') ; }; diff --git a/tests/Bundle/Resources/config/packages/automapper.yaml b/tests/Bundle/Resources/config/packages/automapper.yaml index 9a637de6..bbf55394 100644 --- a/tests/Bundle/Resources/config/packages/automapper.yaml +++ b/tests/Bundle/Resources/config/packages/automapper.yaml @@ -1,7 +1,10 @@ automapper: - normalizer: false + normalizer: + enabled: true + only_registered_mapping: false name_converter: AutoMapper\Tests\Bundle\Resources\App\Service\IdNameConverter map_private_properties: false check_attributes: false - warmup: - - { source: 'AutoMapper\Tests\Bundle\Resources\App\Entity\NestedObject', target: 'array' } \ No newline at end of file + mapping: + mappers: + - { source: 'AutoMapper\Tests\Bundle\Resources\App\Entity\NestedObject', target: 'array' } \ No newline at end of file diff --git a/tests/Bundle/ServiceInstantiationTest.php b/tests/Bundle/ServiceInstantiationTest.php index d148d840..23c3074c 100644 --- a/tests/Bundle/ServiceInstantiationTest.php +++ b/tests/Bundle/ServiceInstantiationTest.php @@ -6,6 +6,7 @@ use AutoMapper\AutoMapperInterface; use AutoMapper\MapperContext; +use AutoMapper\Symfony\Bundle\CacheWarmup\CacheWarmer; use AutoMapper\Tests\Bundle\Resources\App\Entity\AddressDTO; use AutoMapper\Tests\Bundle\Resources\App\Entity\ClassWithMapToContextAttribute; use AutoMapper\Tests\Bundle\Resources\App\Entity\ClassWithPrivateProperty; @@ -37,14 +38,12 @@ protected function setUp(): void public function testWarmup(): void { static::bootKernel(); + $service = static::$kernel->getContainer()->get(CacheWarmer::class); + $service->warmUp(__DIR__ . '/Resources/var/cache/test'); self::assertFileExists(__DIR__ . '/Resources/var/cache/test/automapper/Symfony_Mapper_AutoMapper_Tests_Bundle_Resources_App_Entity_NestedObject_array.php'); self::assertFileExists(__DIR__ . '/Resources/var/cache/test/automapper/Symfony_Mapper_AutoMapper_Tests_Bundle_Resources_App_Entity_User_array.php'); self::assertFileExists(__DIR__ . '/Resources/var/cache/test/automapper/Symfony_Mapper_AutoMapper_Tests_Bundle_Resources_App_Entity_AddressDTO_array.php'); - - self::assertInstanceOf(\Symfony_Mapper_AutoMapper_Tests_Bundle_Resources_App_Entity_NestedObject_array::class, new \Symfony_Mapper_AutoMapper_Tests_Bundle_Resources_App_Entity_NestedObject_array()); - self::assertInstanceOf(\Symfony_Mapper_AutoMapper_Tests_Bundle_Resources_App_Entity_User_array::class, new \Symfony_Mapper_AutoMapper_Tests_Bundle_Resources_App_Entity_User_array()); - self::assertInstanceOf(\Symfony_Mapper_AutoMapper_Tests_Bundle_Resources_App_Entity_AddressDTO_array::class, new \Symfony_Mapper_AutoMapper_Tests_Bundle_Resources_App_Entity_AddressDTO_array()); } public function testAutoMapper(): void diff --git a/tests/Metadata/MetadataRegistryTest.php b/tests/Metadata/MetadataFactoryTest.php similarity index 84% rename from tests/Metadata/MetadataRegistryTest.php rename to tests/Metadata/MetadataFactoryTest.php index db1b4112..758e8fb4 100644 --- a/tests/Metadata/MetadataRegistryTest.php +++ b/tests/Metadata/MetadataFactoryTest.php @@ -9,6 +9,7 @@ use AutoMapper\Extractor\FromTargetMappingExtractor; use AutoMapper\Extractor\SourceTargetMappingExtractor; use AutoMapper\Metadata\GeneratorMetadata; +use AutoMapper\Metadata\MetadataFactory; use AutoMapper\Metadata\MetadataRegistry; use AutoMapper\Metadata\PropertyMetadata; use AutoMapper\Tests\AutoMapperBaseTest; @@ -29,9 +30,9 @@ /** * @author Baptiste Leduc */ -class MetadataRegistryTest extends AutoMapperBaseTest +class MetadataFactoryTest extends AutoMapperBaseTest { - protected MetadataRegistry $registry; + protected MetadataFactory $factory; protected function setUp(): void { @@ -80,13 +81,14 @@ protected function setUp(): void $reflectionExtractor, ); - $this->registry = new MetadataRegistry( + $this->factory = new MetadataFactory( $configuration, $sourceTargetMappingExtractor, $fromSourceMappingExtractor, $fromTargetMappingExtractor, $transformerFactory, new EventDispatcher(), + new MetadataRegistry($configuration) ); } @@ -94,7 +96,7 @@ public function testCreateObjectToArray(): void { $userReflection = new \ReflectionClass(Fixtures\User::class); - $metadata = $this->registry->getGeneratorMetadata(Fixtures\User::class, 'array'); + $metadata = $this->factory->getGeneratorMetadata(Fixtures\User::class, 'array'); self::assertFalse($metadata->hasConstructor()); self::assertFalse($metadata->isTargetCloneable()); self::assertEquals(Fixtures\User::class, $metadata->mapperMetadata->source); @@ -102,11 +104,22 @@ public function testCreateObjectToArray(): void self::assertCount(\count($userReflection->getProperties()), $metadata->propertiesMetadata); } + public function testResolve(): void + { + $registry = new MetadataRegistry(new Configuration()); + $registry->register(Fixtures\User::class, 'array'); + + $this->factory->resolveAllMetadata($registry); + + self::assertTrue($registry->has(Fixtures\User::class, 'array')); + self::assertTrue($registry->has(Fixtures\Address::class, 'array')); + } + public function testCreateArrayToObject(): void { $userReflection = new \ReflectionClass(Fixtures\User::class); - $metadata = $this->registry->getGeneratorMetadata('array', Fixtures\User::class); + $metadata = $this->factory->getGeneratorMetadata('array', Fixtures\User::class); self::assertTrue($metadata->hasConstructor()); self::assertTrue($metadata->isTargetCloneable()); self::assertEquals('array', $metadata->mapperMetadata->source); @@ -119,7 +132,7 @@ public function testCreateArrayToObject(): void public function testCreateWithBothObjects(): void { - $metadata = $this->registry->getGeneratorMetadata(Fixtures\UserConstructorDTO::class, Fixtures\User::class); + $metadata = $this->factory->getGeneratorMetadata(Fixtures\UserConstructorDTO::class, Fixtures\User::class); self::assertTrue($metadata->hasConstructor()); self::assertTrue($metadata->isTargetCloneable()); self::assertEquals(Fixtures\UserConstructorDTO::class, $metadata->mapperMetadata->source); @@ -132,7 +145,7 @@ public function testCreateWithBothObjects(): void public function testHasNotConstructor(): void { - $metadata = $this->registry->getGeneratorMetadata('array', Fixtures\UserDTO::class); + $metadata = $this->factory->getGeneratorMetadata('array', Fixtures\UserDTO::class); self::assertFalse($metadata->hasConstructor()); } @@ -142,7 +155,7 @@ public function testHasNotConstructor(): void */ public function testTargetIsReadOnlyClass(): void { - $metadata = $this->registry->getGeneratorMetadata('array', Fixtures\AddressDTOReadonlyClass::class); + $metadata = $this->factory->getGeneratorMetadata('array', Fixtures\AddressDTOReadonlyClass::class); self::assertEquals(Fixtures\AddressDTOReadonlyClass::class, $metadata->mapperMetadata->target); self::assertTrue($metadata->isTargetReadOnlyClass());