From b5592ee062ed5ca02a7de7249ab91217bd062c87 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Thu, 14 Feb 2019 15:55:47 +0100 Subject: [PATCH 01/38] Initial commit and implementation of symfony/automapper --- composer.json | 3 + src/Symfony/Component/AutoMapper/.gitignore | 3 + .../Component/AutoMapper/AutoMapper.php | 250 +++++++++++ .../AutoMapper/AutoMapperInterface.php | 31 ++ .../AutoMapperRegistryInterface.php | 42 ++ src/Symfony/Component/AutoMapper/CHANGELOG.md | 7 + src/Symfony/Component/AutoMapper/Context.php | 252 +++++++++++ .../AutoMapper/Exception/CompileException.php | 7 + .../Exception/InvalidMappingException.php | 7 + .../Exception/NoMappingFoundException.php | 7 + .../Extractor/AccessorExtractorInterface.php | 23 + .../Extractor/FromSourceMappingExtractor.php | 144 ++++++ .../Extractor/FromTargetMappingExtractor.php | 139 ++++++ .../AutoMapper/Extractor/MappingExtractor.php | 117 +++++ .../Extractor/MappingExtractorInterface.php | 41 ++ .../Extractor/PrivateReflectionExtractor.php | 151 +++++++ .../AutoMapper/Extractor/PropertyMapping.php | 98 ++++ .../AutoMapper/Extractor/ReadAccessor.php | 104 +++++ .../ReadAccessorExtractorInterface.php | 24 + .../Extractor/ReflectionExtractor.php | 143 ++++++ .../SourceTargetMappingExtractor.php | 76 ++++ .../AutoMapper/Extractor/WriteMutator.php | 123 ++++++ .../WriteMutatorExtractorInterface.php | 24 + .../Component/AutoMapper/GeneratedMapper.php | 63 +++ .../AutoMapper/Generator/Generator.php | 417 ++++++++++++++++++ .../Generator/UniqueVariableScope.php | 40 ++ src/Symfony/Component/AutoMapper/LICENSE | 19 + .../Loader/ClassLoaderInterface.php | 25 ++ .../AutoMapper/Loader/EvalLoader.php | 39 ++ .../AutoMapper/Loader/FileLoader.php | 97 ++++ .../MapperGeneratorMetadataFactory.php | 59 +++ .../MapperGeneratorMetadataInterface.php | 61 +++ ...pperGeneratorMetadataRegistryInterface.php | 32 ++ .../Component/AutoMapper/MapperInterface.php | 32 ++ .../Component/AutoMapper/MapperMetadata.php | 286 ++++++++++++ .../AutoMapper/MapperMetadataInterface.php | 51 +++ src/Symfony/Component/AutoMapper/README.md | 13 + .../AutoMapper/Tests/AutoMapperTest.php | 72 +++ .../AutoMapper/Tests/Fixtures/Address.php | 36 ++ .../AutoMapper/Tests/Fixtures/AddressDTO.php | 20 + .../AutoMapper/Tests/Fixtures/User.php | 61 +++ .../AutoMapper/Tests/Fixtures/UserDTO.php | 48 ++ .../AutoMapper/Tests/cache/.gitignore | 1 + .../AbstractUniqueTypeTransformerFactory.php | 44 ++ .../Transformer/ArrayTransformer.php | 76 ++++ .../Transformer/ArrayTransformerFactory.php | 52 +++ .../Transformer/BuiltinTransformer.php | 135 ++++++ .../Transformer/BuiltinTransformerFactory.php | 50 +++ .../Transformer/CallbackTransformer.php | 65 +++ .../Transformer/ChainTransformerFactory.php | 44 ++ .../Transformer/CopyTransformer.php | 48 ++ .../DateTimeToStringTansformer.php | 59 +++ .../DateTimeTransformerFactory.php | 115 +++++ .../Transformer/MapperDependency.php | 50 +++ .../Transformer/MultipleTransformer.php | 113 +++++ .../MultipleTransformerFactory.php | 49 ++ .../Transformer/NullableTransformer.php | 75 ++++ .../NullableTransformerFactory.php | 73 +++ .../Transformer/ObjectTransformer.php | 98 ++++ .../Transformer/ObjectTransformerFactory.php | 66 +++ .../StringToDateTimeTransformer.php | 64 +++ .../TransformerFactoryInterface.php | 33 ++ .../Transformer/TransformerInterface.php | 48 ++ .../UniqueTypeTransformerFactory.php | 56 +++ .../Component/AutoMapper/composer.json | 44 ++ .../Component/AutoMapper/phpunit.xml.dist | 31 ++ 66 files changed, 4776 insertions(+) create mode 100644 src/Symfony/Component/AutoMapper/.gitignore create mode 100644 src/Symfony/Component/AutoMapper/AutoMapper.php create mode 100644 src/Symfony/Component/AutoMapper/AutoMapperInterface.php create mode 100644 src/Symfony/Component/AutoMapper/AutoMapperRegistryInterface.php create mode 100644 src/Symfony/Component/AutoMapper/CHANGELOG.md create mode 100644 src/Symfony/Component/AutoMapper/Context.php create mode 100644 src/Symfony/Component/AutoMapper/Exception/CompileException.php create mode 100644 src/Symfony/Component/AutoMapper/Exception/InvalidMappingException.php create mode 100644 src/Symfony/Component/AutoMapper/Exception/NoMappingFoundException.php create mode 100644 src/Symfony/Component/AutoMapper/Extractor/AccessorExtractorInterface.php create mode 100644 src/Symfony/Component/AutoMapper/Extractor/FromSourceMappingExtractor.php create mode 100644 src/Symfony/Component/AutoMapper/Extractor/FromTargetMappingExtractor.php create mode 100644 src/Symfony/Component/AutoMapper/Extractor/MappingExtractor.php create mode 100644 src/Symfony/Component/AutoMapper/Extractor/MappingExtractorInterface.php create mode 100644 src/Symfony/Component/AutoMapper/Extractor/PrivateReflectionExtractor.php create mode 100644 src/Symfony/Component/AutoMapper/Extractor/PropertyMapping.php create mode 100644 src/Symfony/Component/AutoMapper/Extractor/ReadAccessor.php create mode 100644 src/Symfony/Component/AutoMapper/Extractor/ReadAccessorExtractorInterface.php create mode 100644 src/Symfony/Component/AutoMapper/Extractor/ReflectionExtractor.php create mode 100644 src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php create mode 100644 src/Symfony/Component/AutoMapper/Extractor/WriteMutator.php create mode 100644 src/Symfony/Component/AutoMapper/Extractor/WriteMutatorExtractorInterface.php create mode 100644 src/Symfony/Component/AutoMapper/GeneratedMapper.php create mode 100644 src/Symfony/Component/AutoMapper/Generator/Generator.php create mode 100644 src/Symfony/Component/AutoMapper/Generator/UniqueVariableScope.php create mode 100644 src/Symfony/Component/AutoMapper/LICENSE create mode 100644 src/Symfony/Component/AutoMapper/Loader/ClassLoaderInterface.php create mode 100644 src/Symfony/Component/AutoMapper/Loader/EvalLoader.php create mode 100644 src/Symfony/Component/AutoMapper/Loader/FileLoader.php create mode 100644 src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactory.php create mode 100644 src/Symfony/Component/AutoMapper/MapperGeneratorMetadataInterface.php create mode 100644 src/Symfony/Component/AutoMapper/MapperGeneratorMetadataRegistryInterface.php create mode 100644 src/Symfony/Component/AutoMapper/MapperInterface.php create mode 100644 src/Symfony/Component/AutoMapper/MapperMetadata.php create mode 100644 src/Symfony/Component/AutoMapper/MapperMetadataInterface.php create mode 100644 src/Symfony/Component/AutoMapper/README.md create mode 100644 src/Symfony/Component/AutoMapper/Tests/AutoMapperTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Fixtures/Address.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Fixtures/AddressDTO.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Fixtures/User.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Fixtures/UserDTO.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/cache/.gitignore create mode 100644 src/Symfony/Component/AutoMapper/Transformer/AbstractUniqueTypeTransformerFactory.php create mode 100644 src/Symfony/Component/AutoMapper/Transformer/ArrayTransformer.php create mode 100644 src/Symfony/Component/AutoMapper/Transformer/ArrayTransformerFactory.php create mode 100644 src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformer.php create mode 100644 src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformerFactory.php create mode 100644 src/Symfony/Component/AutoMapper/Transformer/CallbackTransformer.php create mode 100644 src/Symfony/Component/AutoMapper/Transformer/ChainTransformerFactory.php create mode 100644 src/Symfony/Component/AutoMapper/Transformer/CopyTransformer.php create mode 100644 src/Symfony/Component/AutoMapper/Transformer/DateTimeToStringTansformer.php create mode 100644 src/Symfony/Component/AutoMapper/Transformer/DateTimeTransformerFactory.php create mode 100644 src/Symfony/Component/AutoMapper/Transformer/MapperDependency.php create mode 100644 src/Symfony/Component/AutoMapper/Transformer/MultipleTransformer.php create mode 100644 src/Symfony/Component/AutoMapper/Transformer/MultipleTransformerFactory.php create mode 100644 src/Symfony/Component/AutoMapper/Transformer/NullableTransformer.php create mode 100644 src/Symfony/Component/AutoMapper/Transformer/NullableTransformerFactory.php create mode 100644 src/Symfony/Component/AutoMapper/Transformer/ObjectTransformer.php create mode 100644 src/Symfony/Component/AutoMapper/Transformer/ObjectTransformerFactory.php create mode 100644 src/Symfony/Component/AutoMapper/Transformer/StringToDateTimeTransformer.php create mode 100644 src/Symfony/Component/AutoMapper/Transformer/TransformerFactoryInterface.php create mode 100644 src/Symfony/Component/AutoMapper/Transformer/TransformerInterface.php create mode 100644 src/Symfony/Component/AutoMapper/Transformer/UniqueTypeTransformerFactory.php create mode 100644 src/Symfony/Component/AutoMapper/composer.json create mode 100644 src/Symfony/Component/AutoMapper/phpunit.xml.dist diff --git a/composer.json b/composer.json index b1d530e233186..95df7b2e9d76b 100644 --- a/composer.json +++ b/composer.json @@ -20,6 +20,8 @@ "ext-xml": "*", "doctrine/event-manager": "~1.0", "doctrine/persistence": "^1.3", + "fig/link-util": "^1.0", + "nikic/php-parser": "^4.0", "twig/twig": "^2.10|^3.0", "psr/cache": "~1.0", "psr/container": "^1.0", @@ -40,6 +42,7 @@ "replace": { "symfony/asset": "self.version", "symfony/amazon-mailer": "self.version", + "symfony/auto-mapper": "self.version", "symfony/browser-kit": "self.version", "symfony/cache": "self.version", "symfony/config": "self.version", diff --git a/src/Symfony/Component/AutoMapper/.gitignore b/src/Symfony/Component/AutoMapper/.gitignore new file mode 100644 index 0000000000000..c49a5d8df5c65 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Component/AutoMapper/AutoMapper.php b/src/Symfony/Component/AutoMapper/AutoMapper.php new file mode 100644 index 0000000000000..81ffa3d83905a --- /dev/null +++ b/src/Symfony/Component/AutoMapper/AutoMapper.php @@ -0,0 +1,250 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper; + +use Doctrine\Common\Annotations\AnnotationReader; +use PhpParser\ParserFactory; +use Symfony\Component\AutoMapper\Exception\NoMappingFoundException; +use Symfony\Component\AutoMapper\Extractor\FromSourceMappingExtractor; +use Symfony\Component\AutoMapper\Extractor\FromTargetMappingExtractor; +use Symfony\Component\AutoMapper\Extractor\PrivateReflectionExtractor; +use Symfony\Component\AutoMapper\Extractor\SourceTargetMappingExtractor; +use Symfony\Component\AutoMapper\Generator\Generator; +use Symfony\Component\AutoMapper\Loader\ClassLoaderInterface; +use Symfony\Component\AutoMapper\Loader\EvalLoader; +use Symfony\Component\AutoMapper\Transformer\ArrayTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\BuiltinTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\ChainTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\DateTimeTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\MultipleTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\NullableTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\ObjectTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\UniqueTypeTransformerFactory; +use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; +use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; +use Symfony\Component\PropertyInfo\PropertyInfoExtractor; +use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; +use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; +use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; + +/** + * An auto mapper has the role of mapping a source to a target. + * + * @author Joel Wurtz + */ +class AutoMapper implements AutoMapperInterface, AutoMapperRegistryInterface, MapperGeneratorMetadataRegistryInterface +{ + private $metadatas = []; + + /** @var GeneratedMapper[] */ + private $mapperRegistry = []; + + private $classLoader; + + private $mapperConfigurationFactory; + + public function __construct(ClassLoaderInterface $classLoader, MapperGeneratorMetadataFactory $mapperConfigurationFactory = null) + { + $this->classLoader = $classLoader; + $this->mapperConfigurationFactory = $mapperConfigurationFactory; + } + + /** + * {@inheritdoc} + */ + public function register(MapperGeneratorMetadataInterface $metadata): void + { + if (!\array_key_exists($metadata->getSource(), $this->metadatas)) { + $this->metadatas[$metadata->getSource()] = []; + } + + $this->metadatas[$metadata->getSource()][$metadata->getTarget()] = $metadata; + } + + /** + * {@inheritdoc} + */ + public function getMapper(string $source, string $target): MapperInterface + { + $metadata = $this->getMetadata($source, $target); + + if (null === $metadata) { + throw new NoMappingFoundException('No mapping found for source '.$source.' and target '.$target); + } + + $className = $metadata->getMapperClassName(); + + if (\array_key_exists($className, $this->mapperRegistry)) { + return $this->mapperRegistry[$className]; + } + + if (!class_exists($className)) { + $this->classLoader->loadClass($metadata); + } + + $this->mapperRegistry[$className] = new $className(); + $this->mapperRegistry[$className]->injectMappers($this); + + foreach ($metadata->getCallbacks() as $property => $callback) { + $this->mapperRegistry[$className]->addCallback($property, $callback); + } + + return $this->mapperRegistry[$className]; + } + + /** + * {@inheritdoc} + */ + public function hasMapper(string $source, string $target): bool + { + return null !== $this->getMetadata($source, $target); + } + + /** + * {@inheritdoc} + */ + public function map($sourceData, $targetData, Context $context = null) + { + $source = null; + $target = null; + + if (null === $sourceData) { + return null; + } + + if (\is_object($sourceData)) { + $source = \get_class($sourceData); + } + + if (\is_array($sourceData)) { + $source = 'array'; + } + + if (null === $source) { + throw new NoMappingFoundException('Cannot map this value, its neither an object or an array'); + } + + if (null === $context) { + $context = new Context(); + } + + if (\is_object($targetData)) { + $target = \get_class($targetData); + $context->setObjectToPopulate($targetData); + } + + if (\is_array($targetData)) { + $target = 'array'; + $context->setObjectToPopulate($targetData); + } + + if (\is_string($targetData)) { + $target = $targetData; + } + + if (null === $target) { + throw new NoMappingFoundException('Cannot map this value, its neither an object or an array'); + } + + return $this->getMapper($source, $target)->map($sourceData, $context); + } + + /** + * {@inheritdoc} + */ + public function getMetadata(string $source, string $target): ?MapperGeneratorMetadataInterface + { + if (!\array_key_exists($source, $this->metadatas) || !\array_key_exists($target, $this->metadatas[$source])) { + if (null === $this->mapperConfigurationFactory) { + return null; + } + + $this->register($this->mapperConfigurationFactory->create($this, $source, $target)); + } + + return $this->metadatas[$source][$target]; + } + + /** + * Use this for test, benchmark and fast prototyping. + * + * @internal + */ + public static function create(bool $private = true, ClassLoaderInterface $loader = null, AdvancedNameConverterInterface $nameConverter = null, string $classPrefix = 'Mapper_'): self + { + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + + if (null === $loader) { + $loader = new EvalLoader(new Generator( + (new ParserFactory())->create(ParserFactory::PREFER_PHP7), + new ClassDiscriminatorFromClassMetadata($classMetadataFactory) + )); + } + + if ($private) { + $reflectionExtractor = new PrivateReflectionExtractor(); + } else { + $reflectionExtractor = new ReflectionExtractor(); + } + + $phpDocExtractor = new PhpDocExtractor(); + $propertyInfoExtractor = new PropertyInfoExtractor( + [$reflectionExtractor], + [$phpDocExtractor, $reflectionExtractor], + [$reflectionExtractor], + [$reflectionExtractor] + ); + + $accessorExtractor = new \Symfony\Component\AutoMapper\Extractor\ReflectionExtractor($private); + $transformerFactory = new ChainTransformerFactory(); + $sourceTargetMappingExtractor = new SourceTargetMappingExtractor( + $propertyInfoExtractor, + $accessorExtractor, + $transformerFactory, + $classMetadataFactory + ); + + $fromTargetMappingExtractor = new FromTargetMappingExtractor( + $propertyInfoExtractor, + $accessorExtractor, + $transformerFactory, + $classMetadataFactory, + $nameConverter + ); + + $fromSourceMappingExtractor = new FromSourceMappingExtractor( + $propertyInfoExtractor, + $accessorExtractor, + $transformerFactory, + $classMetadataFactory, + $nameConverter + ); + + $autoMapper = new self($loader, new MapperGeneratorMetadataFactory( + $sourceTargetMappingExtractor, + $fromSourceMappingExtractor, + $fromTargetMappingExtractor, + $classPrefix + )); + + $transformerFactory->addTransformerFactory(new MultipleTransformerFactory($transformerFactory)); + $transformerFactory->addTransformerFactory(new NullableTransformerFactory($transformerFactory)); + $transformerFactory->addTransformerFactory(new UniqueTypeTransformerFactory($transformerFactory)); + $transformerFactory->addTransformerFactory(new DateTimeTransformerFactory()); + $transformerFactory->addTransformerFactory(new BuiltinTransformerFactory()); + $transformerFactory->addTransformerFactory(new ArrayTransformerFactory($transformerFactory)); + $transformerFactory->addTransformerFactory(new ObjectTransformerFactory($autoMapper)); + + return $autoMapper; + } +} diff --git a/src/Symfony/Component/AutoMapper/AutoMapperInterface.php b/src/Symfony/Component/AutoMapper/AutoMapperInterface.php new file mode 100644 index 0000000000000..d5f58c60c5476 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/AutoMapperInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper; + +/** + * An auto mapper has the role of mapping a source to a target. + * + * @author Joel Wurtz + */ +interface AutoMapperInterface +{ + /** + * Map data from to target. + * + * @param array|object $source Any data object, which may be an object or an array + * @param string|array|object $target To which type of data, or data, the source should be mapped + * @param Context $context Options mappers have access to + * + * @return array|object The mapped object + */ + public function map($source, $target, Context $context = null); +} diff --git a/src/Symfony/Component/AutoMapper/AutoMapperRegistryInterface.php b/src/Symfony/Component/AutoMapper/AutoMapperRegistryInterface.php new file mode 100644 index 0000000000000..d754ddedcf165 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/AutoMapperRegistryInterface.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper; + +/** + * Allow to retrieve mapper. + * + * @internal + * + * @author Joel Wurtz + */ +interface AutoMapperRegistryInterface +{ + /** + * Get a specific mapper for a source type and a target type. + * + * @param string $source Source type + * @param string $target Target type + * + * @return MapperInterface return associated mapper + */ + public function getMapper(string $source, string $target): MapperInterface; + + /** + * Does a specific mapper exist. + * + * @param string $source Source type + * @param string $target Target type + * + * @return bool + */ + public function hasMapper(string $source, string $target): bool; +} diff --git a/src/Symfony/Component/AutoMapper/CHANGELOG.md b/src/Symfony/Component/AutoMapper/CHANGELOG.md new file mode 100644 index 0000000000000..69abaa606de85 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +4.3.0 +----- + + * Initial release diff --git a/src/Symfony/Component/AutoMapper/Context.php b/src/Symfony/Component/AutoMapper/Context.php new file mode 100644 index 0000000000000..6a6bf1454bbf0 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Context.php @@ -0,0 +1,252 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper; + +/** + * Context for mapping. + * + * Allow to customize how is done the mapping + * + * @author Joel Wurtz + */ +class Context extends \ArrayObject +{ + private $referenceRegistry = []; + + private $countReferenceRegistry = []; + + private $groups; + + private $depth; + + private $object; + + private $circularReferenceLimit; + + private $circularReferenceHandler; + + private $attributes; + + private $ignoredAttributes; + + private $constructorArguments = []; + + /** + * @param array|null $groups Groups to use for mapping + * @param array|null $attributes Attributes to use for mapping (exclude others) + * @param array|null $ignoredAttributes Attributes to exclude from mapping (include others) + */ + public function __construct(array $groups = null, array $attributes = null, array $ignoredAttributes = null) + { + $this->groups = $groups; + $this->depth = 0; + $this->attributes = $attributes; + $this->ignoredAttributes = $ignoredAttributes; + } + + /** + * Whether a reference has reached it's limit. + */ + public function shouldHandleCircularReference(string $reference, ?int $circularReferenceLimit = null): bool + { + if (!isset($this->referenceRegistry[$reference])) { + return false; + } + + if (null === $circularReferenceLimit) { + $circularReferenceLimit = $this->circularReferenceLimit; + } + + if (null !== $circularReferenceLimit) { + return $this->countReferenceRegistry[$reference] >= $circularReferenceLimit; + } + + return true; + } + + /** + * Handle circular reference for a specific reference. + * + * By default will try to keep it and return the previous value + * + * @return mixed + */ + public function &handleCircularReference(string $reference, $object, ?int $circularReferenceLimit = null, callable $callback = null) + { + if (null === $callback) { + $callback = $this->circularReferenceHandler; + } + + if (null !== $callback) { + $value = $callback($object, $this); + + return $value; + } + + if (null === $circularReferenceLimit) { + $circularReferenceLimit = $this->circularReferenceLimit; + } + + if (null !== $circularReferenceLimit && $this->countReferenceRegistry[$reference] >= $circularReferenceLimit) { + throw new CircularReferenceException(sprintf('A circular reference has been detected when mapping the object of type "%s" (configured limit: %d)', \is_object($object) ? \get_class($object) : 'array', $circularReferenceLimit)); + } + + // When no limit defined return the object referenced + ++$this->countReferenceRegistry[$reference]; + + return $this->referenceRegistry[$reference]; + } + + /** + * Get groups for this context. + */ + public function getGroups(): ?array + { + return $this->groups; + } + + /** + * Get current depth. + */ + public function getDepth(): int + { + return $this->depth; + } + + /** + * Set object to populate (by-pass target construction). + */ + public function setObjectToPopulate($object) + { + $this->object = $object; + } + + /** + * Get object to populate. + */ + public function getObjectToPopulate() + { + $object = $this->object; + + if (null !== $object) { + $this->object = null; + } + + return $object; + } + + /** + * Set circular reference limit. + */ + public function setCircularReferenceLimit(?int $circularReferenceLimit): void + { + $this->circularReferenceLimit = $circularReferenceLimit; + } + + /** + * Set circular reference handler. + */ + public function setCircularReferenceHandler(?callable $circularReferenceHandler): void + { + $this->circularReferenceHandler = $circularReferenceHandler; + } + + /** + * Create a new context with a new reference. + */ + public function withReference($reference, &$object): self + { + $new = clone $this; + + $new->referenceRegistry[$reference] = &$object; + $new->countReferenceRegistry[$reference] = 1; + + return $new; + } + + /** + * Check whether an attribute is allowed to be mapped. + */ + public function isAllowedAttribute(string $attribute): bool + { + if (null !== $this->ignoredAttributes && \in_array($attribute, $this->ignoredAttributes, true)) { + return false; + } + + if (null === $this->attributes) { + return true; + } + + return \in_array($attribute, $this->attributes, true); + } + + /** + * Clone context with a incremented depth. + */ + public function withIncrementedDepth(): self + { + $new = clone $this; + ++$new->depth; + + return $new; + } + + /** + * Set the argument of a constructor for a specific class. + */ + public function setConstructorArgument(string $class, string $key, $value): void + { + if ($this->constructorArguments[$class] ?? false) { + $this->constructorArguments[$class] = []; + } + + $this->constructorArguments[$class][$key] = $value; + } + + /** + * Check wether an argument exist for the constructor for a specific class. + */ + public function hasConstructorArgument(string $class, string $key): bool + { + return \array_key_exists($key, $this->constructorArguments[$class] ?? []); + } + + /** + * Get constructor argument for a specific class. + */ + public function getConstructorArgument(string $class, string $key) + { + return $this->constructorArguments[$class][$key]; + } + + /** + * Create a new cloned context, and reload attribute mapping for it. + */ + public function withNewContext(string $attribute): self + { + if (null === $this->attributes) { + return $this; + } + + $new = clone $this; + + if (null !== $this->ignoredAttributes && isset($this->ignoredAttributes[$attribute]) && \is_array($this->ignoredAttributes[$attribute])) { + $new->ignoredAttributes = $this->ignoredAttributes[$attribute]; + } + + if (null !== $this->attributes && isset($this->attributes[$attribute]) && \is_array($this->attributes[$attribute])) { + $new->attributes = $this->attributes[$attribute]; + } + + return $new; + } +} diff --git a/src/Symfony/Component/AutoMapper/Exception/CompileException.php b/src/Symfony/Component/AutoMapper/Exception/CompileException.php new file mode 100644 index 0000000000000..070300fb63aad --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Exception/CompileException.php @@ -0,0 +1,7 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Extractor; + +/** + * Extract accessor and mutator. + * + * @internal + * + * @author Joel Wurtz + */ +interface AccessorExtractorInterface extends ReadAccessorExtractorInterface, WriteMutatorExtractorInterface +{ +} diff --git a/src/Symfony/Component/AutoMapper/Extractor/FromSourceMappingExtractor.php b/src/Symfony/Component/AutoMapper/Extractor/FromSourceMappingExtractor.php new file mode 100644 index 0000000000000..bf54b13a54c1c --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Extractor/FromSourceMappingExtractor.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Extractor; + +use Symfony\Component\AutoMapper\Exception\InvalidMappingException; +use Symfony\Component\AutoMapper\MapperMetadataInterface; +use Symfony\Component\AutoMapper\Transformer\TransformerFactoryInterface; +use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; +use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; +use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; + +/** + * Mapping extracted only from source, useful when not having metadata on the target for dynamic data like array, \stdClass, ... + * + * Can use a NameConverter to use specific properties name in the target + * + * @author Joel Wurtz + */ +final class FromSourceMappingExtractor extends MappingExtractor +{ + private const ALLOWED_TARGETS = ['array', \stdClass::class]; + + private $nameConverter; + + public function __construct(PropertyInfoExtractorInterface $propertyInfoExtractor, AccessorExtractorInterface $accessorExtractor, TransformerFactoryInterface $transformerFactory, ClassMetadataFactoryInterface $classMetadataFactory = null, AdvancedNameConverterInterface $nameConverter = null) + { + parent::__construct($propertyInfoExtractor, $accessorExtractor, $transformerFactory, $classMetadataFactory); + + $this->nameConverter = $nameConverter; + } + + /** + * {@inheritdoc} + */ + public function getPropertiesMapping(MapperMetadataInterface $mapperMetadata): array + { + $sourceProperties = $this->propertyInfoExtractor->getProperties($mapperMetadata->getSource()); + + if (!\in_array($mapperMetadata->getTarget(), self::ALLOWED_TARGETS, true)) { + throw new InvalidMappingException('Only array or stdClass are accepted as a target'); + } + + if (null === $sourceProperties) { + return []; + } + + $sourceProperties = array_unique($sourceProperties); + $mapping = []; + + foreach ($sourceProperties as $property) { + if (!$this->propertyInfoExtractor->isReadable($mapperMetadata->getSource(), $property)) { + continue; + } + + $sourceTypes = $this->propertyInfoExtractor->getTypes($mapperMetadata->getSource(), $property); + + if (null === $sourceTypes) { + continue; + } + + $targetTypes = []; + + foreach ($sourceTypes as $type) { + $targetTypes[] = $this->transformType($mapperMetadata->getTarget(), $type); + } + + $transformer = $this->transformerFactory->getTransformer($sourceTypes, $targetTypes, $mapperMetadata); + + if (null === $transformer) { + continue; + } + + $mapping[] = new PropertyMapping( + $this->getReadAccessor($mapperMetadata->getSource(), $mapperMetadata->getTarget(), $property), + $this->getWriteMutator($mapperMetadata->getSource(), $mapperMetadata->getTarget(), $property), + $transformer, + $property, + false, + $this->getGroups($mapperMetadata->getSource(), $property), + $this->getGroups($mapperMetadata->getTarget(), $property), + $this->getMaxDepth($mapperMetadata->getSource(), $property) + ); + } + + return $mapping; + } + + private function transformType(string $target, Type $type = null): ?Type + { + if (null === $type) { + return null; + } + + $builtinType = $type->getBuiltinType(); + $className = $type->getClassName(); + + if (Type::BUILTIN_TYPE_OBJECT === $type->getBuiltinType() && \stdClass::class !== $type->getClassName()) { + $builtinType = 'array' === $target ? Type::BUILTIN_TYPE_ARRAY : Type::BUILTIN_TYPE_OBJECT; + $className = 'array' === $target ? null : \stdClass::class; + } + + // Use string for datetime + if (Type::BUILTIN_TYPE_OBJECT === $type->getBuiltinType() && (\DateTimeInterface::class === $type->getClassName() || is_subclass_of($type->getClassName(), \DateTimeInterface::class))) { + $builtinType = 'string'; + } + + return new Type( + $builtinType, + $type->isNullable(), + $className, + $type->isCollection(), + $this->transformType($target, $type->getCollectionKeyType()), + $this->transformType($target, $type->getCollectionValueType()) + ); + } + + /** + * {@inheritdoc} + */ + public function getWriteMutator(string $source, string $target, string $property): WriteMutator + { + if (null !== $this->nameConverter) { + $property = $this->nameConverter->normalize($property, $source, $target); + } + + $targetMutator = new WriteMutator(WriteMutator::TYPE_ARRAY_DIMENSION, $property, false); + + if (\stdClass::class === $target) { + $targetMutator = new WriteMutator(WriteMutator::TYPE_PROPERTY, $property, false); + } + + return $targetMutator; + } +} diff --git a/src/Symfony/Component/AutoMapper/Extractor/FromTargetMappingExtractor.php b/src/Symfony/Component/AutoMapper/Extractor/FromTargetMappingExtractor.php new file mode 100644 index 0000000000000..7335613604103 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Extractor/FromTargetMappingExtractor.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Extractor; + +use Symfony\Component\AutoMapper\Exception\InvalidMappingException; +use Symfony\Component\AutoMapper\MapperMetadataInterface; +use Symfony\Component\AutoMapper\Transformer\TransformerFactoryInterface; +use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; +use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; +use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; + +/** + * Mapping extracted only from target, useful when not having metadata on the source for dynamic data like array, \stdClass, ... + * + * Can use a NameConverter to use specific properties name in the source + * + * @author Joel Wurtz + */ +final class FromTargetMappingExtractor extends MappingExtractor +{ + private const ALLOWED_SOURCES = ['array', \stdClass::class]; + + private $nameConverter; + + public function __construct(PropertyInfoExtractorInterface $propertyInfoExtractor, AccessorExtractorInterface $accessorExtractor, TransformerFactoryInterface $transformerFactory, ClassMetadataFactoryInterface $classMetadataFactory = null, AdvancedNameConverterInterface $nameConverter = null) + { + parent::__construct($propertyInfoExtractor, $accessorExtractor, $transformerFactory, $classMetadataFactory); + + $this->nameConverter = $nameConverter; + } + + /** + * {@inheritdoc} + */ + public function getPropertiesMapping(MapperMetadataInterface $mapperMetadata): array + { + $targetProperties = array_unique($this->propertyInfoExtractor->getProperties($mapperMetadata->getTarget())); + + if (!\in_array($mapperMetadata->getSource(), self::ALLOWED_SOURCES, true)) { + throw new InvalidMappingException('Only array or stdClass are accepted as a source'); + } + + if (null === $targetProperties) { + return []; + } + + $mapping = []; + + foreach ($targetProperties as $property) { + if (!$this->propertyInfoExtractor->isWritable($mapperMetadata->getTarget(), $property)) { + continue; + } + + $targetTypes = $this->propertyInfoExtractor->getTypes($mapperMetadata->getTarget(), $property); + + if (null === $targetTypes) { + continue; + } + + $sourceTypes = []; + + foreach ($targetTypes as $type) { + $sourceTypes[] = $this->transformType($mapperMetadata->getSource(), $type); + } + + $transformer = $this->transformerFactory->getTransformer($sourceTypes, $targetTypes, $mapperMetadata); + + if (null === $transformer) { + continue; + } + + $mapping[] = new PropertyMapping( + $this->getReadAccessor($mapperMetadata->getSource(), $mapperMetadata->getTarget(), $property), + $this->getWriteMutator($mapperMetadata->getSource(), $mapperMetadata->getTarget(), $property), + $transformer, + $property, + true, + $this->getGroups($mapperMetadata->getSource(), $property), + $this->getGroups($mapperMetadata->getTarget(), $property), + $this->getMaxDepth($mapperMetadata->getTarget(), $property) + ); + } + + return $mapping; + } + + public function getReadAccessor(string $source, string $target, string $property): ?ReadAccessor + { + if (null !== $this->nameConverter) { + $property = $this->nameConverter->normalize($property, $target, $source); + } + + $sourceAccessor = new ReadAccessor(ReadAccessor::TYPE_ARRAY_DIMENSION, $property); + + if (\stdClass::class === $source) { + $sourceAccessor = new ReadAccessor(ReadAccessor::TYPE_PROPERTY, $property); + } + + return $sourceAccessor; + } + + private function transformType(string $source, Type $type = null): ?Type + { + if (null === $type) { + return null; + } + + $builtinType = $type->getBuiltinType(); + $className = $type->getClassName(); + + if (Type::BUILTIN_TYPE_OBJECT === $type->getBuiltinType() && \stdClass::class !== $type->getClassName()) { + $builtinType = 'array' === $source ? Type::BUILTIN_TYPE_ARRAY : Type::BUILTIN_TYPE_OBJECT; + $className = 'array' === $source ? null : \stdClass::class; + } + + if (Type::BUILTIN_TYPE_OBJECT === $type->getBuiltinType() && (\DateTimeInterface::class === $type->getClassName() || is_subclass_of($type->getClassName(), \DateTimeInterface::class))) { + $builtinType = 'string'; + } + + return new Type( + $builtinType, + $type->isNullable(), + $className, + $type->isCollection(), + $this->transformType($source, $type->getCollectionKeyType()), + $this->transformType($source, $type->getCollectionValueType()) + ); + } +} diff --git a/src/Symfony/Component/AutoMapper/Extractor/MappingExtractor.php b/src/Symfony/Component/AutoMapper/Extractor/MappingExtractor.php new file mode 100644 index 0000000000000..576983598087a --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Extractor/MappingExtractor.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Extractor; + +use Symfony\Component\AutoMapper\Transformer\TransformerFactoryInterface; +use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; + +/** + * @internal + * + * @author Joel Wurtz + */ +abstract class MappingExtractor implements MappingExtractorInterface +{ + protected $propertyInfoExtractor; + + protected $transformerFactory; + + protected $accessorExtractor; + + protected $classMetadataFactory; + + public function __construct(PropertyInfoExtractorInterface $propertyInfoExtractor, AccessorExtractorInterface $accessorExtractor, TransformerFactoryInterface $transformerFactory, ClassMetadataFactoryInterface $classMetadataFactory = null) + { + $this->propertyInfoExtractor = $propertyInfoExtractor; + $this->accessorExtractor = $accessorExtractor; + $this->transformerFactory = $transformerFactory; + $this->classMetadataFactory = $classMetadataFactory; + } + + /** + * {@inheritdoc} + */ + public function getReadAccessor(string $source, string $target, string $property): ?ReadAccessor + { + return $this->accessorExtractor->getReadAccessor($source, $property); + } + + /** + * {@inheritdoc} + */ + public function getWriteMutator(string $source, string $target, string $property): ?WriteMutator + { + return $this->accessorExtractor->getWriteMutator($target, $property); + } + + protected function getMaxDepth($class, $property): ?int + { + if ('array' === $class) { + return null; + } + + if (null === $this->classMetadataFactory) { + return null; + } + + if (!$this->classMetadataFactory->getMetadataFor($class)) { + return null; + } + + $serializerClassMetadata = $this->classMetadataFactory->getMetadataFor($class); + $maxDepth = null; + + foreach ($serializerClassMetadata->getAttributesMetadata() as $serializerAttributeMetadata) { + if ($serializerAttributeMetadata->getName() === $property) { + $maxDepth = $serializerAttributeMetadata->getMaxDepth(); + } + } + + return $maxDepth; + } + + protected function getGroups($class, $property): ?array + { + if ('array' === $class) { + return null; + } + + if (null === $this->classMetadataFactory) { + return null; + } + + if (!$this->classMetadataFactory->getMetadataFor($class)) { + return null; + } + + $serializerClassMetadata = $this->classMetadataFactory->getMetadataFor($class); + $anyGroupFound = false; + $groups = []; + + foreach ($serializerClassMetadata->getAttributesMetadata() as $serializerAttributeMetadata) { + if (\count($serializerAttributeMetadata->getGroups()) > 0) { + $anyGroupFound = true; + } + + if ($serializerAttributeMetadata->getName() === $property) { + $groups = $serializerAttributeMetadata->getGroups(); + } + } + + if (!$anyGroupFound) { + return null; + } + + return $groups; + } +} diff --git a/src/Symfony/Component/AutoMapper/Extractor/MappingExtractorInterface.php b/src/Symfony/Component/AutoMapper/Extractor/MappingExtractorInterface.php new file mode 100644 index 0000000000000..f070c9446d80c --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Extractor/MappingExtractorInterface.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Extractor; + +use Symfony\Component\AutoMapper\MapperMetadataInterface; + +/** + * Extract mapping. + * + * @internal + * + * @author Joel Wurtz + */ +interface MappingExtractorInterface +{ + /** + * Extract properties mapped for a given source and target. + * + * @return PropertyMapping[] + */ + public function getPropertiesMapping(MapperMetadataInterface $mapperMetadata): array; + + /** + * Extract read accessor for a given source, target and property. + */ + public function getReadAccessor(string $source, string $target, string $property): ?ReadAccessor; + + /** + * Extract write mutator for a given source, target and property. + */ + public function getWriteMutator(string $source, string $target, string $property): ?WriteMutator; +} diff --git a/src/Symfony/Component/AutoMapper/Extractor/PrivateReflectionExtractor.php b/src/Symfony/Component/AutoMapper/Extractor/PrivateReflectionExtractor.php new file mode 100644 index 0000000000000..79a5e93648412 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Extractor/PrivateReflectionExtractor.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Extractor; + +use Symfony\Component\Inflector\Inflector; +use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; +use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyListExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; + +/** + * Extracts all (including private) data using the reflection API. + * + * @author Joel Wurtz + */ +final class PrivateReflectionExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface +{ + /** + * @internal + */ + public static $defaultMutatorPrefixes = ['add', 'remove', 'set']; + + /** + * @internal + */ + public static $defaultAccessorPrefixes = ['is', 'can', 'get']; + + /** + * @internal + */ + public static $defaultArrayMutatorPrefixes = ['add', 'remove']; + + private $mutatorPrefixes; + private $accessorPrefixes; + private $arrayMutatorPrefixes; + private $reflectionExtractor; + + public function __construct(array $mutatorPrefixes = null, array $accessorPrefixes = null, array $arrayMutatorPrefixes = null, bool $enableConstructorExtraction = true) + { + $this->mutatorPrefixes = $mutatorPrefixes ?? self::$defaultMutatorPrefixes; + $this->accessorPrefixes = $accessorPrefixes ?? self::$defaultAccessorPrefixes; + $this->arrayMutatorPrefixes = $arrayMutatorPrefixes ?? self::$defaultArrayMutatorPrefixes; + $this->reflectionExtractor = new ReflectionExtractor($mutatorPrefixes, $accessorPrefixes, $arrayMutatorPrefixes, $enableConstructorExtraction); + } + + /** + * {@inheritdoc} + */ + public function getProperties($class, array $context = []) + { + try { + $reflectionClass = new \ReflectionClass($class); + } catch (\ReflectionException $e) { + return; + } + + $propertyFlag = \ReflectionProperty::IS_PRIVATE | \ReflectionProperty::IS_PROTECTED; + $methodFlag = \ReflectionMethod::IS_PRIVATE | \ReflectionMethod::IS_PROTECTED; + + $reflectionProperties = $reflectionClass->getProperties($propertyFlag); + $properties = $this->reflectionExtractor->getProperties($class, $context); + + if (null === $properties) { + $properties = []; + } + + foreach ($reflectionProperties as $reflectionProperty) { + $properties[$reflectionProperty->name] = $reflectionProperty->name; + } + + foreach ($reflectionClass->getMethods($methodFlag) as $reflectionMethod) { + if ($reflectionMethod->isStatic()) { + continue; + } + + $propertyName = $this->getPropertyName($reflectionMethod->name, $reflectionProperties); + + if (!$propertyName || isset($properties[$propertyName])) { + continue; + } + + if (!$reflectionClass->hasProperty($propertyName) && !preg_match('/^[A-Z]{2,}/', $propertyName)) { + $propertyName = lcfirst($propertyName); + } + + $properties[$propertyName] = $propertyName; + } + + return $properties ? array_values($properties) : null; + } + + /** + * {@inheritdoc} + */ + public function getTypes($class, $property, array $context = []) + { + return $this->reflectionExtractor->getTypes($class, $property, $context); + } + + /** + * {@inheritdoc} + */ + public function isReadable($class, $property, array $context = []) + { + $refClass = new \ReflectionClass($class); + + return $refClass->hasProperty($property); + } + + /** + * {@inheritdoc} + */ + public function isWritable($class, $property, array $context = []) + { + $refClass = new \ReflectionClass($class); + + return $refClass->hasProperty($property); + } + + private function getPropertyName(string $methodName, array $reflectionProperties): ?string + { + $pattern = implode('|', array_merge($this->accessorPrefixes, $this->mutatorPrefixes)); + + if ('' !== $pattern && preg_match('/^('.$pattern.')(.+)$/i', $methodName, $matches)) { + if (!\in_array($matches[1], $this->arrayMutatorPrefixes)) { + return $matches[2]; + } + + foreach ($reflectionProperties as $reflectionProperty) { + foreach ((array) Inflector::singularize($reflectionProperty->name) as $name) { + if (strtolower($name) === strtolower($matches[2])) { + return $reflectionProperty->name; + } + } + } + + return $matches[2]; + } + + return null; + } +} diff --git a/src/Symfony/Component/AutoMapper/Extractor/PropertyMapping.php b/src/Symfony/Component/AutoMapper/Extractor/PropertyMapping.php new file mode 100644 index 0000000000000..97bb211d4bb96 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Extractor/PropertyMapping.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Extractor; + +use Symfony\Component\AutoMapper\Transformer\TransformerInterface; + +/** + * Property mapping. + * + * @author Joel Wurtz + */ +final class PropertyMapping +{ + private $readAccessor; + + private $writeMutator; + + private $transformer; + + private $checkExists; + + private $property; + + private $sourceGroups; + + private $targetGroups; + + private $maxDepth; + + public function __construct( + ReadAccessor $readAccessor, + WriteMutator $writeMutator, + TransformerInterface $transformer, + string $property, + bool $checkExists = false, + array $sourceGroups = null, + array $targetGroups = null, + ?int $maxDepth = null + ) { + $this->readAccessor = $readAccessor; + $this->writeMutator = $writeMutator; + $this->transformer = $transformer; + $this->property = $property; + $this->checkExists = $checkExists; + $this->sourceGroups = $sourceGroups; + $this->targetGroups = $targetGroups; + $this->maxDepth = $maxDepth; + } + + public function getReadAccessor(): ReadAccessor + { + return $this->readAccessor; + } + + public function getWriteMutator(): WriteMutator + { + return $this->writeMutator; + } + + public function getTransformer(): TransformerInterface + { + return $this->transformer; + } + + public function getProperty(): string + { + return $this->property; + } + + public function checkExists(): bool + { + return $this->checkExists; + } + + public function getSourceGroups(): ?array + { + return $this->sourceGroups; + } + + public function getTargetGroups(): ?array + { + return $this->targetGroups; + } + + public function getMaxDepth(): ?int + { + return $this->maxDepth; + } +} diff --git a/src/Symfony/Component/AutoMapper/Extractor/ReadAccessor.php b/src/Symfony/Component/AutoMapper/Extractor/ReadAccessor.php new file mode 100644 index 0000000000000..f8bf79de81399 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Extractor/ReadAccessor.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Extractor; + +use PhpParser\Node\Arg; +use PhpParser\Node\Expr; +use PhpParser\Node\Name; +use PhpParser\Node\Param; +use PhpParser\Node\Scalar; +use PhpParser\Node\Stmt; +use Symfony\Component\AutoMapper\Exception\CompileException; + +/** + * Read accessor tell how to read from a property. + * + * @author Joel Wurtz + */ +final class ReadAccessor +{ + public const TYPE_METHOD = 1; + public const TYPE_PROPERTY = 2; + public const TYPE_ARRAY_DIMENSION = 3; + public const TYPE_SOURCE = 4; + + private $type; + + private $name; + + private $private; + + public function __construct(int $type, string $name, $private = false) + { + $this->type = $type; + $this->name = $name; + $this->private = $private; + } + + /** + * Get AST expression for reading property from an input. + * + * @throws CompileException + */ + public function getExpression(Expr\Variable $input): Expr + { + if (self::TYPE_METHOD === $this->type) { + return new Expr\MethodCall($input, $this->name); + } + + if (self::TYPE_PROPERTY === $this->type) { + if ($this->private) { + return new Expr\FuncCall( + new Expr\ArrayDimFetch(new Expr\PropertyFetch(new Expr\Variable('this'), 'extractCallbacks'), new Scalar\String_($this->name)), + [ + new Arg($input), + ] + ); + } + + return new Expr\PropertyFetch($input, $this->name); + } + + if (self::TYPE_ARRAY_DIMENSION === $this->type) { + return new Expr\ArrayDimFetch($input, new Scalar\String_($this->name)); + } + + if (self::TYPE_SOURCE === $this->type) { + return $input; + } + + throw new CompileException('Invalid accessor for read expression'); + } + + /** + * Get AST expression for binding closure when dealing with a private property. + */ + public function getExtractCallback($className): ?Expr + { + if (self::TYPE_PROPERTY !== $this->type || !$this->private) { + return null; + } + + return new Expr\StaticCall(new Name\FullyQualified(\Closure::class), 'bind', [ + new Arg(new Expr\Closure([ + 'params' => [ + new Param(new Expr\Variable('object')), + ], + 'stmts' => [ + new Stmt\Return_(new Expr\PropertyFetch(new Expr\Variable('object'), $this->name)), + ], + ])), + new Arg(new Expr\ConstFetch(new Name('null'))), + new Arg(new Scalar\String_(new Name\FullyQualified($className))), + ]); + } +} diff --git a/src/Symfony/Component/AutoMapper/Extractor/ReadAccessorExtractorInterface.php b/src/Symfony/Component/AutoMapper/Extractor/ReadAccessorExtractorInterface.php new file mode 100644 index 0000000000000..422fc1490c2b4 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Extractor/ReadAccessorExtractorInterface.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Extractor; + +/** + * Extract read accessor for property of a class. + * + * @internal + * + * @author Joel Wurtz + */ +interface ReadAccessorExtractorInterface +{ + public function getReadAccessor(string $class, string $property): ?ReadAccessor; +} diff --git a/src/Symfony/Component/AutoMapper/Extractor/ReflectionExtractor.php b/src/Symfony/Component/AutoMapper/Extractor/ReflectionExtractor.php new file mode 100644 index 0000000000000..2c2dcec294f28 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Extractor/ReflectionExtractor.php @@ -0,0 +1,143 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Extractor; + +/** + * Extract accessor and mutator from reflection. + * + * @author Joel Wurtz + */ +final class ReflectionExtractor implements AccessorExtractorInterface +{ + private $allowPrivate; + + public function __construct($allowPrivate = false) + { + $this->allowPrivate = $allowPrivate; + } + + public function getReadAccessor(string $class, string $property): ?ReadAccessor + { + $reflClass = new \ReflectionClass($class); + $hasProperty = $reflClass->hasProperty($property); + $camelProp = $this->camelize($property); + $getter = 'get'.$camelProp; + $getsetter = lcfirst($camelProp); // jQuery style, e.g. read: last(), write: last($item) + $isser = 'is'.$camelProp; + $hasser = 'has'.$camelProp; + $accessPrivate = false; + + if ($reflClass->hasMethod($getter) && $reflClass->getMethod($getter)->isPublic()) { + $accessType = ReadAccessor::TYPE_METHOD; + $accessName = $getter; + } elseif ($reflClass->hasMethod($getsetter) && $reflClass->getMethod($getsetter)->isPublic()) { + $accessType = ReadAccessor::TYPE_METHOD; + $accessName = $getsetter; + } elseif ($reflClass->hasMethod($isser) && $reflClass->getMethod($isser)->isPublic()) { + $accessType = ReadAccessor::TYPE_METHOD; + $accessName = $isser; + } elseif ($reflClass->hasMethod($hasser) && $reflClass->getMethod($hasser)->isPublic()) { + $accessType = ReadAccessor::TYPE_METHOD; + $accessName = $hasser; + } elseif ($reflClass->hasMethod('__get') && $reflClass->getMethod('__get')->isPublic()) { + $accessType = ReadAccessor::TYPE_PROPERTY; + $accessName = $property; + } elseif ($hasProperty && $reflClass->getProperty($property)->isPublic()) { + $accessType = ReadAccessor::TYPE_PROPERTY; + $accessName = $property; + } elseif ($hasProperty && $this->allowPrivate && $reflClass->getProperty($property)) { + $accessType = ReadAccessor::TYPE_PROPERTY; + $accessName = $property; + $accessPrivate = true; + } else { + return null; + } + + return new ReadAccessor($accessType, $accessName, $accessPrivate); + } + + public function getWriteMutator(string $class, string $property, bool $allowConstruct = true): ?WriteMutator + { + $reflClass = new \ReflectionClass($class); + $hasProperty = $reflClass->hasProperty($property); + $camelized = $this->camelize($property); + $accessParameter = null; + $accessName = null; + $accessType = null; + $accessPrivate = false; + $constructor = $reflClass->getConstructor(); + + if (null !== $constructor) { + foreach ($constructor->getParameters() as $parameter) { + if ($parameter->getName() === $property) { + $accessParameter = $parameter; + } + } + } + + if (null === $accessType) { + $setter = 'set'.$camelized; + $getsetter = lcfirst($camelized); // jQuery style, e.g. read: last(), write: last($item) + + if (null !== $accessParameter && $allowConstruct) { + $accessType = WriteMutator::TYPE_CONSTRUCTOR; + $accessName = $property; + } elseif ($this->isMethodAccessible($reflClass, $setter, 1)) { + $accessType = WriteMutator::TYPE_METHOD; + $accessName = $setter; + } elseif ($this->isMethodAccessible($reflClass, $getsetter, 1)) { + $accessType = WriteMutator::TYPE_METHOD; + $accessName = $getsetter; + } elseif ($this->isMethodAccessible($reflClass, '__set', 2)) { + $accessType = WriteMutator::TYPE_PROPERTY; + $accessName = $property; + } elseif ($hasProperty && $reflClass->getProperty($property)->isPublic()) { + $accessType = WriteMutator::TYPE_PROPERTY; + $accessName = $property; + } elseif ($hasProperty && $this->allowPrivate && $reflClass->getProperty($property)) { + $accessType = WriteMutator::TYPE_PROPERTY; + $accessName = $property; + $accessPrivate = true; + } else { + return null; + } + } + + return new WriteMutator($accessType, $accessName, $accessPrivate, $accessParameter); + } + + /** + * Returns whether a method is public and has the number of required parameters. + */ + private function isMethodAccessible(\ReflectionClass $class, string $methodName, int $parameters): bool + { + if ($class->hasMethod($methodName)) { + $method = $class->getMethod($methodName); + + if ($method->isPublic() + && $method->getNumberOfRequiredParameters() <= $parameters + && $method->getNumberOfParameters() >= $parameters) { + return true; + } + } + + return false; + } + + /** + * Camelizes a given string. + */ + private function camelize(string $string): string + { + return str_replace(' ', '', ucwords(str_replace('_', ' ', $string))); + } +} diff --git a/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php b/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php new file mode 100644 index 0000000000000..014408a6d608e --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php @@ -0,0 +1,76 @@ + + */ +class SourceTargetMappingExtractor extends MappingExtractor +{ + /** + * {@inheritdoc} + */ + public function getPropertiesMapping(MapperMetadataInterface $mapperMetadata): array + { + $sourceProperties = array_unique($this->propertyInfoExtractor->getProperties($mapperMetadata->getSource())); + $targetProperties = array_unique($this->propertyInfoExtractor->getProperties($mapperMetadata->getTarget())); + + if (null === $sourceProperties || null === $targetProperties) { + return []; + } + + $mapping = []; + + foreach ($sourceProperties as $property) { + if (!$this->propertyInfoExtractor->isReadable($mapperMetadata->getSource(), $property)) { + continue; + } + + if (\in_array($property, $targetProperties, true)) { + if (!$this->propertyInfoExtractor->isWritable($mapperMetadata->getTarget(), $property)) { + continue; + } + + $sourceTypes = $this->propertyInfoExtractor->getTypes($mapperMetadata->getSource(), $property); + $targetTypes = $this->propertyInfoExtractor->getTypes($mapperMetadata->getTarget(), $property); + $transformer = $this->transformerFactory->getTransformer($sourceTypes, $targetTypes, $mapperMetadata); + + if (null === $transformer) { + continue; + } + + $sourceAccessor = $this->accessorExtractor->getReadAccessor($mapperMetadata->getSource(), $property); + $targetMutator = $this->accessorExtractor->getWriteMutator($mapperMetadata->getTarget(), $property, false); + + $maxDepthSource = $this->getMaxDepth($mapperMetadata->getSource(), $property); + $maxDepthTarget = $this->getMaxDepth($mapperMetadata->getTarget(), $property); + $maxDepth = null; + + if (null !== $maxDepthSource && null !== $maxDepthTarget) { + $maxDepth = min($maxDepthSource, $maxDepthTarget); + } elseif (null !== $maxDepthSource) { + $maxDepth = $maxDepthSource; + } elseif (null !== $maxDepthTarget) { + $maxDepth = $maxDepthTarget; + } + + $mapping[] = new PropertyMapping( + $sourceAccessor, + $targetMutator, + $transformer, + $property, + false, + $this->getGroups($mapperMetadata->getSource(), $property), + $this->getGroups($mapperMetadata->getTarget(), $property), + $maxDepth + ); + } + } + + return $mapping; + } +} diff --git a/src/Symfony/Component/AutoMapper/Extractor/WriteMutator.php b/src/Symfony/Component/AutoMapper/Extractor/WriteMutator.php new file mode 100644 index 0000000000000..6c499cf80b947 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Extractor/WriteMutator.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Extractor; + +use PhpParser\Node\Arg; +use PhpParser\Node\Expr; +use PhpParser\Node\Name; +use PhpParser\Node\Param; +use PhpParser\Node\Scalar; +use PhpParser\Node\Stmt; +use Symfony\Component\AutoMapper\Exception\CompileException; + +/** + * Write mutator tell how to write to a property. + * + * @author Joel Wurtz + */ +final class WriteMutator +{ + public const TYPE_METHOD = 1; + public const TYPE_PROPERTY = 2; + public const TYPE_ARRAY_DIMENSION = 3; + public const TYPE_CONSTRUCTOR = 4; + + private $type; + private $name; + private $private; + private $parameter; + + public function __construct(int $type, string $name, bool $private = false, \ReflectionParameter $parameter = null) + { + $this->type = $type; + $this->name = $name; + $this->private = $private; + $this->parameter = $parameter; + } + + /** + * Get AST expression for writing from a value to an output. + * + * @throws CompileException + */ + public function getExpression(Expr\Variable $output, Expr $value, bool $byRef = false): ?Expr + { + if (self::TYPE_METHOD === $this->type) { + return new Expr\MethodCall($output, $this->name, [ + new Arg($value), + ]); + } + + if (self::TYPE_PROPERTY === $this->type) { + if ($this->private) { + return new Expr\FuncCall( + new Expr\ArrayDimFetch(new Expr\PropertyFetch(new Expr\Variable('this'), 'hydrateCallbacks'), new Scalar\String_($this->name)), + [ + new Arg($output), + new Arg($value), + ] + ); + } + if ($byRef) { + return new Expr\AssignRef(new Expr\PropertyFetch($output, $this->name), $value); + } + + return new Expr\Assign(new Expr\PropertyFetch($output, $this->name), $value); + } + + if (self::TYPE_ARRAY_DIMENSION === $this->type) { + if ($byRef) { + return new Expr\AssignRef(new Expr\ArrayDimFetch($output, new Scalar\String_($this->name)), $value); + } + + return new Expr\Assign(new Expr\ArrayDimFetch($output, new Scalar\String_($this->name)), $value); + } + + if (self::TYPE_CONSTRUCTOR === $this->type) { + return null; + } + + throw new CompileException('Invalid accessor for write expression'); + } + + /** + * Get AST expression for binding closure when dealing with private property. + */ + public function getHydrateCallback($className): ?Expr + { + if (self::TYPE_PROPERTY !== $this->type || !$this->private) { + return null; + } + + return new Expr\StaticCall(new Name\FullyQualified(\Closure::class), 'bind', [ + new Arg(new Expr\Closure([ + 'params' => [ + new Param(new Expr\Variable('object')), + new Param(new Expr\Variable('value')), + ], + 'stmts' => [ + new Stmt\Expression(new Expr\Assign(new Expr\PropertyFetch(new Expr\Variable('object'), $this->name), new Expr\Variable('value'))), + ], + ])), + new Arg(new Expr\ConstFetch(new Name('null'))), + new Arg(new Scalar\String_(new Name\FullyQualified($className))), + ]); + } + + /** + * Get reflection parameter. + */ + public function getParameter(): ?\ReflectionParameter + { + return $this->parameter; + } +} diff --git a/src/Symfony/Component/AutoMapper/Extractor/WriteMutatorExtractorInterface.php b/src/Symfony/Component/AutoMapper/Extractor/WriteMutatorExtractorInterface.php new file mode 100644 index 0000000000000..623734c2c3b9e --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Extractor/WriteMutatorExtractorInterface.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Extractor; + +/** + * Extract write mutator for property of a class. + * + * @internal + * + * @author Joel Wurtz + */ +interface WriteMutatorExtractorInterface +{ + public function getWriteMutator(string $class, string $property, bool $allowConstruct = true): ?WriteMutator; +} diff --git a/src/Symfony/Component/AutoMapper/GeneratedMapper.php b/src/Symfony/Component/AutoMapper/GeneratedMapper.php new file mode 100644 index 0000000000000..076c3c1c542e4 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/GeneratedMapper.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper; + +/** + * Class derived for each generated mapper. + * + * @internal + * + * @author Joel Wurtz + */ +abstract class GeneratedMapper implements MapperInterface +{ + protected $mappers = []; + + protected $callbacks; + + protected $hydrateCallbacks = []; + + protected $extractCallbacks = []; + + protected $cachedTarget; + + protected $circularReferenceHandler; + + protected $circularReferenceLimit; + + /** + * Add a callable for a specific property. + */ + public function addCallback(string $name, callable $callback): void + { + $this->callbacks[$name] = $callback; + } + + abstract public function &map($value, Context $context); + + /** + * Inject sub mappers. + */ + public function injectMappers(AutoMapperRegistryInterface $autoMapperRegistry): void + { + } + + public function setCircularReferenceHandler(?callable $circularReferenceHandler): void + { + $this->circularReferenceHandler = $circularReferenceHandler; + } + + public function setCircularReferenceLimit(?int $circularReferenceLimit): void + { + $this->circularReferenceLimit = $circularReferenceLimit; + } +} diff --git a/src/Symfony/Component/AutoMapper/Generator/Generator.php b/src/Symfony/Component/AutoMapper/Generator/Generator.php new file mode 100644 index 0000000000000..1cb6cf1724101 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Generator/Generator.php @@ -0,0 +1,417 @@ +parser = $parser ?? (new ParserFactory())->create(ParserFactory::PREFER_PHP7); + $this->classDiscriminator = $classDiscriminator; + } + + /** + * Generate Class AST given metadata for a mapper. + * + * @throws CompileException + */ + public function generate(MapperGeneratorMetadataInterface $mapperGeneratorMetadata): Stmt\Class_ + { + $propertiesMapping = $mapperGeneratorMetadata->getPropertiesMapping(); + + $uniqueVariableScope = new UniqueVariableScope(); + $sourceInput = new Expr\Variable($uniqueVariableScope->getUniqueName('value')); + $result = new Expr\Variable($uniqueVariableScope->getUniqueName('result')); + $hashVariable = new Expr\Variable($uniqueVariableScope->getUniqueName('sourceHash')); + $contextVariable = new Expr\Variable($uniqueVariableScope->getUniqueName('context')); + $constructStatements = []; + $addedDependencies = []; + $canHaveCircularDependency = $mapperGeneratorMetadata->canHaveCircularReference() && 'array' !== $mapperGeneratorMetadata->getSource(); + + $statements = [ + new Stmt\If_(new Expr\BinaryOp\Identical(new Expr\ConstFetch(new Name('null')), $sourceInput), [ + 'stmts' => [new Stmt\Return_($sourceInput)], + ]), + ]; + + 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), + ]), + new Scalar\String_($mapperGeneratorMetadata->getTarget()) + ))); + $statements[] = new Stmt\If_(new Expr\MethodCall($contextVariable, new Name('shouldHandleCircularReference'), [ + new Arg($hashVariable), + new Arg(new Expr\PropertyFetch(new Expr\Variable('this'), 'circularReferenceLimit')), + ]), [ + 'stmts' => [ + new Stmt\Return_(new Expr\MethodCall($contextVariable, 'handleCircularReference', [ + new Arg($hashVariable), + new Arg($sourceInput), + new Arg(new Expr\PropertyFetch(new Expr\Variable('this'), 'circularReferenceLimit')), + new Arg(new Expr\PropertyFetch(new Expr\Variable('this'), 'circularReferenceHandler')), + ])), + ], + ]); + } + + [$createObjectStmts, $inConstructor, $constructStatementsForCreateObjects, $injectMapperStatements] = $this->getCreateObjectStatements($mapperGeneratorMetadata, $result, $contextVariable, $sourceInput, $uniqueVariableScope); + $constructStatements = array_merge($constructStatements, $constructStatementsForCreateObjects); + + $statements[] = new Stmt\Expression(new Expr\Assign($result, new Expr\MethodCall($contextVariable, 'getObjectToPopulate'))); + $statements[] = new Stmt\If_(new Expr\BinaryOp\Identical(new Expr\ConstFetch(new Name('null')), $result), [ + 'stmts' => $createObjectStmts, + ]); + + /** @var PropertyMapping $propertyMapping */ + foreach ($propertiesMapping as $propertyMapping) { + $transformer = $propertyMapping->getTransformer(); + + /* @var PropertyMapping $propertyMapping */ + foreach ($transformer->getDependencies() as $dependency) { + if (isset($addedDependencies[$dependency->getName()])) { + continue; + } + + $injectMapperStatements[] = new Stmt\Expression(new Expr\Assign( + new Expr\ArrayDimFetch(new Expr\PropertyFetch(new Expr\Variable('this'), 'mappers'), new Scalar\String_($dependency->getName())), + new Expr\MethodCall(new Expr\Variable('autoMapperRegistry'), 'getMapper', [ + new Arg(new Scalar\String_($dependency->getSource())), + new Arg(new Scalar\String_($dependency->getTarget())), + ]) + )); + $addedDependencies[$dependency->getName()] = true; + } + } + + if (\count($addedDependencies) > 0) { + if ($canHaveCircularDependency) { + $statements[] = new Stmt\Expression(new Expr\Assign( + $contextVariable, + new Expr\MethodCall($contextVariable, 'withReference', [ + new Arg($hashVariable), + new Arg($result), + ]) + )); + } + + $statements[] = new Stmt\Expression(new Expr\Assign( + $contextVariable, + new Expr\MethodCall($contextVariable, 'withIncrementedDepth') + )); + } + + /** @var PropertyMapping $propertyMapping */ + foreach ($propertiesMapping as $propertyMapping) { + $transformer = $propertyMapping->getTransformer(); + + if (\in_array($propertyMapping->getProperty(), $inConstructor, true)) { + continue; + } + + [$output, $propStatements] = $transformer->transform($propertyMapping->getReadAccessor()->getExpression($sourceInput), $propertyMapping, $uniqueVariableScope); + $writeExpression = $propertyMapping->getWriteMutator()->getExpression($result, $output, $transformer->assignByRef()); + + if (null === $writeExpression) { + continue; + } + + $propStatements[] = new Stmt\Expression($writeExpression); + $conditions = []; + + $extractCallback = $propertyMapping->getReadAccessor()->getExtractCallback($mapperGeneratorMetadata->getSource()); + $hydrateCallback = $propertyMapping->getWriteMutator()->getHydrateCallback($mapperGeneratorMetadata->getTarget()); + + if (null !== $extractCallback) { + $constructStatements[] = new Stmt\Expression(new Expr\Assign( + new Expr\ArrayDimFetch(new Expr\PropertyFetch(new Expr\Variable('this'), 'extractCallbacks'), new Scalar\String_($propertyMapping->getProperty())), + $extractCallback + )); + } + + if (null !== $hydrateCallback) { + $constructStatements[] = new Stmt\Expression(new Expr\Assign( + new Expr\ArrayDimFetch(new Expr\PropertyFetch(new Expr\Variable('this'), 'hydrateCallbacks'), new Scalar\String_($propertyMapping->getProperty())), + $hydrateCallback + )); + } + + if ($propertyMapping->checkExists()) { + if (\stdClass::class === $mapperGeneratorMetadata->getSource()) { + $conditions[] = new Expr\FuncCall(new Name('property_exists'), [ + new Arg($sourceInput), + new Arg(new Scalar\String_($propertyMapping->getProperty())), + ]); + } + + if ('array' === $mapperGeneratorMetadata->getSource()) { + $conditions[] = new Expr\FuncCall(new Name('array_key_exists'), [ + new Arg(new Scalar\String_($propertyMapping->getProperty())), + new Arg($sourceInput), + ]); + } + } + + $conditions[] = new Expr\MethodCall($contextVariable, 'isAllowedAttribute', [ + new Arg(new Scalar\String_($propertyMapping->getProperty())), + ]); + + if (null !== $propertyMapping->getSourceGroups()) { + $conditions[] = new Expr\BinaryOp\BooleanAnd( + new Expr\BinaryOp\NotIdentical( + new Expr\ConstFetch(new Name('null')), + new Expr\MethodCall(new Expr\Variable('context'), 'getGroups') + ), + new Expr\FuncCall(new Name('array_intersect'), [ + new Arg(new Expr\MethodCall(new Expr\Variable('context'), 'getGroups')), + new Arg(new Expr\Array_(array_map(function (string $group) { + return new Expr\ArrayItem(new Scalar\String_($group)); + }, $propertyMapping->getSourceGroups()))), + ]) + ); + } + + if (null !== $propertyMapping->getTargetGroups()) { + $conditions[] = new Expr\BinaryOp\BooleanAnd( + new Expr\BinaryOp\NotIdentical( + new Expr\ConstFetch(new Name('null')), + new Expr\MethodCall(new Expr\Variable('context'), 'getGroups') + ), + new Expr\FuncCall(new Name('array_intersect'), [ + new Arg(new Expr\MethodCall(new Expr\Variable('context'), 'getGroups')), + new Arg(new Expr\Array_(array_map(function (string $group) { + return new Expr\ArrayItem(new Scalar\String_($group)); + }, $propertyMapping->getTargetGroups()))), + ]) + ); + } + + if (null !== $propertyMapping->getMaxDepth()) { + $conditions[] = new Expr\BinaryOp\SmallerOrEqual( + new Expr\MethodCall($contextVariable, 'getDepth'), + new Scalar\LNumber($propertyMapping->getMaxDepth()) + ); + } + + if (\count($conditions) > 0) { + $condition = array_shift($conditions); + + while (\count($conditions) > 0) { + $condition = new Expr\BinaryOp\BooleanAnd($condition, array_shift($conditions)); + } + + $propStatements = [new Stmt\If_($condition, [ + 'stmts' => $propStatements, + ])]; + } + + $statements = array_merge( + $statements, + $propStatements + ); + } + + $statements[] = new Stmt\Return_($result); + + $mapMethod = new Stmt\ClassMethod('map', [ + 'flags' => Stmt\Class_::MODIFIER_PUBLIC, + 'params' => [ + new Param(new Expr\Variable($sourceInput->name)), + new Param(new Expr\Variable('context'), null, new Name\FullyQualified(Context::class)), + ], + 'byRef' => true, + 'stmts' => $statements, + ]); + + $constructMethod = new Stmt\ClassMethod('__construct', [ + 'flags' => Stmt\Class_::MODIFIER_PUBLIC, + 'stmts' => $constructStatements, + ]); + + $classStmts = [$constructMethod, $mapMethod]; + + if (\count($injectMapperStatements) > 0) { + $classStmts[] = new Stmt\ClassMethod('injectMappers', [ + 'flags' => Stmt\Class_::MODIFIER_PUBLIC, + 'params' => [ + new Param(new Expr\Variable('autoMapperRegistry'), null, new Name\FullyQualified(AutoMapperRegistryInterface::class)), + ], + 'returnType' => 'void', + 'stmts' => $injectMapperStatements, + ]); + } + + return new Stmt\Class_(new Name($mapperGeneratorMetadata->getMapperClassName()), [ + 'flags' => Stmt\Class_::MODIFIER_FINAL, + 'extends' => new Name\FullyQualified(GeneratedMapper::class), + 'stmts' => $classStmts, + ]); + } + + private function getCreateObjectStatements(MapperGeneratorMetadataInterface $mapperMetadata, Expr\Variable $result, Expr\Variable $contextVariable, Expr\Variable $sourceInput, UniqueVariableScope $uniqueVariableScope): array + { + if ('array' === $mapperMetadata->getTarget()) { + return [[new Stmt\Expression(new Expr\Assign($result, new Expr\Array_()))], [], [], []]; + } + + if (\stdClass::class === $mapperMetadata->getTarget()) { + return [[new Stmt\Expression(new Expr\Assign($result, new Expr\New_(new Name(\stdClass::class))))], [], [], []]; + } + + $reflectionClass = new \ReflectionClass($mapperMetadata->getTarget()); + $targetConstructor = $reflectionClass->getConstructor(); + $createObjectStatements = []; + $inConstructor = []; + $constructStatements = []; + $injectMapperStatements = []; + /** @var ClassDiscriminatorMapping $classDiscriminatorMapping */ + $classDiscriminatorMapping = 'array' !== $mapperMetadata->getTarget() && null !== $this->classDiscriminator ? $this->classDiscriminator->getMappingForClass($mapperMetadata->getTarget()) : null; + + if (null !== $classDiscriminatorMapping && null !== ($propertyMapping = $mapperMetadata->getPropertyMapping($classDiscriminatorMapping->getTypeProperty()))) { + [$output, $createObjectStatements] = $propertyMapping->getTransformer()->transform($propertyMapping->getReadAccessor()->getExpression($sourceInput), $uniqueVariableScope, $propertyMapping); + + foreach ($classDiscriminatorMapping->getTypesMapping() as $typeValue => $typeTarget) { + $mapperName = 'Discriminator_Mapper_'.$mapperMetadata->getSource().'_'.$typeTarget; + + $injectMapperStatements[] = new Stmt\Expression(new Expr\Assign( + new Expr\ArrayDimFetch(new Expr\PropertyFetch(new Expr\Variable('this'), 'mappers'), new Scalar\String_($mapperName)), + new Expr\MethodCall(new Expr\Variable('autoMapperRegistry'), 'getMapper', [ + new Arg(new Scalar\String_($mapperMetadata->getSource())), + new Arg(new Scalar\String_($typeTarget)), + ]) + )); + $createObjectStatements[] = new Stmt\If_(new Expr\BinaryOp\Identical( + new Scalar\String_($typeValue), + $output + ), [ + 'stmts' => [ + new Stmt\Return_(new Expr\MethodCall(new Expr\ArrayDimFetch( + new Expr\PropertyFetch(new Expr\Variable('this'), 'mappers'), + new Scalar\String_($mapperName) + ), 'map', [ + new Arg($sourceInput), + new Expr\Variable('context'), + ])), + ], + ]); + } + } + + $propertiesMapping = $mapperMetadata->getPropertiesMapping(); + + if (null !== $targetConstructor && $mapperMetadata->hasConstructor()) { + $constructArguments = []; + + /** @var PropertyMapping $propertyMapping */ + foreach ($propertiesMapping as $propertyMapping) { + if (null === $propertyMapping->getWriteMutator()->getParameter()) { + continue; + } + + $constructVar = new Expr\Variable($uniqueVariableScope->getUniqueName('constructArg')); + + [$output, $propStatements] = $propertyMapping->getTransformer()->transform($propertyMapping->getReadAccessor()->getExpression($sourceInput), $propertyMapping, $uniqueVariableScope); + $constructArguments[$propertyMapping->getWriteMutator()->getParameter()->getPosition()] = new Arg($constructVar); + + $propStatements[] = new Stmt\Expression(new Expr\Assign($constructVar, $output)); + $createObjectStatements[] = new Stmt\If_(new Expr\MethodCall($contextVariable, 'hasConstructorArgument', [ + new Arg(new Scalar\String_($mapperMetadata->getTarget())), + new Arg(new Scalar\String_($propertyMapping->getProperty())), + ]), [ + 'stmts' => [ + new Stmt\Expression(new Expr\Assign($constructVar, new Expr\MethodCall($contextVariable, 'getConstructorArgument', [ + new Arg(new Scalar\String_($mapperMetadata->getTarget())), + new Arg(new Scalar\String_($propertyMapping->getProperty())), + ]))), + ], + 'else' => new Stmt\Else_($propStatements), + ]); + + $inConstructor[] = $propertyMapping->getProperty(); + } + + foreach ($targetConstructor->getParameters() as $constructorParameter) { + if (!\array_key_exists($constructorParameter->getPosition(), $constructArguments) && $constructorParameter->isDefaultValueAvailable()) { + $constructVar = new Expr\Variable($uniqueVariableScope->getUniqueName('constructArg')); + + $createObjectStatements[] = new Stmt\If_(new Expr\MethodCall($contextVariable, 'hasConstructorArgument', [ + new Arg(new Scalar\String_($mapperMetadata->getTarget())), + new Arg(new Scalar\String_($constructorParameter->getName())), + ]), [ + 'stmts' => [ + new Stmt\Expression(new Expr\Assign($constructVar, new Expr\MethodCall($contextVariable, 'getConstructorArgument', [ + new Arg(new Scalar\String_($mapperMetadata->getTarget())), + new Arg(new Scalar\String_($constructorParameter->getName())), + ]))), + ], + 'else' => new Stmt\Else_([ + new Stmt\Expression(new Expr\Assign($constructVar, $this->getValueAsExpr($constructorParameter->getDefaultValue()))), + ]), + ]); + + $constructArguments[$constructorParameter->getPosition()] = new Arg($constructVar); + } + } + + $createObjectStatements[] = new Stmt\Expression(new Expr\Assign($result, new Expr\New_(new Name\FullyQualified($mapperMetadata->getTarget()), $constructArguments))); + } elseif (null !== $targetConstructor && $mapperMetadata->isTargetCloneable()) { + $constructStatements[] = new Stmt\Expression(new Expr\Assign( + new Expr\PropertyFetch(new Expr\Variable('this'), 'cachedTarget'), + new Expr\MethodCall(new Expr\New_(new Name\FullyQualified(\ReflectionClass::class), [ + new Arg(new Scalar\String_($mapperMetadata->getTarget())), + ]), 'newInstanceWithoutConstructor') + )); + $createObjectStatements[] = new Stmt\Expression(new Expr\Assign($result, new Expr\Clone_(new Expr\PropertyFetch(new Expr\Variable('this'), 'cachedTarget')))); + } elseif (null !== $targetConstructor) { + $constructStatements[] = new Stmt\Expression(new Expr\Assign( + new Expr\PropertyFetch(new Expr\Variable('this'), 'cachedTarget'), + new Expr\New_(new Name\FullyQualified(\ReflectionClass::class), [ + new Arg(new Scalar\String_($mapperMetadata->getTarget())), + ]) + )); + $createObjectStatements[] = new Stmt\Expression(new Expr\Assign($result, new Expr\MethodCall( + new Expr\PropertyFetch(new Expr\Variable('this'), 'cachedTarget'), + 'newInstanceWithoutConstructor' + ))); + } else { + $createObjectStatements[] = new Stmt\Expression(new Expr\Assign($result, new Expr\New_(new Name\FullyQualified($mapperMetadata->getTarget())))); + } + + return [$createObjectStatements, $inConstructor, $constructStatements, $injectMapperStatements]; + } + + private function getValueAsExpr($value) + { + $expr = $this->parser->parse('expr; + } + + return $expr; + } +} diff --git a/src/Symfony/Component/AutoMapper/Generator/UniqueVariableScope.php b/src/Symfony/Component/AutoMapper/Generator/UniqueVariableScope.php new file mode 100644 index 0000000000000..f174e57c33497 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Generator/UniqueVariableScope.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Generator; + +/** + * Allow to get a unique variable name for a scope (like a method). + * + * @internal + * + * @author Joel Wurtz + */ +final class UniqueVariableScope +{ + private $registry = []; + + /** + * Return an unique name for a variable name. + */ + public function getUniqueName(string $name): string + { + if (!isset($this->registry[$name])) { + $this->registry[$name] = 0; + + return $name; + } + + ++$this->registry[$name]; + + return sprintf('%s_%s', $name, $this->registry[$name]); + } +} diff --git a/src/Symfony/Component/AutoMapper/LICENSE b/src/Symfony/Component/AutoMapper/LICENSE new file mode 100644 index 0000000000000..a677f43763ca4 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Symfony/Component/AutoMapper/Loader/ClassLoaderInterface.php b/src/Symfony/Component/AutoMapper/Loader/ClassLoaderInterface.php new file mode 100644 index 0000000000000..2d142cdd47c0f --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Loader/ClassLoaderInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Loader; + +use Symfony\Component\AutoMapper\MapperGeneratorMetadataInterface; + +/** + * Load (require) a mapping given metadata. + */ +interface ClassLoaderInterface +{ + /** + * @param MapperGeneratorMetadataInterface $mapperMetadata + */ + public function loadClass(MapperGeneratorMetadataInterface $mapperMetadata): void; +} diff --git a/src/Symfony/Component/AutoMapper/Loader/EvalLoader.php b/src/Symfony/Component/AutoMapper/Loader/EvalLoader.php new file mode 100644 index 0000000000000..51a5c67a5d21b --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Loader/EvalLoader.php @@ -0,0 +1,39 @@ +generator = $generator; + $this->printer = new Standard(); + } + + /** + * {@inheritdoc} + */ + public function loadClass(MapperGeneratorMetadataInterface $mapperConfiguration): void + { + $class = $this->generator->compile($mapperConfiguration); + + eval($this->printer->prettyPrint([$class])); + } +} diff --git a/src/Symfony/Component/AutoMapper/Loader/FileLoader.php b/src/Symfony/Component/AutoMapper/Loader/FileLoader.php new file mode 100644 index 0000000000000..27f0ff6d9ec66 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Loader/FileLoader.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Loader; + +use PhpParser\PrettyPrinter\Standard; +use Symfony\Component\AutoMapper\Generator\Generator; +use Symfony\Component\AutoMapper\MapperGeneratorMetadataInterface; + +class FileLoader implements ClassLoaderInterface +{ + private $generator; + + private $directory; + + private $hotReload; + + private $printer; + + private $registry; + + public function __construct(Generator $generator, string $directory, bool $hotReload = true) + { + $this->generator = $generator; + $this->directory = $directory; + $this->hotReload = $hotReload; + $this->printer = new Standard(); + } + + /** + * {@inheritdoc} + */ + public function loadClass(MapperGeneratorMetadataInterface $mapperGeneratorMetadata): void + { + $className = $mapperGeneratorMetadata->getMapperClassName(); + $classPath = $this->directory.\DIRECTORY_SEPARATOR.$className.'.php'; + + if (!$this->hotReload) { + require $classPath; + } + + $hash = $mapperGeneratorMetadata->getHash(); + $registry = $this->getRegistry(); + + if (!isset($registry[$className]) || $registry[$className] !== $hash || !file_exists($classPath)) { + $this->saveMapper($mapperGeneratorMetadata); + } + + require $classPath; + } + + public function saveMapper(MapperGeneratorMetadataInterface $mapperGeneratorMetadata): void + { + $className = $mapperGeneratorMetadata->getMapperClassName(); + $classPath = $this->directory.\DIRECTORY_SEPARATOR.$className.'.php'; + $hash = $mapperGeneratorMetadata->getHash(); + $classCode = $this->printer->prettyPrint([$this->generator->generate($mapperGeneratorMetadata)]); + + file_put_contents($classPath, "addHashToRegistry($className, $hash); + } + + private function addHashToRegistry($className, $hash) + { + $registryPath = $this->directory.\DIRECTORY_SEPARATOR.'registry.php'; + $this->registry[$className] = $hash; + file_put_contents($registryPath, "registry, true).";\n"); + } + + private function getRegistry() + { + if (!file_exists($this->directory)) { + mkdir($this->directory); + } + + if (!$this->registry) { + $registryPath = $this->directory.\DIRECTORY_SEPARATOR.'registry.php'; + + if (!file_exists($registryPath)) { + $this->registry = []; + } else { + $this->registry = require $registryPath; + } + } + + return $this->registry; + } +} diff --git a/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactory.php b/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactory.php new file mode 100644 index 0000000000000..e07b6a4b811f6 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactory.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper; + +use Symfony\Component\AutoMapper\Extractor\FromSourceMappingExtractor; +use Symfony\Component\AutoMapper\Extractor\FromTargetMappingExtractor; +use Symfony\Component\AutoMapper\Extractor\SourceTargetMappingExtractor; + +/** + * Metadata factory, used to autoregistering new mapping without creating them. + * + * @author Joel Wurtz + */ +final class MapperGeneratorMetadataFactory +{ + private $sourceTargetPropertiesMappingExtractor; + private $fromSourcePropertiesMappingExtractor; + private $fromTargetPropertiesMappingExtractor; + private $classPrefix; + + public function __construct( + SourceTargetMappingExtractor $sourceTargetPropertiesMappingExtractor, + FromSourceMappingExtractor $fromSourcePropertiesMappingExtractor, + FromTargetMappingExtractor $fromTargetPropertiesMappingExtractor, + string $classPrefix = 'Mapper_' + ) { + $this->sourceTargetPropertiesMappingExtractor = $sourceTargetPropertiesMappingExtractor; + $this->fromSourcePropertiesMappingExtractor = $fromSourcePropertiesMappingExtractor; + $this->fromTargetPropertiesMappingExtractor = $fromTargetPropertiesMappingExtractor; + $this->classPrefix = $classPrefix; + } + + /** + * Create metadata for a source and target. + */ + public function create(MapperGeneratorMetadataRegistryInterface $autoMapperRegister, string $source, string $target): MapperGeneratorMetadataInterface + { + $extractor = $this->sourceTargetPropertiesMappingExtractor; + + if ('array' === $source || 'stdClass' === $source) { + $extractor = $this->fromTargetPropertiesMappingExtractor; + } + + if ('array' === $target || 'stdClass' === $target) { + $extractor = $this->fromSourcePropertiesMappingExtractor; + } + + return new MapperMetadata($autoMapperRegister, $extractor, $source, $target, $this->classPrefix); + } +} diff --git a/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataInterface.php b/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataInterface.php new file mode 100644 index 0000000000000..ddfb70e78c31f --- /dev/null +++ b/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataInterface.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper; + +/** + * Stores metadata needed when generating a mapper. + * + * @internal + * + * @author Joel Wurtz + */ +interface MapperGeneratorMetadataInterface extends MapperMetadataInterface +{ + /** + * Get mapper class name. + */ + public function getMapperClassName(): string; + + /** + * Get hash (unique key) for those metadatas. + */ + public function getHash(): string; + + /** + * Get a list of callbacks to add for this mapper. + * + * @return callable[] + */ + public function getCallbacks(): array; + + /** + * Whether the target class has a constructor. + */ + public function hasConstructor(): bool; + + /** + * Whether we can use target constructor. + */ + public function isConstructorAllowed(): bool; + + /** + * If not using target constructor, allow to know if we can clone a empty target. + */ + public function isTargetCloneable(): bool; + + /** + * Whether the mapping can have circular reference. + * + * If not the case, allow to not generate code about circular references + */ + public function canHaveCircularReference(): bool; +} diff --git a/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataRegistryInterface.php b/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataRegistryInterface.php new file mode 100644 index 0000000000000..957fdb475f340 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataRegistryInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper; + +/** + * Registry of metadata. + * + * @internal + * + * @author Joel Wurtz + */ +interface MapperGeneratorMetadataRegistryInterface +{ + /** + * Register metadata. + */ + public function register(MapperGeneratorMetadataInterface $configuration): void; + + /** + * Get metadata for a source and a target. + */ + public function getMetadata(string $source, string $target): ?MapperGeneratorMetadataInterface; +} diff --git a/src/Symfony/Component/AutoMapper/MapperInterface.php b/src/Symfony/Component/AutoMapper/MapperInterface.php new file mode 100644 index 0000000000000..4c66b5d3dc48f --- /dev/null +++ b/src/Symfony/Component/AutoMapper/MapperInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper; + +/** + * Interface implemented by a single mapper. + * + * Each specific mapper should implements this interface + * + * @internal + * + * @author Joel Wurtz + */ +interface MapperInterface +{ + /** + * @param mixed $value Value to map + * @param Context $context Options mapper have access to + * + * @return mixed The mapped value + */ + public function &map($value, Context $context); +} diff --git a/src/Symfony/Component/AutoMapper/MapperMetadata.php b/src/Symfony/Component/AutoMapper/MapperMetadata.php new file mode 100644 index 0000000000000..f2f55398476fe --- /dev/null +++ b/src/Symfony/Component/AutoMapper/MapperMetadata.php @@ -0,0 +1,286 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper; + +use Symfony\Component\AutoMapper\Extractor\MappingExtractorInterface; +use Symfony\Component\AutoMapper\Extractor\PropertyMapping; +use Symfony\Component\AutoMapper\Extractor\ReadAccessor; +use Symfony\Component\AutoMapper\Transformer\CallbackTransformer; +use Symfony\Component\AutoMapper\Transformer\MapperDependency; + +/** + * Mapper metadata. + * + * @author Joel Wurtz + */ +class MapperMetadata implements MapperGeneratorMetadataInterface +{ + private $mappingExtractor; + + private $customMapping = []; + + private $propertiesMapping; + + private $metadataRegistry; + + private $source; + + private $target; + + private $className; + + private $isConstructorAllowed; + + private $dateTimeFormat; + + private $classPrefix; + + public function __construct(MapperGeneratorMetadataRegistryInterface $metadataRegistry, MappingExtractorInterface $mappingExtractor, string $source, string $target, string $classPrefix = 'Mapper_') + { + $this->mappingExtractor = $mappingExtractor; + $this->metadataRegistry = $metadataRegistry; + $this->source = $source; + $this->target = $target; + $this->isConstructorAllowed = true; + $this->dateTimeFormat = \DateTime::RFC3339; + $this->classPrefix = $classPrefix; + } + + /** + * {@inheritdoc} + */ + public function getPropertiesMapping(): array + { + if (null === $this->propertiesMapping) { + $this->buildPropertyMapping(); + } + + return $this->propertiesMapping; + } + + /** + * {@inheritdoc} + */ + public function getPropertyMapping(string $property): ?PropertyMapping + { + if (null === $this->propertiesMapping) { + $this->buildPropertyMapping(); + } + + return $this->propertiesMapping[$property] ?? null; + } + + /** + * {@inheritdoc} + */ + public function hasConstructor(): bool + { + if (!$this->isConstructorAllowed()) { + return false; + } + + if (\in_array($this->target, ['array', \stdClass::class], true)) { + return false; + } + + $reflection = new \ReflectionClass($this->getTarget()); + $constructor = $reflection->getConstructor(); + + if (null === $constructor) { + return false; + } + + $parameters = $constructor->getParameters(); + $mandatoryParameters = []; + + foreach ($parameters as $parameter) { + if (!$parameter->isOptional()) { + $mandatoryParameters[] = $parameter; + } + } + + if (0 === \count($mandatoryParameters)) { + return true; + } + + foreach ($mandatoryParameters as $mandatoryParameter) { + $readAccessor = $this->mappingExtractor->getReadAccessor($this->source, $this->target, $mandatoryParameter->getName()); + + if (null === $readAccessor) { + return false; + } + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function isTargetCloneable(): bool + { + $reflection = new \ReflectionClass($this->getTarget()); + + return $reflection->isCloneable() && !$reflection->hasMethod('__clone'); + } + + /** + * {@inheritdoc} + */ + public function canHaveCircularReference(): bool + { + $checked = []; + + return $this->checkCircularMapperConfiguration($this, $checked); + } + + /** + * {@inheritdoc} + */ + public function getMapperClassName(): string + { + if (null !== $this->className) { + return $this->className; + } + + return $this->className = sprintf('%s%s_%s', $this->classPrefix, str_replace('\\', '_', $this->source), str_replace('\\', '_', $this->target)); + } + + /** + * {@inheritdoc} + */ + public function getHash(): string + { + $hash = ''; + + if (!\in_array($this->source, ['array', \stdClass::class], true)) { + $reflection = new \ReflectionClass($this->source); + $hash .= filemtime($reflection->getFileName()); + } + + if (!\in_array($this->target, ['array', \stdClass::class], true)) { + $reflection = new \ReflectionClass($this->target); + $hash .= filemtime($reflection->getFileName()); + } + + return $hash; + } + + /** + * {@inheritdoc} + */ + public function isConstructorAllowed(): bool + { + return $this->isConstructorAllowed; + } + + /** + * {@inheritdoc} + */ + public function getSource(): string + { + return $this->source; + } + + /** + * {@inheritdoc} + */ + public function getTarget(): string + { + return $this->target; + } + + /** + * {@inheritdoc} + */ + public function getDateTimeFormat(): string + { + return $this->dateTimeFormat; + } + + /** + * {@inheritdoc} + */ + public function getCallbacks(): array + { + return $this->customMapping; + } + + /** + * Set DateTime format to use when generating a mapper. + */ + public function setDateTimeFormat(string $dateTimeFormat): void + { + $this->dateTimeFormat = $dateTimeFormat; + } + + /** + * Whether or not the constructor should be used. + */ + public function setConstructorAllowed(bool $isConstructorAllowed): void + { + $this->isConstructorAllowed = $isConstructorAllowed; + } + + /** + * Set a callable to use when mapping a specific property. + */ + public function forMember(string $property, callable $callback): void + { + $this->customMapping[$property] = $callback; + } + + private function buildPropertyMapping() + { + $this->propertiesMapping = []; + + foreach ($this->mappingExtractor->getPropertiesMapping($this) as $propertyMapping) { + $this->propertiesMapping[$propertyMapping->getProperty()] = $propertyMapping; + } + + foreach ($this->customMapping as $property => $callback) { + $this->propertiesMapping[$property] = new PropertyMapping( + new ReadAccessor(ReadAccessor::TYPE_SOURCE, $property), + $this->mappingExtractor->getWriteMutator($this->source, $this->target, $property), + new CallbackTransformer($property), + $property, + false + ); + } + } + + private function checkCircularMapperConfiguration(MapperGeneratorMetadataInterface $configuration, &$checked) + { + foreach ($configuration->getPropertiesMapping() as $propertyMapping) { + /** @var MapperDependency $dependency */ + 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->metadataRegistry->getMetadata($dependency->getSource(), $dependency->getTarget()); + + if (null !== $subConfiguration && true === $this->checkCircularMapperConfiguration($subConfiguration, $checked)) { + return true; + } + } + } + + return false; + } +} diff --git a/src/Symfony/Component/AutoMapper/MapperMetadataInterface.php b/src/Symfony/Component/AutoMapper/MapperMetadataInterface.php new file mode 100644 index 0000000000000..19702c71e9e61 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/MapperMetadataInterface.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper; + +use Symfony\Component\AutoMapper\Extractor\PropertyMapping; + +/** + * Stores metadata needed for mapping data. + * + * @internal + * + * @author Joel Wurtz + */ +interface MapperMetadataInterface +{ + /** + * Get the source type mapped. + */ + public function getSource(): string; + + /** + * Get the target type mapped. + */ + public function getTarget(): string; + + /** + * Get properties to map between source and target. + * + * @return PropertyMapping[] + */ + public function getPropertiesMapping(): array; + + /** + * Get property to map by name, or null if not mapped. + */ + public function getPropertyMapping(string $property): ?PropertyMapping; + + /** + * Get date time format to use when mapping date time to string. + */ + public function getDateTimeFormat(): string; +} diff --git a/src/Symfony/Component/AutoMapper/README.md b/src/Symfony/Component/AutoMapper/README.md new file mode 100644 index 0000000000000..dd85a41fb6cbc --- /dev/null +++ b/src/Symfony/Component/AutoMapper/README.md @@ -0,0 +1,13 @@ +AutoMapper Component +==================== + +The AutoMapper component maps data between different domains. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/auto_mapper/introduction.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Component/AutoMapper/Tests/AutoMapperTest.php b/src/Symfony/Component/AutoMapper/Tests/AutoMapperTest.php new file mode 100644 index 0000000000000..c0eed42b3d4a0 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/AutoMapperTest.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests; + +use Doctrine\Common\Annotations\AnnotationReader; +use PhpParser\ParserFactory; +use Symfony\Bundle\TwigBundle\Tests\TestCase; +use Symfony\Component\AutoMapper\AutoMapper; +use Symfony\Component\AutoMapper\Generator\Generator; +use Symfony\Component\AutoMapper\Loader\FileLoader; +use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; +use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; + +/** + * @author Joel Wurtz + */ +class AutoMapperTest extends TestCase +{ + /** @var AutoMapper */ + private $autoMapper; + + public function setUp() + { + @unlink(__DIR__.'/cache/registry.php'); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + + $loader = new FileLoader(new Generator( + (new ParserFactory())->create(ParserFactory::PREFER_PHP7), + new ClassDiscriminatorFromClassMetadata($classMetadataFactory) + ), __DIR__.'/cache'); + + $this->autoMapper = AutoMapper::create(true, $loader); + } + + public function testAutoMapping() + { + $userMetadata = $this->autoMapper->getMetadata(Fixtures\User::class, Fixtures\UserDTO::class); + $userMetadata->forMember('yearOfBirth', function (Fixtures\User $user) { + return ((int) date('Y')) - ((int) $user->age); + }); + + $address = new Fixtures\Address(); + $address->setCity('Toulon'); + $user = new Fixtures\User(1, 'yolo', '13'); + $user->address = $address; + $user->addresses[] = $address; + + /** @var Fixtures\UserDTO $userDto */ + $userDto = $this->autoMapper->map($user, Fixtures\UserDTO::class); + + self::assertInstanceOf(Fixtures\UserDTO::class, $userDto); + self::assertSame(1, $userDto->id); + self::assertSame('yolo', $userDto->name); + self::assertSame(13, $userDto->age); + self::assertSame(((int) date('Y')) - 13, $userDto->yearOfBirth); + self::assertCount(1, $userDto->addresses); + self::assertInstanceOf(Fixtures\AddressDTO::class, $userDto->address); + self::assertInstanceOf(Fixtures\AddressDTO::class, $userDto->addresses[0]); + self::assertSame('Toulon', $userDto->address->city); + self::assertSame('Toulon', $userDto->addresses[0]->city); + } +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Fixtures/Address.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/Address.php new file mode 100644 index 0000000000000..32eac16125d7c --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/Address.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Fixtures; + +class Address +{ + /** + * @var string|null + */ + private $city; + + /** + * @return string + */ + public function getCity(): ?string + { + return $this->city; + } + + /** + * @param string $city + */ + public function setCity(?string $city): void + { + $this->city = $city; + } +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Fixtures/AddressDTO.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/AddressDTO.php new file mode 100644 index 0000000000000..a8f91820ffe21 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/AddressDTO.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Fixtures; + +class AddressDTO +{ + /** + * @var string|null + */ + public $city; +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Fixtures/User.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/User.php new file mode 100644 index 0000000000000..f6406c8db3166 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/User.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Fixtures; + +class User +{ + /** + * @var int + */ + private $id; + /** + * @var string + */ + public $name; + /** + * @var string|int + */ + public $age; + /** + * @var string + */ + private $email; + /** + * @var Address + */ + public $address; + /** + * @var Address[] + */ + public $addresses = []; + /** + * @var \DateTimeInterface + */ + public $createdAt; + + public function __construct($id, $name, $age) + { + $this->id = $id; + $this->name = $name; + $this->age = $age; + $this->email = 'test'; + $this->createdAt = new \DateTime(); + } + + /** + * @return int + */ + public function getId() + { + return $this->id; + } +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Fixtures/UserDTO.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/UserDTO.php new file mode 100644 index 0000000000000..85ca8db99cba1 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/UserDTO.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Fixtures; + +class UserDTO +{ + /** + * @var int + */ + public $id; + /** + * @var string + */ + public $name; + /** + * @var int + */ + public $age; + /** + * @var int + */ + public $yearOfBirth; + /** + * @var string + */ + public $email; + /** + * @var AddressDTO|null + */ + public $address; + /** + * @var AddressDTO[] + */ + public $addresses = []; + /** + * @var \DateTime|null + */ + public $createdAt; +} diff --git a/src/Symfony/Component/AutoMapper/Tests/cache/.gitignore b/src/Symfony/Component/AutoMapper/Tests/cache/.gitignore new file mode 100644 index 0000000000000..72e8ffc0db8aa --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/cache/.gitignore @@ -0,0 +1 @@ +* diff --git a/src/Symfony/Component/AutoMapper/Transformer/AbstractUniqueTypeTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/AbstractUniqueTypeTransformerFactory.php new file mode 100644 index 0000000000000..9ed1826bd74e2 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Transformer/AbstractUniqueTypeTransformerFactory.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Transformer; + +use Symfony\Component\AutoMapper\MapperMetadataInterface; +use Symfony\Component\PropertyInfo\Type; + +/** + * Abstract transformer which is used by transformer needing transforming only from one single type to one single type. + * + * @author Joel Wurtz + */ +abstract class AbstractUniqueTypeTransformerFactory implements TransformerFactoryInterface +{ + /** + * {@inheritdoc} + */ + public function getTransformer(?array $sourcesTypes, ?array $targetTypes, MapperMetadataInterface $mapperMetadata): ?TransformerInterface + { + $nbSourcesTypes = $sourcesTypes ? \count($sourcesTypes) : 0; + $nbTargetsTypes = $targetTypes ? \count($targetTypes) : 0; + + if (0 === $nbSourcesTypes || $nbSourcesTypes > 1 || !$sourcesTypes[0] instanceof Type) { + return null; + } + + if (0 === $nbTargetsTypes || $nbTargetsTypes > 1 || !$targetTypes[0] instanceof Type) { + return null; + } + + return $this->createTransformer($sourcesTypes[0], $targetTypes[0], $mapperMetadata); + } + + abstract protected function createTransformer(Type $sourceType, Type $targetType, MapperMetadataInterface $mapperMetadata): ?TransformerInterface; +} diff --git a/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformer.php new file mode 100644 index 0000000000000..93aedd3b8f89f --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformer.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Transformer; + +use PhpParser\Node\Expr; +use PhpParser\Node\Stmt; +use Symfony\Component\AutoMapper\Extractor\PropertyMapping; +use Symfony\Component\AutoMapper\Generator\UniqueVariableScope; + +/** + * Transformer array decorator. + * + * @author Joel Wurtz + */ +class ArrayTransformer implements TransformerInterface +{ + private $itemTransformer; + + public function __construct(TransformerInterface $itemTransformer) + { + $this->itemTransformer = $itemTransformer; + } + + /** + * {@inheritdoc} + */ + public function transform(Expr $input, PropertyMapping $propertyMapping, UniqueVariableScope $uniqueVariableScope): array + { + $valuesVar = new Expr\Variable($uniqueVariableScope->getUniqueName('values')); + $statements = [ + // $values = []; + new Stmt\Expression(new Expr\Assign($valuesVar, new Expr\Array_())), + ]; + + $loopValueVar = new Expr\Variable($uniqueVariableScope->getUniqueName('value')); + + [$output, $itemStatements] = $this->itemTransformer->transform($loopValueVar, $propertyMapping, $uniqueVariableScope); + + if ($this->itemTransformer->assignByRef()) { + $itemStatements[] = new Stmt\Expression(new Expr\AssignRef(new Expr\ArrayDimFetch($valuesVar), $output)); + } else { + $itemStatements[] = new Stmt\Expression(new Expr\Assign(new Expr\ArrayDimFetch($valuesVar), $output)); + } + + $statements[] = new Stmt\Foreach_($input, $loopValueVar, [ + 'stmts' => $itemStatements, + ]); + + return [$valuesVar, $statements]; + } + + /** + * {@inheritdoc} + */ + public function assignByRef(): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + public function getDependencies(): array + { + return $this->itemTransformer->getDependencies(); + } +} diff --git a/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformerFactory.php new file mode 100644 index 0000000000000..de27057d82976 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformerFactory.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Transformer; + +use Symfony\Component\AutoMapper\MapperMetadataInterface; +use Symfony\Component\PropertyInfo\Type; + +/** + * Create a decorated transformer to handle array type. + * + * @author Joel Wurtz + */ +class ArrayTransformerFactory extends AbstractUniqueTypeTransformerFactory +{ + private $chainTransformerFactory; + + public function __construct(ChainTransformerFactory $chainTransformerFactory) + { + $this->chainTransformerFactory = $chainTransformerFactory; + } + + /** + * {@inheritdoc} + */ + protected function createTransformer(Type $sourceType, Type $targetType, MapperMetadataInterface $mapperMetadata): ?TransformerInterface + { + if (!$sourceType->isCollection()) { + return null; + } + + if (!$targetType->isCollection()) { + return null; + } + + $subItemTransformer = $this->chainTransformerFactory->getTransformer([$sourceType->getCollectionValueType()], [$targetType->getCollectionValueType()], $mapperMetadata); + + if (null !== $subItemTransformer) { + return new ArrayTransformer($subItemTransformer); + } + + return null; + } +} diff --git a/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformer.php new file mode 100644 index 0000000000000..2abb58a64bece --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformer.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Transformer; + +use PhpParser\Node\Arg; +use PhpParser\Node\Expr; +use PhpParser\Node\Expr\Cast; +use PhpParser\Node\Name; +use Symfony\Component\AutoMapper\Extractor\PropertyMapping; +use Symfony\Component\AutoMapper\Generator\UniqueVariableScope; +use Symfony\Component\PropertyInfo\Type; + +/** + * Built in transformer to handle PHP scalar types. + * + * @author Joel Wurtz + */ +class BuiltinTransformer implements TransformerInterface +{ + private const CAST_MAPPING = [ + Type::BUILTIN_TYPE_BOOL => [ + Type::BUILTIN_TYPE_INT => Cast\Int_::class, + Type::BUILTIN_TYPE_STRING => Cast\String_::class, + Type::BUILTIN_TYPE_FLOAT => Cast\Double::class, + Type::BUILTIN_TYPE_ARRAY => 'toArray', + Type::BUILTIN_TYPE_ITERABLE => 'toArray', + ], + Type::BUILTIN_TYPE_FLOAT => [ + Type::BUILTIN_TYPE_STRING => Cast\String_::class, + Type::BUILTIN_TYPE_INT => Cast\Int_::class, + Type::BUILTIN_TYPE_BOOL => Cast\Bool_::class, + Type::BUILTIN_TYPE_ARRAY => 'toArray', + Type::BUILTIN_TYPE_ITERABLE => 'toArray', + ], + Type::BUILTIN_TYPE_INT => [ + Type::BUILTIN_TYPE_FLOAT => Cast\Double::class, + Type::BUILTIN_TYPE_STRING => Cast\String_::class, + Type::BUILTIN_TYPE_BOOL => Cast\Bool_::class, + Type::BUILTIN_TYPE_ARRAY => 'toArray', + Type::BUILTIN_TYPE_ITERABLE => 'toArray', + ], + Type::BUILTIN_TYPE_ITERABLE => [ + Type::BUILTIN_TYPE_ARRAY => 'fromIteratorToArray', + ], + Type::BUILTIN_TYPE_ARRAY => [ + Type::BUILTIN_TYPE_ITERABLE => null, + ], + Type::BUILTIN_TYPE_STRING => [ + Type::BUILTIN_TYPE_ARRAY => 'toArray', + Type::BUILTIN_TYPE_ITERABLE => 'toArray', + Type::BUILTIN_TYPE_FLOAT => Cast\Double::class, + Type::BUILTIN_TYPE_INT => Cast\Int_::class, + Type::BUILTIN_TYPE_BOOL => Cast\Bool_::class, + ], + Type::BUILTIN_TYPE_CALLABLE => [], + Type::BUILTIN_TYPE_RESOURCE => [], + ]; + + /** @var Type */ + private $sourceType; + + /** @var Type[] */ + private $targetTypes; + + public function __construct(Type $sourceType, array $targetTypes) + { + $this->sourceType = $sourceType; + $this->targetTypes = $targetTypes; + } + + /** + * {@inheritdoc} + */ + public function transform(Expr $input, PropertyMapping $propertyMapping, UniqueVariableScope $uniqueVariableScope): array + { + $targetTypes = array_map(function (Type $type) { + return $type->getBuiltinType(); + }, $this->targetTypes); + + // Source type is in target => no cast + if (\in_array($this->sourceType->getBuiltinType(), $targetTypes)) { + return [$input, []]; + } + + // Cast needed + foreach (self::CAST_MAPPING[$this->sourceType->getBuiltinType()] as $castType => $castMethod) { + if (\in_array($castType, $targetTypes)) { + if (method_exists($this, $castMethod)) { + return [$this->$castMethod($input), []]; + } + + return [new $castMethod($input), []]; + } + } + + return [$input, []]; + } + + /** + * {@inheritdoc} + */ + public function getDependencies(): array + { + return []; + } + + /** + * {@inheritdoc} + */ + public function assignByRef(): bool + { + return false; + } + + private function toArray(Expr $input) + { + return new Expr\Array_([new Expr\ArrayItem($input)]); + } + + private function fromIteratorToArray(Expr $input) + { + return new Expr\FuncCall(new Name('iterator_to_array'), [ + new Arg($input), + ]); + } +} diff --git a/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformerFactory.php new file mode 100644 index 0000000000000..27e7177a22134 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformerFactory.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Transformer; + +use Symfony\Component\AutoMapper\MapperMetadataInterface; +use Symfony\Component\PropertyInfo\Type; + +/** + * @author Joel Wurtz + */ +class BuiltinTransformerFactory implements TransformerFactoryInterface +{ + private const BUILTIN = [ + Type::BUILTIN_TYPE_BOOL, + Type::BUILTIN_TYPE_CALLABLE, + Type::BUILTIN_TYPE_FLOAT, + Type::BUILTIN_TYPE_INT, + Type::BUILTIN_TYPE_ITERABLE, + Type::BUILTIN_TYPE_NULL, + Type::BUILTIN_TYPE_RESOURCE, + Type::BUILTIN_TYPE_STRING, + ]; + + public function getTransformer(?array $sourcesTypes, ?array $targetTypes, MapperMetadataInterface $mapperMetadata): ?TransformerInterface + { + $nbSourcesTypes = $sourcesTypes ? \count($sourcesTypes) : 0; + + if (null === $sourcesTypes || 0 === $nbSourcesTypes || $nbSourcesTypes > 1 || !$sourcesTypes[0] instanceof Type) { + return null; + } + + /** @var Type $propertyType */ + $propertyType = $sourcesTypes[0]; + + if (\in_array($propertyType->getBuiltinType(), self::BUILTIN, true)) { + return new BuiltinTransformer($propertyType, $targetTypes); + } + + return null; + } +} diff --git a/src/Symfony/Component/AutoMapper/Transformer/CallbackTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/CallbackTransformer.php new file mode 100644 index 0000000000000..ac3149e6702ad --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Transformer/CallbackTransformer.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Transformer; + +use PhpParser\Node\Arg; +use PhpParser\Node\Expr; +use PhpParser\Node\Scalar; +use Symfony\Component\AutoMapper\Extractor\PropertyMapping; +use Symfony\Component\AutoMapper\Generator\UniqueVariableScope; + +/** + * Handle custom callback transformation. + * + * @author Joel Wurtz + */ +class CallbackTransformer implements TransformerInterface +{ + private $callbackName; + + public function __construct(string $callbackName) + { + $this->callbackName = $callbackName; + } + + /** + * {@inheritdoc} + */ + public function transform(Expr $input, PropertyMapping $propertyMapping, UniqueVariableScope $uniqueVariableScope): array + { + /* + * $output = $this->callbacks[$callbackName]($input); + */ + return [new Expr\FuncCall( + new Expr\ArrayDimFetch(new Expr\PropertyFetch(new Expr\Variable('this'), 'callbacks'), new Scalar\String_($this->callbackName)), [ + new Arg($input), + ]), + [], + ]; + } + + /** + * {@inheritdoc} + */ + public function getDependencies(): array + { + return []; + } + + /** + * {@inheritdoc} + */ + public function assignByRef(): bool + { + return false; + } +} diff --git a/src/Symfony/Component/AutoMapper/Transformer/ChainTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/ChainTransformerFactory.php new file mode 100644 index 0000000000000..f228f8b3b6663 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Transformer/ChainTransformerFactory.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Transformer; + +use Symfony\Component\AutoMapper\MapperMetadataInterface; + +/** + * @author Joel Wurtz + */ +class ChainTransformerFactory implements TransformerFactoryInterface +{ + /** @var TransformerFactoryInterface[] */ + private $factories = []; + + public function addTransformerFactory(TransformerFactoryInterface $transformerFactory) + { + $this->factories[] = $transformerFactory; + } + + /** + * {@inheritdoc} + */ + public function getTransformer(?array $sourcesTypes, ?array $targetTypes, MapperMetadataInterface $mapperMetadata): ?TransformerInterface + { + foreach ($this->factories as $factory) { + $transformer = $factory->getTransformer($sourcesTypes, $targetTypes, $mapperMetadata); + + if (null !== $transformer) { + return $transformer; + } + } + + return null; + } +} diff --git a/src/Symfony/Component/AutoMapper/Transformer/CopyTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/CopyTransformer.php new file mode 100644 index 0000000000000..c4ddbe17aaa83 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Transformer/CopyTransformer.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Transformer; + +use PhpParser\Node\Expr; +use Symfony\Component\AutoMapper\Extractor\PropertyMapping; +use Symfony\Component\AutoMapper\Generator\UniqueVariableScope; + +/** + * Does not do any transformation, output = input. + * + * @author Joel Wurtz + */ +class CopyTransformer implements TransformerInterface +{ + /** + * {@inheritdoc} + */ + public function transform(Expr $input, PropertyMapping $propertyMapping, UniqueVariableScope $uniqueVariableScope): array + { + return [$input, []]; + } + + /** + * {@inheritdoc} + */ + public function getDependencies(): array + { + return []; + } + + /** + * {@inheritdoc} + */ + public function assignByRef(): bool + { + return false; + } +} diff --git a/src/Symfony/Component/AutoMapper/Transformer/DateTimeToStringTansformer.php b/src/Symfony/Component/AutoMapper/Transformer/DateTimeToStringTansformer.php new file mode 100644 index 0000000000000..d250a6a86694f --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Transformer/DateTimeToStringTansformer.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Transformer; + +use PhpParser\Node\Arg; +use PhpParser\Node\Expr; +use PhpParser\Node\Scalar\String_; +use Symfony\Component\AutoMapper\Extractor\PropertyMapping; +use Symfony\Component\AutoMapper\Generator\UniqueVariableScope; + +/** + * Transform a \DateTimeInterface object to a string. + * + * @author Joel Wurtz + */ +class DateTimeToStringTansformer implements TransformerInterface +{ + private $format; + + public function __construct(string $format = \DateTimeInterface::RFC3339) + { + $this->format = $format; + } + + /** + * {@inheritdoc} + */ + public function transform(Expr $input, PropertyMapping $propertyMapping, UniqueVariableScope $uniqueVariableScope): array + { + return [new Expr\MethodCall($input, 'format', [ + new Arg(new String_($this->format)), + ]), []]; + } + + /** + * {@inheritdoc} + */ + public function getDependencies(): array + { + return []; + } + + /** + * {@inheritdoc} + */ + public function assignByRef(): bool + { + return false; + } +} diff --git a/src/Symfony/Component/AutoMapper/Transformer/DateTimeTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/DateTimeTransformerFactory.php new file mode 100644 index 0000000000000..e373820acd127 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Transformer/DateTimeTransformerFactory.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Transformer; + +use Symfony\Component\AutoMapper\MapperMetadataInterface; +use Symfony\Component\PropertyInfo\Type; + +/** + * @author Joel Wurtz + */ +class DateTimeTransformerFactory extends AbstractUniqueTypeTransformerFactory +{ + /** + * {@inheritdoc} + */ + protected function createTransformer(Type $sourceType, Type $targetType, MapperMetadataInterface $mapperMetadata): ?TransformerInterface + { + $isSourceDate = $this->isDateTimeType($sourceType); + $isTargetDate = $this->isDateTimeType($targetType); + + if ($isSourceDate && $isTargetDate) { + return $this->createTransformerForSourceAndTarget($sourceType, $targetType); + } + + if ($isSourceDate) { + return $this->createTransformerForSource($targetType, $mapperMetadata); + } + + if ($isTargetDate) { + return $this->createTransformerForTarget($sourceType, $targetType, $mapperMetadata); + } + + return null; + } + + protected function createTransformerForSourceAndTarget(Type $sourceType, Type $targetType): ?TransformerInterface + { + $isSourceMutable = $this->isDateTimeMutable($sourceType); + $isTargetMutable = $this->isDateTimeMutable($targetType); + + if ($isSourceMutable === $isTargetMutable) { + return new CopyTransformer(); + } + + // Source is mutable but target is not, transform into a datetime immutable type @TODO + if ($isSourceMutable) { + return new CopyTransformer(); + } + + // Target is mutable but source is not, transform into a datetime mutable type @TODO + return new CopyTransformer(); + } + + protected function createTransformerForSource(Type $targetType, MapperMetadataInterface $mapperMetadata): ?TransformerInterface + { + if (Type::BUILTIN_TYPE_STRING === $targetType->getBuiltinType()) { + return new DateTimeToStringTansformer($mapperMetadata->getDateTimeFormat()); + } + + return null; + } + + protected function createTransformerForTarget(Type $sourceType, Type $targetType, MapperMetadataInterface $mapperMetadata): ?TransformerInterface + { + if (Type::BUILTIN_TYPE_STRING === $sourceType->getBuiltinType()) { + return new StringToDateTimeTransformer($this->getClassName($targetType), $mapperMetadata->getDateTimeFormat()); + } + + return null; + } + + private function isDateTimeType(Type $type): bool + { + if (Type::BUILTIN_TYPE_OBJECT !== $type->getBuiltinType()) { + return false; + } + + if (\DateTimeInterface::class !== $type->getClassName() && !is_subclass_of($type->getClassName(), \DateTimeInterface::class)) { + return false; + } + + return true; + } + + private function getClassName(Type $type): ?string + { + if (\DateTimeInterface::class !== $type->getClassName()) { + return \DateTimeImmutable::class; + } + + return $type->getClassName(); + } + + private function isDateTimeMutable(Type $type): bool + { + if (Type::BUILTIN_TYPE_OBJECT !== $type->getBuiltinType()) { + return false; + } + + if (\DateTime::class !== $type->getClassName() && !is_subclass_of($type->getClassName(), \DateTime::class)) { + return false; + } + + return true; + } +} diff --git a/src/Symfony/Component/AutoMapper/Transformer/MapperDependency.php b/src/Symfony/Component/AutoMapper/Transformer/MapperDependency.php new file mode 100644 index 0000000000000..cb13dda2c1cf0 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Transformer/MapperDependency.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Transformer; + +/** + * Represent a dependency on a mapper (allow to inject sub mappers). + * + * @internal + * + * @author Joel Wurtz + */ +final class MapperDependency +{ + private $name; + + private $source; + + private $target; + + public function __construct(string $name, string $source, string $target) + { + $this->name = $name; + $this->source = $source; + $this->target = $target; + } + + public function getName(): string + { + return $this->name; + } + + public function getSource(): string + { + return $this->source; + } + + public function getTarget(): string + { + return $this->target; + } +} diff --git a/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformer.php new file mode 100644 index 0000000000000..70fd5d66f3891 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformer.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Transformer; + +use PhpParser\Node\Arg; +use PhpParser\Node\Expr; +use PhpParser\Node\Name; +use PhpParser\Node\Stmt; +use Symfony\Component\AutoMapper\Extractor\PropertyMapping; +use Symfony\Component\AutoMapper\Generator\UniqueVariableScope; +use Symfony\Component\PropertyInfo\Type; + +/** + * Multiple transformer decorator. + * + * Decorate transformers with condition to handle property with multiples source types + * It will always use the first target type possible for transformation + * + * @author Joel Wurtz + */ +class MultipleTransformer implements TransformerInterface +{ + private const CONDITION_MAPPING = [ + Type::BUILTIN_TYPE_BOOL => 'is_bool', + Type::BUILTIN_TYPE_INT => 'is_int', + Type::BUILTIN_TYPE_FLOAT => 'is_float', + Type::BUILTIN_TYPE_STRING => 'is_string', + Type::BUILTIN_TYPE_NULL => 'is_null', + Type::BUILTIN_TYPE_ARRAY => 'is_array', + Type::BUILTIN_TYPE_OBJECT => 'is_object', + Type::BUILTIN_TYPE_RESOURCE => 'is_resource', + Type::BUILTIN_TYPE_CALLABLE => 'is_callable', + Type::BUILTIN_TYPE_ITERABLE => 'is_iterable', + ]; + + private $transformers = []; + + public function addTransformer(TransformerInterface $transformer, Type $sourceType) + { + $this->transformers[] = [ + 'transformer' => $transformer, + 'type' => $sourceType, + ]; + } + + /** + * {@inheritdoc} + */ + public function transform(Expr $input, PropertyMapping $propertyMapping, UniqueVariableScope $uniqueVariableScope): array + { + $output = new Expr\Variable($uniqueVariableScope->getUniqueName('value')); + $statements = [ + new Stmt\Expression(new Expr\Assign($output, $input)), + ]; + + foreach ($this->transformers as $transformerData) { + $transformer = $transformerData['transformer']; + $type = $transformerData['type']; + + [$transformerOutput, $transformerStatements] = $transformer->transform($input, $propertyMapping, $uniqueVariableScope); + + $assignClass = $transformer->assignByRef() ? Expr\AssignRef::class : Expr\Assign::class; + $statements[] = new Stmt\If_( + new Expr\FuncCall( + new Name(self::CONDITION_MAPPING[$type->getBuiltinType()]), + [ + new Arg($input), + ] + ), + [ + 'stmts' => array_merge( + $transformerStatements, [ + new Stmt\Expression(new $assignClass($output, $transformerOutput)), + ] + ), + ] + ); + } + + return [$output, $statements]; + } + + /** + * {@inheritdoc} + */ + public function assignByRef(): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + public function getDependencies(): array + { + $dependencies = []; + + foreach ($this->transformers as $transformerData) { + $dependencies = array_merge($dependencies, $transformerData['transformer']->getDependencies()); + } + + return $dependencies; + } +} diff --git a/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformerFactory.php new file mode 100644 index 0000000000000..e5fac9d268091 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformerFactory.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Transformer; + +use Symfony\Component\AutoMapper\MapperMetadataInterface; + +/** + * @author Joel Wurtz + */ +class MultipleTransformerFactory implements TransformerFactoryInterface +{ + private $chainTransformerFactory; + + public function __construct(ChainTransformerFactory $chainTransformerFactory) + { + $this->chainTransformerFactory = $chainTransformerFactory; + } + + /** + * {@inheritdoc} + */ + public function getTransformer(?array $sourcesTypes, ?array $targetTypes, MapperMetadataInterface $mapperMetadata): ?TransformerInterface + { + if (null === $sourcesTypes || 0 === \count($sourcesTypes)) { + return null; + } + + if (\count($sourcesTypes) > 1) { + $transformer = new MultipleTransformer(); + + foreach ($sourcesTypes as $sourceType) { + $transformer->addTransformer($this->chainTransformerFactory->getTransformer([$sourceType], $targetTypes, $mapperMetadata), $sourceType); + } + + return $transformer; + } + + return null; + } +} diff --git a/src/Symfony/Component/AutoMapper/Transformer/NullableTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/NullableTransformer.php new file mode 100644 index 0000000000000..601c9db0c96d5 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Transformer/NullableTransformer.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Transformer; + +use PhpParser\Node\Expr; +use PhpParser\Node\Name; +use PhpParser\Node\Stmt; +use Symfony\Component\AutoMapper\Extractor\PropertyMapping; +use Symfony\Component\AutoMapper\Generator\UniqueVariableScope; + +/** + * Tansformer decorator to handle null values. + * + * @author Joel Wurtz + */ +class NullableTransformer implements TransformerInterface +{ + private $itemTransformer; + private $isTargetNullable; + + public function __construct(TransformerInterface $itemTransformer, bool $isTargetNullable) + { + $this->itemTransformer = $itemTransformer; + $this->isTargetNullable = $isTargetNullable; + } + + /** + * {@inheritdoc} + */ + public function transform(Expr $input, PropertyMapping $propertyMapping, UniqueVariableScope $uniqueVariableScope): array + { + [$output, $itemStatements] = $this->itemTransformer->transform($input, $propertyMapping, $uniqueVariableScope); + + $newOutput = null; + $statements = []; + $assignClass = $this->itemTransformer->assignByRef() ? Expr\AssignRef::class : Expr\Assign::class; + + if ($this->isTargetNullable) { + $newOutput = new Expr\Variable($uniqueVariableScope->getUniqueName('value')); + $statements[] = new Stmt\Expression(new Expr\Assign($newOutput, new Expr\ConstFetch(new Name('null')))); + $itemStatements[] = new Stmt\Expression(new $assignClass($newOutput, $output)); + } + + $statements[] = new Stmt\If_(new Expr\BinaryOp\NotIdentical(new Expr\ConstFetch(new Name('null')), $input), [ + 'stmts' => $itemStatements, + ]); + + return [$newOutput ?? $output, $statements]; + } + + /** + * {@inheritdoc} + */ + public function getDependencies(): array + { + return $this->itemTransformer->getDependencies(); + } + + /** + * {@inheritdoc} + */ + public function assignByRef(): bool + { + return false; + } +} diff --git a/src/Symfony/Component/AutoMapper/Transformer/NullableTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/NullableTransformerFactory.php new file mode 100644 index 0000000000000..d9306c2e173d5 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Transformer/NullableTransformerFactory.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Transformer; + +use Symfony\Component\AutoMapper\MapperMetadataInterface; +use Symfony\Component\PropertyInfo\Type; + +/** + * @author Joel Wurtz + */ +class NullableTransformerFactory implements TransformerFactoryInterface +{ + private $chainTransformerFactory; + + public function __construct(ChainTransformerFactory $chainTransformerFactory) + { + $this->chainTransformerFactory = $chainTransformerFactory; + } + + /** + * {@inheritdoc} + */ + public function getTransformer(?array $sourcesTypes, ?array $targetTypes, MapperMetadataInterface $mapperMetadata): ?TransformerInterface + { + $nbSourcesTypes = $sourcesTypes ? \count($sourcesTypes) : 0; + + if (null === $sourcesTypes || 0 === $nbSourcesTypes || $nbSourcesTypes > 1) { + return null; + } + + /** @var Type $propertyType */ + $propertyType = $sourcesTypes[0]; + + if (!$propertyType->isNullable()) { + return null; + } + + $isTargetNullable = false; + + foreach ($targetTypes as $targetType) { + if ($targetType->isNullable()) { + $isTargetNullable = true; + + break; + } + } + + $subTransformer = $this->chainTransformerFactory->getTransformer([new Type( + $propertyType->getBuiltinType(), + false, + $propertyType->getClassName(), + $propertyType->isCollection(), + $propertyType->getCollectionKeyType(), + $propertyType->getCollectionValueType() + )], $targetTypes, $mapperMetadata); + + if (null === $subTransformer) { + return null; + } + + // Remove nullable property here to avoid infinite loop + return new NullableTransformer($subTransformer, $isTargetNullable); + } +} diff --git a/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformer.php new file mode 100644 index 0000000000000..389c24774a1a4 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformer.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Transformer; + +use PhpParser\Node\Arg; +use PhpParser\Node\Expr; +use PhpParser\Node\Scalar; +use Symfony\Component\AutoMapper\Extractor\PropertyMapping; +use Symfony\Component\AutoMapper\Generator\UniqueVariableScope; +use Symfony\Component\PropertyInfo\Type; + +/** + * Transform to an object which can be mapped by AutoMapper (sub mapping). + * + * @author Joel Wurtz + */ +class ObjectTransformer implements TransformerInterface +{ + private $sourceType; + + private $targetType; + + public function __construct(Type $sourceType, Type $targetType) + { + $this->sourceType = $sourceType; + $this->targetType = $targetType; + } + + /** + * {@inheritdoc} + */ + public function transform(Expr $input, PropertyMapping $propertyMapping, UniqueVariableScope $uniqueVariableScope): array + { + $mapperName = $this->getDependencyName(); + + return [new Expr\MethodCall(new Expr\ArrayDimFetch( + new Expr\PropertyFetch(new Expr\Variable('this'), 'mappers'), + new Scalar\String_($mapperName) + ), 'map', [ + new Arg($input), + new Arg(new Expr\MethodCall(new Expr\Variable('context'), 'withNewContext', [ + new Arg(new Scalar\String_($propertyMapping->getProperty())), + ])), + ]), []]; + } + + /** + * {@inheritdoc} + */ + public function assignByRef(): bool + { + return true; + } + + /** + * {@inheritdoc} + */ + public function getDependencies(): array + { + return [new MapperDependency($this->getDependencyName(), $this->getSource(), $this->getTarget())]; + } + + private function getDependencyName(): string + { + return 'Mapper_'.$this->getSource().'_'.$this->getTarget(); + } + + private function getSource(): string + { + $sourceTypeName = 'array'; + + if (Type::BUILTIN_TYPE_OBJECT === $this->sourceType->getBuiltinType()) { + $sourceTypeName = $this->sourceType->getClassName(); + } + + return $sourceTypeName; + } + + private function getTarget(): string + { + $targetTypeName = 'array'; + + if (Type::BUILTIN_TYPE_OBJECT === $this->targetType->getBuiltinType()) { + $targetTypeName = $this->targetType->getClassName(); + } + + return $targetTypeName; + } +} diff --git a/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformerFactory.php new file mode 100644 index 0000000000000..66572da2ccdd8 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformerFactory.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Transformer; + +use Symfony\Component\AutoMapper\AutoMapperRegistryInterface; +use Symfony\Component\AutoMapper\MapperMetadataInterface; +use Symfony\Component\PropertyInfo\Type; + +/** + * @author Joel Wurtz + */ +class ObjectTransformerFactory extends AbstractUniqueTypeTransformerFactory +{ + private $autoMapper; + + public function __construct(AutoMapperRegistryInterface $autoMapper) + { + $this->autoMapper = $autoMapper; + } + + /** + * {@inheritdoc} + */ + protected function createTransformer(Type $sourceType, Type $targetType, MapperMetadataInterface $mapperMetadata): ?TransformerInterface + { + // Only deal with source type being an object or an array that is not a collection + if (!$this->isObjectType($sourceType) || !$this->isObjectType($targetType)) { + return null; + } + + $sourceTypeName = 'array'; + $targetTypeName = 'array'; + + if (Type::BUILTIN_TYPE_OBJECT === $sourceType->getBuiltinType()) { + $sourceTypeName = $sourceType->getClassName(); + } + + if (Type::BUILTIN_TYPE_OBJECT === $targetType->getBuiltinType()) { + $targetTypeName = $targetType->getClassName(); + } + + if ($this->autoMapper->hasMapper($sourceTypeName, $targetTypeName)) { + return new ObjectTransformer($sourceType, $targetType); + } + + return null; + } + + private function isObjectType(Type $type): bool + { + return + Type::BUILTIN_TYPE_OBJECT === $type->getBuiltinType() + || + (Type::BUILTIN_TYPE_ARRAY === $type->getBuiltinType() && !$type->isCollection()) + ; + } +} diff --git a/src/Symfony/Component/AutoMapper/Transformer/StringToDateTimeTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/StringToDateTimeTransformer.php new file mode 100644 index 0000000000000..e49c8b724339c --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Transformer/StringToDateTimeTransformer.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Transformer; + +use PhpParser\Node\Arg; +use PhpParser\Node\Expr; +use PhpParser\Node\Name; +use PhpParser\Node\Scalar\String_; +use Symfony\Component\AutoMapper\Extractor\PropertyMapping; +use Symfony\Component\AutoMapper\Generator\UniqueVariableScope; + +/** + * Transform a string to a \DateTimeInterface object. + * + * @author Joel Wurtz + */ +class StringToDateTimeTransformer implements TransformerInterface +{ + private $className; + + private $format; + + public function __construct(string $className, string $format = \DateTimeInterface::RFC3339) + { + $this->className = $className; + $this->format = $format; + } + + /** + * {@inheritdoc} + */ + public function transform(Expr $input, PropertyMapping $propertyMapping, UniqueVariableScope $uniqueVariableScope): array + { + return [new Expr\StaticCall(new Name\FullyQualified($this->className), 'createFromFormat', [ + new Arg(new String_($this->format)), + new Arg($input), + ]), []]; + } + + /** + * {@inheritdoc} + */ + public function assignByRef(): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + public function getDependencies(): array + { + return []; + } +} diff --git a/src/Symfony/Component/AutoMapper/Transformer/TransformerFactoryInterface.php b/src/Symfony/Component/AutoMapper/Transformer/TransformerFactoryInterface.php new file mode 100644 index 0000000000000..4e7d2eed920d3 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Transformer/TransformerFactoryInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Transformer; + +use Symfony\Component\AutoMapper\MapperMetadataInterface; +use Symfony\Component\PropertyInfo\Type; + +/** + * Create transformer. + * + * @internal + * + * @author Joel Wurtz + */ +interface TransformerFactoryInterface +{ + /** + * Get transformer to use when mapping from an array of type to another array of type. + * + * @param Type[] $sourcesTypes + * @param Type[] $targetTypes + */ + public function getTransformer(?array $sourcesTypes, ?array $targetTypes, MapperMetadataInterface $mapperMetadata): ?TransformerInterface; +} diff --git a/src/Symfony/Component/AutoMapper/Transformer/TransformerInterface.php b/src/Symfony/Component/AutoMapper/Transformer/TransformerInterface.php new file mode 100644 index 0000000000000..b688fcf1f8acf --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Transformer/TransformerInterface.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Transformer; + +use PhpParser\Node\Expr; +use PhpParser\Node\Stmt; +use Symfony\Component\AutoMapper\Extractor\PropertyMapping; +use Symfony\Component\AutoMapper\Generator\UniqueVariableScope; + +/** + * Transformer tell how to transform a property mapping. + * + * @internal + * + * @author Joel Wurtz + */ +interface TransformerInterface +{ + /** + * Get AST output and expressions for transforming a property mapping given an input. + * + * @return [Expr, Stmt[]] First value is the output expression, second value is an array of stmt needed to get the output + */ + public function transform(Expr $input, PropertyMapping $propertyMapping, UniqueVariableScope $uniqueVariableScope): array; + + /** + * Get dependencies for this transformer. + * + * @return MapperDependency[] + */ + public function getDependencies(): array; + + /** + * Should the resulting output be assigned by ref. + * + * @return bool + */ + public function assignByRef(): bool; +} diff --git a/src/Symfony/Component/AutoMapper/Transformer/UniqueTypeTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/UniqueTypeTransformerFactory.php new file mode 100644 index 0000000000000..95b00455357e3 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Transformer/UniqueTypeTransformerFactory.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Transformer; + +use Symfony\Component\AutoMapper\MapperMetadataInterface; + +/** + * Reduce array of type to only one type on source and target. + * + * @author Joel Wurtz + */ +class UniqueTypeTransformerFactory implements TransformerFactoryInterface +{ + private $chainTransformerFactory; + + public function __construct(ChainTransformerFactory $chainTransformerFactory) + { + $this->chainTransformerFactory = $chainTransformerFactory; + } + + /** + * {@inheritdoc} + */ + public function getTransformer(?array $sourcesTypes, ?array $targetTypes, MapperMetadataInterface $mapperMetadata): ?TransformerInterface + { + $nbSourcesTypes = $sourcesTypes ? \count($sourcesTypes) : 0; + $nbTargetsTypes = $targetTypes ? \count($targetTypes) : 0; + + if (null === $sourcesTypes || 0 === $nbSourcesTypes || $nbSourcesTypes > 1) { + return null; + } + + if (null === $targetTypes || $nbTargetsTypes <= 1) { + return null; + } + + foreach ($targetTypes as $targetType) { + $transformer = $this->chainTransformerFactory->getTransformer($sourcesTypes, [$targetType], $mapperMetadata); + + if (null !== $transformer) { + return $transformer; + } + } + + return null; + } +} diff --git a/src/Symfony/Component/AutoMapper/composer.json b/src/Symfony/Component/AutoMapper/composer.json new file mode 100644 index 0000000000000..cebc0d5d07c45 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/composer.json @@ -0,0 +1,44 @@ +{ + "name": "symfony/auto-mapper", + "type": "library", + "description": "Symfony AutoMapper Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Joel Wurtz", + "email": "jwurtz@jolicode.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": "^7.1.3", + "nikic/php-parser": "^4.0", + "symfony/property-info": "~3.4|~4.0" + }, + "require-dev": { + "symfony/serializer": "^4.2" + }, + "suggest": { + "symfony/serializer": "Allow to bridge mappers to normalizer and denormalizer" + }, + "conflict": { + "symfony/serializer": "<4.2" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\AutoMapper\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + } +} diff --git a/src/Symfony/Component/AutoMapper/phpunit.xml.dist b/src/Symfony/Component/AutoMapper/phpunit.xml.dist new file mode 100644 index 0000000000000..17341305b1f04 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + From 936bc2f51410eb52f4d7dae17e826b1ffb2d3f0e Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Thu, 14 Feb 2019 17:34:19 +0100 Subject: [PATCH 02/38] Add normalizer bridge --- .../AutoMapper/AutoMapperNormalizer.php | 93 +++++++++++++++++++ src/Symfony/Component/AutoMapper/Context.php | 4 + .../Exception/CircularReferenceException.php | 7 ++ 3 files changed, 104 insertions(+) create mode 100644 src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php create mode 100644 src/Symfony/Component/AutoMapper/Exception/CircularReferenceException.php diff --git a/src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php b/src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php new file mode 100644 index 0000000000000..2b1f2808a861b --- /dev/null +++ b/src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper; + +use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; + +/** + * Bridge for symfony/serializer. + * + * @author Joel Wurtz + */ +class AutoMapperNormalizer implements NormalizerInterface, DenormalizerInterface +{ + private $autoMapper; + + public function __construct(AutoMapper $autoMapper) + { + $this->autoMapper = $autoMapper; + } + + public function normalize($object, $format = null, array $context = []) + { + $autoMapperContext = $this->createAutoMapperContext($context); + + return $this->autoMapper->map($object, 'array', $autoMapperContext); + } + + public function denormalize($data, $class, $format = null, array $context = []) + { + $autoMapperContext = $this->createAutoMapperContext($context); + + return $this->autoMapper->map($data, $class, $autoMapperContext); + } + + public function supportsNormalization($data, $format = null) + { + if (!\is_object($data)) { + return false; + } + + return $this->autoMapper->hasMapper(\get_class($data), 'array'); + } + + public function supportsDenormalization($data, $type, $format = null) + { + return $this->autoMapper->hasMapper('array', $type); + } + + public function hasCacheableSupportsMethod(): bool + { + return true; + } + + private function createAutoMapperContext(array $serializerContext = []): Context + { + $circularReferenceLimit = 1; + + if (isset($serializerContext[AbstractNormalizer::CIRCULAR_REFERENCE_LIMIT]) && \is_int($serializerContext[AbstractNormalizer::CIRCULAR_REFERENCE_LIMIT])) { + $circularReferenceLimit = $serializerContext[AbstractNormalizer::CIRCULAR_REFERENCE_LIMIT]; + } + + $context = new Context( + $serializerContext[AbstractNormalizer::GROUPS] ?? null, + $serializerContext[AbstractNormalizer::ATTRIBUTES] ?? null, + $serializerContextContext[AbstractNormalizer::IGNORED_ATTRIBUTES] ?? null + ); + + if (isset($serializerContext[AbstractNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS])) { + foreach ($serializerContext[AbstractNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS] as $class => $keyArgs) { + foreach ($keyArgs as $key => $value) { + $context->setConstructorArgument($class, $key, $value); + } + } + } + + $context->setCircularReferenceLimit($circularReferenceLimit); + $context->setObjectToPopulate($serializerContext[AbstractNormalizer::OBJECT_TO_POPULATE] ?? null); + $context->setCircularReferenceHandler($serializerContext[AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER] ?? null); + + return $context; + } +} diff --git a/src/Symfony/Component/AutoMapper/Context.php b/src/Symfony/Component/AutoMapper/Context.php index 6a6bf1454bbf0..7ac8328d6efb1 100644 --- a/src/Symfony/Component/AutoMapper/Context.php +++ b/src/Symfony/Component/AutoMapper/Context.php @@ -11,6 +11,8 @@ namespace Symfony\Component\AutoMapper; +use Symfony\Component\AutoMapper\Exception\CircularReferenceException; + /** * Context for mapping. * @@ -47,6 +49,8 @@ class Context extends \ArrayObject */ public function __construct(array $groups = null, array $attributes = null, array $ignoredAttributes = null) { + parent::__construct(); + $this->groups = $groups; $this->depth = 0; $this->attributes = $attributes; diff --git a/src/Symfony/Component/AutoMapper/Exception/CircularReferenceException.php b/src/Symfony/Component/AutoMapper/Exception/CircularReferenceException.php new file mode 100644 index 0000000000000..afd6e915eece6 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Exception/CircularReferenceException.php @@ -0,0 +1,7 @@ + Date: Thu, 14 Feb 2019 17:53:23 +0100 Subject: [PATCH 03/38] Add missing licence --- .../AutoMapper/Exception/CircularReferenceException.php | 9 +++++++++ .../Component/AutoMapper/Exception/CompileException.php | 9 +++++++++ .../AutoMapper/Exception/InvalidMappingException.php | 9 +++++++++ .../AutoMapper/Exception/NoMappingFoundException.php | 9 +++++++++ .../Extractor/SourceTargetMappingExtractor.php | 9 +++++++++ src/Symfony/Component/AutoMapper/Generator/Generator.php | 9 +++++++++ 6 files changed, 54 insertions(+) diff --git a/src/Symfony/Component/AutoMapper/Exception/CircularReferenceException.php b/src/Symfony/Component/AutoMapper/Exception/CircularReferenceException.php index afd6e915eece6..8298410c87356 100644 --- a/src/Symfony/Component/AutoMapper/Exception/CircularReferenceException.php +++ b/src/Symfony/Component/AutoMapper/Exception/CircularReferenceException.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\AutoMapper\Exception; class CircularReferenceException extends \RuntimeException diff --git a/src/Symfony/Component/AutoMapper/Exception/CompileException.php b/src/Symfony/Component/AutoMapper/Exception/CompileException.php index 070300fb63aad..96c3b97c0869b 100644 --- a/src/Symfony/Component/AutoMapper/Exception/CompileException.php +++ b/src/Symfony/Component/AutoMapper/Exception/CompileException.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\AutoMapper\Exception; class CompileException extends \RuntimeException diff --git a/src/Symfony/Component/AutoMapper/Exception/InvalidMappingException.php b/src/Symfony/Component/AutoMapper/Exception/InvalidMappingException.php index 40006a8a50af6..d64cb2f06386e 100644 --- a/src/Symfony/Component/AutoMapper/Exception/InvalidMappingException.php +++ b/src/Symfony/Component/AutoMapper/Exception/InvalidMappingException.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\AutoMapper\Exception; class InvalidMappingException extends \RuntimeException diff --git a/src/Symfony/Component/AutoMapper/Exception/NoMappingFoundException.php b/src/Symfony/Component/AutoMapper/Exception/NoMappingFoundException.php index 38519d7ae4254..6f67546026e8d 100644 --- a/src/Symfony/Component/AutoMapper/Exception/NoMappingFoundException.php +++ b/src/Symfony/Component/AutoMapper/Exception/NoMappingFoundException.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\AutoMapper\Exception; class NoMappingFoundException extends \RuntimeException diff --git a/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php b/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php index 014408a6d608e..763977ca334c0 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php +++ b/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\AutoMapper\Extractor; use Symfony\Component\AutoMapper\MapperMetadataInterface; diff --git a/src/Symfony/Component/AutoMapper/Generator/Generator.php b/src/Symfony/Component/AutoMapper/Generator/Generator.php index 1cb6cf1724101..07ca7af95dbac 100644 --- a/src/Symfony/Component/AutoMapper/Generator/Generator.php +++ b/src/Symfony/Component/AutoMapper/Generator/Generator.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\AutoMapper\Generator; use PhpParser\Node\Arg; From d272d9ce3c31bef1c956c3c309fd8f831d7f6316 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Thu, 14 Feb 2019 18:16:27 +0100 Subject: [PATCH 04/38] Fix typo in interface --- src/Symfony/Component/AutoMapper/AutoMapperInterface.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/AutoMapper/AutoMapperInterface.php b/src/Symfony/Component/AutoMapper/AutoMapperInterface.php index d5f58c60c5476..7d78f88451b6f 100644 --- a/src/Symfony/Component/AutoMapper/AutoMapperInterface.php +++ b/src/Symfony/Component/AutoMapper/AutoMapperInterface.php @@ -19,7 +19,7 @@ interface AutoMapperInterface { /** - * Map data from to target. + * Map data from a source to a target. * * @param array|object $source Any data object, which may be an object or an array * @param string|array|object $target To which type of data, or data, the source should be mapped From beb0f49cfa6307fcf445a9b5b738cb29bef86289 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Fri, 15 Feb 2019 10:35:19 +0100 Subject: [PATCH 05/38] Add attribute checking option --- .../AutoMapper/Generator/Generator.php | 8 +++++--- .../MapperGeneratorMetadataInterface.php | 5 +++++ .../Component/AutoMapper/MapperMetadata.php | 19 +++++++++++++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/AutoMapper/Generator/Generator.php b/src/Symfony/Component/AutoMapper/Generator/Generator.php index 07ca7af95dbac..1a2258ef75875 100644 --- a/src/Symfony/Component/AutoMapper/Generator/Generator.php +++ b/src/Symfony/Component/AutoMapper/Generator/Generator.php @@ -185,9 +185,11 @@ public function generate(MapperGeneratorMetadataInterface $mapperGeneratorMetada } } - $conditions[] = new Expr\MethodCall($contextVariable, 'isAllowedAttribute', [ - new Arg(new Scalar\String_($propertyMapping->getProperty())), - ]); + if ($mapperGeneratorMetadata->shouldCheckAttributes()) { + $conditions[] = new Expr\MethodCall($contextVariable, 'isAllowedAttribute', [ + new Arg(new Scalar\String_($propertyMapping->getProperty())), + ]); + } if (null !== $propertyMapping->getSourceGroups()) { $conditions[] = new Expr\BinaryOp\BooleanAnd( diff --git a/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataInterface.php b/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataInterface.php index ddfb70e78c31f..12887ffafe5b1 100644 --- a/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataInterface.php +++ b/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataInterface.php @@ -47,6 +47,11 @@ public function hasConstructor(): bool; */ public function isConstructorAllowed(): bool; + /** + * Whether we should generate attributes checking. + */ + public function shouldCheckAttributes(): bool; + /** * If not using target constructor, allow to know if we can clone a empty target. */ diff --git a/src/Symfony/Component/AutoMapper/MapperMetadata.php b/src/Symfony/Component/AutoMapper/MapperMetadata.php index f2f55398476fe..0c78f4db15218 100644 --- a/src/Symfony/Component/AutoMapper/MapperMetadata.php +++ b/src/Symfony/Component/AutoMapper/MapperMetadata.php @@ -44,6 +44,8 @@ class MapperMetadata implements MapperGeneratorMetadataInterface private $classPrefix; + private $attributeChecking; + public function __construct(MapperGeneratorMetadataRegistryInterface $metadataRegistry, MappingExtractorInterface $mappingExtractor, string $source, string $target, string $classPrefix = 'Mapper_') { $this->mappingExtractor = $mappingExtractor; @@ -53,6 +55,7 @@ public function __construct(MapperGeneratorMetadataRegistryInterface $metadataRe $this->isConstructorAllowed = true; $this->dateTimeFormat = \DateTime::RFC3339; $this->classPrefix = $classPrefix; + $this->attributeChecking = true; } /** @@ -215,6 +218,14 @@ public function getCallbacks(): array return $this->customMapping; } + /** + * {@inheritdoc} + */ + public function shouldCheckAttributes(): bool + { + return $this->attributeChecking; + } + /** * Set DateTime format to use when generating a mapper. */ @@ -239,6 +250,14 @@ public function forMember(string $property, callable $callback): void $this->customMapping[$property] = $callback; } + /** + * Whether or not attribute checking code should be generated. + */ + public function setAttributeChecking(bool $attributeChecking): void + { + $this->attributeChecking = $attributeChecking; + } + private function buildPropertyMapping() { $this->propertiesMapping = []; From e4c1385e1f703101cb7cc43021d36e66b514ac5d Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Fri, 15 Feb 2019 11:27:36 +0100 Subject: [PATCH 06/38] Allow attribute checking to be set in factory --- src/Symfony/Component/AutoMapper/AutoMapper.php | 10 +++++++++- .../MapperGeneratorMetadataFactory.php | 16 ++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/AutoMapper/AutoMapper.php b/src/Symfony/Component/AutoMapper/AutoMapper.php index 81ffa3d83905a..909fc8118cc44 100644 --- a/src/Symfony/Component/AutoMapper/AutoMapper.php +++ b/src/Symfony/Component/AutoMapper/AutoMapper.php @@ -180,7 +180,7 @@ public function getMetadata(string $source, string $target): ?MapperGeneratorMet * * @internal */ - public static function create(bool $private = true, ClassLoaderInterface $loader = null, AdvancedNameConverterInterface $nameConverter = null, string $classPrefix = 'Mapper_'): self + public static function create(bool $private = true, ClassLoaderInterface $loader = null, AdvancedNameConverterInterface $nameConverter = null, string $classPrefix = 'Mapper_', bool $attributeChecking = true): self { $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); @@ -230,6 +230,14 @@ public static function create(bool $private = true, ClassLoaderInterface $loader $nameConverter ); + $factory = new MapperGeneratorMetadataFactory( + $sourceTargetMappingExtractor, + $fromSourceMappingExtractor, + $fromTargetMappingExtractor, + $classPrefix + ); + $factory->setAttributeChecking($attributeChecking); + $autoMapper = new self($loader, new MapperGeneratorMetadataFactory( $sourceTargetMappingExtractor, $fromSourceMappingExtractor, diff --git a/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactory.php b/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactory.php index e07b6a4b811f6..339b776885544 100644 --- a/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactory.php +++ b/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactory.php @@ -26,6 +26,7 @@ final class MapperGeneratorMetadataFactory private $fromSourcePropertiesMappingExtractor; private $fromTargetPropertiesMappingExtractor; private $classPrefix; + private $attributeChecking = true; public function __construct( SourceTargetMappingExtractor $sourceTargetPropertiesMappingExtractor, @@ -39,10 +40,18 @@ public function __construct( $this->classPrefix = $classPrefix; } + /** + * Whether or not attribute checking code should be generated. + */ + public function setAttributeChecking(bool $attributeChecking): void + { + $this->attributeChecking = $attributeChecking; + } + /** * Create metadata for a source and target. */ - public function create(MapperGeneratorMetadataRegistryInterface $autoMapperRegister, string $source, string $target): MapperGeneratorMetadataInterface + public function create(MapperGeneratorMetadataRegistryInterface $autoMapperRegister, string $source, string $target): MapperMetadata { $extractor = $this->sourceTargetPropertiesMappingExtractor; @@ -54,6 +63,9 @@ public function create(MapperGeneratorMetadataRegistryInterface $autoMapperRegis $extractor = $this->fromSourcePropertiesMappingExtractor; } - return new MapperMetadata($autoMapperRegister, $extractor, $source, $target, $this->classPrefix); + $mapperMetadata = new MapperMetadata($autoMapperRegister, $extractor, $source, $target, $this->classPrefix); + $mapperMetadata->setAttributeChecking($this->attributeChecking); + + return $mapperMetadata; } } From cf1fdf2240b4a42d25811dad26d86ef14b178172 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Fri, 15 Feb 2019 13:43:07 +0100 Subject: [PATCH 07/38] Add empty test classes --- .../Tests/AutoMapperNormalizerTest.php | 18 ++++++++++++++++++ .../Component/AutoMapper/Tests/ContextTest.php | 18 ++++++++++++++++++ .../FromSourceMappingExtractorTest.php | 18 ++++++++++++++++++ .../FromTargetMappingExtractorTest.php | 18 ++++++++++++++++++ .../PrivateReflectionExtractorTest.php | 18 ++++++++++++++++++ .../Tests/Extractor/ReadAccessorTest.php | 18 ++++++++++++++++++ .../Extractor/ReflectionExtractorTest.php | 18 ++++++++++++++++++ .../SourceTargetMappingExtractorTest.php | 18 ++++++++++++++++++ .../Tests/Extractor/WriteMutatorTest.php | 18 ++++++++++++++++++ .../Generator/UniqueVariableScopeTest.php | 18 ++++++++++++++++++ .../AutoMapper/Tests/Loader/EvalLoaderTest.php | 18 ++++++++++++++++++ .../AutoMapper/Tests/Loader/FileLoaderTest.php | 18 ++++++++++++++++++ .../MapperGeneratorMetadataFactoryTest.php | 18 ++++++++++++++++++ .../ArrayTransformerFactoryTest.php | 18 ++++++++++++++++++ .../Tests/Transformer/ArrayTransformerTest.php | 18 ++++++++++++++++++ .../BuiltinTransformerFactoryTest.php | 18 ++++++++++++++++++ .../Transformer/BuiltinTransformerTest.php | 18 ++++++++++++++++++ .../Transformer/CallbackTransformerTest.php | 18 ++++++++++++++++++ .../ChainTransformerFactoryTest.php | 18 ++++++++++++++++++ .../Tests/Transformer/CopyTransformerTest.php | 18 ++++++++++++++++++ .../DateTimeToStringTransformerTest.php | 18 ++++++++++++++++++ .../DateTimeTransformerFactoryTest.php | 18 ++++++++++++++++++ .../MultipleTransformerFactoryTest.php | 18 ++++++++++++++++++ .../Transformer/MultipleTransformerTest.php | 18 ++++++++++++++++++ .../NullableTransformerFactoryTest.php | 18 ++++++++++++++++++ .../Transformer/NullableTransformerTest.php | 18 ++++++++++++++++++ .../ObjectTransformerFactoryTest.php | 18 ++++++++++++++++++ .../Transformer/ObjectTransformerTest.php | 18 ++++++++++++++++++ .../StringToDateTimeTransformerTest.php | 18 ++++++++++++++++++ .../UniqueTypeTransformerFactoryTest.php | 18 ++++++++++++++++++ 30 files changed, 540 insertions(+) create mode 100644 src/Symfony/Component/AutoMapper/Tests/AutoMapperNormalizerTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/ContextTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Extractor/FromSourceMappingExtractorTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Extractor/FromTargetMappingExtractorTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Extractor/PrivateReflectionExtractorTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Extractor/ReadAccessorTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Extractor/ReflectionExtractorTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Extractor/SourceTargetMappingExtractorTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Extractor/WriteMutatorTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Generator/UniqueVariableScopeTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Loader/EvalLoaderTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Loader/FileLoaderTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/MapperGeneratorMetadataFactoryTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Transformer/ArrayTransformerFactoryTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Transformer/ArrayTransformerTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Transformer/BuiltinTransformerFactoryTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Transformer/BuiltinTransformerTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Transformer/CallbackTransformerTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Transformer/ChainTransformerFactoryTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Transformer/CopyTransformerTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Transformer/DateTimeToStringTransformerTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Transformer/DateTimeTransformerFactoryTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Transformer/MultipleTransformerFactoryTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Transformer/MultipleTransformerTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Transformer/NullableTransformerFactoryTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Transformer/NullableTransformerTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Transformer/ObjectTransformerFactoryTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Transformer/ObjectTransformerTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Transformer/StringToDateTimeTransformerTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Transformer/UniqueTypeTransformerFactoryTest.php diff --git a/src/Symfony/Component/AutoMapper/Tests/AutoMapperNormalizerTest.php b/src/Symfony/Component/AutoMapper/Tests/AutoMapperNormalizerTest.php new file mode 100644 index 0000000000000..27a9935f6895b --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/AutoMapperNormalizerTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests; + +use PHPUnit\Framework\TestCase; + +class AutoMapperNormalizerTest extends TestCase +{ +} diff --git a/src/Symfony/Component/AutoMapper/Tests/ContextTest.php b/src/Symfony/Component/AutoMapper/Tests/ContextTest.php new file mode 100644 index 0000000000000..70e6c2bb938d1 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/ContextTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests; + +use PHPUnit\Framework\TestCase; + +class ContextTest extends TestCase +{ +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Extractor/FromSourceMappingExtractorTest.php b/src/Symfony/Component/AutoMapper/Tests/Extractor/FromSourceMappingExtractorTest.php new file mode 100644 index 0000000000000..d770735965a10 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Extractor/FromSourceMappingExtractorTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Extractor; + +use PHPUnit\Framework\TestCase; + +class FromSourceMappingExtractorTest extends TestCase +{ +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Extractor/FromTargetMappingExtractorTest.php b/src/Symfony/Component/AutoMapper/Tests/Extractor/FromTargetMappingExtractorTest.php new file mode 100644 index 0000000000000..13447ec03adc7 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Extractor/FromTargetMappingExtractorTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Extractor; + +use PHPUnit\Framework\TestCase; + +class FromTargetMappingExtractorTest extends TestCase +{ +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Extractor/PrivateReflectionExtractorTest.php b/src/Symfony/Component/AutoMapper/Tests/Extractor/PrivateReflectionExtractorTest.php new file mode 100644 index 0000000000000..43cf28d37def2 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Extractor/PrivateReflectionExtractorTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Extractor; + +use PHPUnit\Framework\TestCase; + +class PrivateReflectionExtractorTest extends TestCase +{ +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Extractor/ReadAccessorTest.php b/src/Symfony/Component/AutoMapper/Tests/Extractor/ReadAccessorTest.php new file mode 100644 index 0000000000000..445d49cccf15e --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Extractor/ReadAccessorTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Extractor; + +use PHPUnit\Framework\TestCase; + +class ReadAccessorTest extends TestCase +{ +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Extractor/ReflectionExtractorTest.php b/src/Symfony/Component/AutoMapper/Tests/Extractor/ReflectionExtractorTest.php new file mode 100644 index 0000000000000..a9ca4799bc6be --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Extractor/ReflectionExtractorTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Extractor; + +use PHPUnit\Framework\TestCase; + +class ReflectionExtractorTest extends TestCase +{ +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Extractor/SourceTargetMappingExtractorTest.php b/src/Symfony/Component/AutoMapper/Tests/Extractor/SourceTargetMappingExtractorTest.php new file mode 100644 index 0000000000000..ac37a1ed659a0 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Extractor/SourceTargetMappingExtractorTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Extractor; + +use PHPUnit\Framework\TestCase; + +class SourceTargetMappingExtractorTest extends TestCase +{ +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Extractor/WriteMutatorTest.php b/src/Symfony/Component/AutoMapper/Tests/Extractor/WriteMutatorTest.php new file mode 100644 index 0000000000000..d2c1a9170a30d --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Extractor/WriteMutatorTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Extractor; + +use PHPUnit\Framework\TestCase; + +class WriteMutatorTest extends TestCase +{ +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Generator/UniqueVariableScopeTest.php b/src/Symfony/Component/AutoMapper/Tests/Generator/UniqueVariableScopeTest.php new file mode 100644 index 0000000000000..7157d4a5012a7 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Generator/UniqueVariableScopeTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Generator; + +use PHPUnit\Framework\TestCase; + +class UniqueVariableScopeTest extends TestCase +{ +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Loader/EvalLoaderTest.php b/src/Symfony/Component/AutoMapper/Tests/Loader/EvalLoaderTest.php new file mode 100644 index 0000000000000..6217a347359a4 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Loader/EvalLoaderTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Loader; + +use PHPUnit\Framework\TestCase; + +class EvalLoaderTest extends TestCase +{ +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Loader/FileLoaderTest.php b/src/Symfony/Component/AutoMapper/Tests/Loader/FileLoaderTest.php new file mode 100644 index 0000000000000..d2ea5b9a02934 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Loader/FileLoaderTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Loader; + +use PHPUnit\Framework\TestCase; + +class FileLoaderTest extends TestCase +{ +} diff --git a/src/Symfony/Component/AutoMapper/Tests/MapperGeneratorMetadataFactoryTest.php b/src/Symfony/Component/AutoMapper/Tests/MapperGeneratorMetadataFactoryTest.php new file mode 100644 index 0000000000000..e6b9f45c4caac --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/MapperGeneratorMetadataFactoryTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests; + +use PHPUnit\Framework\TestCase; + +class MapperGeneratorMetadataFactoryTest extends TestCase +{ +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/ArrayTransformerFactoryTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/ArrayTransformerFactoryTest.php new file mode 100644 index 0000000000000..6cd4f4859ba5e --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/ArrayTransformerFactoryTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Transformer; + +use PHPUnit\Framework\TestCase; + +class ArrayTransformerFactoryTest extends TestCase +{ +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/ArrayTransformerTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/ArrayTransformerTest.php new file mode 100644 index 0000000000000..27ba919bef990 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/ArrayTransformerTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Transformer; + +use PHPUnit\Framework\TestCase; + +class ArrayTransformerTest extends TestCase +{ +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/BuiltinTransformerFactoryTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/BuiltinTransformerFactoryTest.php new file mode 100644 index 0000000000000..919bdbd1a8ecf --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/BuiltinTransformerFactoryTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Transformer; + +use PHPUnit\Framework\TestCase; + +class BuiltinTransformerFactoryTest extends TestCase +{ +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/BuiltinTransformerTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/BuiltinTransformerTest.php new file mode 100644 index 0000000000000..99c91862bf825 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/BuiltinTransformerTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Transformer; + +use PHPUnit\Framework\TestCase; + +class BuiltinTransformerTest extends TestCase +{ +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/CallbackTransformerTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/CallbackTransformerTest.php new file mode 100644 index 0000000000000..8ac3dbd4d3360 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/CallbackTransformerTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Transformer; + +use PHPUnit\Framework\TestCase; + +class CallbackTransformerTest extends TestCase +{ +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/ChainTransformerFactoryTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/ChainTransformerFactoryTest.php new file mode 100644 index 0000000000000..fa77f2b7b7053 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/ChainTransformerFactoryTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Transformer; + +use PHPUnit\Framework\TestCase; + +class ChainTransformerFactoryTest extends TestCase +{ +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/CopyTransformerTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/CopyTransformerTest.php new file mode 100644 index 0000000000000..b0ce1b5ebcef1 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/CopyTransformerTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Transformer; + +use PHPUnit\Framework\TestCase; + +class CopyTransformerTest extends TestCase +{ +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/DateTimeToStringTransformerTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/DateTimeToStringTransformerTest.php new file mode 100644 index 0000000000000..8a362817fa6db --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/DateTimeToStringTransformerTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Transformer; + +use PHPUnit\Framework\TestCase; + +class DateTimeToStringTransformerTest extends TestCase +{ +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/DateTimeTransformerFactoryTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/DateTimeTransformerFactoryTest.php new file mode 100644 index 0000000000000..b0514b45464a9 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/DateTimeTransformerFactoryTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Transformer; + +use PHPUnit\Framework\TestCase; + +class DateTimeTransformerFactoryTest extends TestCase +{ +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/MultipleTransformerFactoryTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/MultipleTransformerFactoryTest.php new file mode 100644 index 0000000000000..c47906a60f1b2 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/MultipleTransformerFactoryTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Transformer; + +use PHPUnit\Framework\TestCase; + +class MultipleTransformerFactoryTest extends TestCase +{ +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/MultipleTransformerTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/MultipleTransformerTest.php new file mode 100644 index 0000000000000..12629d374755d --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/MultipleTransformerTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Transformer; + +use PHPUnit\Framework\TestCase; + +class MultipleTransformerTest extends TestCase +{ +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/NullableTransformerFactoryTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/NullableTransformerFactoryTest.php new file mode 100644 index 0000000000000..dc8851800173b --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/NullableTransformerFactoryTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Transformer; + +use PHPUnit\Framework\TestCase; + +class NullableTransformerFactoryTest extends TestCase +{ +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/NullableTransformerTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/NullableTransformerTest.php new file mode 100644 index 0000000000000..e4a70b1b5b153 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/NullableTransformerTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Transformer; + +use PHPUnit\Framework\TestCase; + +class NullableTransformerTest extends TestCase +{ +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/ObjectTransformerFactoryTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/ObjectTransformerFactoryTest.php new file mode 100644 index 0000000000000..31938a252e172 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/ObjectTransformerFactoryTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Transformer; + +use PHPUnit\Framework\TestCase; + +class ObjectTransformerFactoryTest extends TestCase +{ +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/ObjectTransformerTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/ObjectTransformerTest.php new file mode 100644 index 0000000000000..016aa9e92f177 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/ObjectTransformerTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Transformer; + +use PHPUnit\Framework\TestCase; + +class ObjectTransformerTest extends TestCase +{ +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/StringToDateTimeTransformerTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/StringToDateTimeTransformerTest.php new file mode 100644 index 0000000000000..9fcb1b328cec3 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/StringToDateTimeTransformerTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Transformer; + +use PHPUnit\Framework\TestCase; + +class StringToDateTimeTransformerTest extends TestCase +{ +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/UniqueTypeTransformerFactoryTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/UniqueTypeTransformerFactoryTest.php new file mode 100644 index 0000000000000..8dc3fb2542a83 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/UniqueTypeTransformerFactoryTest.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Transformer; + +use PHPUnit\Framework\TestCase; + +class UniqueTypeTransformerFactoryTest extends TestCase +{ +} From fc1d87d43cdd1925eb1f679e9bee8859f1fea754 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Wed, 20 Feb 2019 00:40:41 +0100 Subject: [PATCH 08/38] Add automapper tests --- .../Component/AutoMapper/AutoMapper.php | 33 +- src/Symfony/Component/AutoMapper/Context.php | 2 +- .../AutoMapper/Extractor/ReadAccessor.php | 15 + .../SourceTargetMappingExtractor.php | 6 +- .../AutoMapper/Extractor/WriteMutator.php | 15 + .../AutoMapper/Generator/Generator.php | 8 +- .../Generator/UniqueVariableScope.php | 2 + .../AutoMapper/Loader/EvalLoader.php | 4 +- .../AutoMapper/Tests/AutoMapperTest.php | 486 +++++++++++++++++- .../Tests/Extractor/ReadAccessorTest.php | 18 - .../Extractor/ReflectionExtractorTest.php | 101 ++++ .../SourceTargetMappingExtractorTest.php | 18 - .../Bar.php} | 12 +- .../EvalLoaderTest.php => Fixtures/Cat.php} | 6 +- .../FileLoaderTest.php => Fixtures/Dog.php} | 6 +- .../AutoMapper/Tests/Fixtures/Foo.php | 34 ++ .../AutoMapper/Tests/Fixtures/FooMaxDepth.php | 45 ++ .../Node.php} | 11 +- .../AutoMapper/Tests/Fixtures/Pet.php | 26 + .../AutoMapper/Tests/Fixtures/PrivateUser.php | 31 ++ .../Tests/Fixtures/PrivateUserDTO.php | 40 ++ .../ReflectionExtractorTestFixture.php | 51 ++ .../AutoMapper/Tests/Fixtures/User.php | 18 + .../Tests/Fixtures/UserConstructorDTO.php | 60 +++ .../AutoMapper/Tests/Fixtures/UserDTO.php | 29 +- .../UserDTONoAge.php} | 15 +- .../UserDTONoName.php} | 10 +- .../Generator/UniqueVariableScopeTest.php | 13 + .../Transformer/ArrayTransformerFactory.php | 6 +- .../NullableTransformerFactory.php | 4 + .../UniqueTypeTransformerFactory.php | 4 + 31 files changed, 1047 insertions(+), 82 deletions(-) delete mode 100644 src/Symfony/Component/AutoMapper/Tests/Extractor/ReadAccessorTest.php delete mode 100644 src/Symfony/Component/AutoMapper/Tests/Extractor/SourceTargetMappingExtractorTest.php rename src/Symfony/Component/AutoMapper/Tests/{Extractor/PrivateReflectionExtractorTest.php => Fixtures/Bar.php} (53%) rename src/Symfony/Component/AutoMapper/Tests/{Loader/EvalLoaderTest.php => Fixtures/Cat.php} (66%) rename src/Symfony/Component/AutoMapper/Tests/{Loader/FileLoaderTest.php => Fixtures/Dog.php} (66%) create mode 100644 src/Symfony/Component/AutoMapper/Tests/Fixtures/Foo.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Fixtures/FooMaxDepth.php rename src/Symfony/Component/AutoMapper/Tests/{Extractor/FromSourceMappingExtractorTest.php => Fixtures/Node.php} (61%) create mode 100644 src/Symfony/Component/AutoMapper/Tests/Fixtures/Pet.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Fixtures/PrivateUser.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Fixtures/PrivateUserDTO.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Fixtures/ReflectionExtractorTestFixture.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Fixtures/UserConstructorDTO.php rename src/Symfony/Component/AutoMapper/Tests/{Extractor/FromTargetMappingExtractorTest.php => Fixtures/UserDTONoAge.php} (58%) rename src/Symfony/Component/AutoMapper/Tests/{Extractor/WriteMutatorTest.php => Fixtures/UserDTONoName.php} (65%) diff --git a/src/Symfony/Component/AutoMapper/AutoMapper.php b/src/Symfony/Component/AutoMapper/AutoMapper.php index 909fc8118cc44..66acb51c302fc 100644 --- a/src/Symfony/Component/AutoMapper/AutoMapper.php +++ b/src/Symfony/Component/AutoMapper/AutoMapper.php @@ -131,7 +131,7 @@ public function map($sourceData, $targetData, Context $context = null) } if (null === $source) { - throw new NoMappingFoundException('Cannot map this value, its neither an object or an array'); + throw new NoMappingFoundException('Cannot map this value, source is neither an object or an array.'); } if (null === $context) { @@ -153,7 +153,11 @@ public function map($sourceData, $targetData, Context $context = null) } if (null === $target) { - throw new NoMappingFoundException('Cannot map this value, its neither an object or an array'); + throw new NoMappingFoundException('Cannot map this value, target is neither an object or an array.'); + } + + if ('array' === $source && 'array' === $target) { + throw new NoMappingFoundException('Cannot map this value, both source and target are array.'); } return $this->getMapper($source, $target)->map($sourceData, $context); @@ -180,7 +184,14 @@ public function getMetadata(string $source, string $target): ?MapperGeneratorMet * * @internal */ - public static function create(bool $private = true, ClassLoaderInterface $loader = null, AdvancedNameConverterInterface $nameConverter = null, string $classPrefix = 'Mapper_', bool $attributeChecking = true): self + public static function create( + bool $private = true, + ClassLoaderInterface $loader = null, + AdvancedNameConverterInterface $nameConverter = null, + string $classPrefix = 'Mapper_', + bool $attributeChecking = true, + bool $autoRegister = true + ): self { $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); @@ -238,12 +249,16 @@ public static function create(bool $private = true, ClassLoaderInterface $loader ); $factory->setAttributeChecking($attributeChecking); - $autoMapper = new self($loader, new MapperGeneratorMetadataFactory( - $sourceTargetMappingExtractor, - $fromSourceMappingExtractor, - $fromTargetMappingExtractor, - $classPrefix - )); + if ($autoRegister) { + $autoMapper = new self($loader, new MapperGeneratorMetadataFactory( + $sourceTargetMappingExtractor, + $fromSourceMappingExtractor, + $fromTargetMappingExtractor, + $classPrefix + )); + } else { + $autoMapper = new self($loader); + } $transformerFactory->addTransformerFactory(new MultipleTransformerFactory($transformerFactory)); $transformerFactory->addTransformerFactory(new NullableTransformerFactory($transformerFactory)); diff --git a/src/Symfony/Component/AutoMapper/Context.php b/src/Symfony/Component/AutoMapper/Context.php index 7ac8328d6efb1..9e856c38ee8e5 100644 --- a/src/Symfony/Component/AutoMapper/Context.php +++ b/src/Symfony/Component/AutoMapper/Context.php @@ -237,7 +237,7 @@ public function getConstructorArgument(string $class, string $key) */ public function withNewContext(string $attribute): self { - if (null === $this->attributes) { + if (null === $this->attributes && null === $this->ignoredAttributes) { return $this; } diff --git a/src/Symfony/Component/AutoMapper/Extractor/ReadAccessor.php b/src/Symfony/Component/AutoMapper/Extractor/ReadAccessor.php index f8bf79de81399..93ee7a5615acf 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/ReadAccessor.php +++ b/src/Symfony/Component/AutoMapper/Extractor/ReadAccessor.php @@ -44,6 +44,21 @@ public function __construct(int $type, string $name, $private = false) $this->private = $private; } + public function getType(): int + { + return $this->type; + } + + public function getName(): string + { + return $this->name; + } + + public function isPrivate(): bool + { + return $this->private; + } + /** * Get AST expression for reading property from an input. * diff --git a/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php b/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php index 763977ca334c0..d6449d8814ba2 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php +++ b/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php @@ -40,7 +40,9 @@ public function getPropertiesMapping(MapperMetadataInterface $mapperMetadata): a } if (\in_array($property, $targetProperties, true)) { - if (!$this->propertyInfoExtractor->isWritable($mapperMetadata->getTarget(), $property)) { + $targetMutatorConstruct = $this->accessorExtractor->getWriteMutator($mapperMetadata->getTarget(), $property, true); + + if (($targetMutatorConstruct === null || $targetMutatorConstruct->getParameter() === null) && !$this->propertyInfoExtractor->isWritable($mapperMetadata->getTarget(), $property)) { continue; } @@ -69,7 +71,7 @@ public function getPropertiesMapping(MapperMetadataInterface $mapperMetadata): a $mapping[] = new PropertyMapping( $sourceAccessor, - $targetMutator, + $targetMutator ?? $targetMutatorConstruct, $transformer, $property, false, diff --git a/src/Symfony/Component/AutoMapper/Extractor/WriteMutator.php b/src/Symfony/Component/AutoMapper/Extractor/WriteMutator.php index 6c499cf80b947..92dc1cb25c071 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/WriteMutator.php +++ b/src/Symfony/Component/AutoMapper/Extractor/WriteMutator.php @@ -44,6 +44,21 @@ public function __construct(int $type, string $name, bool $private = false, \Ref $this->parameter = $parameter; } + public function getType(): int + { + return $this->type; + } + + public function getName(): string + { + return $this->name; + } + + public function isPrivate(): bool + { + return $this->private; + } + /** * Get AST expression for writing from a value to an output. * diff --git a/src/Symfony/Component/AutoMapper/Generator/Generator.php b/src/Symfony/Component/AutoMapper/Generator/Generator.php index 1a2258ef75875..1e4d127705c43 100644 --- a/src/Symfony/Component/AutoMapper/Generator/Generator.php +++ b/src/Symfony/Component/AutoMapper/Generator/Generator.php @@ -303,7 +303,7 @@ private function getCreateObjectStatements(MapperGeneratorMetadataInterface $map $classDiscriminatorMapping = 'array' !== $mapperMetadata->getTarget() && null !== $this->classDiscriminator ? $this->classDiscriminator->getMappingForClass($mapperMetadata->getTarget()) : null; if (null !== $classDiscriminatorMapping && null !== ($propertyMapping = $mapperMetadata->getPropertyMapping($classDiscriminatorMapping->getTypeProperty()))) { - [$output, $createObjectStatements] = $propertyMapping->getTransformer()->transform($propertyMapping->getReadAccessor()->getExpression($sourceInput), $uniqueVariableScope, $propertyMapping); + [$output, $createObjectStatements] = $propertyMapping->getTransformer()->transform($propertyMapping->getReadAccessor()->getExpression($sourceInput), $propertyMapping, $uniqueVariableScope); foreach ($classDiscriminatorMapping->getTypesMapping() as $typeValue => $typeTarget) { $mapperName = 'Discriminator_Mapper_'.$mapperMetadata->getSource().'_'.$typeTarget; @@ -339,14 +339,14 @@ private function getCreateObjectStatements(MapperGeneratorMetadataInterface $map /** @var PropertyMapping $propertyMapping */ foreach ($propertiesMapping as $propertyMapping) { - if (null === $propertyMapping->getWriteMutator()->getParameter()) { + if (null === ($parameter = $propertyMapping->getWriteMutator()->getParameter())) { continue; } $constructVar = new Expr\Variable($uniqueVariableScope->getUniqueName('constructArg')); [$output, $propStatements] = $propertyMapping->getTransformer()->transform($propertyMapping->getReadAccessor()->getExpression($sourceInput), $propertyMapping, $uniqueVariableScope); - $constructArguments[$propertyMapping->getWriteMutator()->getParameter()->getPosition()] = new Arg($constructVar); + $constructArguments[$parameter->getPosition()] = new Arg($constructVar); $propStatements[] = new Stmt\Expression(new Expr\Assign($constructVar, $output)); $createObjectStatements[] = new Stmt\If_(new Expr\MethodCall($contextVariable, 'hasConstructorArgument', [ @@ -388,6 +388,8 @@ private function getCreateObjectStatements(MapperGeneratorMetadataInterface $map } } + ksort($constructArguments); + $createObjectStatements[] = new Stmt\Expression(new Expr\Assign($result, new Expr\New_(new Name\FullyQualified($mapperMetadata->getTarget()), $constructArguments))); } elseif (null !== $targetConstructor && $mapperMetadata->isTargetCloneable()) { $constructStatements[] = new Stmt\Expression(new Expr\Assign( diff --git a/src/Symfony/Component/AutoMapper/Generator/UniqueVariableScope.php b/src/Symfony/Component/AutoMapper/Generator/UniqueVariableScope.php index f174e57c33497..a72444b8a29a8 100644 --- a/src/Symfony/Component/AutoMapper/Generator/UniqueVariableScope.php +++ b/src/Symfony/Component/AutoMapper/Generator/UniqueVariableScope.php @@ -27,6 +27,8 @@ final class UniqueVariableScope */ public function getUniqueName(string $name): string { + $name = strtolower($name); + if (!isset($this->registry[$name])) { $this->registry[$name] = 0; diff --git a/src/Symfony/Component/AutoMapper/Loader/EvalLoader.php b/src/Symfony/Component/AutoMapper/Loader/EvalLoader.php index 51a5c67a5d21b..bb222b5b29eaf 100644 --- a/src/Symfony/Component/AutoMapper/Loader/EvalLoader.php +++ b/src/Symfony/Component/AutoMapper/Loader/EvalLoader.php @@ -30,9 +30,9 @@ public function __construct(Generator $generator) /** * {@inheritdoc} */ - public function loadClass(MapperGeneratorMetadataInterface $mapperConfiguration): void + public function loadClass(MapperGeneratorMetadataInterface $mapperGeneratorMetadata): void { - $class = $this->generator->compile($mapperConfiguration); + $class = $this->generator->generate($mapperGeneratorMetadata); eval($this->printer->prettyPrint([$class])); } diff --git a/src/Symfony/Component/AutoMapper/Tests/AutoMapperTest.php b/src/Symfony/Component/AutoMapper/Tests/AutoMapperTest.php index c0eed42b3d4a0..69924842901a6 100644 --- a/src/Symfony/Component/AutoMapper/Tests/AutoMapperTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/AutoMapperTest.php @@ -13,13 +13,17 @@ use Doctrine\Common\Annotations\AnnotationReader; use PhpParser\ParserFactory; -use Symfony\Bundle\TwigBundle\Tests\TestCase; +use Symfony\Bundle\FrameworkBundle\Tests\TestCase; use Symfony\Component\AutoMapper\AutoMapper; +use Symfony\Component\AutoMapper\Context; +use Symfony\Component\AutoMapper\Exception\CircularReferenceException; use Symfony\Component\AutoMapper\Generator\Generator; use Symfony\Component\AutoMapper\Loader\FileLoader; +use Symfony\Component\AutoMapper\Tests\Fixtures\Bar; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; +use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; /** * @author Joel Wurtz @@ -29,17 +33,19 @@ class AutoMapperTest extends TestCase /** @var AutoMapper */ private $autoMapper; + private $loader; + public function setUp() { @unlink(__DIR__.'/cache/registry.php'); $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); - $loader = new FileLoader(new Generator( + $this->loader = new FileLoader(new Generator( (new ParserFactory())->create(ParserFactory::PREFER_PHP7), new ClassDiscriminatorFromClassMetadata($classMetadataFactory) ), __DIR__.'/cache'); - $this->autoMapper = AutoMapper::create(true, $loader); + $this->autoMapper = AutoMapper::create(true, $this->loader); } public function testAutoMapping() @@ -54,13 +60,14 @@ public function testAutoMapping() $user = new Fixtures\User(1, 'yolo', '13'); $user->address = $address; $user->addresses[] = $address; + $user->money = 20.10; /** @var Fixtures\UserDTO $userDto */ $userDto = $this->autoMapper->map($user, Fixtures\UserDTO::class); self::assertInstanceOf(Fixtures\UserDTO::class, $userDto); self::assertSame(1, $userDto->id); - self::assertSame('yolo', $userDto->name); + self::assertSame('yolo', $userDto->getName()); self::assertSame(13, $userDto->age); self::assertSame(((int) date('Y')) - 13, $userDto->yearOfBirth); self::assertCount(1, $userDto->addresses); @@ -68,5 +75,476 @@ public function testAutoMapping() self::assertInstanceOf(Fixtures\AddressDTO::class, $userDto->addresses[0]); self::assertSame('Toulon', $userDto->address->city); self::assertSame('Toulon', $userDto->addresses[0]->city); + self::assertInternalType('array', $userDto->money); + self::assertCount(1, $userDto->money); + self::assertSame(20.10, $userDto->money[0]); + } + + public function testAutoMapperFromArray() + { + $user = [ + 'id' => 1, + 'address' => [ + 'city' => 'Toulon', + ], + 'createdAt' => '1987-04-30T06:00:00Z', + ]; + + /** @var Fixtures\UserDTO $userDto */ + $userDto = $this->autoMapper->map($user, Fixtures\UserDTO::class); + + self::assertInstanceOf(Fixtures\UserDTO::class, $userDto); + self::assertEquals(1, $userDto->id); + self::assertInstanceOf(Fixtures\AddressDTO::class, $userDto->address); + self::assertSame('Toulon', $userDto->address->city); + self::assertInstanceOf(\DateTimeInterface::class, $userDto->createdAt); + self::assertEquals(1987, $userDto->createdAt->format('Y')); + } + + public function testAutoMapperToArray() + { + $address = new Fixtures\Address(); + $address->setCity('Toulon'); + $user = new Fixtures\User(1, 'yolo', '13'); + $user->address = $address; + $user->addresses[] = $address; + + $userData = $this->autoMapper->map($user, 'array'); + + self::assertInternalType('array', $userData); + self::assertEquals(1, $userData['id']); + self::assertInternalType('array', $userData['address']); + self::assertInternalType('string', $userData['createdAt']); + } + + public function testAutoMapperFromStdObject() + { + $user = new \stdClass(); + $user->id = 1; + + /** @var UserDTO $userDto */ + $userDto = $this->autoMapper->map($user, Fixtures\UserDTO::class); + + self::assertInstanceOf(Fixtures\UserDTO::class, $userDto); + self::assertEquals(1, $userDto->id); + } + + public function testAutoMapperToStdObject() + { + $userDto = new Fixtures\UserDTO(); + $userDto->id = 1; + + $user = $this->autoMapper->map($userDto, \stdClass::class); + + self::assertInstanceOf(\stdClass::class, $user); + self::assertEquals(1, $user->id); + } + + public function testGroupsSourceTarget() + { + $foo = new Fixtures\Foo(); + $foo->setId(10); + + $bar = $this->autoMapper->map($foo, Bar::class, new Context(['group2'])); + + self::assertInstanceOf(Bar::class, $bar); + self::assertEquals(10, $bar->id); + + $bar = $this->autoMapper->map($foo, Bar::class, new Context(['group1', 'group3'])); + + self::assertInstanceOf(Bar::class, $bar); + self::assertEquals(10, $bar->id); + + $bar = $this->autoMapper->map($foo, Bar::class, new Context(['group1'])); + + self::assertInstanceOf(Bar::class, $bar); + self::assertNull($bar->id); + + $bar = $this->autoMapper->map($foo, Bar::class, new Context([])); + + self::assertInstanceOf(Bar::class, $bar); + self::assertNull($bar->id); + + $bar = $this->autoMapper->map($foo, Bar::class); + + self::assertInstanceOf(Bar::class, $bar); + self::assertNull($bar->id); + } + + public function testGroupsToArray() + { + $foo = new Fixtures\Foo(); + $foo->setId(10); + + $fooArray = $this->autoMapper->map($foo, 'array', new Context(['group1'])); + + self::assertInternalType('array', $fooArray); + self::assertEquals(10, $fooArray['id']); + + $fooArray = $this->autoMapper->map($foo, 'array', new Context([])); + + self::assertInternalType('array', $fooArray); + self::assertArrayNotHasKey('id', $fooArray); + + $fooArray = $this->autoMapper->map($foo, 'array'); + + self::assertInternalType('array', $fooArray); + self::assertArrayNotHasKey('id', $fooArray); + } + + public function testDeepCloning() + { + $nodeA = new Fixtures\Node(); + $nodeB = new Fixtures\Node(); + $nodeB->parent = $nodeA; + $nodeC = new Fixtures\Node(); + $nodeC->parent = $nodeB; + $nodeA->parent = $nodeC; + + $newNode = $this->autoMapper->map($nodeA, Fixtures\Node::class); + + self::assertInstanceOf(Fixtures\Node::class, $newNode); + self::assertNotSame($newNode, $nodeA); + self::assertInstanceOf(Fixtures\Node::class, $newNode->parent); + self::assertNotSame($newNode->parent, $nodeA->parent); + self::assertInstanceOf(Fixtures\Node::class, $newNode->parent->parent); + self::assertNotSame($newNode->parent->parent, $nodeA->parent->parent); + self::assertInstanceOf(Fixtures\Node::class, $newNode->parent->parent->parent); + self::assertSame($newNode, $newNode->parent->parent->parent); + } + + public function testDeepCloningArray() + { + $nodeA = new Fixtures\Node(); + $nodeB = new Fixtures\Node(); + $nodeB->parent = $nodeA; + $nodeC = new Fixtures\Node(); + $nodeC->parent = $nodeB; + $nodeA->parent = $nodeC; + + $newNode = $this->autoMapper->map($nodeA, 'array'); + + self::assertInternalType('array', $newNode); + self::assertInternalType('array', $newNode['parent']); + self::assertInternalType('array', $newNode['parent']['parent']); + self::assertInternalType('array', $newNode['parent']['parent']['parent']); + self::assertSame($newNode, $newNode['parent']['parent']['parent']); + } + + public function testCircularReferenceArray() + { + $nodeA = new Fixtures\Node(); + $nodeB = new Fixtures\Node(); + + $nodeA->childs[] = $nodeB; + $nodeB->childs[] = $nodeA; + + $newNode = $this->autoMapper->map($nodeA, 'array'); + + self::assertInternalType('array', $newNode); + self::assertInternalType('array', $newNode['childs'][0]); + self::assertInternalType('array', $newNode['childs'][0]['childs'][0]); + self::assertSame($newNode, $newNode['childs'][0]['childs'][0]); + } + + public function testPrivate() + { + $user = new Fixtures\PrivateUser(10, 'foo', 'bar'); + /** @var PrivateUserDTO $userDto */ + $userDto = $this->autoMapper->map($user, Fixtures\PrivateUserDTO::class); + + self::assertInstanceOf(Fixtures\PrivateUserDTO::class, $userDto); + self::assertSame(10, $userDto->getId()); + self::assertSame('foo', $userDto->getFirstName()); + self::assertSame('bar', $userDto->getLastName()); + } + + public function testConstructor() + { + $autoMapper = AutoMapper::create(false, $this->loader); + + $user = new Fixtures\UserDTO(); + $user->id = 10; + $user->setName('foo'); + $user->age = 3; + /** @var Fixtures\UserConstructorDTO $userDto */ + $userDto = $autoMapper->map($user, Fixtures\UserConstructorDTO::class); + + self::assertInstanceOf(Fixtures\UserConstructorDTO::class, $userDto); + self::assertSame('10', $userDto->getId()); + self::assertSame('foo', $userDto->getName()); + self::assertSame(3, $userDto->getAge()); + } + + public function testConstructorWithDefault() + { + $user = new Fixtures\UserDTONoAge(); + $user->id = 10; + $user->name = 'foo'; + /** @var Fixtures\UserConstructorDTO $userDto */ + $userDto = $this->autoMapper->map($user, Fixtures\UserConstructorDTO::class); + + self::assertInstanceOf(Fixtures\UserConstructorDTO::class, $userDto); + self::assertSame('10', $userDto->getId()); + self::assertSame('foo', $userDto->getName()); + self::assertSame(30, $userDto->getAge()); + } + + public function testConstructorDisable() + { + $user = new Fixtures\UserDTONoName(); + $user->id = 10; + /** @var Fixtures\UserConstructorDTO $userDto */ + $userDto = $this->autoMapper->map($user, Fixtures\UserConstructorDTO::class); + + self::assertInstanceOf(Fixtures\UserConstructorDTO::class, $userDto); + self::assertSame('10', $userDto->getId()); + self::assertNull($userDto->getName()); + self::assertNull($userDto->getAge()); + } + + public function testMaxDepth() + { + $foo = new Fixtures\FooMaxDepth(0, new Fixtures\FooMaxDepth(1, new Fixtures\FooMaxDepth(2, new Fixtures\FooMaxDepth(3, new Fixtures\FooMaxDepth(4))))); + $fooArray = $this->autoMapper->map($foo, 'array'); + + self::assertNotNull($fooArray['child']); + self::assertNotNull($fooArray['child']['child']); + self::assertFalse(isset($fooArray['child']['child']['child'])); + } + + public function testObjectToPopulate() + { + $configurationUser = $this->autoMapper->getMetadata(Fixtures\User::class, Fixtures\UserDTO::class); + $configurationUser->forMember('yearOfBirth', function (Fixtures\User $user) { + return ((int) date('Y')) - ((int) $user->age); + }); + + $user = new Fixtures\User(1, 'yolo', '13'); + $userDtoToPopulate = new Fixtures\UserDTO(); + $context = new Context(); + $context->setObjectToPopulate($userDtoToPopulate); + + $userDto = $this->autoMapper->map($user, Fixtures\UserDTO::class, $context); + + self::assertSame($userDtoToPopulate, $userDto); + } + + public function testObjectToPopulateWithoutContext() + { + $configurationUser = $this->autoMapper->getMetadata(Fixtures\User::class, Fixtures\UserDTO::class); + $configurationUser->forMember('yearOfBirth', function (Fixtures\User $user) { + return ((int) date('Y')) - ((int) $user->age); + }); + + $user = new Fixtures\User(1, 'yolo', '13'); + $userDtoToPopulate = new Fixtures\UserDTO(); + + $userDto = $this->autoMapper->map($user, $userDtoToPopulate); + + self::assertSame($userDtoToPopulate, $userDto); + } + + public function testArrayToPopulate() + { + $configurationUser = $this->autoMapper->getMetadata(Fixtures\User::class, Fixtures\UserDTO::class); + $configurationUser->forMember('yearOfBirth', function (Fixtures\User $user) { + return ((int) date('Y')) - ((int) $user->age); + }); + + $user = new Fixtures\User(1, 'yolo', '13'); + $array = []; + $arrayMapped = $this->autoMapper->map($user, $array); + + self::assertInternalType('array', $arrayMapped); + self::assertSame(1, $arrayMapped['id']); + self::assertSame('yolo', $arrayMapped['name']); + self::assertSame('13', $arrayMapped['age']); + } + + public function testCircularReferenceLimitOnContext() + { + $nodeA = new Fixtures\Node(); + $nodeA->parent = $nodeA; + + $context = new Context(); + $context->setCircularReferenceLimit(1); + + $this->expectException(CircularReferenceException::class); + + $this->autoMapper->map($nodeA, 'array', $context); + } + + public function testCircularReferenceLimitOnMapper() + { + $nodeA = new Fixtures\Node(); + $nodeA->parent = $nodeA; + + $mapper = $this->autoMapper->getMapper(Fixtures\Node::class, 'array'); + $mapper->setCircularReferenceLimit(1); + + $this->expectException(CircularReferenceException::class); + + $mapper->map($nodeA, new Context()); + } + + public function testCircularReferenceHandlerOnContext() + { + $nodeA = new Fixtures\Node(); + $nodeA->parent = $nodeA; + + $context = new Context(); + $context->setCircularReferenceHandler(function () { + return 'foo'; + }); + + $nodeArray = $this->autoMapper->map($nodeA, 'array', $context); + + self::assertSame('foo', $nodeArray['parent']); + } + + public function testCircularReferenceHandlerOnMapper() + { + $nodeA = new Fixtures\Node(); + $nodeA->parent = $nodeA; + + $mapper = $this->autoMapper->getMapper(Fixtures\Node::class, 'array'); + $mapper->setCircularReferenceHandler(function () { + return 'foo'; + }); + + $nodeArray = $mapper->map($nodeA, new Context()); + + self::assertSame('foo', $nodeArray['parent']); + } + + public function testAllowedAttributes() + { + $configurationUser = $this->autoMapper->getMetadata(Fixtures\User::class, Fixtures\UserDTO::class); + $configurationUser->forMember('yearOfBirth', function (Fixtures\User $user) { + return ((int) date('Y')) - ((int) $user->age); + }); + + $user = new Fixtures\User(1, 'yolo', '13'); + $context = new Context(null, ['id', 'age'], null); + + $userDto = $this->autoMapper->map($user, Fixtures\UserDTO::class, $context); + + self::assertNull($userDto->getName()); + } + + public function testIgnoredAttributes() + { + $configurationUser = $this->autoMapper->getMetadata(Fixtures\User::class, Fixtures\UserDTO::class); + $configurationUser->forMember('yearOfBirth', function (Fixtures\User $user) { + return ((int) date('Y')) - ((int) $user->age); + }); + + $user = new Fixtures\User(1, 'yolo', '13'); + $context = new Context(null, null, ['name']); + + $userDto = $this->autoMapper->map($user, Fixtures\UserDTO::class, $context); + + self::assertNull($userDto->getName()); + } + + public function testNameConverter() + { + $nameConverter = new class() implements AdvancedNameConverterInterface { + public function normalize($propertyName, string $class = null, string $format = null, array $context = []) + { + if ($propertyName === 'id') { + return '@id'; + } + + return $propertyName; + } + + public function denormalize($propertyName, string $class = null, string $format = null, array $context = []) + { + if ($propertyName === '@id') { + return 'id'; + } + + return $propertyName; + } + }; + + $autoMapper = AutoMapper::create(true, null, $nameConverter, 'Mapper2_'); + $user = new Fixtures\User(1, 'yolo', '13'); + + $userArray = $autoMapper->map($user, 'array', new Context()); + + self::assertInternalType('array', $userArray); + self::assertArrayHasKey('@id', $userArray); + self::assertSame(1, $userArray['@id']); + } + + public function testDefaultArguments() + { + $user = new Fixtures\UserDTONoAge(); + $user->id = 10; + $user->name = 'foo'; + /** @var UserConstructorDTO $userDto */ + $context = new Context(); + $context->setConstructorArgument(Fixtures\UserConstructorDTO::class, 'age', 50); + + $userDto = $this->autoMapper->map($user, Fixtures\UserConstructorDTO::class, $context); + + self::assertInstanceOf(Fixtures\UserConstructorDTO::class, $userDto); + self::assertSame(50, $userDto->getAge()); + } + + public function testDiscriminator() + { + $data = [ + 'type' => 'cat' + ]; + + $pet = $this->autoMapper->map($data, Fixtures\Pet::class); + + self::assertInstanceOf(Fixtures\Cat::class, $pet); + } + + public function testAutomapNull() + { + $array = $this->autoMapper->map(null, 'array'); + + self::assertNull($array); + } + + /** + * @expectedException \Symfony\Component\AutoMapper\Exception\NoMappingFoundException + */ + public function testInvalidMappingBothArray() + { + $data = ['test' => 'foo']; + $array = $this->autoMapper->map($data, 'array'); + } + + /** + * @expectedException \Symfony\Component\AutoMapper\Exception\NoMappingFoundException + */ + public function testInvalidMappingSource() + { + $array = $this->autoMapper->map('test', 'array'); + } + + /** + * @expectedException \Symfony\Component\AutoMapper\Exception\NoMappingFoundException + */ + public function testInvalidMappingTarget() + { + $data = ['test' => 'foo']; + $array = $this->autoMapper->map($data, 3); + } + + /** + * @expectedException \Symfony\Component\AutoMapper\Exception\NoMappingFoundException + */ + public function testNoAutoRegister() + { + $automapper = AutoMapper::create(false, null, null, 'Mapper_', true, false); + $automapper->getMapper(Fixtures\User::class, Fixtures\UserDTO::class); } } diff --git a/src/Symfony/Component/AutoMapper/Tests/Extractor/ReadAccessorTest.php b/src/Symfony/Component/AutoMapper/Tests/Extractor/ReadAccessorTest.php deleted file mode 100644 index 445d49cccf15e..0000000000000 --- a/src/Symfony/Component/AutoMapper/Tests/Extractor/ReadAccessorTest.php +++ /dev/null @@ -1,18 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\AutoMapper\Tests\Extractor; - -use PHPUnit\Framework\TestCase; - -class ReadAccessorTest extends TestCase -{ -} diff --git a/src/Symfony/Component/AutoMapper/Tests/Extractor/ReflectionExtractorTest.php b/src/Symfony/Component/AutoMapper/Tests/Extractor/ReflectionExtractorTest.php index a9ca4799bc6be..f047e732fd34d 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Extractor/ReflectionExtractorTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/Extractor/ReflectionExtractorTest.php @@ -12,7 +12,108 @@ namespace Symfony\Component\AutoMapper\Tests\Extractor; use PHPUnit\Framework\TestCase; +use Symfony\Component\AutoMapper\Extractor\ReadAccessor; +use Symfony\Component\AutoMapper\Extractor\ReflectionExtractor; +use Symfony\Component\AutoMapper\Extractor\WriteMutator; +use Symfony\Component\AutoMapper\Tests\Fixtures\ReflectionExtractorTestFixture; class ReflectionExtractorTest extends TestCase { + /** @var ReflectionExtractor */ + private $reflectionExtractor; + + public function setUp() + { + $this->reflectionExtractor = new ReflectionExtractor(true); + } + + public function testReadAccessorGetter() + { + $accessor = $this->reflectionExtractor->getReadAccessor(ReflectionExtractorTestFixture::class, 'foo'); + + self::assertNotNull($accessor); + self::assertSame(ReadAccessor::TYPE_METHOD, $accessor->getType()); + self::assertSame('getFoo', $accessor->getName()); + self::assertFalse($accessor->isPrivate()); + } + + public function testReadAccessorGetterSetter() + { + $accessor = $this->reflectionExtractor->getReadAccessor(ReflectionExtractorTestFixture::class, 'bar'); + + self::assertNotNull($accessor); + self::assertSame(ReadAccessor::TYPE_METHOD, $accessor->getType()); + self::assertSame('bar', $accessor->getName()); + self::assertFalse($accessor->isPrivate()); + } + + public function testReadAccessorIsser() + { + $accessor = $this->reflectionExtractor->getReadAccessor(ReflectionExtractorTestFixture::class, 'baz'); + + self::assertNotNull($accessor); + self::assertSame(ReadAccessor::TYPE_METHOD, $accessor->getType()); + self::assertSame('isBaz', $accessor->getName()); + self::assertFalse($accessor->isPrivate()); + } + + public function testReadAccessorHasser() + { + $accessor = $this->reflectionExtractor->getReadAccessor(ReflectionExtractorTestFixture::class, 'foz'); + + self::assertNotNull($accessor); + self::assertSame(ReadAccessor::TYPE_METHOD, $accessor->getType()); + self::assertSame('hasFoz', $accessor->getName()); + self::assertFalse($accessor->isPrivate()); + } + + public function testReadAccessorMagicGet() + { + $accessor = $this->reflectionExtractor->getReadAccessor(ReflectionExtractorTestFixture::class, 'magicGet'); + + self::assertNotNull($accessor); + self::assertSame(ReadAccessor::TYPE_PROPERTY, $accessor->getType()); + self::assertSame('magicGet', $accessor->getName()); + self::assertFalse($accessor->isPrivate()); + } + + public function testWriteMutatorSetter() + { + $mutator = $this->reflectionExtractor->getWriteMutator(ReflectionExtractorTestFixture::class, 'foo'); + + self::assertNotNull($mutator); + self::assertSame(WriteMutator::TYPE_METHOD, $mutator->getType()); + self::assertSame('setFoo', $mutator->getName()); + self::assertFalse($mutator->isPrivate()); + } + + public function testWriteMutatorGetterSetter() + { + $mutator = $this->reflectionExtractor->getWriteMutator(ReflectionExtractorTestFixture::class, 'bar'); + + self::assertNotNull($mutator); + self::assertSame(WriteMutator::TYPE_METHOD, $mutator->getType()); + self::assertSame('bar', $mutator->getName()); + self::assertFalse($mutator->isPrivate()); + } + + public function testWriteMutatorMagicSet() + { + $mutator = $this->reflectionExtractor->getWriteMutator(ReflectionExtractorTestFixture::class, 'magicSet'); + + self::assertNotNull($mutator); + self::assertSame(WriteMutator::TYPE_PROPERTY, $mutator->getType()); + self::assertSame('magicSet', $mutator->getName()); + self::assertFalse($mutator->isPrivate()); + } + + public function testWriteMutatorConstructor() + { + $mutator = $this->reflectionExtractor->getWriteMutator(ReflectionExtractorTestFixture::class, 'propertyConstruct'); + + self::assertNotNull($mutator); + self::assertSame(WriteMutator::TYPE_CONSTRUCTOR, $mutator->getType()); + self::assertSame('propertyConstruct', $mutator->getName()); + self::assertFalse($mutator->isPrivate()); + } } diff --git a/src/Symfony/Component/AutoMapper/Tests/Extractor/SourceTargetMappingExtractorTest.php b/src/Symfony/Component/AutoMapper/Tests/Extractor/SourceTargetMappingExtractorTest.php deleted file mode 100644 index ac37a1ed659a0..0000000000000 --- a/src/Symfony/Component/AutoMapper/Tests/Extractor/SourceTargetMappingExtractorTest.php +++ /dev/null @@ -1,18 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\AutoMapper\Tests\Extractor; - -use PHPUnit\Framework\TestCase; - -class SourceTargetMappingExtractorTest extends TestCase -{ -} diff --git a/src/Symfony/Component/AutoMapper/Tests/Extractor/PrivateReflectionExtractorTest.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/Bar.php similarity index 53% rename from src/Symfony/Component/AutoMapper/Tests/Extractor/PrivateReflectionExtractorTest.php rename to src/Symfony/Component/AutoMapper/Tests/Fixtures/Bar.php index 43cf28d37def2..532a2bd345660 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Extractor/PrivateReflectionExtractorTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/Bar.php @@ -9,10 +9,16 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\AutoMapper\Tests\Extractor; +namespace Symfony\Component\AutoMapper\Tests\Fixtures; -use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Annotation\Groups; -class PrivateReflectionExtractorTest extends TestCase +class Bar { + /** + * @var int + * + * @Groups({"group2", "group3"}) + */ + public $id; } diff --git a/src/Symfony/Component/AutoMapper/Tests/Loader/EvalLoaderTest.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/Cat.php similarity index 66% rename from src/Symfony/Component/AutoMapper/Tests/Loader/EvalLoaderTest.php rename to src/Symfony/Component/AutoMapper/Tests/Fixtures/Cat.php index 6217a347359a4..2d2bdd84199c6 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Loader/EvalLoaderTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/Cat.php @@ -9,10 +9,8 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\AutoMapper\Tests\Loader; +namespace Symfony\Component\AutoMapper\Tests\Fixtures; -use PHPUnit\Framework\TestCase; - -class EvalLoaderTest extends TestCase +class Cat extends Pet { } diff --git a/src/Symfony/Component/AutoMapper/Tests/Loader/FileLoaderTest.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/Dog.php similarity index 66% rename from src/Symfony/Component/AutoMapper/Tests/Loader/FileLoaderTest.php rename to src/Symfony/Component/AutoMapper/Tests/Fixtures/Dog.php index d2ea5b9a02934..140362eff8a92 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Loader/FileLoaderTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/Dog.php @@ -9,10 +9,8 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\AutoMapper\Tests\Loader; +namespace Symfony\Component\AutoMapper\Tests\Fixtures; -use PHPUnit\Framework\TestCase; - -class FileLoaderTest extends TestCase +class Dog extends Pet { } diff --git a/src/Symfony/Component/AutoMapper/Tests/Fixtures/Foo.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/Foo.php new file mode 100644 index 0000000000000..e64a3f6571784 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/Foo.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Fixtures; + +use Symfony\Component\Serializer\Annotation\Groups; + +class Foo +{ + /** + * @var int + * + * @Groups({"group1", "group2"}) + */ + private $id = 0; + + public function getId(): int + { + return $this->id; + } + + public function setId(int $id): void + { + $this->id = $id; + } +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Fixtures/FooMaxDepth.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/FooMaxDepth.php new file mode 100644 index 0000000000000..17800f86274c9 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/FooMaxDepth.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Fixtures; + +use Symfony\Component\Serializer\Annotation\MaxDepth; + +class FooMaxDepth +{ + /** + * @var int + */ + private $id; + + /** + * @var FooMaxDepth|null + * + * @MaxDepth(2) + */ + private $child; + + public function __construct(int $id, ?self $child = null) + { + $this->id = $id; + $this->child = $child; + } + + public function getId(): int + { + return $this->id; + } + + public function getChild(): ?self + { + return $this->child; + } +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Extractor/FromSourceMappingExtractorTest.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/Node.php similarity index 61% rename from src/Symfony/Component/AutoMapper/Tests/Extractor/FromSourceMappingExtractorTest.php rename to src/Symfony/Component/AutoMapper/Tests/Fixtures/Node.php index d770735965a10..ce204ffd34893 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Extractor/FromSourceMappingExtractorTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/Node.php @@ -9,10 +9,13 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\AutoMapper\Tests\Extractor; +namespace Symfony\Component\AutoMapper\Tests\Fixtures; -use PHPUnit\Framework\TestCase; - -class FromSourceMappingExtractorTest extends TestCase +class Node { + /** @var Node */ + public $parent; + + /** @var Node[] */ + public $childs = []; } diff --git a/src/Symfony/Component/AutoMapper/Tests/Fixtures/Pet.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/Pet.php new file mode 100644 index 0000000000000..d71303f71a42e --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/Pet.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Fixtures; + +use Symfony\Component\Serializer\Annotation\DiscriminatorMap; + +/** + * @DiscriminatorMap(typeProperty="type", mapping={ + * "cat"="Symfony\Component\AutoMapper\Tests\Fixtures\Cat", + * "dog"="Symfony\Component\AutoMapper\Tests\Fixtures\Dog" + * }) + */ +class Pet +{ + /** @var string */ + public $type; +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Fixtures/PrivateUser.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/PrivateUser.php new file mode 100644 index 0000000000000..8ac22a738b255 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/PrivateUser.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Fixtures; + +class PrivateUser +{ + /** @var int */ + private $id; + + /** @var string */ + private $firstName; + + /** @var string */ + private $lastName; + + public function __construct(int $id, string $firstName, string $lastName) + { + $this->id = $id; + $this->firstName = $firstName; + $this->lastName = $lastName; + } +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Fixtures/PrivateUserDTO.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/PrivateUserDTO.php new file mode 100644 index 0000000000000..19ce3d1b0472d --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/PrivateUserDTO.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Fixtures; + + +class PrivateUserDTO +{ + /** @var int */ + private $id; + + /** @var string */ + private $firstName; + + /** @var string */ + private $lastName; + + public function getId(): int + { + return $this->id; + } + + public function getFirstName(): string + { + return $this->firstName; + } + + public function getLastName(): string + { + return $this->lastName; + } +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Fixtures/ReflectionExtractorTestFixture.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/ReflectionExtractorTestFixture.php new file mode 100644 index 0000000000000..145e60dbf7af9 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/ReflectionExtractorTestFixture.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Fixtures; + +class ReflectionExtractorTestFixture +{ + public function __construct($propertyConstruct) + { + } + + public function getFoo(): string + { + return 'string'; + } + + public function setFoo(string $foo) + { + } + + public function bar(?string $bar): string + { + return 'string'; + } + + public function isBaz(): bool + { + return true; + } + + public function hasFoz(): bool + { + return false; + } + + public function __get($name) + { + } + + public function __set($name, $value) + { + } +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Fixtures/User.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/User.php index f6406c8db3166..8afe79a711851 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Fixtures/User.php +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/User.php @@ -17,31 +17,47 @@ class User * @var int */ private $id; + /** * @var string */ public $name; + /** * @var string|int */ public $age; + /** * @var string */ private $email; + /** * @var Address */ public $address; + /** * @var Address[] */ public $addresses = []; + /** * @var \DateTimeInterface */ public $createdAt; + /** + * @var float + */ + public $money; + + /** + * @var iterable + */ + public $languages; + public function __construct($id, $name, $age) { $this->id = $id; @@ -49,6 +65,8 @@ public function __construct($id, $name, $age) $this->age = $age; $this->email = 'test'; $this->createdAt = new \DateTime(); + $this->money = 20.10; + $this->languages = new \ArrayObject(); } /** diff --git a/src/Symfony/Component/AutoMapper/Tests/Fixtures/UserConstructorDTO.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/UserConstructorDTO.php new file mode 100644 index 0000000000000..057dea9df01fb --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/UserConstructorDTO.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Fixtures; + +class UserConstructorDTO +{ + /** + * @var string + */ + private $id; + /** + * @var ?string + */ + private $name; + + /** + * @var int + */ + private $age; + + public function __construct(string $id, string $name, int $age = 30) + { + $this->id = $id; + $this->name = $name; + $this->age = $age; + } + + /** + * @return int + */ + public function getId(): string + { + return $this->id; + } + + /** + * @return string + */ + public function getName(): ?string + { + return $this->name; + } + + /** + * @return int + */ + public function getAge() + { + return $this->age; + } +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Fixtures/UserDTO.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/UserDTO.php index 85ca8db99cba1..0ad412609b9bd 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Fixtures/UserDTO.php +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/UserDTO.php @@ -17,32 +17,59 @@ class UserDTO * @var int */ public $id; + /** * @var string */ - public $name; + private $name; + /** * @var int */ public $age; + /** * @var int */ public $yearOfBirth; + /** * @var string */ public $email; + /** * @var AddressDTO|null */ public $address; + /** * @var AddressDTO[] */ public $addresses = []; + /** * @var \DateTime|null */ public $createdAt; + + /** + * @var array|null + */ + public $money; + + /** + * @var array + */ + public $languages = []; + + public function setName($name) + { + $this->name = $name; + } + + public function getName() + { + return $this->name; + } } diff --git a/src/Symfony/Component/AutoMapper/Tests/Extractor/FromTargetMappingExtractorTest.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/UserDTONoAge.php similarity index 58% rename from src/Symfony/Component/AutoMapper/Tests/Extractor/FromTargetMappingExtractorTest.php rename to src/Symfony/Component/AutoMapper/Tests/Fixtures/UserDTONoAge.php index 13447ec03adc7..8e1cb033742a7 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Extractor/FromTargetMappingExtractorTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/UserDTONoAge.php @@ -9,10 +9,17 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\AutoMapper\Tests\Extractor; +namespace Symfony\Component\AutoMapper\Tests\Fixtures; -use PHPUnit\Framework\TestCase; - -class FromTargetMappingExtractorTest extends TestCase +class UserDTONoAge { + /** + * @var int + */ + public $id; + + /** + * @var string + */ + public $name; } diff --git a/src/Symfony/Component/AutoMapper/Tests/Extractor/WriteMutatorTest.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/UserDTONoName.php similarity index 65% rename from src/Symfony/Component/AutoMapper/Tests/Extractor/WriteMutatorTest.php rename to src/Symfony/Component/AutoMapper/Tests/Fixtures/UserDTONoName.php index d2c1a9170a30d..b8ad0a08d2faf 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Extractor/WriteMutatorTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/UserDTONoName.php @@ -9,10 +9,12 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\AutoMapper\Tests\Extractor; +namespace Symfony\Component\AutoMapper\Tests\Fixtures; -use PHPUnit\Framework\TestCase; - -class WriteMutatorTest extends TestCase +class UserDTONoName { + /** + * @var int + */ + public $id; } diff --git a/src/Symfony/Component/AutoMapper/Tests/Generator/UniqueVariableScopeTest.php b/src/Symfony/Component/AutoMapper/Tests/Generator/UniqueVariableScopeTest.php index 7157d4a5012a7..6814c972df4a3 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Generator/UniqueVariableScopeTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/Generator/UniqueVariableScopeTest.php @@ -12,7 +12,20 @@ namespace Symfony\Component\AutoMapper\Tests\Generator; use PHPUnit\Framework\TestCase; +use Symfony\Component\AutoMapper\Generator\UniqueVariableScope; class UniqueVariableScopeTest extends TestCase { + public function testVariableNameNotEquals() + { + $uniqueVariable = new UniqueVariableScope(); + $var1 = $uniqueVariable->getUniqueName('value'); + $var2 = $uniqueVariable->getUniqueName('value'); + $var3 = $uniqueVariable->getUniqueName('VALUE'); + + self::assertNotSame($var1, $var2); + self::assertNotSame($var1, $var3); + self::assertNotSame($var2, $var3); + self::assertNotSame(strtolower($var1), strtolower($var3)); + } } diff --git a/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformerFactory.php index de27057d82976..3807a85d785b4 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformerFactory.php +++ b/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformerFactory.php @@ -41,7 +41,11 @@ protected function createTransformer(Type $sourceType, Type $targetType, MapperM return null; } - $subItemTransformer = $this->chainTransformerFactory->getTransformer([$sourceType->getCollectionValueType()], [$targetType->getCollectionValueType()], $mapperMetadata); + if (null === $sourceType->getCollectionValueType() || null === $targetType->getCollectionValueType()) { + $subItemTransformer = new CopyTransformer(); + } else { + $subItemTransformer = $this->chainTransformerFactory->getTransformer([$sourceType->getCollectionValueType()], [$targetType->getCollectionValueType()], $mapperMetadata); + } if (null !== $subItemTransformer) { return new ArrayTransformer($subItemTransformer); diff --git a/src/Symfony/Component/AutoMapper/Transformer/NullableTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/NullableTransformerFactory.php index d9306c2e173d5..0f41fc151511b 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/NullableTransformerFactory.php +++ b/src/Symfony/Component/AutoMapper/Transformer/NullableTransformerFactory.php @@ -40,6 +40,10 @@ public function getTransformer(?array $sourcesTypes, ?array $targetTypes, Mapper /** @var Type $propertyType */ $propertyType = $sourcesTypes[0]; + if (null === $propertyType) { + var_dump($sourcesTypes); + } + if (!$propertyType->isNullable()) { return null; } diff --git a/src/Symfony/Component/AutoMapper/Transformer/UniqueTypeTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/UniqueTypeTransformerFactory.php index 95b00455357e3..e18c49d93bb3a 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/UniqueTypeTransformerFactory.php +++ b/src/Symfony/Component/AutoMapper/Transformer/UniqueTypeTransformerFactory.php @@ -44,6 +44,10 @@ public function getTransformer(?array $sourcesTypes, ?array $targetTypes, Mapper } foreach ($targetTypes as $targetType) { + if (null === $targetType) { + continue; + } + $transformer = $this->chainTransformerFactory->getTransformer($sourcesTypes, [$targetType], $mapperMetadata); if (null !== $transformer) { From bd387f58167647a65a1a8c68a30b2e3a2c4b02db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 25 Mar 2019 21:26:16 +0100 Subject: [PATCH 09/38] Apply suggestions from @dunglas code review Co-Authored-By: joelwurtz --- src/Symfony/Component/AutoMapper/AutoMapper.php | 6 +++--- src/Symfony/Component/AutoMapper/AutoMapperInterface.php | 2 +- .../Component/AutoMapper/AutoMapperRegistryInterface.php | 4 ++-- src/Symfony/Component/AutoMapper/Context.php | 2 +- .../AutoMapper/Extractor/AccessorExtractorInterface.php | 2 +- .../Component/AutoMapper/Extractor/MappingExtractor.php | 2 +- .../AutoMapper/Extractor/MappingExtractorInterface.php | 8 ++++---- .../AutoMapper/Extractor/ReflectionExtractor.php | 2 +- .../AutoMapper/Extractor/SourceTargetMappingExtractor.php | 2 +- .../Component/AutoMapper/Extractor/WriteMutator.php | 2 +- .../Extractor/WriteMutatorExtractorInterface.php | 4 ++-- .../AutoMapper/Generator/UniqueVariableScope.php | 2 +- src/Symfony/Component/AutoMapper/LICENSE | 2 +- .../Component/AutoMapper/Loader/ClassLoaderInterface.php | 2 +- src/Symfony/Component/AutoMapper/Loader/EvalLoader.php | 2 +- src/Symfony/Component/AutoMapper/MapperMetadata.php | 2 +- 16 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/Symfony/Component/AutoMapper/AutoMapper.php b/src/Symfony/Component/AutoMapper/AutoMapper.php index 66acb51c302fc..7e4c86c40627b 100644 --- a/src/Symfony/Component/AutoMapper/AutoMapper.php +++ b/src/Symfony/Component/AutoMapper/AutoMapper.php @@ -38,11 +38,11 @@ use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; /** - * An auto mapper has the role of mapping a source to a target. + * Maps a source data structure (object or array) to a target one. * * @author Joel Wurtz */ -class AutoMapper implements AutoMapperInterface, AutoMapperRegistryInterface, MapperGeneratorMetadataRegistryInterface +final class AutoMapper implements AutoMapperInterface, AutoMapperRegistryInterface, MapperGeneratorMetadataRegistryInterface { private $metadatas = []; @@ -168,7 +168,7 @@ public function map($sourceData, $targetData, Context $context = null) */ public function getMetadata(string $source, string $target): ?MapperGeneratorMetadataInterface { - if (!\array_key_exists($source, $this->metadatas) || !\array_key_exists($target, $this->metadatas[$source])) { + if (!isset($this->metadatas[$source][$target])) { if (null === $this->mapperConfigurationFactory) { return null; } diff --git a/src/Symfony/Component/AutoMapper/AutoMapperInterface.php b/src/Symfony/Component/AutoMapper/AutoMapperInterface.php index 7d78f88451b6f..c5e4e8baa4340 100644 --- a/src/Symfony/Component/AutoMapper/AutoMapperInterface.php +++ b/src/Symfony/Component/AutoMapper/AutoMapperInterface.php @@ -19,7 +19,7 @@ interface AutoMapperInterface { /** - * Map data from a source to a target. + * Maps data from a source to a target. * * @param array|object $source Any data object, which may be an object or an array * @param string|array|object $target To which type of data, or data, the source should be mapped diff --git a/src/Symfony/Component/AutoMapper/AutoMapperRegistryInterface.php b/src/Symfony/Component/AutoMapper/AutoMapperRegistryInterface.php index d754ddedcf165..1347ef7d55e2f 100644 --- a/src/Symfony/Component/AutoMapper/AutoMapperRegistryInterface.php +++ b/src/Symfony/Component/AutoMapper/AutoMapperRegistryInterface.php @@ -12,7 +12,7 @@ namespace Symfony\Component\AutoMapper; /** - * Allow to retrieve mapper. + * Allows to retrieve a mapper. * * @internal * @@ -21,7 +21,7 @@ interface AutoMapperRegistryInterface { /** - * Get a specific mapper for a source type and a target type. + * Gets a specific mapper for a source type and a target type. * * @param string $source Source type * @param string $target Target type diff --git a/src/Symfony/Component/AutoMapper/Context.php b/src/Symfony/Component/AutoMapper/Context.php index 9e856c38ee8e5..f3307ede047b4 100644 --- a/src/Symfony/Component/AutoMapper/Context.php +++ b/src/Symfony/Component/AutoMapper/Context.php @@ -16,7 +16,7 @@ /** * Context for mapping. * - * Allow to customize how is done the mapping + * Allows to customize how is done the mapping * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Extractor/AccessorExtractorInterface.php b/src/Symfony/Component/AutoMapper/Extractor/AccessorExtractorInterface.php index 2c83e4de80f4f..21a8bc74ed480 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/AccessorExtractorInterface.php +++ b/src/Symfony/Component/AutoMapper/Extractor/AccessorExtractorInterface.php @@ -12,7 +12,7 @@ namespace Symfony\Component\AutoMapper\Extractor; /** - * Extract accessor and mutator. + * Extracts accessor and mutator. * * @internal * diff --git a/src/Symfony/Component/AutoMapper/Extractor/MappingExtractor.php b/src/Symfony/Component/AutoMapper/Extractor/MappingExtractor.php index 576983598087a..6f63fbb178188 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/MappingExtractor.php +++ b/src/Symfony/Component/AutoMapper/Extractor/MappingExtractor.php @@ -99,7 +99,7 @@ protected function getGroups($class, $property): ?array $groups = []; foreach ($serializerClassMetadata->getAttributesMetadata() as $serializerAttributeMetadata) { - if (\count($serializerAttributeMetadata->getGroups()) > 0) { + if ($serializerAttributeMetadata->getGroups()) { $anyGroupFound = true; } diff --git a/src/Symfony/Component/AutoMapper/Extractor/MappingExtractorInterface.php b/src/Symfony/Component/AutoMapper/Extractor/MappingExtractorInterface.php index f070c9446d80c..2645bb2601419 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/MappingExtractorInterface.php +++ b/src/Symfony/Component/AutoMapper/Extractor/MappingExtractorInterface.php @@ -14,7 +14,7 @@ use Symfony\Component\AutoMapper\MapperMetadataInterface; /** - * Extract mapping. + * Extracts mapping. * * @internal * @@ -23,19 +23,19 @@ interface MappingExtractorInterface { /** - * Extract properties mapped for a given source and target. + * Extracts properties mapped for a given source and target. * * @return PropertyMapping[] */ public function getPropertiesMapping(MapperMetadataInterface $mapperMetadata): array; /** - * Extract read accessor for a given source, target and property. + * Extracts read accessor for a given source, target and property. */ public function getReadAccessor(string $source, string $target, string $property): ?ReadAccessor; /** - * Extract write mutator for a given source, target and property. + * Extracts write mutator for a given source, target and property. */ public function getWriteMutator(string $source, string $target, string $property): ?WriteMutator; } diff --git a/src/Symfony/Component/AutoMapper/Extractor/ReflectionExtractor.php b/src/Symfony/Component/AutoMapper/Extractor/ReflectionExtractor.php index 2c2dcec294f28..23f61621c375e 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/ReflectionExtractor.php +++ b/src/Symfony/Component/AutoMapper/Extractor/ReflectionExtractor.php @@ -12,7 +12,7 @@ namespace Symfony\Component\AutoMapper\Extractor; /** - * Extract accessor and mutator from reflection. + * Extracts accessor and mutator from reflection. * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php b/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php index d6449d8814ba2..d609fabd0b26c 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php +++ b/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php @@ -14,7 +14,7 @@ use Symfony\Component\AutoMapper\MapperMetadataInterface; /** - * Extract mapping between two objects, only give properties that have the same name. + * Extracts mapping between two objects, only gives properties that have the same name. * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Extractor/WriteMutator.php b/src/Symfony/Component/AutoMapper/Extractor/WriteMutator.php index 92dc1cb25c071..de42362738c8b 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/WriteMutator.php +++ b/src/Symfony/Component/AutoMapper/Extractor/WriteMutator.php @@ -20,7 +20,7 @@ use Symfony\Component\AutoMapper\Exception\CompileException; /** - * Write mutator tell how to write to a property. + * Writes mutator tell how to write to a property. * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Extractor/WriteMutatorExtractorInterface.php b/src/Symfony/Component/AutoMapper/Extractor/WriteMutatorExtractorInterface.php index 623734c2c3b9e..5b64918305c58 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/WriteMutatorExtractorInterface.php +++ b/src/Symfony/Component/AutoMapper/Extractor/WriteMutatorExtractorInterface.php @@ -12,7 +12,7 @@ namespace Symfony\Component\AutoMapper\Extractor; /** - * Extract write mutator for property of a class. + * Extracts write mutator for property of a class. * * @internal * @@ -20,5 +20,5 @@ */ interface WriteMutatorExtractorInterface { - public function getWriteMutator(string $class, string $property, bool $allowConstruct = true): ?WriteMutator; + public function getWriteMutator(string $class, string $property, bool $allowConstructor = true): ?WriteMutator; } diff --git a/src/Symfony/Component/AutoMapper/Generator/UniqueVariableScope.php b/src/Symfony/Component/AutoMapper/Generator/UniqueVariableScope.php index a72444b8a29a8..93dba4cd24af9 100644 --- a/src/Symfony/Component/AutoMapper/Generator/UniqueVariableScope.php +++ b/src/Symfony/Component/AutoMapper/Generator/UniqueVariableScope.php @@ -12,7 +12,7 @@ namespace Symfony\Component\AutoMapper\Generator; /** - * Allow to get a unique variable name for a scope (like a method). + * Allows to get a unique variable name for a scope (like a method). * * @internal * diff --git a/src/Symfony/Component/AutoMapper/LICENSE b/src/Symfony/Component/AutoMapper/LICENSE index a677f43763ca4..1a1869751d250 100644 --- a/src/Symfony/Component/AutoMapper/LICENSE +++ b/src/Symfony/Component/AutoMapper/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2019 Fabien Potencier +Copyright (c) 2019 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Component/AutoMapper/Loader/ClassLoaderInterface.php b/src/Symfony/Component/AutoMapper/Loader/ClassLoaderInterface.php index 2d142cdd47c0f..5efd88cc12221 100644 --- a/src/Symfony/Component/AutoMapper/Loader/ClassLoaderInterface.php +++ b/src/Symfony/Component/AutoMapper/Loader/ClassLoaderInterface.php @@ -14,7 +14,7 @@ use Symfony\Component\AutoMapper\MapperGeneratorMetadataInterface; /** - * Load (require) a mapping given metadata. + * Loads (require) a mapping given metadata. */ interface ClassLoaderInterface { diff --git a/src/Symfony/Component/AutoMapper/Loader/EvalLoader.php b/src/Symfony/Component/AutoMapper/Loader/EvalLoader.php index bb222b5b29eaf..58dd58b4b805f 100644 --- a/src/Symfony/Component/AutoMapper/Loader/EvalLoader.php +++ b/src/Symfony/Component/AutoMapper/Loader/EvalLoader.php @@ -13,7 +13,7 @@ use Symfony\Component\AutoMapper\MapperGeneratorMetadataInterface; /** - * Use a generator and eval to requiring mapping of a class. + * Uses a generator and eval to requiring mapping of a class. */ class EvalLoader implements ClassLoaderInterface { diff --git a/src/Symfony/Component/AutoMapper/MapperMetadata.php b/src/Symfony/Component/AutoMapper/MapperMetadata.php index 0c78f4db15218..ef40b1803dc49 100644 --- a/src/Symfony/Component/AutoMapper/MapperMetadata.php +++ b/src/Symfony/Component/AutoMapper/MapperMetadata.php @@ -111,7 +111,7 @@ public function hasConstructor(): bool } } - if (0 === \count($mandatoryParameters)) { + if (!$mandatoryParameters) { return true; } From 20b7735255345da2858b00fe8469997aa3200d13 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Mon, 25 Mar 2019 21:53:59 +0100 Subject: [PATCH 10/38] Add expiremental annotation where needed, add final to class that should not be extended --- .../Component/AutoMapper/AutoMapper.php | 2 ++ .../AutoMapper/AutoMapperInterface.php | 2 ++ .../AutoMapper/AutoMapperNormalizer.php | 2 ++ .../Exception/CircularReferenceException.php | 5 +++++ .../AutoMapper/Exception/CompileException.php | 5 +++++ .../Exception/InvalidMappingException.php | 5 +++++ .../Exception/NoMappingFoundException.php | 5 +++++ .../Extractor/FromSourceMappingExtractor.php | 2 ++ .../Extractor/FromTargetMappingExtractor.php | 2 ++ .../AutoMapper/Extractor/PropertyMapping.php | 2 ++ .../SourceTargetMappingExtractor.php | 2 ++ .../AutoMapper/Generator/Generator.php | 6 +++++- .../AutoMapper/Loader/EvalLoader.php | 21 ++++++++++++------- .../AutoMapper/Loader/FileLoader.php | 9 +++++++- .../MapperGeneratorMetadataFactory.php | 2 ++ .../Component/AutoMapper/MapperMetadata.php | 2 ++ .../Tests/AutoMapperNormalizerTest.php | 18 ---------------- .../AutoMapper/Tests/ContextTest.php | 18 ---------------- .../Transformer/ArrayTransformer.php | 4 +++- .../Transformer/ArrayTransformerFactory.php | 4 +++- .../Transformer/BuiltinTransformer.php | 4 +++- .../Transformer/BuiltinTransformerFactory.php | 4 +++- .../Transformer/CallbackTransformer.php | 4 +++- .../Transformer/ChainTransformerFactory.php | 4 +++- .../Transformer/CopyTransformer.php | 4 +++- .../DateTimeToStringTansformer.php | 4 +++- .../DateTimeTransformerFactory.php | 4 +++- .../Transformer/MultipleTransformer.php | 4 +++- .../MultipleTransformerFactory.php | 4 +++- .../Transformer/NullableTransformer.php | 4 +++- .../NullableTransformerFactory.php | 4 +++- .../Transformer/ObjectTransformer.php | 4 +++- .../Transformer/ObjectTransformerFactory.php | 4 +++- .../StringToDateTimeTransformer.php | 4 +++- .../UniqueTypeTransformerFactory.php | 2 +- 35 files changed, 114 insertions(+), 62 deletions(-) delete mode 100644 src/Symfony/Component/AutoMapper/Tests/AutoMapperNormalizerTest.php delete mode 100644 src/Symfony/Component/AutoMapper/Tests/ContextTest.php diff --git a/src/Symfony/Component/AutoMapper/AutoMapper.php b/src/Symfony/Component/AutoMapper/AutoMapper.php index 7e4c86c40627b..673b2faa3de92 100644 --- a/src/Symfony/Component/AutoMapper/AutoMapper.php +++ b/src/Symfony/Component/AutoMapper/AutoMapper.php @@ -40,6 +40,8 @@ /** * Maps a source data structure (object or array) to a target one. * + * @expiremental + * * @author Joel Wurtz */ final class AutoMapper implements AutoMapperInterface, AutoMapperRegistryInterface, MapperGeneratorMetadataRegistryInterface diff --git a/src/Symfony/Component/AutoMapper/AutoMapperInterface.php b/src/Symfony/Component/AutoMapper/AutoMapperInterface.php index c5e4e8baa4340..c006e3d852fdd 100644 --- a/src/Symfony/Component/AutoMapper/AutoMapperInterface.php +++ b/src/Symfony/Component/AutoMapper/AutoMapperInterface.php @@ -14,6 +14,8 @@ /** * An auto mapper has the role of mapping a source to a target. * + * @expiremental + * * @author Joel Wurtz */ interface AutoMapperInterface diff --git a/src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php b/src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php index 2b1f2808a861b..f884d38b432d1 100644 --- a/src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php +++ b/src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php @@ -18,6 +18,8 @@ /** * Bridge for symfony/serializer. * + * @expiremental + * * @author Joel Wurtz */ class AutoMapperNormalizer implements NormalizerInterface, DenormalizerInterface diff --git a/src/Symfony/Component/AutoMapper/Exception/CircularReferenceException.php b/src/Symfony/Component/AutoMapper/Exception/CircularReferenceException.php index 8298410c87356..540a275db3b7c 100644 --- a/src/Symfony/Component/AutoMapper/Exception/CircularReferenceException.php +++ b/src/Symfony/Component/AutoMapper/Exception/CircularReferenceException.php @@ -11,6 +11,11 @@ namespace Symfony\Component\AutoMapper\Exception; +/** + * @expiremental + * + * @author Joel Wurtz + */ class CircularReferenceException extends \RuntimeException { } diff --git a/src/Symfony/Component/AutoMapper/Exception/CompileException.php b/src/Symfony/Component/AutoMapper/Exception/CompileException.php index 96c3b97c0869b..35d7091814b3c 100644 --- a/src/Symfony/Component/AutoMapper/Exception/CompileException.php +++ b/src/Symfony/Component/AutoMapper/Exception/CompileException.php @@ -11,6 +11,11 @@ namespace Symfony\Component\AutoMapper\Exception; +/** + * @expiremental + * + * @author Joel Wurtz + */ class CompileException extends \RuntimeException { } diff --git a/src/Symfony/Component/AutoMapper/Exception/InvalidMappingException.php b/src/Symfony/Component/AutoMapper/Exception/InvalidMappingException.php index d64cb2f06386e..c265e9ce8724a 100644 --- a/src/Symfony/Component/AutoMapper/Exception/InvalidMappingException.php +++ b/src/Symfony/Component/AutoMapper/Exception/InvalidMappingException.php @@ -11,6 +11,11 @@ namespace Symfony\Component\AutoMapper\Exception; +/** + * @expiremental + * + * @author Joel Wurtz + */ class InvalidMappingException extends \RuntimeException { } diff --git a/src/Symfony/Component/AutoMapper/Exception/NoMappingFoundException.php b/src/Symfony/Component/AutoMapper/Exception/NoMappingFoundException.php index 6f67546026e8d..4bd6f6d6aec2e 100644 --- a/src/Symfony/Component/AutoMapper/Exception/NoMappingFoundException.php +++ b/src/Symfony/Component/AutoMapper/Exception/NoMappingFoundException.php @@ -11,6 +11,11 @@ namespace Symfony\Component\AutoMapper\Exception; +/** + * @expiremental + * + * @author Joel Wurtz + */ class NoMappingFoundException extends \RuntimeException { } diff --git a/src/Symfony/Component/AutoMapper/Extractor/FromSourceMappingExtractor.php b/src/Symfony/Component/AutoMapper/Extractor/FromSourceMappingExtractor.php index bf54b13a54c1c..744eba8423664 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/FromSourceMappingExtractor.php +++ b/src/Symfony/Component/AutoMapper/Extractor/FromSourceMappingExtractor.php @@ -24,6 +24,8 @@ * * Can use a NameConverter to use specific properties name in the target * + * @expiremental + * * @author Joel Wurtz */ final class FromSourceMappingExtractor extends MappingExtractor diff --git a/src/Symfony/Component/AutoMapper/Extractor/FromTargetMappingExtractor.php b/src/Symfony/Component/AutoMapper/Extractor/FromTargetMappingExtractor.php index 7335613604103..18c45f32ae9b0 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/FromTargetMappingExtractor.php +++ b/src/Symfony/Component/AutoMapper/Extractor/FromTargetMappingExtractor.php @@ -24,6 +24,8 @@ * * Can use a NameConverter to use specific properties name in the source * + * @expiremental + * * @author Joel Wurtz */ final class FromTargetMappingExtractor extends MappingExtractor diff --git a/src/Symfony/Component/AutoMapper/Extractor/PropertyMapping.php b/src/Symfony/Component/AutoMapper/Extractor/PropertyMapping.php index 97bb211d4bb96..1316518f97075 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/PropertyMapping.php +++ b/src/Symfony/Component/AutoMapper/Extractor/PropertyMapping.php @@ -16,6 +16,8 @@ /** * Property mapping. * + * @expiremental + * * @author Joel Wurtz */ final class PropertyMapping diff --git a/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php b/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php index d609fabd0b26c..c518aca1a2729 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php +++ b/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php @@ -16,6 +16,8 @@ /** * Extracts mapping between two objects, only gives properties that have the same name. * + * @expiremental + * * @author Joel Wurtz */ class SourceTargetMappingExtractor extends MappingExtractor diff --git a/src/Symfony/Component/AutoMapper/Generator/Generator.php b/src/Symfony/Component/AutoMapper/Generator/Generator.php index 1e4d127705c43..362d170c1d817 100644 --- a/src/Symfony/Component/AutoMapper/Generator/Generator.php +++ b/src/Symfony/Component/AutoMapper/Generator/Generator.php @@ -30,8 +30,12 @@ /** * Generate code for a mapping class. + * + * @expiremental + * + * @author Joel Wurtz */ -class Generator +final class Generator { private $parser; diff --git a/src/Symfony/Component/AutoMapper/Loader/EvalLoader.php b/src/Symfony/Component/AutoMapper/Loader/EvalLoader.php index 58dd58b4b805f..0da58aa4e2a49 100644 --- a/src/Symfony/Component/AutoMapper/Loader/EvalLoader.php +++ b/src/Symfony/Component/AutoMapper/Loader/EvalLoader.php @@ -1,9 +1,12 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. */ namespace Symfony\Component\AutoMapper\Loader; @@ -13,9 +16,13 @@ use Symfony\Component\AutoMapper\MapperGeneratorMetadataInterface; /** - * Uses a generator and eval to requiring mapping of a class. + * Use eval to load mappers + * + * @expiremental + * + * @author Joel Wurtz */ -class EvalLoader implements ClassLoaderInterface +final class EvalLoader implements ClassLoaderInterface { private $generator; diff --git a/src/Symfony/Component/AutoMapper/Loader/FileLoader.php b/src/Symfony/Component/AutoMapper/Loader/FileLoader.php index 27f0ff6d9ec66..6e64c1a492467 100644 --- a/src/Symfony/Component/AutoMapper/Loader/FileLoader.php +++ b/src/Symfony/Component/AutoMapper/Loader/FileLoader.php @@ -15,7 +15,14 @@ use Symfony\Component\AutoMapper\Generator\Generator; use Symfony\Component\AutoMapper\MapperGeneratorMetadataInterface; -class FileLoader implements ClassLoaderInterface +/** + * Use file system to load mapper, and persist them using a registry + * + * @expiremental + * + * @author Joel Wurtz + */ +final class FileLoader implements ClassLoaderInterface { private $generator; diff --git a/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactory.php b/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactory.php index 339b776885544..c58cfed48e9e4 100644 --- a/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactory.php +++ b/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactory.php @@ -18,6 +18,8 @@ /** * Metadata factory, used to autoregistering new mapping without creating them. * + * @expiremental + * * @author Joel Wurtz */ final class MapperGeneratorMetadataFactory diff --git a/src/Symfony/Component/AutoMapper/MapperMetadata.php b/src/Symfony/Component/AutoMapper/MapperMetadata.php index ef40b1803dc49..ddc97444c899b 100644 --- a/src/Symfony/Component/AutoMapper/MapperMetadata.php +++ b/src/Symfony/Component/AutoMapper/MapperMetadata.php @@ -20,6 +20,8 @@ /** * Mapper metadata. * + * @expiremental + * * @author Joel Wurtz */ class MapperMetadata implements MapperGeneratorMetadataInterface diff --git a/src/Symfony/Component/AutoMapper/Tests/AutoMapperNormalizerTest.php b/src/Symfony/Component/AutoMapper/Tests/AutoMapperNormalizerTest.php deleted file mode 100644 index 27a9935f6895b..0000000000000 --- a/src/Symfony/Component/AutoMapper/Tests/AutoMapperNormalizerTest.php +++ /dev/null @@ -1,18 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\AutoMapper\Tests; - -use PHPUnit\Framework\TestCase; - -class AutoMapperNormalizerTest extends TestCase -{ -} diff --git a/src/Symfony/Component/AutoMapper/Tests/ContextTest.php b/src/Symfony/Component/AutoMapper/Tests/ContextTest.php deleted file mode 100644 index 70e6c2bb938d1..0000000000000 --- a/src/Symfony/Component/AutoMapper/Tests/ContextTest.php +++ /dev/null @@ -1,18 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\AutoMapper\Tests; - -use PHPUnit\Framework\TestCase; - -class ContextTest extends TestCase -{ -} diff --git a/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformer.php index 93aedd3b8f89f..bcea040f9f4df 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformer.php @@ -19,9 +19,11 @@ /** * Transformer array decorator. * + * @expiremental + * * @author Joel Wurtz */ -class ArrayTransformer implements TransformerInterface +final class ArrayTransformer implements TransformerInterface { private $itemTransformer; diff --git a/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformerFactory.php index 3807a85d785b4..99b288fbddea9 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformerFactory.php +++ b/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformerFactory.php @@ -17,9 +17,11 @@ /** * Create a decorated transformer to handle array type. * + * @expiremental + * * @author Joel Wurtz */ -class ArrayTransformerFactory extends AbstractUniqueTypeTransformerFactory +final class ArrayTransformerFactory extends AbstractUniqueTypeTransformerFactory { private $chainTransformerFactory; diff --git a/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformer.php index 2abb58a64bece..1be46719d4bd3 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformer.php @@ -22,9 +22,11 @@ /** * Built in transformer to handle PHP scalar types. * + * @expiremental + * * @author Joel Wurtz */ -class BuiltinTransformer implements TransformerInterface +final class BuiltinTransformer implements TransformerInterface { private const CAST_MAPPING = [ Type::BUILTIN_TYPE_BOOL => [ diff --git a/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformerFactory.php index 27e7177a22134..8686fec915343 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformerFactory.php +++ b/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformerFactory.php @@ -15,9 +15,11 @@ use Symfony\Component\PropertyInfo\Type; /** + * @expiremental + * * @author Joel Wurtz */ -class BuiltinTransformerFactory implements TransformerFactoryInterface +final class BuiltinTransformerFactory implements TransformerFactoryInterface { private const BUILTIN = [ Type::BUILTIN_TYPE_BOOL, diff --git a/src/Symfony/Component/AutoMapper/Transformer/CallbackTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/CallbackTransformer.php index ac3149e6702ad..db423dc0ab312 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/CallbackTransformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/CallbackTransformer.php @@ -20,9 +20,11 @@ /** * Handle custom callback transformation. * + * @expiremental + * * @author Joel Wurtz */ -class CallbackTransformer implements TransformerInterface +final class CallbackTransformer implements TransformerInterface { private $callbackName; diff --git a/src/Symfony/Component/AutoMapper/Transformer/ChainTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/ChainTransformerFactory.php index f228f8b3b6663..840fb483b96b2 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/ChainTransformerFactory.php +++ b/src/Symfony/Component/AutoMapper/Transformer/ChainTransformerFactory.php @@ -14,9 +14,11 @@ use Symfony\Component\AutoMapper\MapperMetadataInterface; /** + * @expiremental + * * @author Joel Wurtz */ -class ChainTransformerFactory implements TransformerFactoryInterface +final class ChainTransformerFactory implements TransformerFactoryInterface { /** @var TransformerFactoryInterface[] */ private $factories = []; diff --git a/src/Symfony/Component/AutoMapper/Transformer/CopyTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/CopyTransformer.php index c4ddbe17aaa83..41e53a07dd993 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/CopyTransformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/CopyTransformer.php @@ -18,9 +18,11 @@ /** * Does not do any transformation, output = input. * + * @expiremental + * * @author Joel Wurtz */ -class CopyTransformer implements TransformerInterface +final class CopyTransformer implements TransformerInterface { /** * {@inheritdoc} diff --git a/src/Symfony/Component/AutoMapper/Transformer/DateTimeToStringTansformer.php b/src/Symfony/Component/AutoMapper/Transformer/DateTimeToStringTansformer.php index d250a6a86694f..51a3d9ddaad6a 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/DateTimeToStringTansformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/DateTimeToStringTansformer.php @@ -20,9 +20,11 @@ /** * Transform a \DateTimeInterface object to a string. * + * @expiremental + * * @author Joel Wurtz */ -class DateTimeToStringTansformer implements TransformerInterface +final class DateTimeToStringTansformer implements TransformerInterface { private $format; diff --git a/src/Symfony/Component/AutoMapper/Transformer/DateTimeTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/DateTimeTransformerFactory.php index e373820acd127..8cdda8230bc8a 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/DateTimeTransformerFactory.php +++ b/src/Symfony/Component/AutoMapper/Transformer/DateTimeTransformerFactory.php @@ -15,9 +15,11 @@ use Symfony\Component\PropertyInfo\Type; /** + * @expiremental + * * @author Joel Wurtz */ -class DateTimeTransformerFactory extends AbstractUniqueTypeTransformerFactory +final class DateTimeTransformerFactory extends AbstractUniqueTypeTransformerFactory { /** * {@inheritdoc} diff --git a/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformer.php index 70fd5d66f3891..23097b759c550 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformer.php @@ -25,9 +25,11 @@ * Decorate transformers with condition to handle property with multiples source types * It will always use the first target type possible for transformation * + * @expiremental + * * @author Joel Wurtz */ -class MultipleTransformer implements TransformerInterface +final class MultipleTransformer implements TransformerInterface { private const CONDITION_MAPPING = [ Type::BUILTIN_TYPE_BOOL => 'is_bool', diff --git a/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformerFactory.php index e5fac9d268091..f4121c09e55ce 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformerFactory.php +++ b/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformerFactory.php @@ -14,9 +14,11 @@ use Symfony\Component\AutoMapper\MapperMetadataInterface; /** + * @expiremental + * * @author Joel Wurtz */ -class MultipleTransformerFactory implements TransformerFactoryInterface +final class MultipleTransformerFactory implements TransformerFactoryInterface { private $chainTransformerFactory; diff --git a/src/Symfony/Component/AutoMapper/Transformer/NullableTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/NullableTransformer.php index 601c9db0c96d5..9a4f421cb8946 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/NullableTransformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/NullableTransformer.php @@ -20,9 +20,11 @@ /** * Tansformer decorator to handle null values. * + * @expiremental + * * @author Joel Wurtz */ -class NullableTransformer implements TransformerInterface +final class NullableTransformer implements TransformerInterface { private $itemTransformer; private $isTargetNullable; diff --git a/src/Symfony/Component/AutoMapper/Transformer/NullableTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/NullableTransformerFactory.php index 0f41fc151511b..641ead4e248b0 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/NullableTransformerFactory.php +++ b/src/Symfony/Component/AutoMapper/Transformer/NullableTransformerFactory.php @@ -15,9 +15,11 @@ use Symfony\Component\PropertyInfo\Type; /** + * @expiremental + * * @author Joel Wurtz */ -class NullableTransformerFactory implements TransformerFactoryInterface +final class NullableTransformerFactory implements TransformerFactoryInterface { private $chainTransformerFactory; diff --git a/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformer.php index 389c24774a1a4..05835755818da 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformer.php @@ -21,9 +21,11 @@ /** * Transform to an object which can be mapped by AutoMapper (sub mapping). * + * @expiremental + * * @author Joel Wurtz */ -class ObjectTransformer implements TransformerInterface +final class ObjectTransformer implements TransformerInterface { private $sourceType; diff --git a/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformerFactory.php index 66572da2ccdd8..409a06c0390b0 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformerFactory.php +++ b/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformerFactory.php @@ -16,9 +16,11 @@ use Symfony\Component\PropertyInfo\Type; /** + * @expiremental + * * @author Joel Wurtz */ -class ObjectTransformerFactory extends AbstractUniqueTypeTransformerFactory +final class ObjectTransformerFactory extends AbstractUniqueTypeTransformerFactory { private $autoMapper; diff --git a/src/Symfony/Component/AutoMapper/Transformer/StringToDateTimeTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/StringToDateTimeTransformer.php index e49c8b724339c..2287b0c858408 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/StringToDateTimeTransformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/StringToDateTimeTransformer.php @@ -21,9 +21,11 @@ /** * Transform a string to a \DateTimeInterface object. * + * @expiremental + * * @author Joel Wurtz */ -class StringToDateTimeTransformer implements TransformerInterface +final class StringToDateTimeTransformer implements TransformerInterface { private $className; diff --git a/src/Symfony/Component/AutoMapper/Transformer/UniqueTypeTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/UniqueTypeTransformerFactory.php index e18c49d93bb3a..54be1581874fd 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/UniqueTypeTransformerFactory.php +++ b/src/Symfony/Component/AutoMapper/Transformer/UniqueTypeTransformerFactory.php @@ -18,7 +18,7 @@ * * @author Joel Wurtz */ -class UniqueTypeTransformerFactory implements TransformerFactoryInterface +final class UniqueTypeTransformerFactory implements TransformerFactoryInterface { private $chainTransformerFactory; From 12e5d1fd3c66b255e3531d032724ebc0229762af Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Mon, 25 Mar 2019 22:13:53 +0100 Subject: [PATCH 11/38] Fix cs and typo --- .../Component/AutoMapper/AutoMapper.php | 47 +++++++------------ .../AutoMapper/Extractor/MappingExtractor.php | 12 ++--- .../SourceTargetMappingExtractor.php | 2 +- .../AutoMapper/Generator/Generator.php | 15 +++--- .../Generator/UniqueVariableScope.php | 2 +- .../MapperGeneratorMetadataFactory.php | 12 ++--- 6 files changed, 33 insertions(+), 57 deletions(-) diff --git a/src/Symfony/Component/AutoMapper/AutoMapper.php b/src/Symfony/Component/AutoMapper/AutoMapper.php index 673b2faa3de92..c99ce481ef13e 100644 --- a/src/Symfony/Component/AutoMapper/AutoMapper.php +++ b/src/Symfony/Component/AutoMapper/AutoMapper.php @@ -46,7 +46,7 @@ */ final class AutoMapper implements AutoMapperInterface, AutoMapperRegistryInterface, MapperGeneratorMetadataRegistryInterface { - private $metadatas = []; + private $metadata = []; /** @var GeneratedMapper[] */ private $mapperRegistry = []; @@ -66,11 +66,7 @@ public function __construct(ClassLoaderInterface $classLoader, MapperGeneratorMe */ public function register(MapperGeneratorMetadataInterface $metadata): void { - if (!\array_key_exists($metadata->getSource(), $this->metadatas)) { - $this->metadatas[$metadata->getSource()] = []; - } - - $this->metadatas[$metadata->getSource()][$metadata->getTarget()] = $metadata; + $this->metadata[$metadata->getSource()][$metadata->getTarget()] = $metadata; } /** @@ -170,7 +166,7 @@ public function map($sourceData, $targetData, Context $context = null) */ public function getMetadata(string $source, string $target): ?MapperGeneratorMetadataInterface { - if (!isset($this->metadatas[$source][$target])) { + if (!isset($this->metadata[$source][$target])) { if (null === $this->mapperConfigurationFactory) { return null; } @@ -178,13 +174,11 @@ public function getMetadata(string $source, string $target): ?MapperGeneratorMet $this->register($this->mapperConfigurationFactory->create($this, $source, $target)); } - return $this->metadatas[$source][$target]; + return $this->metadata[$source][$target]; } /** - * Use this for test, benchmark and fast prototyping. - * - * @internal + * Create a default automapper */ public static function create( bool $private = true, @@ -193,8 +187,7 @@ public static function create( string $classPrefix = 'Mapper_', bool $attributeChecking = true, bool $autoRegister = true - ): self - { + ): self { $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); if (null === $loader) { @@ -204,11 +197,7 @@ public static function create( )); } - if ($private) { - $reflectionExtractor = new PrivateReflectionExtractor(); - } else { - $reflectionExtractor = new ReflectionExtractor(); - } + $reflectionExtractor = $private ? new PrivateReflectionExtractor() : new ReflectionExtractor(); $phpDocExtractor = new PhpDocExtractor(); $propertyInfoExtractor = new PropertyInfoExtractor( @@ -247,20 +236,16 @@ public static function create( $sourceTargetMappingExtractor, $fromSourceMappingExtractor, $fromTargetMappingExtractor, - $classPrefix + $classPrefix, + $attributeChecking ); - $factory->setAttributeChecking($attributeChecking); - - if ($autoRegister) { - $autoMapper = new self($loader, new MapperGeneratorMetadataFactory( - $sourceTargetMappingExtractor, - $fromSourceMappingExtractor, - $fromTargetMappingExtractor, - $classPrefix - )); - } else { - $autoMapper = new self($loader); - } + + $autoMapper = $autoRegister ? new self($loader, new MapperGeneratorMetadataFactory( + $sourceTargetMappingExtractor, + $fromSourceMappingExtractor, + $fromTargetMappingExtractor, + $classPrefix + )) : new self($loader); $transformerFactory->addTransformerFactory(new MultipleTransformerFactory($transformerFactory)); $transformerFactory->addTransformerFactory(new NullableTransformerFactory($transformerFactory)); diff --git a/src/Symfony/Component/AutoMapper/Extractor/MappingExtractor.php b/src/Symfony/Component/AutoMapper/Extractor/MappingExtractor.php index 6f63fbb178188..3a38c950a77db 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/MappingExtractor.php +++ b/src/Symfony/Component/AutoMapper/Extractor/MappingExtractor.php @@ -86,11 +86,7 @@ protected function getGroups($class, $property): ?array return null; } - if (null === $this->classMetadataFactory) { - return null; - } - - if (!$this->classMetadataFactory->getMetadataFor($class)) { + if (null === $this->classMetadataFactory || !$this->classMetadataFactory->getMetadataFor($class)) { return null; } @@ -99,12 +95,14 @@ protected function getGroups($class, $property): ?array $groups = []; foreach ($serializerClassMetadata->getAttributesMetadata() as $serializerAttributeMetadata) { - if ($serializerAttributeMetadata->getGroups()) { + $groupsFound = $serializerAttributeMetadata->getGroups(); + + if ($groupsFound) { $anyGroupFound = true; } if ($serializerAttributeMetadata->getName() === $property) { - $groups = $serializerAttributeMetadata->getGroups(); + $groups = $groupsFound; } } diff --git a/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php b/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php index c518aca1a2729..65386e19cf8ba 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php +++ b/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php @@ -44,7 +44,7 @@ public function getPropertiesMapping(MapperMetadataInterface $mapperMetadata): a if (\in_array($property, $targetProperties, true)) { $targetMutatorConstruct = $this->accessorExtractor->getWriteMutator($mapperMetadata->getTarget(), $property, true); - if (($targetMutatorConstruct === null || $targetMutatorConstruct->getParameter() === null) && !$this->propertyInfoExtractor->isWritable($mapperMetadata->getTarget(), $property)) { + if ((null === $targetMutatorConstruct || null === $targetMutatorConstruct->getParameter()) && !$this->propertyInfoExtractor->isWritable($mapperMetadata->getTarget(), $property)) { continue; } diff --git a/src/Symfony/Component/AutoMapper/Generator/Generator.php b/src/Symfony/Component/AutoMapper/Generator/Generator.php index 362d170c1d817..da86555b7df7c 100644 --- a/src/Symfony/Component/AutoMapper/Generator/Generator.php +++ b/src/Symfony/Component/AutoMapper/Generator/Generator.php @@ -29,7 +29,7 @@ use Symfony\Component\Serializer\Mapping\ClassDiscriminatorResolverInterface; /** - * Generate code for a mapping class. + * Generates code for a mapping class. * * @expiremental * @@ -121,7 +121,7 @@ public function generate(MapperGeneratorMetadataInterface $mapperGeneratorMetada } } - if (\count($addedDependencies) > 0) { + if ($addedDependencies) { if ($canHaveCircularDependency) { $statements[] = new Stmt\Expression(new Expr\Assign( $contextVariable, @@ -232,10 +232,10 @@ public function generate(MapperGeneratorMetadataInterface $mapperGeneratorMetada ); } - if (\count($conditions) > 0) { + if ($conditions) { $condition = array_shift($conditions); - while (\count($conditions) > 0) { + while ($conditions) { $condition = new Expr\BinaryOp\BooleanAnd($condition, array_shift($conditions)); } @@ -244,10 +244,9 @@ public function generate(MapperGeneratorMetadataInterface $mapperGeneratorMetada ])]; } - $statements = array_merge( - $statements, - $propStatements - ); + foreach ($propStatements as $propStatement) { + $statements[] = $propStatement; + } } $statements[] = new Stmt\Return_($result); diff --git a/src/Symfony/Component/AutoMapper/Generator/UniqueVariableScope.php b/src/Symfony/Component/AutoMapper/Generator/UniqueVariableScope.php index 93dba4cd24af9..ef6b7e9be95e9 100644 --- a/src/Symfony/Component/AutoMapper/Generator/UniqueVariableScope.php +++ b/src/Symfony/Component/AutoMapper/Generator/UniqueVariableScope.php @@ -37,6 +37,6 @@ public function getUniqueName(string $name): string ++$this->registry[$name]; - return sprintf('%s_%s', $name, $this->registry[$name]); + return "{$name}_{$this->registry[$name]}"; } } diff --git a/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactory.php b/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactory.php index c58cfed48e9e4..b257631ef1be4 100644 --- a/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactory.php +++ b/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactory.php @@ -28,25 +28,19 @@ final class MapperGeneratorMetadataFactory private $fromSourcePropertiesMappingExtractor; private $fromTargetPropertiesMappingExtractor; private $classPrefix; - private $attributeChecking = true; + private $attributeChecking; public function __construct( SourceTargetMappingExtractor $sourceTargetPropertiesMappingExtractor, FromSourceMappingExtractor $fromSourcePropertiesMappingExtractor, FromTargetMappingExtractor $fromTargetPropertiesMappingExtractor, - string $classPrefix = 'Mapper_' + string $classPrefix = 'Mapper_', + bool $attributeChecking = true ) { $this->sourceTargetPropertiesMappingExtractor = $sourceTargetPropertiesMappingExtractor; $this->fromSourcePropertiesMappingExtractor = $fromSourcePropertiesMappingExtractor; $this->fromTargetPropertiesMappingExtractor = $fromTargetPropertiesMappingExtractor; $this->classPrefix = $classPrefix; - } - - /** - * Whether or not attribute checking code should be generated. - */ - public function setAttributeChecking(bool $attributeChecking): void - { $this->attributeChecking = $attributeChecking; } From 26e0c8af3e4146eae5dc18b1d3202c7c0c3168ed Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Mon, 25 Mar 2019 22:23:22 +0100 Subject: [PATCH 12/38] Avoid too many function calls --- .../AutoMapper/Generator/Generator.php | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/Symfony/Component/AutoMapper/Generator/Generator.php b/src/Symfony/Component/AutoMapper/Generator/Generator.php index da86555b7df7c..e030a573d5e0c 100644 --- a/src/Symfony/Component/AutoMapper/Generator/Generator.php +++ b/src/Symfony/Component/AutoMapper/Generator/Generator.php @@ -288,33 +288,36 @@ public function generate(MapperGeneratorMetadataInterface $mapperGeneratorMetada private function getCreateObjectStatements(MapperGeneratorMetadataInterface $mapperMetadata, Expr\Variable $result, Expr\Variable $contextVariable, Expr\Variable $sourceInput, UniqueVariableScope $uniqueVariableScope): array { - if ('array' === $mapperMetadata->getTarget()) { + $target = $mapperMetadata->getTarget(); + $source = $mapperMetadata->getSource(); + + if ('array' === $target) { return [[new Stmt\Expression(new Expr\Assign($result, new Expr\Array_()))], [], [], []]; } - if (\stdClass::class === $mapperMetadata->getTarget()) { + if (\stdClass::class === $target) { return [[new Stmt\Expression(new Expr\Assign($result, new Expr\New_(new Name(\stdClass::class))))], [], [], []]; } - $reflectionClass = new \ReflectionClass($mapperMetadata->getTarget()); + $reflectionClass = new \ReflectionClass($target); $targetConstructor = $reflectionClass->getConstructor(); $createObjectStatements = []; $inConstructor = []; $constructStatements = []; $injectMapperStatements = []; /** @var ClassDiscriminatorMapping $classDiscriminatorMapping */ - $classDiscriminatorMapping = 'array' !== $mapperMetadata->getTarget() && null !== $this->classDiscriminator ? $this->classDiscriminator->getMappingForClass($mapperMetadata->getTarget()) : null; + $classDiscriminatorMapping = 'array' !== $target && null !== $this->classDiscriminator ? $this->classDiscriminator->getMappingForClass($target) : null; if (null !== $classDiscriminatorMapping && null !== ($propertyMapping = $mapperMetadata->getPropertyMapping($classDiscriminatorMapping->getTypeProperty()))) { [$output, $createObjectStatements] = $propertyMapping->getTransformer()->transform($propertyMapping->getReadAccessor()->getExpression($sourceInput), $propertyMapping, $uniqueVariableScope); foreach ($classDiscriminatorMapping->getTypesMapping() as $typeValue => $typeTarget) { - $mapperName = 'Discriminator_Mapper_'.$mapperMetadata->getSource().'_'.$typeTarget; + $mapperName = 'Discriminator_Mapper_'.$source.'_'.$typeTarget; $injectMapperStatements[] = new Stmt\Expression(new Expr\Assign( new Expr\ArrayDimFetch(new Expr\PropertyFetch(new Expr\Variable('this'), 'mappers'), new Scalar\String_($mapperName)), new Expr\MethodCall(new Expr\Variable('autoMapperRegistry'), 'getMapper', [ - new Arg(new Scalar\String_($mapperMetadata->getSource())), + new Arg(new Scalar\String_($source)), new Arg(new Scalar\String_($typeTarget)), ]) )); @@ -353,12 +356,12 @@ private function getCreateObjectStatements(MapperGeneratorMetadataInterface $map $propStatements[] = new Stmt\Expression(new Expr\Assign($constructVar, $output)); $createObjectStatements[] = new Stmt\If_(new Expr\MethodCall($contextVariable, 'hasConstructorArgument', [ - new Arg(new Scalar\String_($mapperMetadata->getTarget())), + new Arg(new Scalar\String_($target)), new Arg(new Scalar\String_($propertyMapping->getProperty())), ]), [ 'stmts' => [ new Stmt\Expression(new Expr\Assign($constructVar, new Expr\MethodCall($contextVariable, 'getConstructorArgument', [ - new Arg(new Scalar\String_($mapperMetadata->getTarget())), + new Arg(new Scalar\String_($target)), new Arg(new Scalar\String_($propertyMapping->getProperty())), ]))), ], @@ -373,12 +376,12 @@ private function getCreateObjectStatements(MapperGeneratorMetadataInterface $map $constructVar = new Expr\Variable($uniqueVariableScope->getUniqueName('constructArg')); $createObjectStatements[] = new Stmt\If_(new Expr\MethodCall($contextVariable, 'hasConstructorArgument', [ - new Arg(new Scalar\String_($mapperMetadata->getTarget())), + new Arg(new Scalar\String_($target)), new Arg(new Scalar\String_($constructorParameter->getName())), ]), [ 'stmts' => [ new Stmt\Expression(new Expr\Assign($constructVar, new Expr\MethodCall($contextVariable, 'getConstructorArgument', [ - new Arg(new Scalar\String_($mapperMetadata->getTarget())), + new Arg(new Scalar\String_($target)), new Arg(new Scalar\String_($constructorParameter->getName())), ]))), ], @@ -393,12 +396,12 @@ private function getCreateObjectStatements(MapperGeneratorMetadataInterface $map ksort($constructArguments); - $createObjectStatements[] = new Stmt\Expression(new Expr\Assign($result, new Expr\New_(new Name\FullyQualified($mapperMetadata->getTarget()), $constructArguments))); + $createObjectStatements[] = new Stmt\Expression(new Expr\Assign($result, new Expr\New_(new Name\FullyQualified($target), $constructArguments))); } elseif (null !== $targetConstructor && $mapperMetadata->isTargetCloneable()) { $constructStatements[] = new Stmt\Expression(new Expr\Assign( new Expr\PropertyFetch(new Expr\Variable('this'), 'cachedTarget'), new Expr\MethodCall(new Expr\New_(new Name\FullyQualified(\ReflectionClass::class), [ - new Arg(new Scalar\String_($mapperMetadata->getTarget())), + new Arg(new Scalar\String_($target)), ]), 'newInstanceWithoutConstructor') )); $createObjectStatements[] = new Stmt\Expression(new Expr\Assign($result, new Expr\Clone_(new Expr\PropertyFetch(new Expr\Variable('this'), 'cachedTarget')))); @@ -406,7 +409,7 @@ private function getCreateObjectStatements(MapperGeneratorMetadataInterface $map $constructStatements[] = new Stmt\Expression(new Expr\Assign( new Expr\PropertyFetch(new Expr\Variable('this'), 'cachedTarget'), new Expr\New_(new Name\FullyQualified(\ReflectionClass::class), [ - new Arg(new Scalar\String_($mapperMetadata->getTarget())), + new Arg(new Scalar\String_($target)), ]) )); $createObjectStatements[] = new Stmt\Expression(new Expr\Assign($result, new Expr\MethodCall( @@ -414,7 +417,7 @@ private function getCreateObjectStatements(MapperGeneratorMetadataInterface $map 'newInstanceWithoutConstructor' ))); } else { - $createObjectStatements[] = new Stmt\Expression(new Expr\Assign($result, new Expr\New_(new Name\FullyQualified($mapperMetadata->getTarget())))); + $createObjectStatements[] = new Stmt\Expression(new Expr\Assign($result, new Expr\New_(new Name\FullyQualified($target)))); } return [$createObjectStatements, $inConstructor, $constructStatements, $injectMapperStatements]; From 678537af79771c19a2ee5c947babdc5732f60830 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Tue, 26 Mar 2019 00:09:18 +0100 Subject: [PATCH 13/38] Add interface for generator metadatas --- .../Component/AutoMapper/AutoMapper.php | 14 ++++------- .../AutoMapperRegistryInterface.php | 2 -- .../MapperGeneratorMetadataFactory.php | 4 ++-- ...apperGeneratorMetadataFactoryInterface.php | 24 +++++++++++++++++++ 4 files changed, 30 insertions(+), 14 deletions(-) create mode 100644 src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactoryInterface.php diff --git a/src/Symfony/Component/AutoMapper/AutoMapper.php b/src/Symfony/Component/AutoMapper/AutoMapper.php index c99ce481ef13e..454c384678de9 100644 --- a/src/Symfony/Component/AutoMapper/AutoMapper.php +++ b/src/Symfony/Component/AutoMapper/AutoMapper.php @@ -46,6 +46,7 @@ */ final class AutoMapper implements AutoMapperInterface, AutoMapperRegistryInterface, MapperGeneratorMetadataRegistryInterface { + /** @var MapperGeneratorMetadataInterface[] */ private $metadata = []; /** @var GeneratedMapper[] */ @@ -55,7 +56,7 @@ final class AutoMapper implements AutoMapperInterface, AutoMapperRegistryInterfa private $mapperConfigurationFactory; - public function __construct(ClassLoaderInterface $classLoader, MapperGeneratorMetadataFactory $mapperConfigurationFactory = null) + public function __construct(ClassLoaderInterface $classLoader, MapperGeneratorMetadataFactoryInterface $mapperConfigurationFactory = null) { $this->classLoader = $classLoader; $this->mapperConfigurationFactory = $mapperConfigurationFactory; @@ -178,7 +179,7 @@ public function getMetadata(string $source, string $target): ?MapperGeneratorMet } /** - * Create a default automapper + * Create an automapper. */ public static function create( bool $private = true, @@ -232,19 +233,12 @@ public static function create( $nameConverter ); - $factory = new MapperGeneratorMetadataFactory( + $autoMapper = $autoRegister ? new self($loader, new MapperGeneratorMetadataFactory( $sourceTargetMappingExtractor, $fromSourceMappingExtractor, $fromTargetMappingExtractor, $classPrefix, $attributeChecking - ); - - $autoMapper = $autoRegister ? new self($loader, new MapperGeneratorMetadataFactory( - $sourceTargetMappingExtractor, - $fromSourceMappingExtractor, - $fromTargetMappingExtractor, - $classPrefix )) : new self($loader); $transformerFactory->addTransformerFactory(new MultipleTransformerFactory($transformerFactory)); diff --git a/src/Symfony/Component/AutoMapper/AutoMapperRegistryInterface.php b/src/Symfony/Component/AutoMapper/AutoMapperRegistryInterface.php index 1347ef7d55e2f..0b2dc60feada3 100644 --- a/src/Symfony/Component/AutoMapper/AutoMapperRegistryInterface.php +++ b/src/Symfony/Component/AutoMapper/AutoMapperRegistryInterface.php @@ -35,8 +35,6 @@ public function getMapper(string $source, string $target): MapperInterface; * * @param string $source Source type * @param string $target Target type - * - * @return bool */ public function hasMapper(string $source, string $target): bool; } diff --git a/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactory.php b/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactory.php index b257631ef1be4..1c4ba9f60a215 100644 --- a/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactory.php +++ b/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactory.php @@ -22,7 +22,7 @@ * * @author Joel Wurtz */ -final class MapperGeneratorMetadataFactory +final class MapperGeneratorMetadataFactory implements MapperGeneratorMetadataFactoryInterface { private $sourceTargetPropertiesMappingExtractor; private $fromSourcePropertiesMappingExtractor; @@ -47,7 +47,7 @@ public function __construct( /** * Create metadata for a source and target. */ - public function create(MapperGeneratorMetadataRegistryInterface $autoMapperRegister, string $source, string $target): MapperMetadata + public function create(MapperGeneratorMetadataRegistryInterface $autoMapperRegister, string $source, string $target): MapperGeneratorMetadataInterface { $extractor = $this->sourceTargetPropertiesMappingExtractor; diff --git a/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactoryInterface.php b/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactoryInterface.php new file mode 100644 index 0000000000000..7dc2647cc19f7 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactoryInterface.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper; + +/** + * Metadata factory, used to autoregistering new mapping without creating them. + * + * @expiremental + * + * @author Joel Wurtz + */ +interface MapperGeneratorMetadataFactoryInterface +{ + public function create(MapperGeneratorMetadataRegistryInterface $autoMapperRegister, string $source, string $target): MapperGeneratorMetadataInterface; +} From ceeeaf18a31ec42c64ebd8ae607a6244b2da37c2 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Tue, 26 Mar 2019 01:35:06 +0100 Subject: [PATCH 14/38] Use array for context and provide helper class --- .../Component/AutoMapper/AutoMapper.php | 10 +- .../AutoMapper/AutoMapperInterface.php | 4 +- .../AutoMapper/AutoMapperNormalizer.php | 4 +- src/Symfony/Component/AutoMapper/Context.php | 256 ------------------ .../Component/AutoMapper/GeneratedMapper.php | 2 - .../AutoMapper/Generator/Generator.php | 62 +++-- .../AutoMapper/Loader/EvalLoader.php | 2 +- .../Component/AutoMapper/MapperContext.php | 242 +++++++++++++++++ .../Component/AutoMapper/MapperInterface.php | 4 +- .../AutoMapper/Tests/AutoMapperTest.php | 43 ++- .../Transformer/ObjectTransformer.php | 5 +- 11 files changed, 320 insertions(+), 314 deletions(-) delete mode 100644 src/Symfony/Component/AutoMapper/Context.php create mode 100644 src/Symfony/Component/AutoMapper/MapperContext.php diff --git a/src/Symfony/Component/AutoMapper/AutoMapper.php b/src/Symfony/Component/AutoMapper/AutoMapper.php index 454c384678de9..c68884efb02e2 100644 --- a/src/Symfony/Component/AutoMapper/AutoMapper.php +++ b/src/Symfony/Component/AutoMapper/AutoMapper.php @@ -112,7 +112,7 @@ public function hasMapper(string $source, string $target): bool /** * {@inheritdoc} */ - public function map($sourceData, $targetData, Context $context = null) + public function map($sourceData, $targetData, array $context = []) { $source = null; $target = null; @@ -133,18 +133,14 @@ public function map($sourceData, $targetData, Context $context = null) throw new NoMappingFoundException('Cannot map this value, source is neither an object or an array.'); } - if (null === $context) { - $context = new Context(); - } - if (\is_object($targetData)) { $target = \get_class($targetData); - $context->setObjectToPopulate($targetData); + $context[MapperContext::TARGET_TO_POPULATE] = $targetData; } if (\is_array($targetData)) { $target = 'array'; - $context->setObjectToPopulate($targetData); + $context[MapperContext::TARGET_TO_POPULATE] = $targetData; } if (\is_string($targetData)) { diff --git a/src/Symfony/Component/AutoMapper/AutoMapperInterface.php b/src/Symfony/Component/AutoMapper/AutoMapperInterface.php index c006e3d852fdd..70e121ba252f1 100644 --- a/src/Symfony/Component/AutoMapper/AutoMapperInterface.php +++ b/src/Symfony/Component/AutoMapper/AutoMapperInterface.php @@ -25,9 +25,9 @@ interface AutoMapperInterface * * @param array|object $source Any data object, which may be an object or an array * @param string|array|object $target To which type of data, or data, the source should be mapped - * @param Context $context Options mappers have access to + * @param array $context Mapper context * * @return array|object The mapped object */ - public function map($source, $target, Context $context = null); + public function map($source, $target, array $context = []); } diff --git a/src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php b/src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php index f884d38b432d1..94fe164361322 100644 --- a/src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php +++ b/src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php @@ -64,7 +64,7 @@ public function hasCacheableSupportsMethod(): bool return true; } - private function createAutoMapperContext(array $serializerContext = []): Context + private function createAutoMapperContext(array $serializerContext = []): MapperContext { $circularReferenceLimit = 1; @@ -72,7 +72,7 @@ private function createAutoMapperContext(array $serializerContext = []): Context $circularReferenceLimit = $serializerContext[AbstractNormalizer::CIRCULAR_REFERENCE_LIMIT]; } - $context = new Context( + $context = new MapperContext( $serializerContext[AbstractNormalizer::GROUPS] ?? null, $serializerContext[AbstractNormalizer::ATTRIBUTES] ?? null, $serializerContextContext[AbstractNormalizer::IGNORED_ATTRIBUTES] ?? null diff --git a/src/Symfony/Component/AutoMapper/Context.php b/src/Symfony/Component/AutoMapper/Context.php deleted file mode 100644 index f3307ede047b4..0000000000000 --- a/src/Symfony/Component/AutoMapper/Context.php +++ /dev/null @@ -1,256 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\AutoMapper; - -use Symfony\Component\AutoMapper\Exception\CircularReferenceException; - -/** - * Context for mapping. - * - * Allows to customize how is done the mapping - * - * @author Joel Wurtz - */ -class Context extends \ArrayObject -{ - private $referenceRegistry = []; - - private $countReferenceRegistry = []; - - private $groups; - - private $depth; - - private $object; - - private $circularReferenceLimit; - - private $circularReferenceHandler; - - private $attributes; - - private $ignoredAttributes; - - private $constructorArguments = []; - - /** - * @param array|null $groups Groups to use for mapping - * @param array|null $attributes Attributes to use for mapping (exclude others) - * @param array|null $ignoredAttributes Attributes to exclude from mapping (include others) - */ - public function __construct(array $groups = null, array $attributes = null, array $ignoredAttributes = null) - { - parent::__construct(); - - $this->groups = $groups; - $this->depth = 0; - $this->attributes = $attributes; - $this->ignoredAttributes = $ignoredAttributes; - } - - /** - * Whether a reference has reached it's limit. - */ - public function shouldHandleCircularReference(string $reference, ?int $circularReferenceLimit = null): bool - { - if (!isset($this->referenceRegistry[$reference])) { - return false; - } - - if (null === $circularReferenceLimit) { - $circularReferenceLimit = $this->circularReferenceLimit; - } - - if (null !== $circularReferenceLimit) { - return $this->countReferenceRegistry[$reference] >= $circularReferenceLimit; - } - - return true; - } - - /** - * Handle circular reference for a specific reference. - * - * By default will try to keep it and return the previous value - * - * @return mixed - */ - public function &handleCircularReference(string $reference, $object, ?int $circularReferenceLimit = null, callable $callback = null) - { - if (null === $callback) { - $callback = $this->circularReferenceHandler; - } - - if (null !== $callback) { - $value = $callback($object, $this); - - return $value; - } - - if (null === $circularReferenceLimit) { - $circularReferenceLimit = $this->circularReferenceLimit; - } - - if (null !== $circularReferenceLimit && $this->countReferenceRegistry[$reference] >= $circularReferenceLimit) { - throw new CircularReferenceException(sprintf('A circular reference has been detected when mapping the object of type "%s" (configured limit: %d)', \is_object($object) ? \get_class($object) : 'array', $circularReferenceLimit)); - } - - // When no limit defined return the object referenced - ++$this->countReferenceRegistry[$reference]; - - return $this->referenceRegistry[$reference]; - } - - /** - * Get groups for this context. - */ - public function getGroups(): ?array - { - return $this->groups; - } - - /** - * Get current depth. - */ - public function getDepth(): int - { - return $this->depth; - } - - /** - * Set object to populate (by-pass target construction). - */ - public function setObjectToPopulate($object) - { - $this->object = $object; - } - - /** - * Get object to populate. - */ - public function getObjectToPopulate() - { - $object = $this->object; - - if (null !== $object) { - $this->object = null; - } - - return $object; - } - - /** - * Set circular reference limit. - */ - public function setCircularReferenceLimit(?int $circularReferenceLimit): void - { - $this->circularReferenceLimit = $circularReferenceLimit; - } - - /** - * Set circular reference handler. - */ - public function setCircularReferenceHandler(?callable $circularReferenceHandler): void - { - $this->circularReferenceHandler = $circularReferenceHandler; - } - - /** - * Create a new context with a new reference. - */ - public function withReference($reference, &$object): self - { - $new = clone $this; - - $new->referenceRegistry[$reference] = &$object; - $new->countReferenceRegistry[$reference] = 1; - - return $new; - } - - /** - * Check whether an attribute is allowed to be mapped. - */ - public function isAllowedAttribute(string $attribute): bool - { - if (null !== $this->ignoredAttributes && \in_array($attribute, $this->ignoredAttributes, true)) { - return false; - } - - if (null === $this->attributes) { - return true; - } - - return \in_array($attribute, $this->attributes, true); - } - - /** - * Clone context with a incremented depth. - */ - public function withIncrementedDepth(): self - { - $new = clone $this; - ++$new->depth; - - return $new; - } - - /** - * Set the argument of a constructor for a specific class. - */ - public function setConstructorArgument(string $class, string $key, $value): void - { - if ($this->constructorArguments[$class] ?? false) { - $this->constructorArguments[$class] = []; - } - - $this->constructorArguments[$class][$key] = $value; - } - - /** - * Check wether an argument exist for the constructor for a specific class. - */ - public function hasConstructorArgument(string $class, string $key): bool - { - return \array_key_exists($key, $this->constructorArguments[$class] ?? []); - } - - /** - * Get constructor argument for a specific class. - */ - public function getConstructorArgument(string $class, string $key) - { - return $this->constructorArguments[$class][$key]; - } - - /** - * Create a new cloned context, and reload attribute mapping for it. - */ - public function withNewContext(string $attribute): self - { - if (null === $this->attributes && null === $this->ignoredAttributes) { - return $this; - } - - $new = clone $this; - - if (null !== $this->ignoredAttributes && isset($this->ignoredAttributes[$attribute]) && \is_array($this->ignoredAttributes[$attribute])) { - $new->ignoredAttributes = $this->ignoredAttributes[$attribute]; - } - - if (null !== $this->attributes && isset($this->attributes[$attribute]) && \is_array($this->attributes[$attribute])) { - $new->attributes = $this->attributes[$attribute]; - } - - return $new; - } -} diff --git a/src/Symfony/Component/AutoMapper/GeneratedMapper.php b/src/Symfony/Component/AutoMapper/GeneratedMapper.php index 076c3c1c542e4..78c0b20ce1fa1 100644 --- a/src/Symfony/Component/AutoMapper/GeneratedMapper.php +++ b/src/Symfony/Component/AutoMapper/GeneratedMapper.php @@ -42,8 +42,6 @@ public function addCallback(string $name, callable $callback): void $this->callbacks[$name] = $callback; } - abstract public function &map($value, Context $context); - /** * Inject sub mappers. */ diff --git a/src/Symfony/Component/AutoMapper/Generator/Generator.php b/src/Symfony/Component/AutoMapper/Generator/Generator.php index e030a573d5e0c..db7d391149a04 100644 --- a/src/Symfony/Component/AutoMapper/Generator/Generator.php +++ b/src/Symfony/Component/AutoMapper/Generator/Generator.php @@ -20,10 +20,10 @@ use PhpParser\Parser; use PhpParser\ParserFactory; use Symfony\Component\AutoMapper\AutoMapperRegistryInterface; -use Symfony\Component\AutoMapper\Context; use Symfony\Component\AutoMapper\Exception\CompileException; use Symfony\Component\AutoMapper\Extractor\PropertyMapping; use Symfony\Component\AutoMapper\GeneratedMapper; +use Symfony\Component\AutoMapper\MapperContext; use Symfony\Component\AutoMapper\MapperGeneratorMetadataInterface; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorResolverInterface; @@ -77,12 +77,14 @@ public function generate(MapperGeneratorMetadataInterface $mapperGeneratorMetada ]), new Scalar\String_($mapperGeneratorMetadata->getTarget()) ))); - $statements[] = new Stmt\If_(new Expr\MethodCall($contextVariable, new Name('shouldHandleCircularReference'), [ + $statements[] = new Stmt\If_(new Expr\StaticCall(new Name\FullyQualified(MapperContext::class), new Name('shouldHandleCircularReference'), [ + new Arg($contextVariable), new Arg($hashVariable), new Arg(new Expr\PropertyFetch(new Expr\Variable('this'), 'circularReferenceLimit')), ]), [ 'stmts' => [ - new Stmt\Return_(new Expr\MethodCall($contextVariable, 'handleCircularReference', [ + new Stmt\Return_(new Expr\StaticCall(new Name\FullyQualified(MapperContext::class), 'handleCircularReference', [ + new Arg($contextVariable), new Arg($hashVariable), new Arg($sourceInput), new Arg(new Expr\PropertyFetch(new Expr\Variable('this'), 'circularReferenceLimit')), @@ -95,7 +97,10 @@ public function generate(MapperGeneratorMetadataInterface $mapperGeneratorMetada [$createObjectStmts, $inConstructor, $constructStatementsForCreateObjects, $injectMapperStatements] = $this->getCreateObjectStatements($mapperGeneratorMetadata, $result, $contextVariable, $sourceInput, $uniqueVariableScope); $constructStatements = array_merge($constructStatements, $constructStatementsForCreateObjects); - $statements[] = new Stmt\Expression(new Expr\Assign($result, new Expr\MethodCall($contextVariable, 'getObjectToPopulate'))); + $statements[] = new Stmt\Expression(new Expr\Assign($result, new Expr\BinaryOp\Coalesce( + new Expr\ArrayDimFetch($contextVariable, new Scalar\String_(MapperContext::TARGET_TO_POPULATE)), + new Expr\ConstFetch(new Name('null')) + ))); $statements[] = new Stmt\If_(new Expr\BinaryOp\Identical(new Expr\ConstFetch(new Name('null')), $result), [ 'stmts' => $createObjectStmts, ]); @@ -125,7 +130,8 @@ public function generate(MapperGeneratorMetadataInterface $mapperGeneratorMetada if ($canHaveCircularDependency) { $statements[] = new Stmt\Expression(new Expr\Assign( $contextVariable, - new Expr\MethodCall($contextVariable, 'withReference', [ + new Expr\StaticCall(new Name\FullyQualified(MapperContext::class), 'withReference', [ + new Arg($contextVariable), new Arg($hashVariable), new Arg($result), ]) @@ -134,7 +140,9 @@ public function generate(MapperGeneratorMetadataInterface $mapperGeneratorMetada $statements[] = new Stmt\Expression(new Expr\Assign( $contextVariable, - new Expr\MethodCall($contextVariable, 'withIncrementedDepth') + new Expr\StaticCall(new Name\FullyQualified(MapperContext::class), 'withIncrementedDepth', [ + new Arg($contextVariable), + ]) )); } @@ -190,7 +198,8 @@ public function generate(MapperGeneratorMetadataInterface $mapperGeneratorMetada } if ($mapperGeneratorMetadata->shouldCheckAttributes()) { - $conditions[] = new Expr\MethodCall($contextVariable, 'isAllowedAttribute', [ + $conditions[] = new Expr\StaticCall(new Name\FullyQualified(MapperContext::class), 'isAllowedAttribute', [ + new Arg($contextVariable), new Arg(new Scalar\String_($propertyMapping->getProperty())), ]); } @@ -199,10 +208,16 @@ public function generate(MapperGeneratorMetadataInterface $mapperGeneratorMetada $conditions[] = new Expr\BinaryOp\BooleanAnd( new Expr\BinaryOp\NotIdentical( new Expr\ConstFetch(new Name('null')), - new Expr\MethodCall(new Expr\Variable('context'), 'getGroups') + new Expr\BinaryOp\Coalesce( + new Expr\ArrayDimFetch($contextVariable, new Scalar\String_(MapperContext::GROUPS)), + new Expr\Array_() + ) ), new Expr\FuncCall(new Name('array_intersect'), [ - new Arg(new Expr\MethodCall(new Expr\Variable('context'), 'getGroups')), + new Arg(new Expr\BinaryOp\Coalesce( + new Expr\ArrayDimFetch($contextVariable, new Scalar\String_(MapperContext::GROUPS)), + new Expr\Array_() + )), new Arg(new Expr\Array_(array_map(function (string $group) { return new Expr\ArrayItem(new Scalar\String_($group)); }, $propertyMapping->getSourceGroups()))), @@ -214,10 +229,16 @@ public function generate(MapperGeneratorMetadataInterface $mapperGeneratorMetada $conditions[] = new Expr\BinaryOp\BooleanAnd( new Expr\BinaryOp\NotIdentical( new Expr\ConstFetch(new Name('null')), - new Expr\MethodCall(new Expr\Variable('context'), 'getGroups') + new Expr\BinaryOp\Coalesce( + new Expr\ArrayDimFetch($contextVariable, new Scalar\String_(MapperContext::GROUPS)), + new Expr\Array_() + ) ), new Expr\FuncCall(new Name('array_intersect'), [ - new Arg(new Expr\MethodCall(new Expr\Variable('context'), 'getGroups')), + new Arg(new Expr\BinaryOp\Coalesce( + new Expr\ArrayDimFetch($contextVariable, new Scalar\String_(MapperContext::GROUPS)), + new Expr\Array_() + )), new Arg(new Expr\Array_(array_map(function (string $group) { return new Expr\ArrayItem(new Scalar\String_($group)); }, $propertyMapping->getTargetGroups()))), @@ -227,7 +248,10 @@ public function generate(MapperGeneratorMetadataInterface $mapperGeneratorMetada if (null !== $propertyMapping->getMaxDepth()) { $conditions[] = new Expr\BinaryOp\SmallerOrEqual( - new Expr\MethodCall($contextVariable, 'getDepth'), + new Expr\BinaryOp\Coalesce( + new Expr\ArrayDimFetch($contextVariable, new Scalar\String_(MapperContext::DEPTH)), + new Expr\ConstFetch(new Name('0')) + ), new Scalar\LNumber($propertyMapping->getMaxDepth()) ); } @@ -255,7 +279,7 @@ public function generate(MapperGeneratorMetadataInterface $mapperGeneratorMetada 'flags' => Stmt\Class_::MODIFIER_PUBLIC, 'params' => [ new Param(new Expr\Variable($sourceInput->name)), - new Param(new Expr\Variable('context'), null, new Name\FullyQualified(Context::class)), + new Param(new Expr\Variable('context'), new Expr\Array_(), 'array'), ], 'byRef' => true, 'stmts' => $statements, @@ -355,12 +379,14 @@ private function getCreateObjectStatements(MapperGeneratorMetadataInterface $map $constructArguments[$parameter->getPosition()] = new Arg($constructVar); $propStatements[] = new Stmt\Expression(new Expr\Assign($constructVar, $output)); - $createObjectStatements[] = new Stmt\If_(new Expr\MethodCall($contextVariable, 'hasConstructorArgument', [ + $createObjectStatements[] = new Stmt\If_(new Expr\StaticCall(new Name\FullyQualified(MapperContext::class), 'hasConstructorArgument', [ + new Arg($contextVariable), new Arg(new Scalar\String_($target)), new Arg(new Scalar\String_($propertyMapping->getProperty())), ]), [ 'stmts' => [ - new Stmt\Expression(new Expr\Assign($constructVar, new Expr\MethodCall($contextVariable, 'getConstructorArgument', [ + new Stmt\Expression(new Expr\Assign($constructVar, new Expr\StaticCall(new Name\FullyQualified(MapperContext::class), 'getConstructorArgument', [ + new Arg($contextVariable), new Arg(new Scalar\String_($target)), new Arg(new Scalar\String_($propertyMapping->getProperty())), ]))), @@ -375,12 +401,14 @@ private function getCreateObjectStatements(MapperGeneratorMetadataInterface $map if (!\array_key_exists($constructorParameter->getPosition(), $constructArguments) && $constructorParameter->isDefaultValueAvailable()) { $constructVar = new Expr\Variable($uniqueVariableScope->getUniqueName('constructArg')); - $createObjectStatements[] = new Stmt\If_(new Expr\MethodCall($contextVariable, 'hasConstructorArgument', [ + $createObjectStatements[] = new Stmt\If_(new Expr\StaticCall(new Name\FullyQualified(MapperContext::class), 'hasConstructorArgument', [ + new Arg($contextVariable), new Arg(new Scalar\String_($target)), new Arg(new Scalar\String_($constructorParameter->getName())), ]), [ 'stmts' => [ - new Stmt\Expression(new Expr\Assign($constructVar, new Expr\MethodCall($contextVariable, 'getConstructorArgument', [ + new Stmt\Expression(new Expr\Assign($constructVar, new Expr\StaticCall(new Name\FullyQualified(MapperContext::class), 'getConstructorArgument', [ + new Arg($contextVariable), new Arg(new Scalar\String_($target)), new Arg(new Scalar\String_($constructorParameter->getName())), ]))), diff --git a/src/Symfony/Component/AutoMapper/Loader/EvalLoader.php b/src/Symfony/Component/AutoMapper/Loader/EvalLoader.php index 0da58aa4e2a49..d1e7c3806af96 100644 --- a/src/Symfony/Component/AutoMapper/Loader/EvalLoader.php +++ b/src/Symfony/Component/AutoMapper/Loader/EvalLoader.php @@ -16,7 +16,7 @@ use Symfony\Component\AutoMapper\MapperGeneratorMetadataInterface; /** - * Use eval to load mappers + * Use eval to load mappers. * * @expiremental * diff --git a/src/Symfony/Component/AutoMapper/MapperContext.php b/src/Symfony/Component/AutoMapper/MapperContext.php new file mode 100644 index 0000000000000..320c86323c8b3 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/MapperContext.php @@ -0,0 +1,242 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper; + +use Symfony\Component\AutoMapper\Exception\CircularReferenceException; + +/** + * Context for mapping. + * + * Allows to customize how is done the mapping + * + * @author Joel Wurtz + */ +class MapperContext +{ + public const GROUPS = 'groups'; + public const ALLOWED_ATTRIBUTES = 'allowed_attributes'; + public const IGNORED_ATTRIBUTES = 'ignored_attributes'; + public const CIRCULAR_REFERENCE_LIMIT = 'circular_reference_limit'; + public const CIRCULAR_REFERENCE_HANDLER = 'circular_reference_handler'; + public const CIRCULAR_REFERENCE_REGISTRY = 'circular_reference_registry'; + public const CIRCULAR_COUNT_REFERENCE_REGISTRY = 'circular_count_reference_registry'; + public const DEPTH = 'depth'; + public const TARGET_TO_POPULATE = 'target_to_populate'; + public const CONSTRUCTOR_ARGUMENTS = 'constructor_arguments'; + + private $context = [ + self::DEPTH => 0, + self::CIRCULAR_REFERENCE_REGISTRY => [], + self::CIRCULAR_COUNT_REFERENCE_REGISTRY => [], + self::CONSTRUCTOR_ARGUMENTS => [], + ]; + + public function toArray(): array + { + return $this->context; + } + + /** + * @return $this + */ + public function setGroups(?array $groups) + { + $this->context[self::GROUPS] = $groups; + + return $this; + } + + /** + * @return $this + */ + public function setAllowedAttributes(?array $allowedAttributes) + { + $this->context[self::ALLOWED_ATTRIBUTES] = $allowedAttributes; + + return $this; + } + + /** + * @return $this + */ + public function setIgnoredAttributes(?array $ignoredAttributes) + { + $this->context[self::IGNORED_ATTRIBUTES] = $ignoredAttributes; + + return $this; + } + + /** + * @return $this + */ + public function setCircularReferenceLimit(?int $circularReferenceLimit) + { + $this->context[self::CIRCULAR_REFERENCE_LIMIT] = $circularReferenceLimit; + + return $this; + } + + /** + * @return $this + */ + public function setCircularReferenceHandler(?callable $circularReferenceHandler) + { + $this->context[self::CIRCULAR_REFERENCE_HANDLER] = $circularReferenceHandler; + + return $this; + } + + /** + * @return $this + */ + public function setTargetToPopulate($target) + { + $this->context[self::TARGET_TO_POPULATE] = $target; + + return $this; + } + + /** + * @return $this + */ + public function setConstructorArgument(string $class, string $key, $value): void + { + $this->context[self::CONSTRUCTOR_ARGUMENTS][$class][$key] = $value; + } + + /** + * Whether a reference has reached it's limit. + */ + public static function shouldHandleCircularReference(array $context, string $reference, ?int $circularReferenceLimit = null): bool + { + if (!\array_key_exists($reference, $context[self::CIRCULAR_REFERENCE_REGISTRY] ?? [])) { + return false; + } + + if (null === $circularReferenceLimit) { + $circularReferenceLimit = $context[self::CIRCULAR_REFERENCE_LIMIT] ?? null; + } + + if (null !== $circularReferenceLimit) { + return $circularReferenceLimit <= ($context[self::CIRCULAR_COUNT_REFERENCE_REGISTRY][$reference] ?? 0); + } + + return true; + } + + /** + * Handle circular reference for a specific reference. + * + * By default will try to keep it and return the previous value + * + * @return mixed + */ + public static function &handleCircularReference(array $context, string $reference, $object, ?int $circularReferenceLimit = null, callable $callback = null) + { + if (null === $callback) { + $callback = $context[self::CIRCULAR_REFERENCE_HANDLER] ?? null; + } + + if (null !== $callback) { + // Cannot directly return here, as we need to return by reference, and callback may not be declared as reference return + $value = $callback($object, $context); + + return $value; + } + + if (null === $circularReferenceLimit) { + $circularReferenceLimit = $context[self::CIRCULAR_REFERENCE_LIMIT] ?? null; + } + + if (null !== $circularReferenceLimit && $circularReferenceLimit <= ($context[self::CIRCULAR_COUNT_REFERENCE_REGISTRY][$reference] ?? 0)) { + throw new CircularReferenceException(sprintf('A circular reference has been detected when mapping the object of type "%s" (configured limit: %d)', \is_object($object) ? \get_class($object) : 'array', $circularReferenceLimit)); + } + + // When no limit defined return the object referenced + return $context[self::CIRCULAR_REFERENCE_REGISTRY][$reference]; + } + + /** + * Create a new context with a new reference. + */ + public static function withReference(array $context, string $reference, &$object): array + { + $context[self::CIRCULAR_REFERENCE_REGISTRY][$reference] = &$object; + $context[self::CIRCULAR_COUNT_REFERENCE_REGISTRY][$reference] = $context[self::CIRCULAR_COUNT_REFERENCE_REGISTRY][$reference] ?? 0; + $context[self::CIRCULAR_COUNT_REFERENCE_REGISTRY][$reference]++; + + return $context; + } + + /** + * Check whether an attribute is allowed to be mapped. + */ + public static function isAllowedAttribute(array $context, string $attribute): bool + { + if (($context[self::IGNORED_ATTRIBUTES] ?? false) && \in_array($attribute, $context[self::IGNORED_ATTRIBUTES], true)) { + return false; + } + + if (!($context[self::ALLOWED_ATTRIBUTES] ?? false)) { + return true; + } + + return \in_array($attribute, $context[self::ALLOWED_ATTRIBUTES], true); + } + + /** + * Clone context with a incremented depth. + */ + public static function withIncrementedDepth(array $context): array + { + $context[self::DEPTH] = $context[self::DEPTH] ?? 0; + $context[self::DEPTH]++; + + return $context; + } + + /** + * Check wether an argument exist for the constructor for a specific class. + */ + public static function hasConstructorArgument(array $context, string $class, string $key): bool + { + return \array_key_exists($key, $context[self::CONSTRUCTOR_ARGUMENTS][$class] ?? []); + } + + /** + * Get constructor argument for a specific class. + */ + public static function getConstructorArgument(array $context, string $class, string $key) + { + return $context[self::CONSTRUCTOR_ARGUMENTS][$class][$key] ?? null; + } + + /** + * Create a new context, and reload attribute mapping for it. + */ + public static function withNewContext(array $context, string $attribute): array + { + if (!($context[self::ALLOWED_ATTRIBUTES] ?? false) && !($context[self::IGNORED_ATTRIBUTES] ?? false)) { + return $context; + } + + if (\is_array($context[self::IGNORED_ATTRIBUTES][$attribute] ?? false)) { + $context[self::IGNORED_ATTRIBUTES] = $context[self::IGNORED_ATTRIBUTES][$attribute]; + } + + if (\is_array($context[self::ALLOWED_ATTRIBUTES][$attribute] ?? false)) { + $context[self::ALLOWED_ATTRIBUTES] = $context[self::ALLOWED_ATTRIBUTES][$attribute]; + } + + return $context; + } +} diff --git a/src/Symfony/Component/AutoMapper/MapperInterface.php b/src/Symfony/Component/AutoMapper/MapperInterface.php index 4c66b5d3dc48f..778bbfaa0a67b 100644 --- a/src/Symfony/Component/AutoMapper/MapperInterface.php +++ b/src/Symfony/Component/AutoMapper/MapperInterface.php @@ -24,9 +24,9 @@ interface MapperInterface { /** * @param mixed $value Value to map - * @param Context $context Options mapper have access to + * @param array $context Options mapper have access to * * @return mixed The mapped value */ - public function &map($value, Context $context); + public function &map($value, array $context = []); } diff --git a/src/Symfony/Component/AutoMapper/Tests/AutoMapperTest.php b/src/Symfony/Component/AutoMapper/Tests/AutoMapperTest.php index 69924842901a6..05b39f2916580 100644 --- a/src/Symfony/Component/AutoMapper/Tests/AutoMapperTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/AutoMapperTest.php @@ -15,7 +15,7 @@ use PhpParser\ParserFactory; use Symfony\Bundle\FrameworkBundle\Tests\TestCase; use Symfony\Component\AutoMapper\AutoMapper; -use Symfony\Component\AutoMapper\Context; +use Symfony\Component\AutoMapper\MapperContext; use Symfony\Component\AutoMapper\Exception\CircularReferenceException; use Symfony\Component\AutoMapper\Generator\Generator; use Symfony\Component\AutoMapper\Loader\FileLoader; @@ -145,22 +145,22 @@ public function testGroupsSourceTarget() $foo = new Fixtures\Foo(); $foo->setId(10); - $bar = $this->autoMapper->map($foo, Bar::class, new Context(['group2'])); + $bar = $this->autoMapper->map($foo, Bar::class, [MapperContext::GROUPS => ['group2']]); self::assertInstanceOf(Bar::class, $bar); self::assertEquals(10, $bar->id); - $bar = $this->autoMapper->map($foo, Bar::class, new Context(['group1', 'group3'])); + $bar = $this->autoMapper->map($foo, Bar::class, [MapperContext::GROUPS => ['group1', 'group3']]); self::assertInstanceOf(Bar::class, $bar); self::assertEquals(10, $bar->id); - $bar = $this->autoMapper->map($foo, Bar::class, new Context(['group1'])); + $bar = $this->autoMapper->map($foo, Bar::class, [MapperContext::GROUPS => ['group1']]); self::assertInstanceOf(Bar::class, $bar); self::assertNull($bar->id); - $bar = $this->autoMapper->map($foo, Bar::class, new Context([])); + $bar = $this->autoMapper->map($foo, Bar::class, [MapperContext::GROUPS => []]); self::assertInstanceOf(Bar::class, $bar); self::assertNull($bar->id); @@ -176,12 +176,12 @@ public function testGroupsToArray() $foo = new Fixtures\Foo(); $foo->setId(10); - $fooArray = $this->autoMapper->map($foo, 'array', new Context(['group1'])); + $fooArray = $this->autoMapper->map($foo, 'array', [MapperContext::GROUPS => ['group1']]); self::assertInternalType('array', $fooArray); self::assertEquals(10, $fooArray['id']); - $fooArray = $this->autoMapper->map($foo, 'array', new Context([])); + $fooArray = $this->autoMapper->map($foo, 'array', [MapperContext::GROUPS => []]); self::assertInternalType('array', $fooArray); self::assertArrayNotHasKey('id', $fooArray); @@ -322,10 +322,8 @@ public function testObjectToPopulate() $user = new Fixtures\User(1, 'yolo', '13'); $userDtoToPopulate = new Fixtures\UserDTO(); - $context = new Context(); - $context->setObjectToPopulate($userDtoToPopulate); - $userDto = $this->autoMapper->map($user, Fixtures\UserDTO::class, $context); + $userDto = $this->autoMapper->map($user, Fixtures\UserDTO::class, [MapperContext::TARGET_TO_POPULATE => $userDtoToPopulate]); self::assertSame($userDtoToPopulate, $userDto); } @@ -367,12 +365,12 @@ public function testCircularReferenceLimitOnContext() $nodeA = new Fixtures\Node(); $nodeA->parent = $nodeA; - $context = new Context(); + $context = new MapperContext(); $context->setCircularReferenceLimit(1); $this->expectException(CircularReferenceException::class); - $this->autoMapper->map($nodeA, 'array', $context); + $this->autoMapper->map($nodeA, 'array', $context->toArray()); } public function testCircularReferenceLimitOnMapper() @@ -385,7 +383,7 @@ public function testCircularReferenceLimitOnMapper() $this->expectException(CircularReferenceException::class); - $mapper->map($nodeA, new Context()); + $mapper->map($nodeA); } public function testCircularReferenceHandlerOnContext() @@ -393,12 +391,12 @@ public function testCircularReferenceHandlerOnContext() $nodeA = new Fixtures\Node(); $nodeA->parent = $nodeA; - $context = new Context(); + $context = new MapperContext(); $context->setCircularReferenceHandler(function () { return 'foo'; }); - $nodeArray = $this->autoMapper->map($nodeA, 'array', $context); + $nodeArray = $this->autoMapper->map($nodeA, 'array', $context->toArray()); self::assertSame('foo', $nodeArray['parent']); } @@ -413,7 +411,7 @@ public function testCircularReferenceHandlerOnMapper() return 'foo'; }); - $nodeArray = $mapper->map($nodeA, new Context()); + $nodeArray = $mapper->map($nodeA); self::assertSame('foo', $nodeArray['parent']); } @@ -426,9 +424,8 @@ public function testAllowedAttributes() }); $user = new Fixtures\User(1, 'yolo', '13'); - $context = new Context(null, ['id', 'age'], null); - $userDto = $this->autoMapper->map($user, Fixtures\UserDTO::class, $context); + $userDto = $this->autoMapper->map($user, Fixtures\UserDTO::class, [MapperContext::ALLOWED_ATTRIBUTES => ['id' ,'age']]); self::assertNull($userDto->getName()); } @@ -441,9 +438,7 @@ public function testIgnoredAttributes() }); $user = new Fixtures\User(1, 'yolo', '13'); - $context = new Context(null, null, ['name']); - - $userDto = $this->autoMapper->map($user, Fixtures\UserDTO::class, $context); + $userDto = $this->autoMapper->map($user, Fixtures\UserDTO::class, [MapperContext::IGNORED_ATTRIBUTES => ['name']]); self::assertNull($userDto->getName()); } @@ -473,7 +468,7 @@ public function denormalize($propertyName, string $class = null, string $format $autoMapper = AutoMapper::create(true, null, $nameConverter, 'Mapper2_'); $user = new Fixtures\User(1, 'yolo', '13'); - $userArray = $autoMapper->map($user, 'array', new Context()); + $userArray = $autoMapper->map($user, 'array'); self::assertInternalType('array', $userArray); self::assertArrayHasKey('@id', $userArray); @@ -486,10 +481,10 @@ public function testDefaultArguments() $user->id = 10; $user->name = 'foo'; /** @var UserConstructorDTO $userDto */ - $context = new Context(); + $context = new MapperContext(); $context->setConstructorArgument(Fixtures\UserConstructorDTO::class, 'age', 50); - $userDto = $this->autoMapper->map($user, Fixtures\UserConstructorDTO::class, $context); + $userDto = $this->autoMapper->map($user, Fixtures\UserConstructorDTO::class, $context->toArray()); self::assertInstanceOf(Fixtures\UserConstructorDTO::class, $userDto); self::assertSame(50, $userDto->getAge()); diff --git a/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformer.php index 05835755818da..3142d862b754a 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformer.php @@ -13,9 +13,11 @@ use PhpParser\Node\Arg; use PhpParser\Node\Expr; +use PhpParser\Node\Name; use PhpParser\Node\Scalar; use Symfony\Component\AutoMapper\Extractor\PropertyMapping; use Symfony\Component\AutoMapper\Generator\UniqueVariableScope; +use Symfony\Component\AutoMapper\MapperContext; use Symfony\Component\PropertyInfo\Type; /** @@ -49,7 +51,8 @@ public function transform(Expr $input, PropertyMapping $propertyMapping, UniqueV new Scalar\String_($mapperName) ), 'map', [ new Arg($input), - new Arg(new Expr\MethodCall(new Expr\Variable('context'), 'withNewContext', [ + new Arg(new Expr\StaticCall(new Name\FullyQualified(MapperContext::class), 'withNewContext', [ + new Arg(new Expr\Variable('context')), new Arg(new Scalar\String_($propertyMapping->getProperty())), ])), ]), []]; From ec97f74603849ed50bedbbc45e6bef3eb9d14b54 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Tue, 26 Mar 2019 01:43:39 +0100 Subject: [PATCH 15/38] Use new context construction in normalizer bridge --- .../AutoMapper/AutoMapperNormalizer.php | 32 ++++++++----------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php b/src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php index 94fe164361322..6e665f5c9a459 100644 --- a/src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php +++ b/src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php @@ -14,6 +14,7 @@ use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; +use ZendTest\Code\Scanner\TestAsset\MapperExample\Mapper; /** * Bridge for symfony/serializer. @@ -64,32 +65,25 @@ public function hasCacheableSupportsMethod(): bool return true; } - private function createAutoMapperContext(array $serializerContext = []): MapperContext + private function createAutoMapperContext(array $serializerContext = []): array { - $circularReferenceLimit = 1; - - if (isset($serializerContext[AbstractNormalizer::CIRCULAR_REFERENCE_LIMIT]) && \is_int($serializerContext[AbstractNormalizer::CIRCULAR_REFERENCE_LIMIT])) { - $circularReferenceLimit = $serializerContext[AbstractNormalizer::CIRCULAR_REFERENCE_LIMIT]; - } - - $context = new MapperContext( - $serializerContext[AbstractNormalizer::GROUPS] ?? null, - $serializerContext[AbstractNormalizer::ATTRIBUTES] ?? null, - $serializerContextContext[AbstractNormalizer::IGNORED_ATTRIBUTES] ?? null - ); - - if (isset($serializerContext[AbstractNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS])) { + $context = [ + MapperContext::GROUPS => $serializerContext[AbstractNormalizer::GROUPS] ?? null, + MapperContext::ALLOWED_ATTRIBUTES => $serializerContext[AbstractNormalizer::ATTRIBUTES] ?? null, + MapperContext::IGNORED_ATTRIBUTES => $serializerContext[AbstractNormalizer::IGNORED_ATTRIBUTES] ?? null, + MapperContext::TARGET_TO_POPULATE => $serializerContext[AbstractNormalizer::OBJECT_TO_POPULATE] ?? null, + MapperContext::CIRCULAR_REFERENCE_LIMIT => $serializerContext[AbstractNormalizer::CIRCULAR_REFERENCE_LIMIT] ?? 1, + MapperContext::CIRCULAR_REFERENCE_HANDLER => $serializerContext[AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER] ?? null, + ]; + + if ($serializerContext[AbstractNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS]) { foreach ($serializerContext[AbstractNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS] as $class => $keyArgs) { foreach ($keyArgs as $key => $value) { - $context->setConstructorArgument($class, $key, $value); + $context[MapperContext::CONSTRUCTOR_ARGUMENTS][$class][$key] = $value; } } } - $context->setCircularReferenceLimit($circularReferenceLimit); - $context->setObjectToPopulate($serializerContext[AbstractNormalizer::OBJECT_TO_POPULATE] ?? null); - $context->setCircularReferenceHandler($serializerContext[AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER] ?? null); - return $context; } } From 92784b15fe77b38d88e2d70f9c38370814874918 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Tue, 26 Mar 2019 01:52:02 +0100 Subject: [PATCH 16/38] Fix test case class test --- src/Symfony/Component/AutoMapper/Tests/AutoMapperTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/AutoMapper/Tests/AutoMapperTest.php b/src/Symfony/Component/AutoMapper/Tests/AutoMapperTest.php index 05b39f2916580..aaab633490923 100644 --- a/src/Symfony/Component/AutoMapper/Tests/AutoMapperTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/AutoMapperTest.php @@ -13,7 +13,7 @@ use Doctrine\Common\Annotations\AnnotationReader; use PhpParser\ParserFactory; -use Symfony\Bundle\FrameworkBundle\Tests\TestCase; +use PHPUnit\Framework\TestCase; use Symfony\Component\AutoMapper\AutoMapper; use Symfony\Component\AutoMapper\MapperContext; use Symfony\Component\AutoMapper\Exception\CircularReferenceException; From eb6f638e4941230c60b0e24bfbff9be57a6511e2 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Tue, 26 Mar 2019 01:54:57 +0100 Subject: [PATCH 17/38] Remove useless class --- src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php b/src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php index 6e665f5c9a459..f56621b72941b 100644 --- a/src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php +++ b/src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php @@ -14,7 +14,6 @@ use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; -use ZendTest\Code\Scanner\TestAsset\MapperExample\Mapper; /** * Bridge for symfony/serializer. From fab7135b99f3908997215204b2542fc80c76e6a7 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Tue, 26 Mar 2019 02:13:04 +0100 Subject: [PATCH 18/38] expiremental > expiremental in 4.3 --- src/Symfony/Component/AutoMapper/AutoMapper.php | 2 +- src/Symfony/Component/AutoMapper/AutoMapperInterface.php | 2 +- src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php | 2 +- .../AutoMapper/Exception/CircularReferenceException.php | 2 +- .../Component/AutoMapper/Exception/CompileException.php | 2 +- .../AutoMapper/Exception/InvalidMappingException.php | 2 +- .../AutoMapper/Exception/NoMappingFoundException.php | 2 +- .../AutoMapper/Extractor/FromSourceMappingExtractor.php | 2 +- .../AutoMapper/Extractor/FromTargetMappingExtractor.php | 2 +- .../Component/AutoMapper/Extractor/PropertyMapping.php | 2 +- .../AutoMapper/Extractor/SourceTargetMappingExtractor.php | 2 +- src/Symfony/Component/AutoMapper/Generator/Generator.php | 2 +- src/Symfony/Component/AutoMapper/Loader/EvalLoader.php | 2 +- src/Symfony/Component/AutoMapper/Loader/FileLoader.php | 2 +- .../Component/AutoMapper/MapperGeneratorMetadataFactory.php | 2 +- .../AutoMapper/MapperGeneratorMetadataFactoryInterface.php | 2 +- src/Symfony/Component/AutoMapper/MapperMetadata.php | 2 +- src/Symfony/Component/AutoMapper/README.md | 5 +++++ .../Component/AutoMapper/Transformer/ArrayTransformer.php | 2 +- .../AutoMapper/Transformer/ArrayTransformerFactory.php | 2 +- .../Component/AutoMapper/Transformer/BuiltinTransformer.php | 2 +- .../AutoMapper/Transformer/BuiltinTransformerFactory.php | 2 +- .../Component/AutoMapper/Transformer/CallbackTransformer.php | 2 +- .../AutoMapper/Transformer/ChainTransformerFactory.php | 2 +- .../Component/AutoMapper/Transformer/CopyTransformer.php | 2 +- .../AutoMapper/Transformer/DateTimeToStringTansformer.php | 2 +- .../AutoMapper/Transformer/DateTimeTransformerFactory.php | 2 +- .../Component/AutoMapper/Transformer/MultipleTransformer.php | 2 +- .../AutoMapper/Transformer/MultipleTransformerFactory.php | 2 +- .../Component/AutoMapper/Transformer/NullableTransformer.php | 2 +- .../AutoMapper/Transformer/NullableTransformerFactory.php | 2 +- .../Component/AutoMapper/Transformer/ObjectTransformer.php | 2 +- .../AutoMapper/Transformer/ObjectTransformerFactory.php | 2 +- .../AutoMapper/Transformer/StringToDateTimeTransformer.php | 2 +- 34 files changed, 38 insertions(+), 33 deletions(-) diff --git a/src/Symfony/Component/AutoMapper/AutoMapper.php b/src/Symfony/Component/AutoMapper/AutoMapper.php index c68884efb02e2..0d26c94362291 100644 --- a/src/Symfony/Component/AutoMapper/AutoMapper.php +++ b/src/Symfony/Component/AutoMapper/AutoMapper.php @@ -40,7 +40,7 @@ /** * Maps a source data structure (object or array) to a target one. * - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/AutoMapperInterface.php b/src/Symfony/Component/AutoMapper/AutoMapperInterface.php index 70e121ba252f1..3e805f7d1686c 100644 --- a/src/Symfony/Component/AutoMapper/AutoMapperInterface.php +++ b/src/Symfony/Component/AutoMapper/AutoMapperInterface.php @@ -14,7 +14,7 @@ /** * An auto mapper has the role of mapping a source to a target. * - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php b/src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php index f56621b72941b..fc4052e232a4f 100644 --- a/src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php +++ b/src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php @@ -18,7 +18,7 @@ /** * Bridge for symfony/serializer. * - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Exception/CircularReferenceException.php b/src/Symfony/Component/AutoMapper/Exception/CircularReferenceException.php index 540a275db3b7c..1334b22a5d352 100644 --- a/src/Symfony/Component/AutoMapper/Exception/CircularReferenceException.php +++ b/src/Symfony/Component/AutoMapper/Exception/CircularReferenceException.php @@ -12,7 +12,7 @@ namespace Symfony\Component\AutoMapper\Exception; /** - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Exception/CompileException.php b/src/Symfony/Component/AutoMapper/Exception/CompileException.php index 35d7091814b3c..3064f389edc99 100644 --- a/src/Symfony/Component/AutoMapper/Exception/CompileException.php +++ b/src/Symfony/Component/AutoMapper/Exception/CompileException.php @@ -12,7 +12,7 @@ namespace Symfony\Component\AutoMapper\Exception; /** - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Exception/InvalidMappingException.php b/src/Symfony/Component/AutoMapper/Exception/InvalidMappingException.php index c265e9ce8724a..68e57093edb7d 100644 --- a/src/Symfony/Component/AutoMapper/Exception/InvalidMappingException.php +++ b/src/Symfony/Component/AutoMapper/Exception/InvalidMappingException.php @@ -12,7 +12,7 @@ namespace Symfony\Component\AutoMapper\Exception; /** - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Exception/NoMappingFoundException.php b/src/Symfony/Component/AutoMapper/Exception/NoMappingFoundException.php index 4bd6f6d6aec2e..e92f5b4ecc489 100644 --- a/src/Symfony/Component/AutoMapper/Exception/NoMappingFoundException.php +++ b/src/Symfony/Component/AutoMapper/Exception/NoMappingFoundException.php @@ -12,7 +12,7 @@ namespace Symfony\Component\AutoMapper\Exception; /** - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Extractor/FromSourceMappingExtractor.php b/src/Symfony/Component/AutoMapper/Extractor/FromSourceMappingExtractor.php index 744eba8423664..6c87741c2652c 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/FromSourceMappingExtractor.php +++ b/src/Symfony/Component/AutoMapper/Extractor/FromSourceMappingExtractor.php @@ -24,7 +24,7 @@ * * Can use a NameConverter to use specific properties name in the target * - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Extractor/FromTargetMappingExtractor.php b/src/Symfony/Component/AutoMapper/Extractor/FromTargetMappingExtractor.php index 18c45f32ae9b0..e3a8509b745b1 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/FromTargetMappingExtractor.php +++ b/src/Symfony/Component/AutoMapper/Extractor/FromTargetMappingExtractor.php @@ -24,7 +24,7 @@ * * Can use a NameConverter to use specific properties name in the source * - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Extractor/PropertyMapping.php b/src/Symfony/Component/AutoMapper/Extractor/PropertyMapping.php index 1316518f97075..25649867f6f03 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/PropertyMapping.php +++ b/src/Symfony/Component/AutoMapper/Extractor/PropertyMapping.php @@ -16,7 +16,7 @@ /** * Property mapping. * - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php b/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php index 65386e19cf8ba..e52fd348a2a8c 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php +++ b/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php @@ -16,7 +16,7 @@ /** * Extracts mapping between two objects, only gives properties that have the same name. * - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Generator/Generator.php b/src/Symfony/Component/AutoMapper/Generator/Generator.php index db7d391149a04..d889c49a033e6 100644 --- a/src/Symfony/Component/AutoMapper/Generator/Generator.php +++ b/src/Symfony/Component/AutoMapper/Generator/Generator.php @@ -31,7 +31,7 @@ /** * Generates code for a mapping class. * - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Loader/EvalLoader.php b/src/Symfony/Component/AutoMapper/Loader/EvalLoader.php index d1e7c3806af96..b48001b6cb6f8 100644 --- a/src/Symfony/Component/AutoMapper/Loader/EvalLoader.php +++ b/src/Symfony/Component/AutoMapper/Loader/EvalLoader.php @@ -18,7 +18,7 @@ /** * Use eval to load mappers. * - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Loader/FileLoader.php b/src/Symfony/Component/AutoMapper/Loader/FileLoader.php index 6e64c1a492467..55d8a3e49d560 100644 --- a/src/Symfony/Component/AutoMapper/Loader/FileLoader.php +++ b/src/Symfony/Component/AutoMapper/Loader/FileLoader.php @@ -18,7 +18,7 @@ /** * Use file system to load mapper, and persist them using a registry * - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactory.php b/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactory.php index 1c4ba9f60a215..d013085e74391 100644 --- a/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactory.php +++ b/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactory.php @@ -18,7 +18,7 @@ /** * Metadata factory, used to autoregistering new mapping without creating them. * - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactoryInterface.php b/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactoryInterface.php index 7dc2647cc19f7..0818529deb894 100644 --- a/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactoryInterface.php +++ b/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactoryInterface.php @@ -14,7 +14,7 @@ /** * Metadata factory, used to autoregistering new mapping without creating them. * - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/MapperMetadata.php b/src/Symfony/Component/AutoMapper/MapperMetadata.php index ddc97444c899b..99db0007a747e 100644 --- a/src/Symfony/Component/AutoMapper/MapperMetadata.php +++ b/src/Symfony/Component/AutoMapper/MapperMetadata.php @@ -20,7 +20,7 @@ /** * Mapper metadata. * - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/README.md b/src/Symfony/Component/AutoMapper/README.md index dd85a41fb6cbc..6ed424aedc4d3 100644 --- a/src/Symfony/Component/AutoMapper/README.md +++ b/src/Symfony/Component/AutoMapper/README.md @@ -3,6 +3,11 @@ AutoMapper Component The AutoMapper component maps data between different domains. +**This Component is experimental**. +[Experimental features](https://symfony.com/doc/current/contributing/code/experimental.html) +are not covered by Symfony's +[Backward Compatibility Promise](https://symfony.com/doc/current/contributing/code/bc.html). + Resources --------- diff --git a/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformer.php index bcea040f9f4df..e7a8af745d135 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformer.php @@ -19,7 +19,7 @@ /** * Transformer array decorator. * - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformerFactory.php index 99b288fbddea9..cc4a926ea5303 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformerFactory.php +++ b/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformerFactory.php @@ -17,7 +17,7 @@ /** * Create a decorated transformer to handle array type. * - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformer.php index 1be46719d4bd3..a35543e1ca76c 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformer.php @@ -22,7 +22,7 @@ /** * Built in transformer to handle PHP scalar types. * - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformerFactory.php index 8686fec915343..8bc03b397bb8a 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformerFactory.php +++ b/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformerFactory.php @@ -15,7 +15,7 @@ use Symfony\Component\PropertyInfo\Type; /** - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/CallbackTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/CallbackTransformer.php index db423dc0ab312..891e2f93360ec 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/CallbackTransformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/CallbackTransformer.php @@ -20,7 +20,7 @@ /** * Handle custom callback transformation. * - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/ChainTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/ChainTransformerFactory.php index 840fb483b96b2..7571d14ecc368 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/ChainTransformerFactory.php +++ b/src/Symfony/Component/AutoMapper/Transformer/ChainTransformerFactory.php @@ -14,7 +14,7 @@ use Symfony\Component\AutoMapper\MapperMetadataInterface; /** - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/CopyTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/CopyTransformer.php index 41e53a07dd993..35503d3a8e047 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/CopyTransformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/CopyTransformer.php @@ -18,7 +18,7 @@ /** * Does not do any transformation, output = input. * - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/DateTimeToStringTansformer.php b/src/Symfony/Component/AutoMapper/Transformer/DateTimeToStringTansformer.php index 51a3d9ddaad6a..1b98be6468516 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/DateTimeToStringTansformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/DateTimeToStringTansformer.php @@ -20,7 +20,7 @@ /** * Transform a \DateTimeInterface object to a string. * - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/DateTimeTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/DateTimeTransformerFactory.php index 8cdda8230bc8a..2af6a6ebfc505 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/DateTimeTransformerFactory.php +++ b/src/Symfony/Component/AutoMapper/Transformer/DateTimeTransformerFactory.php @@ -15,7 +15,7 @@ use Symfony\Component\PropertyInfo\Type; /** - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformer.php index 23097b759c550..330b5172fde6c 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformer.php @@ -25,7 +25,7 @@ * Decorate transformers with condition to handle property with multiples source types * It will always use the first target type possible for transformation * - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformerFactory.php index f4121c09e55ce..686f93b2f10c1 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformerFactory.php +++ b/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformerFactory.php @@ -14,7 +14,7 @@ use Symfony\Component\AutoMapper\MapperMetadataInterface; /** - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/NullableTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/NullableTransformer.php index 9a4f421cb8946..3bfbef33edc20 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/NullableTransformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/NullableTransformer.php @@ -20,7 +20,7 @@ /** * Tansformer decorator to handle null values. * - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/NullableTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/NullableTransformerFactory.php index 641ead4e248b0..491c00afdcc32 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/NullableTransformerFactory.php +++ b/src/Symfony/Component/AutoMapper/Transformer/NullableTransformerFactory.php @@ -15,7 +15,7 @@ use Symfony\Component\PropertyInfo\Type; /** - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformer.php index 3142d862b754a..9d0628969497d 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformer.php @@ -23,7 +23,7 @@ /** * Transform to an object which can be mapped by AutoMapper (sub mapping). * - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformerFactory.php index 409a06c0390b0..f63c02abef9f7 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformerFactory.php +++ b/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformerFactory.php @@ -16,7 +16,7 @@ use Symfony\Component\PropertyInfo\Type; /** - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/StringToDateTimeTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/StringToDateTimeTransformer.php index 2287b0c858408..91b100aed7412 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/StringToDateTimeTransformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/StringToDateTimeTransformer.php @@ -21,7 +21,7 @@ /** * Transform a string to a \DateTimeInterface object. * - * @expiremental + * @expiremental in 4.3 * * @author Joel Wurtz */ From 5154440f804eb1a4898e139821b1fb762a98290f Mon Sep 17 00:00:00 2001 From: Baptiste Leduc Date: Tue, 31 Dec 2019 18:41:24 +0100 Subject: [PATCH 19/38] Fixing tests --- .../Loader/ClassLoaderInterface.php | 3 - .../AutoMapper/Loader/FileLoader.php | 2 +- .../Component/AutoMapper/MapperContext.php | 4 +- .../Component/AutoMapper/MapperInterface.php | 4 +- .../AutoMapper/Tests/AutoMapperTest.php | 167 +++++++++--------- .../Extractor/ReflectionExtractorTest.php | 20 +-- .../AutoMapper/Tests/Fixtures/Bar.php | 14 +- .../AutoMapper/Tests/Fixtures/Foo.php | 2 +- .../AutoMapper/Tests/Fixtures/FooMaxDepth.php | 6 +- .../AutoMapper/Tests/Fixtures/Node.php | 8 +- .../Tests/Fixtures/PrivateUserDTO.php | 1 - .../Generator/UniqueVariableScopeTest.php | 2 +- .../Transformer/TransformerInterface.php | 2 - .../Component/AutoMapper/composer.json | 2 +- .../Component/AutoMapper/phpunit.xml.dist | 1 - 15 files changed, 121 insertions(+), 117 deletions(-) diff --git a/src/Symfony/Component/AutoMapper/Loader/ClassLoaderInterface.php b/src/Symfony/Component/AutoMapper/Loader/ClassLoaderInterface.php index 5efd88cc12221..cd995ccf5d4ba 100644 --- a/src/Symfony/Component/AutoMapper/Loader/ClassLoaderInterface.php +++ b/src/Symfony/Component/AutoMapper/Loader/ClassLoaderInterface.php @@ -18,8 +18,5 @@ */ interface ClassLoaderInterface { - /** - * @param MapperGeneratorMetadataInterface $mapperMetadata - */ public function loadClass(MapperGeneratorMetadataInterface $mapperMetadata): void; } diff --git a/src/Symfony/Component/AutoMapper/Loader/FileLoader.php b/src/Symfony/Component/AutoMapper/Loader/FileLoader.php index 55d8a3e49d560..48f7f69c80858 100644 --- a/src/Symfony/Component/AutoMapper/Loader/FileLoader.php +++ b/src/Symfony/Component/AutoMapper/Loader/FileLoader.php @@ -16,7 +16,7 @@ use Symfony\Component\AutoMapper\MapperGeneratorMetadataInterface; /** - * Use file system to load mapper, and persist them using a registry + * Use file system to load mapper, and persist them using a registry. * * @expiremental in 4.3 * diff --git a/src/Symfony/Component/AutoMapper/MapperContext.php b/src/Symfony/Component/AutoMapper/MapperContext.php index 320c86323c8b3..a88e1d0bc0e92 100644 --- a/src/Symfony/Component/AutoMapper/MapperContext.php +++ b/src/Symfony/Component/AutoMapper/MapperContext.php @@ -172,7 +172,7 @@ public static function withReference(array $context, string $reference, &$object { $context[self::CIRCULAR_REFERENCE_REGISTRY][$reference] = &$object; $context[self::CIRCULAR_COUNT_REFERENCE_REGISTRY][$reference] = $context[self::CIRCULAR_COUNT_REFERENCE_REGISTRY][$reference] ?? 0; - $context[self::CIRCULAR_COUNT_REFERENCE_REGISTRY][$reference]++; + ++$context[self::CIRCULAR_COUNT_REFERENCE_REGISTRY][$reference]; return $context; } @@ -199,7 +199,7 @@ public static function isAllowedAttribute(array $context, string $attribute): bo public static function withIncrementedDepth(array $context): array { $context[self::DEPTH] = $context[self::DEPTH] ?? 0; - $context[self::DEPTH]++; + ++$context[self::DEPTH]; return $context; } diff --git a/src/Symfony/Component/AutoMapper/MapperInterface.php b/src/Symfony/Component/AutoMapper/MapperInterface.php index 778bbfaa0a67b..aa5567259fb8b 100644 --- a/src/Symfony/Component/AutoMapper/MapperInterface.php +++ b/src/Symfony/Component/AutoMapper/MapperInterface.php @@ -23,8 +23,8 @@ interface MapperInterface { /** - * @param mixed $value Value to map - * @param array $context Options mapper have access to + * @param mixed $value Value to map + * @param array $context Options mapper have access to * * @return mixed The mapped value */ diff --git a/src/Symfony/Component/AutoMapper/Tests/AutoMapperTest.php b/src/Symfony/Component/AutoMapper/Tests/AutoMapperTest.php index aaab633490923..9c9a19358ee79 100644 --- a/src/Symfony/Component/AutoMapper/Tests/AutoMapperTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/AutoMapperTest.php @@ -15,11 +15,11 @@ use PhpParser\ParserFactory; use PHPUnit\Framework\TestCase; use Symfony\Component\AutoMapper\AutoMapper; -use Symfony\Component\AutoMapper\MapperContext; use Symfony\Component\AutoMapper\Exception\CircularReferenceException; +use Symfony\Component\AutoMapper\Exception\NoMappingFoundException; use Symfony\Component\AutoMapper\Generator\Generator; use Symfony\Component\AutoMapper\Loader\FileLoader; -use Symfony\Component\AutoMapper\Tests\Fixtures\Bar; +use Symfony\Component\AutoMapper\MapperContext; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; @@ -35,7 +35,7 @@ class AutoMapperTest extends TestCase private $loader; - public function setUp() + public function setUp(): void { @unlink(__DIR__.'/cache/registry.php'); $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); @@ -48,7 +48,7 @@ public function setUp() $this->autoMapper = AutoMapper::create(true, $this->loader); } - public function testAutoMapping() + public function testAutoMapping(): void { $userMetadata = $this->autoMapper->getMetadata(Fixtures\User::class, Fixtures\UserDTO::class); $userMetadata->forMember('yearOfBirth', function (Fixtures\User $user) { @@ -75,12 +75,12 @@ public function testAutoMapping() self::assertInstanceOf(Fixtures\AddressDTO::class, $userDto->addresses[0]); self::assertSame('Toulon', $userDto->address->city); self::assertSame('Toulon', $userDto->addresses[0]->city); - self::assertInternalType('array', $userDto->money); + self::assertIsArray($userDto->money); self::assertCount(1, $userDto->money); self::assertSame(20.10, $userDto->money[0]); } - public function testAutoMapperFromArray() + public function testAutoMapperFromArray(): void { $user = [ 'id' => 1, @@ -101,7 +101,7 @@ public function testAutoMapperFromArray() self::assertEquals(1987, $userDto->createdAt->format('Y')); } - public function testAutoMapperToArray() + public function testAutoMapperToArray(): void { $address = new Fixtures\Address(); $address->setCity('Toulon'); @@ -111,25 +111,25 @@ public function testAutoMapperToArray() $userData = $this->autoMapper->map($user, 'array'); - self::assertInternalType('array', $userData); + self::assertIsArray($userData); self::assertEquals(1, $userData['id']); - self::assertInternalType('array', $userData['address']); - self::assertInternalType('string', $userData['createdAt']); + self::assertIsArray($userData['address']); + self::assertIsString($userData['createdAt']); } - public function testAutoMapperFromStdObject() + public function testAutoMapperFromStdObject(): void { $user = new \stdClass(); $user->id = 1; - /** @var UserDTO $userDto */ + /** @var Fixtures\UserDTO $userDto */ $userDto = $this->autoMapper->map($user, Fixtures\UserDTO::class); self::assertInstanceOf(Fixtures\UserDTO::class, $userDto); self::assertEquals(1, $userDto->id); } - public function testAutoMapperToStdObject() + public function testAutoMapperToStdObject(): void { $userDto = new Fixtures\UserDTO(); $userDto->id = 1; @@ -140,59 +140,59 @@ public function testAutoMapperToStdObject() self::assertEquals(1, $user->id); } - public function testGroupsSourceTarget() + public function testGroupsSourceTarget(): void { $foo = new Fixtures\Foo(); $foo->setId(10); - $bar = $this->autoMapper->map($foo, Bar::class, [MapperContext::GROUPS => ['group2']]); + $bar = $this->autoMapper->map($foo, Fixtures\Bar::class, [MapperContext::GROUPS => ['group2']]); - self::assertInstanceOf(Bar::class, $bar); - self::assertEquals(10, $bar->id); + self::assertInstanceOf(Fixtures\Bar::class, $bar); + self::assertEquals(10, $bar->getId()); - $bar = $this->autoMapper->map($foo, Bar::class, [MapperContext::GROUPS => ['group1', 'group3']]); + $bar = $this->autoMapper->map($foo, Fixtures\Bar::class, [MapperContext::GROUPS => ['group1', 'group3']]); - self::assertInstanceOf(Bar::class, $bar); - self::assertEquals(10, $bar->id); + self::assertInstanceOf(Fixtures\Bar::class, $bar); + self::assertEquals(10, $bar->getId()); - $bar = $this->autoMapper->map($foo, Bar::class, [MapperContext::GROUPS => ['group1']]); + $bar = $this->autoMapper->map($foo, Fixtures\Bar::class, [MapperContext::GROUPS => ['group1']]); - self::assertInstanceOf(Bar::class, $bar); - self::assertNull($bar->id); + self::assertInstanceOf(Fixtures\Bar::class, $bar); + self::assertNull($bar->getId()); - $bar = $this->autoMapper->map($foo, Bar::class, [MapperContext::GROUPS => []]); + $bar = $this->autoMapper->map($foo, Fixtures\Bar::class, [MapperContext::GROUPS => []]); - self::assertInstanceOf(Bar::class, $bar); - self::assertNull($bar->id); + self::assertInstanceOf(Fixtures\Bar::class, $bar); + self::assertNull($bar->getId()); - $bar = $this->autoMapper->map($foo, Bar::class); + $bar = $this->autoMapper->map($foo, Fixtures\Bar::class); - self::assertInstanceOf(Bar::class, $bar); - self::assertNull($bar->id); + self::assertInstanceOf(Fixtures\Bar::class, $bar); + self::assertNull($bar->getId()); } - public function testGroupsToArray() + public function testGroupsToArray(): void { $foo = new Fixtures\Foo(); $foo->setId(10); $fooArray = $this->autoMapper->map($foo, 'array', [MapperContext::GROUPS => ['group1']]); - self::assertInternalType('array', $fooArray); + self::assertIsArray($fooArray); self::assertEquals(10, $fooArray['id']); $fooArray = $this->autoMapper->map($foo, 'array', [MapperContext::GROUPS => []]); - self::assertInternalType('array', $fooArray); + self::assertIsArray($fooArray); self::assertArrayNotHasKey('id', $fooArray); $fooArray = $this->autoMapper->map($foo, 'array'); - self::assertInternalType('array', $fooArray); + self::assertIsArray($fooArray); self::assertArrayNotHasKey('id', $fooArray); } - public function testDeepCloning() + public function testDeepCloning(): void { $nodeA = new Fixtures\Node(); $nodeB = new Fixtures\Node(); @@ -213,7 +213,7 @@ public function testDeepCloning() self::assertSame($newNode, $newNode->parent->parent->parent); } - public function testDeepCloningArray() + public function testDeepCloningArray(): void { $nodeA = new Fixtures\Node(); $nodeB = new Fixtures\Node(); @@ -224,14 +224,14 @@ public function testDeepCloningArray() $newNode = $this->autoMapper->map($nodeA, 'array'); - self::assertInternalType('array', $newNode); - self::assertInternalType('array', $newNode['parent']); - self::assertInternalType('array', $newNode['parent']['parent']); - self::assertInternalType('array', $newNode['parent']['parent']['parent']); + self::assertIsArray($newNode); + self::assertIsArray($newNode['parent']); + self::assertIsArray($newNode['parent']['parent']); + self::assertIsArray($newNode['parent']['parent']['parent']); self::assertSame($newNode, $newNode['parent']['parent']['parent']); } - public function testCircularReferenceArray() + public function testCircularReferenceArray(): void { $nodeA = new Fixtures\Node(); $nodeB = new Fixtures\Node(); @@ -241,16 +241,16 @@ public function testCircularReferenceArray() $newNode = $this->autoMapper->map($nodeA, 'array'); - self::assertInternalType('array', $newNode); - self::assertInternalType('array', $newNode['childs'][0]); - self::assertInternalType('array', $newNode['childs'][0]['childs'][0]); + self::assertIsArray($newNode); + self::assertIsArray($newNode['childs'][0]); + self::assertIsArray($newNode['childs'][0]['childs'][0]); self::assertSame($newNode, $newNode['childs'][0]['childs'][0]); } - public function testPrivate() + public function testPrivate(): void { $user = new Fixtures\PrivateUser(10, 'foo', 'bar'); - /** @var PrivateUserDTO $userDto */ + /** @var Fixtures\PrivateUserDTO $userDto */ $userDto = $this->autoMapper->map($user, Fixtures\PrivateUserDTO::class); self::assertInstanceOf(Fixtures\PrivateUserDTO::class, $userDto); @@ -259,7 +259,7 @@ public function testPrivate() self::assertSame('bar', $userDto->getLastName()); } - public function testConstructor() + public function testConstructor(): void { $autoMapper = AutoMapper::create(false, $this->loader); @@ -276,7 +276,7 @@ public function testConstructor() self::assertSame(3, $userDto->getAge()); } - public function testConstructorWithDefault() + public function testConstructorWithDefault(): void { $user = new Fixtures\UserDTONoAge(); $user->id = 10; @@ -290,7 +290,7 @@ public function testConstructorWithDefault() self::assertSame(30, $userDto->getAge()); } - public function testConstructorDisable() + public function testConstructorDisable(): void { $user = new Fixtures\UserDTONoName(); $user->id = 10; @@ -303,7 +303,7 @@ public function testConstructorDisable() self::assertNull($userDto->getAge()); } - public function testMaxDepth() + public function testMaxDepth(): void { $foo = new Fixtures\FooMaxDepth(0, new Fixtures\FooMaxDepth(1, new Fixtures\FooMaxDepth(2, new Fixtures\FooMaxDepth(3, new Fixtures\FooMaxDepth(4))))); $fooArray = $this->autoMapper->map($foo, 'array'); @@ -313,7 +313,7 @@ public function testMaxDepth() self::assertFalse(isset($fooArray['child']['child']['child'])); } - public function testObjectToPopulate() + public function testObjectToPopulate(): void { $configurationUser = $this->autoMapper->getMetadata(Fixtures\User::class, Fixtures\UserDTO::class); $configurationUser->forMember('yearOfBirth', function (Fixtures\User $user) { @@ -328,7 +328,7 @@ public function testObjectToPopulate() self::assertSame($userDtoToPopulate, $userDto); } - public function testObjectToPopulateWithoutContext() + public function testObjectToPopulateWithoutContext(): void { $configurationUser = $this->autoMapper->getMetadata(Fixtures\User::class, Fixtures\UserDTO::class); $configurationUser->forMember('yearOfBirth', function (Fixtures\User $user) { @@ -343,7 +343,7 @@ public function testObjectToPopulateWithoutContext() self::assertSame($userDtoToPopulate, $userDto); } - public function testArrayToPopulate() + public function testArrayToPopulate(): void { $configurationUser = $this->autoMapper->getMetadata(Fixtures\User::class, Fixtures\UserDTO::class); $configurationUser->forMember('yearOfBirth', function (Fixtures\User $user) { @@ -354,13 +354,13 @@ public function testArrayToPopulate() $array = []; $arrayMapped = $this->autoMapper->map($user, $array); - self::assertInternalType('array', $arrayMapped); + self::assertIsArray($arrayMapped); self::assertSame(1, $arrayMapped['id']); self::assertSame('yolo', $arrayMapped['name']); self::assertSame('13', $arrayMapped['age']); } - public function testCircularReferenceLimitOnContext() + public function testCircularReferenceLimitOnContext(): void { $nodeA = new Fixtures\Node(); $nodeA->parent = $nodeA; @@ -373,7 +373,7 @@ public function testCircularReferenceLimitOnContext() $this->autoMapper->map($nodeA, 'array', $context->toArray()); } - public function testCircularReferenceLimitOnMapper() + public function testCircularReferenceLimitOnMapper(): void { $nodeA = new Fixtures\Node(); $nodeA->parent = $nodeA; @@ -386,7 +386,7 @@ public function testCircularReferenceLimitOnMapper() $mapper->map($nodeA); } - public function testCircularReferenceHandlerOnContext() + public function testCircularReferenceHandlerOnContext(): void { $nodeA = new Fixtures\Node(); $nodeA->parent = $nodeA; @@ -401,7 +401,7 @@ public function testCircularReferenceHandlerOnContext() self::assertSame('foo', $nodeArray['parent']); } - public function testCircularReferenceHandlerOnMapper() + public function testCircularReferenceHandlerOnMapper(): void { $nodeA = new Fixtures\Node(); $nodeA->parent = $nodeA; @@ -416,7 +416,7 @@ public function testCircularReferenceHandlerOnMapper() self::assertSame('foo', $nodeArray['parent']); } - public function testAllowedAttributes() + public function testAllowedAttributes(): void { $configurationUser = $this->autoMapper->getMetadata(Fixtures\User::class, Fixtures\UserDTO::class); $configurationUser->forMember('yearOfBirth', function (Fixtures\User $user) { @@ -425,12 +425,12 @@ public function testAllowedAttributes() $user = new Fixtures\User(1, 'yolo', '13'); - $userDto = $this->autoMapper->map($user, Fixtures\UserDTO::class, [MapperContext::ALLOWED_ATTRIBUTES => ['id' ,'age']]); + $userDto = $this->autoMapper->map($user, Fixtures\UserDTO::class, [MapperContext::ALLOWED_ATTRIBUTES => ['id', 'age']]); self::assertNull($userDto->getName()); } - public function testIgnoredAttributes() + public function testIgnoredAttributes(): void { $configurationUser = $this->autoMapper->getMetadata(Fixtures\User::class, Fixtures\UserDTO::class); $configurationUser->forMember('yearOfBirth', function (Fixtures\User $user) { @@ -443,12 +443,12 @@ public function testIgnoredAttributes() self::assertNull($userDto->getName()); } - public function testNameConverter() + public function testNameConverter(): void { $nameConverter = new class() implements AdvancedNameConverterInterface { public function normalize($propertyName, string $class = null, string $format = null, array $context = []) { - if ($propertyName === 'id') { + if ('id' === $propertyName) { return '@id'; } @@ -457,7 +457,7 @@ public function normalize($propertyName, string $class = null, string $format = public function denormalize($propertyName, string $class = null, string $format = null, array $context = []) { - if ($propertyName === '@id') { + if ('@id' === $propertyName) { return 'id'; } @@ -470,30 +470,31 @@ public function denormalize($propertyName, string $class = null, string $format $userArray = $autoMapper->map($user, 'array'); - self::assertInternalType('array', $userArray); + self::assertIsArray($userArray); self::assertArrayHasKey('@id', $userArray); self::assertSame(1, $userArray['@id']); } - public function testDefaultArguments() + public function testDefaultArguments(): void { $user = new Fixtures\UserDTONoAge(); $user->id = 10; $user->name = 'foo'; - /** @var UserConstructorDTO $userDto */ + $context = new MapperContext(); $context->setConstructorArgument(Fixtures\UserConstructorDTO::class, 'age', 50); + /** @var Fixtures\UserConstructorDTO $userDto */ $userDto = $this->autoMapper->map($user, Fixtures\UserConstructorDTO::class, $context->toArray()); self::assertInstanceOf(Fixtures\UserConstructorDTO::class, $userDto); self::assertSame(50, $userDto->getAge()); } - public function testDiscriminator() + public function testDiscriminator(): void { $data = [ - 'type' => 'cat' + 'type' => 'cat', ]; $pet = $this->autoMapper->map($data, Fixtures\Pet::class); @@ -501,44 +502,40 @@ public function testDiscriminator() self::assertInstanceOf(Fixtures\Cat::class, $pet); } - public function testAutomapNull() + public function testAutomapNull(): void { $array = $this->autoMapper->map(null, 'array'); self::assertNull($array); } - /** - * @expectedException \Symfony\Component\AutoMapper\Exception\NoMappingFoundException - */ - public function testInvalidMappingBothArray() + public function testInvalidMappingBothArray(): void { + self::expectException(NoMappingFoundException::class); + $data = ['test' => 'foo']; $array = $this->autoMapper->map($data, 'array'); } - /** - * @expectedException \Symfony\Component\AutoMapper\Exception\NoMappingFoundException - */ - public function testInvalidMappingSource() + public function testInvalidMappingSource(): void { + self::expectException(NoMappingFoundException::class); + $array = $this->autoMapper->map('test', 'array'); } - /** - * @expectedException \Symfony\Component\AutoMapper\Exception\NoMappingFoundException - */ - public function testInvalidMappingTarget() + public function testInvalidMappingTarget(): void { + self::expectException(NoMappingFoundException::class); + $data = ['test' => 'foo']; $array = $this->autoMapper->map($data, 3); } - /** - * @expectedException \Symfony\Component\AutoMapper\Exception\NoMappingFoundException - */ - public function testNoAutoRegister() + public function testNoAutoRegister(): void { + self::expectException(NoMappingFoundException::class); + $automapper = AutoMapper::create(false, null, null, 'Mapper_', true, false); $automapper->getMapper(Fixtures\User::class, Fixtures\UserDTO::class); } diff --git a/src/Symfony/Component/AutoMapper/Tests/Extractor/ReflectionExtractorTest.php b/src/Symfony/Component/AutoMapper/Tests/Extractor/ReflectionExtractorTest.php index f047e732fd34d..2edab913fabd6 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Extractor/ReflectionExtractorTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/Extractor/ReflectionExtractorTest.php @@ -22,12 +22,12 @@ class ReflectionExtractorTest extends TestCase /** @var ReflectionExtractor */ private $reflectionExtractor; - public function setUp() + public function setUp(): void { $this->reflectionExtractor = new ReflectionExtractor(true); } - public function testReadAccessorGetter() + public function testReadAccessorGetter(): void { $accessor = $this->reflectionExtractor->getReadAccessor(ReflectionExtractorTestFixture::class, 'foo'); @@ -37,7 +37,7 @@ public function testReadAccessorGetter() self::assertFalse($accessor->isPrivate()); } - public function testReadAccessorGetterSetter() + public function testReadAccessorGetterSetter(): void { $accessor = $this->reflectionExtractor->getReadAccessor(ReflectionExtractorTestFixture::class, 'bar'); @@ -47,7 +47,7 @@ public function testReadAccessorGetterSetter() self::assertFalse($accessor->isPrivate()); } - public function testReadAccessorIsser() + public function testReadAccessorIsser(): void { $accessor = $this->reflectionExtractor->getReadAccessor(ReflectionExtractorTestFixture::class, 'baz'); @@ -57,7 +57,7 @@ public function testReadAccessorIsser() self::assertFalse($accessor->isPrivate()); } - public function testReadAccessorHasser() + public function testReadAccessorHasser(): void { $accessor = $this->reflectionExtractor->getReadAccessor(ReflectionExtractorTestFixture::class, 'foz'); @@ -67,7 +67,7 @@ public function testReadAccessorHasser() self::assertFalse($accessor->isPrivate()); } - public function testReadAccessorMagicGet() + public function testReadAccessorMagicGet(): void { $accessor = $this->reflectionExtractor->getReadAccessor(ReflectionExtractorTestFixture::class, 'magicGet'); @@ -77,7 +77,7 @@ public function testReadAccessorMagicGet() self::assertFalse($accessor->isPrivate()); } - public function testWriteMutatorSetter() + public function testWriteMutatorSetter(): void { $mutator = $this->reflectionExtractor->getWriteMutator(ReflectionExtractorTestFixture::class, 'foo'); @@ -87,7 +87,7 @@ public function testWriteMutatorSetter() self::assertFalse($mutator->isPrivate()); } - public function testWriteMutatorGetterSetter() + public function testWriteMutatorGetterSetter(): void { $mutator = $this->reflectionExtractor->getWriteMutator(ReflectionExtractorTestFixture::class, 'bar'); @@ -97,7 +97,7 @@ public function testWriteMutatorGetterSetter() self::assertFalse($mutator->isPrivate()); } - public function testWriteMutatorMagicSet() + public function testWriteMutatorMagicSet(): void { $mutator = $this->reflectionExtractor->getWriteMutator(ReflectionExtractorTestFixture::class, 'magicSet'); @@ -107,7 +107,7 @@ public function testWriteMutatorMagicSet() self::assertFalse($mutator->isPrivate()); } - public function testWriteMutatorConstructor() + public function testWriteMutatorConstructor(): void { $mutator = $this->reflectionExtractor->getWriteMutator(ReflectionExtractorTestFixture::class, 'propertyConstruct'); diff --git a/src/Symfony/Component/AutoMapper/Tests/Fixtures/Bar.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/Bar.php index 532a2bd345660..e7c542f9920a1 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Fixtures/Bar.php +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/Bar.php @@ -16,9 +16,19 @@ class Bar { /** - * @var int + * @var int|null * * @Groups({"group2", "group3"}) */ - public $id; + private $id; + + public function getId(): ?int + { + return $this->id; + } + + public function setId(?int $id): void + { + $this->id = $id; + } } diff --git a/src/Symfony/Component/AutoMapper/Tests/Fixtures/Foo.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/Foo.php index e64a3f6571784..7f254931b5de7 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Fixtures/Foo.php +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/Foo.php @@ -18,7 +18,7 @@ class Foo /** * @var int * - * @Groups({"group1", "group2"}) + * @Groups({"group1", "group2", "group3"}) */ private $id = 0; diff --git a/src/Symfony/Component/AutoMapper/Tests/Fixtures/FooMaxDepth.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/FooMaxDepth.php index 17800f86274c9..413212803bfba 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Fixtures/FooMaxDepth.php +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/FooMaxDepth.php @@ -15,9 +15,9 @@ class FooMaxDepth { - /** - * @var int - */ + /** + * @var int + */ private $id; /** diff --git a/src/Symfony/Component/AutoMapper/Tests/Fixtures/Node.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/Node.php index ce204ffd34893..3aa953d994efc 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Fixtures/Node.php +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/Node.php @@ -13,9 +13,13 @@ class Node { - /** @var Node */ + /** + * @var Node + */ public $parent; - /** @var Node[] */ + /** + * @var Node[] + */ public $childs = []; } diff --git a/src/Symfony/Component/AutoMapper/Tests/Fixtures/PrivateUserDTO.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/PrivateUserDTO.php index 19ce3d1b0472d..841f4d8982fa4 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Fixtures/PrivateUserDTO.php +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/PrivateUserDTO.php @@ -11,7 +11,6 @@ namespace Symfony\Component\AutoMapper\Tests\Fixtures; - class PrivateUserDTO { /** @var int */ diff --git a/src/Symfony/Component/AutoMapper/Tests/Generator/UniqueVariableScopeTest.php b/src/Symfony/Component/AutoMapper/Tests/Generator/UniqueVariableScopeTest.php index 6814c972df4a3..243a0e5eb410b 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Generator/UniqueVariableScopeTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/Generator/UniqueVariableScopeTest.php @@ -16,7 +16,7 @@ class UniqueVariableScopeTest extends TestCase { - public function testVariableNameNotEquals() + public function testVariableNameNotEquals(): void { $uniqueVariable = new UniqueVariableScope(); $var1 = $uniqueVariable->getUniqueName('value'); diff --git a/src/Symfony/Component/AutoMapper/Transformer/TransformerInterface.php b/src/Symfony/Component/AutoMapper/Transformer/TransformerInterface.php index b688fcf1f8acf..adbe6b050e7de 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/TransformerInterface.php +++ b/src/Symfony/Component/AutoMapper/Transformer/TransformerInterface.php @@ -41,8 +41,6 @@ public function getDependencies(): array; /** * Should the resulting output be assigned by ref. - * - * @return bool */ public function assignByRef(): bool; } diff --git a/src/Symfony/Component/AutoMapper/composer.json b/src/Symfony/Component/AutoMapper/composer.json index cebc0d5d07c45..e455193d26312 100644 --- a/src/Symfony/Component/AutoMapper/composer.json +++ b/src/Symfony/Component/AutoMapper/composer.json @@ -38,7 +38,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "5.1-dev" } } } diff --git a/src/Symfony/Component/AutoMapper/phpunit.xml.dist b/src/Symfony/Component/AutoMapper/phpunit.xml.dist index 17341305b1f04..4e17ef5e45ed5 100644 --- a/src/Symfony/Component/AutoMapper/phpunit.xml.dist +++ b/src/Symfony/Component/AutoMapper/phpunit.xml.dist @@ -22,7 +22,6 @@ ./ - ./Resources ./Tests ./vendor From c6888552f8a9993eb390fe6a01089c5b38fe92ca Mon Sep 17 00:00:00 2001 From: Baptiste Leduc Date: Fri, 3 Jan 2020 11:59:56 +0100 Subject: [PATCH 20/38] Added AutoMapperNormalizerTest --- .../Component/AutoMapper/AutoMapper.php | 2 +- .../AutoMapper/AutoMapperNormalizer.php | 4 +- .../AutoMapper/Tests/AutoMapperBaseTest.php | 45 +++++++++++ .../Tests/AutoMapperNormalizerTest.php | 75 +++++++++++++++++++ .../AutoMapper/Tests/AutoMapperTest.php | 28 +------ 5 files changed, 124 insertions(+), 30 deletions(-) create mode 100644 src/Symfony/Component/AutoMapper/Tests/AutoMapperBaseTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/AutoMapperNormalizerTest.php diff --git a/src/Symfony/Component/AutoMapper/AutoMapper.php b/src/Symfony/Component/AutoMapper/AutoMapper.php index 0d26c94362291..a56b655604b4a 100644 --- a/src/Symfony/Component/AutoMapper/AutoMapper.php +++ b/src/Symfony/Component/AutoMapper/AutoMapper.php @@ -204,7 +204,7 @@ public static function create( [$reflectionExtractor] ); - $accessorExtractor = new \Symfony\Component\AutoMapper\Extractor\ReflectionExtractor($private); + $accessorExtractor = new Extractor\ReflectionExtractor($private); $transformerFactory = new ChainTransformerFactory(); $sourceTargetMappingExtractor = new SourceTargetMappingExtractor( $propertyInfoExtractor, diff --git a/src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php b/src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php index fc4052e232a4f..ac27d2b4494a4 100644 --- a/src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php +++ b/src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php @@ -47,7 +47,7 @@ public function denormalize($data, $class, $format = null, array $context = []) public function supportsNormalization($data, $format = null) { - if (!\is_object($data)) { + if (!\is_object($data) || $data instanceof \stdClass) { return false; } @@ -75,7 +75,7 @@ private function createAutoMapperContext(array $serializerContext = []): array MapperContext::CIRCULAR_REFERENCE_HANDLER => $serializerContext[AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER] ?? null, ]; - if ($serializerContext[AbstractNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS]) { + if (\array_key_exists(AbstractNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS, $serializerContext) && is_iterable($serializerContext[AbstractNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS])) { foreach ($serializerContext[AbstractNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS] as $class => $keyArgs) { foreach ($keyArgs as $key => $value) { $context[MapperContext::CONSTRUCTOR_ARGUMENTS][$class][$key] = $value; diff --git a/src/Symfony/Component/AutoMapper/Tests/AutoMapperBaseTest.php b/src/Symfony/Component/AutoMapper/Tests/AutoMapperBaseTest.php new file mode 100644 index 0000000000000..6bca45d242980 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/AutoMapperBaseTest.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests; + +use Doctrine\Common\Annotations\AnnotationReader; +use PhpParser\ParserFactory; +use PHPUnit\Framework\TestCase; +use Symfony\Component\AutoMapper\AutoMapper; +use Symfony\Component\AutoMapper\Generator\Generator; +use Symfony\Component\AutoMapper\Loader\ClassLoaderInterface; +use Symfony\Component\AutoMapper\Loader\FileLoader; +use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; +use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; + +abstract class AutoMapperBaseTest extends TestCase +{ + /** @var AutoMapper */ + protected $autoMapper; + + /** @var ClassLoaderInterface */ + protected $loader; + + public function setUp(): void + { + @unlink(__DIR__.'/cache/registry.php'); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + + $this->loader = new FileLoader(new Generator( + (new ParserFactory())->create(ParserFactory::PREFER_PHP7), + new ClassDiscriminatorFromClassMetadata($classMetadataFactory) + ), __DIR__.'/cache'); + + $this->autoMapper = AutoMapper::create(true, $this->loader); + } +} diff --git a/src/Symfony/Component/AutoMapper/Tests/AutoMapperNormalizerTest.php b/src/Symfony/Component/AutoMapper/Tests/AutoMapperNormalizerTest.php new file mode 100644 index 0000000000000..5ed308607ce6d --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/AutoMapperNormalizerTest.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests; + +use Symfony\Component\AutoMapper\AutoMapperNormalizer; + +class AutoMapperNormalizerTest extends AutoMapperBaseTest +{ + /** @var AutoMapperNormalizer */ + protected $normalizer; + + public function setUp(): void + { + parent::setUp(); + $this->normalizer = new AutoMapperNormalizer($this->autoMapper); + } + + public function testNormalize() + { + $object = new Fixtures\User(1, 'Jack', 37); + $expected = ['id' => 1, 'name' => 'Jack', 'age' => 37]; + + $normalized = $this->normalizer->normalize($object); + self::assertIsArray($normalized); + self::assertEquals($expected['id'], $normalized['id']); + self::assertEquals($expected['name'], $normalized['name']); + self::assertEquals($expected['age'], $normalized['age']); + } + + public function testDenormalize() + { + $source = ['id' => 1, 'name' => 'Jack', 'age' => 37]; + + /** @var Fixtures\User $denormalized */ + $denormalized = $this->normalizer->denormalize($source, Fixtures\User::class); + self::assertInstanceOf(Fixtures\User::class, $denormalized); + self::assertEquals($source['id'], $denormalized->getId()); + self::assertEquals($source['name'], $denormalized->name); + self::assertEquals($source['age'], $denormalized->age); + } + + public function testSupportsNormalization() + { + self::assertFalse($this->normalizer->supportsNormalization(['foo'])); + self::assertFalse($this->normalizer->supportsNormalization('{"foo":1}')); + + $object = new Fixtures\User(1, 'Jack', 37); + self::assertTrue($this->normalizer->supportsNormalization($object)); + + $stdClass = new \stdClass(); + $stdClass->id = 1; + $stdClass->name = 'Jack'; + $stdClass->age = 37; + self::assertFalse($this->normalizer->supportsNormalization($stdClass)); + } + + public function testSupportsDenormalization() + { + self::assertTrue($this->normalizer->supportsDenormalization(['foo' => 1], 'array')); + self::assertTrue($this->normalizer->supportsDenormalization(['foo' => 1], 'json')); + + $user = ['id' => 1, 'name' => 'Jack', 'age' => 37]; + self::assertTrue($this->normalizer->supportsDenormalization($user, Fixtures\User::class)); + self::assertTrue($this->normalizer->supportsDenormalization($user, \stdClass::class)); + } +} diff --git a/src/Symfony/Component/AutoMapper/Tests/AutoMapperTest.php b/src/Symfony/Component/AutoMapper/Tests/AutoMapperTest.php index 9c9a19358ee79..604d7df8d97c5 100644 --- a/src/Symfony/Component/AutoMapper/Tests/AutoMapperTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/AutoMapperTest.php @@ -11,43 +11,17 @@ namespace Symfony\Component\AutoMapper\Tests; -use Doctrine\Common\Annotations\AnnotationReader; -use PhpParser\ParserFactory; -use PHPUnit\Framework\TestCase; use Symfony\Component\AutoMapper\AutoMapper; use Symfony\Component\AutoMapper\Exception\CircularReferenceException; use Symfony\Component\AutoMapper\Exception\NoMappingFoundException; -use Symfony\Component\AutoMapper\Generator\Generator; -use Symfony\Component\AutoMapper\Loader\FileLoader; use Symfony\Component\AutoMapper\MapperContext; -use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata; -use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; -use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; /** * @author Joel Wurtz */ -class AutoMapperTest extends TestCase +class AutoMapperTest extends AutoMapperBaseTest { - /** @var AutoMapper */ - private $autoMapper; - - private $loader; - - public function setUp(): void - { - @unlink(__DIR__.'/cache/registry.php'); - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); - - $this->loader = new FileLoader(new Generator( - (new ParserFactory())->create(ParserFactory::PREFER_PHP7), - new ClassDiscriminatorFromClassMetadata($classMetadataFactory) - ), __DIR__.'/cache'); - - $this->autoMapper = AutoMapper::create(true, $this->loader); - } - public function testAutoMapping(): void { $userMetadata = $this->autoMapper->getMetadata(Fixtures\User::class, Fixtures\UserDTO::class); From 9f16b43259fa1900a2aa1a68cecc638c4fb0359b Mon Sep 17 00:00:00 2001 From: Baptiste Leduc Date: Sun, 5 Jan 2020 19:23:27 +0100 Subject: [PATCH 21/38] Context tests --- src/Symfony/Component/AutoMapper/Context.php | 262 ++++++++++++++++++ .../NoConstructorArgumentFoundException.php | 21 ++ .../Tests/AutoMapperNormalizerTest.php | 8 +- .../AutoMapper/Tests/ContextTest.php | 108 ++++++++ 4 files changed, 395 insertions(+), 4 deletions(-) create mode 100644 src/Symfony/Component/AutoMapper/Context.php create mode 100644 src/Symfony/Component/AutoMapper/Exception/NoConstructorArgumentFoundException.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/ContextTest.php diff --git a/src/Symfony/Component/AutoMapper/Context.php b/src/Symfony/Component/AutoMapper/Context.php new file mode 100644 index 0000000000000..9dfdfe19325e9 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Context.php @@ -0,0 +1,262 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper; + +use Symfony\Component\AutoMapper\Exception\CircularReferenceException; +use Symfony\Component\AutoMapper\Exception\NoConstructorArgumentFoundException; + +/** + * Context for mapping. + * + * Allows to customize how is done the mapping + * + * @author Joel Wurtz + */ +class Context extends \ArrayObject +{ + private $referenceRegistry = []; + + private $countReferenceRegistry = []; + + private $groups; + + private $depth; + + private $object; + + private $circularReferenceLimit; + + private $circularReferenceHandler; + + private $attributes; + + private $ignoredAttributes; + + private $constructorArguments = []; + + /** + * @param array|null $groups Groups to use for mapping + * @param array|null $attributes Attributes to use for mapping (exclude others) + * @param array|null $ignoredAttributes Attributes to exclude from mapping (include others) + */ + public function __construct(array $groups = null, array $attributes = null, array $ignoredAttributes = null) + { + parent::__construct(); + + $this->groups = $groups; + $this->depth = 0; + $this->attributes = $attributes; + $this->ignoredAttributes = $ignoredAttributes; + } + + /** + * Whether a reference has reached it's limit. + */ + public function shouldHandleCircularReference(string $reference, ?int $circularReferenceLimit = null): bool + { + if (!\array_key_exists($reference, $this->referenceRegistry)) { + return false; + } + + if (null === $circularReferenceLimit) { + $circularReferenceLimit = $this->circularReferenceLimit; + } + + if (null !== $circularReferenceLimit) { + return $this->countReferenceRegistry[$reference] >= $circularReferenceLimit; + } + + return true; + } + + /** + * Handle circular reference for a specific reference. + * + * By default will try to keep it and return the previous value + * + * @return mixed + */ + public function &handleCircularReference(string $reference, $object, ?int $circularReferenceLimit = null, callable $callback = null) + { + if (null === $callback) { + $callback = $this->circularReferenceHandler; + } + + if (null !== $callback) { + // Cannot directly return here, as we need to return by reference, and callback may not be declared as reference return + $value = $callback($object, $this); + + return $value; + } + + if (null === $circularReferenceLimit) { + $circularReferenceLimit = $this->circularReferenceLimit; + } + + if (null !== $circularReferenceLimit && $this->countReferenceRegistry[$reference] >= $circularReferenceLimit) { + throw new CircularReferenceException(sprintf('A circular reference has been detected when mapping the object of type "%s" (configured limit: %d)', \is_object($object) ? \get_class($object) : 'array', $circularReferenceLimit)); + } + + // When no limit defined return the object referenced + ++$this->countReferenceRegistry[$reference]; + + return $this->referenceRegistry[$reference]; + } + + /** + * Get groups for this context. + */ + public function getGroups(): ?array + { + return $this->groups; + } + + /** + * Get current depth. + */ + public function getDepth(): int + { + return $this->depth; + } + + /** + * Set object to populate (by-pass target construction). + */ + public function setObjectToPopulate($object) + { + $this->object = $object; + } + + /** + * Get object to populate. + */ + public function getObjectToPopulate() + { + $object = $this->object; + + if (null !== $object) { + $this->object = null; + } + + return $object; + } + + /** + * Set circular reference limit. + */ + public function setCircularReferenceLimit(?int $circularReferenceLimit): void + { + $this->circularReferenceLimit = $circularReferenceLimit; + } + + /** + * Set circular reference handler. + */ + public function setCircularReferenceHandler(?callable $circularReferenceHandler): void + { + $this->circularReferenceHandler = $circularReferenceHandler; + } + + /** + * Create a new context with a new reference. + */ + public function withReference($reference, &$object): self + { + $new = clone $this; + + $new->referenceRegistry[$reference] = &$object; + $new->countReferenceRegistry[$reference] = 1; + + return $new; + } + + /** + * Check whether an attribute is allowed to be mapped. + */ + public function isAllowedAttribute(string $attribute): bool + { + if (null !== $this->ignoredAttributes && \in_array($attribute, $this->ignoredAttributes, true)) { + return false; + } + + if (null === $this->attributes) { + return true; + } + + return \in_array($attribute, $this->attributes, true); + } + + /** + * Clone context with a incremented depth. + */ + public function withIncrementedDepth(): self + { + $new = clone $this; + ++$new->depth; + + return $new; + } + + /** + * Set the argument of a constructor for a specific class. + */ + public function setConstructorArgument(string $class, string $key, $value): void + { + if (!\array_key_exists($class, $this->constructorArguments)) { + $this->constructorArguments[$class] = []; + } + + $this->constructorArguments[$class][$key] = $value; + } + + /** + * Check wether an argument exist for the constructor for a specific class. + */ + public function hasConstructorArgument(string $class, string $key): bool + { + return \array_key_exists($key, $this->constructorArguments[$class] ?? []); + } + + /** + * Get constructor argument for a specific class. + */ + public function getConstructorArgument(string $class, string $key) + { + if (!\array_key_exists($key, $this->constructorArguments[$class] ?? [])) { + throw new NoConstructorArgumentFoundException(); + } + + return $this->constructorArguments[$class][$key]; + } + + /** + * Create a new cloned context, and reload attribute mapping for it. + */ + public function withNewContext(string $attribute): self + { + if (null === $this->attributes && null === $this->ignoredAttributes) { + return $this; + } + + $new = clone $this; + + if (null !== $this->ignoredAttributes && isset($this->ignoredAttributes[$attribute]) && \is_array($this->ignoredAttributes[$attribute])) { + $new->ignoredAttributes = $this->ignoredAttributes[$attribute]; + } + + if (null !== $this->attributes && isset($this->attributes[$attribute]) && \is_array($this->attributes[$attribute])) { + $new->attributes = $this->attributes[$attribute]; + } + + return $new; + } +} diff --git a/src/Symfony/Component/AutoMapper/Exception/NoConstructorArgumentFoundException.php b/src/Symfony/Component/AutoMapper/Exception/NoConstructorArgumentFoundException.php new file mode 100644 index 0000000000000..4a186e180be0c --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Exception/NoConstructorArgumentFoundException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Exception; + +/** + * @expiremental in 4.3 + * + * @author Baptiste Leduc + */ +class NoConstructorArgumentFoundException extends \RuntimeException +{ +} diff --git a/src/Symfony/Component/AutoMapper/Tests/AutoMapperNormalizerTest.php b/src/Symfony/Component/AutoMapper/Tests/AutoMapperNormalizerTest.php index 5ed308607ce6d..190670fe2e848 100644 --- a/src/Symfony/Component/AutoMapper/Tests/AutoMapperNormalizerTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/AutoMapperNormalizerTest.php @@ -24,7 +24,7 @@ public function setUp(): void $this->normalizer = new AutoMapperNormalizer($this->autoMapper); } - public function testNormalize() + public function testNormalize(): void { $object = new Fixtures\User(1, 'Jack', 37); $expected = ['id' => 1, 'name' => 'Jack', 'age' => 37]; @@ -36,7 +36,7 @@ public function testNormalize() self::assertEquals($expected['age'], $normalized['age']); } - public function testDenormalize() + public function testDenormalize(): void { $source = ['id' => 1, 'name' => 'Jack', 'age' => 37]; @@ -48,7 +48,7 @@ public function testDenormalize() self::assertEquals($source['age'], $denormalized->age); } - public function testSupportsNormalization() + public function testSupportsNormalization(): void { self::assertFalse($this->normalizer->supportsNormalization(['foo'])); self::assertFalse($this->normalizer->supportsNormalization('{"foo":1}')); @@ -63,7 +63,7 @@ public function testSupportsNormalization() self::assertFalse($this->normalizer->supportsNormalization($stdClass)); } - public function testSupportsDenormalization() + public function testSupportsDenormalization(): void { self::assertTrue($this->normalizer->supportsDenormalization(['foo' => 1], 'array')); self::assertTrue($this->normalizer->supportsDenormalization(['foo' => 1], 'json')); diff --git a/src/Symfony/Component/AutoMapper/Tests/ContextTest.php b/src/Symfony/Component/AutoMapper/Tests/ContextTest.php new file mode 100644 index 0000000000000..71d7c1e43b311 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/ContextTest.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\AutoMapper\Context; +use Symfony\Component\AutoMapper\Exception\CircularReferenceException; +use Symfony\Component\AutoMapper\Exception\NoConstructorArgumentFoundException; + +class ContextTest extends TestCase +{ + public function testIsAllowedAttribute(): void + { + $context = new Context(null, ['id', 'age']); + self::assertTrue($context->isAllowedAttribute('id')); + self::assertFalse($context->isAllowedAttribute('name')); + self::assertTrue($context->isAllowedAttribute('age')); + + $context = new Context(null, null, ['name']); + self::assertTrue($context->isAllowedAttribute('id')); + self::assertFalse($context->isAllowedAttribute('name')); + self::assertTrue($context->isAllowedAttribute('age')); + + $context = new Context(null, ['id', 'age'], ['age']); + self::assertTrue($context->isAllowedAttribute('id')); + self::assertFalse($context->isAllowedAttribute('name')); + self::assertFalse($context->isAllowedAttribute('age')); + } + + public function testCircularReferenceLimit(): void + { + // with no circularReferenceLimit + $object = new \stdClass(); + $context = new Context(); + $subContext = $context->withReference('reference', $object); + self::assertTrue($subContext->shouldHandleCircularReference('reference')); + + // with circularReferenceLimit + $object = new \stdClass(); + $context = new Context(); + $context->setCircularReferenceLimit(3); + $subContext = $context->withReference('reference', $object); + + for ($i = 0; $i <= 2; ++$i) { + if (2 === $i) { + self::assertTrue($subContext->shouldHandleCircularReference('reference')); + break; + } + + self::assertFalse($subContext->shouldHandleCircularReference('reference')); + + // fake handleCircularReference to increment countReferenceRegistry + $subContext->handleCircularReference('reference', $object); + } + + self::expectException(CircularReferenceException::class); + self::expectExceptionMessage('A circular reference has been detected when mapping the object of type "stdClass" (configured limit: 3)'); + $subContext->handleCircularReference('reference', $object); + } + + public function testCircularReferenceHandler(): void + { + $object = new \stdClass(); + $context = new Context(); + $context->setCircularReferenceHandler(function ($object) { + return $object; + }); + $subContext = $context->withReference('reference', $object); + self::assertTrue($subContext->shouldHandleCircularReference('reference')); + self::assertEquals($object, $context->handleCircularReference('reference', $object)); + } + + public function testConstructorArgument(): void + { + $context = new Context(); + $context->setConstructorArgument(Fixtures\User::class, 'id', 10); + $context->setConstructorArgument(Fixtures\User::class, 'age', 50); + + self::assertTrue($context->hasConstructorArgument(Fixtures\User::class, 'id')); + self::assertFalse($context->hasConstructorArgument(Fixtures\User::class, 'name')); + self::assertTrue($context->hasConstructorArgument(Fixtures\User::class, 'age')); + + self::assertEquals(10, $context->getConstructorArgument(Fixtures\User::class, 'id')); + self::assertEquals(50, $context->getConstructorArgument(Fixtures\User::class, 'age')); + + self::expectException(NoConstructorArgumentFoundException::class); + $context->getConstructorArgument(Fixtures\User::class, 'name'); + } + + public function testGroups(): void + { + $expected = ['group1', 'group4']; + $context = new Context($expected); + + self::assertEquals($expected, $context->getGroups()); + self::assertTrue(\in_array('group1', $context->getGroups())); + self::assertFalse(\in_array('group2', $context->getGroups())); + } +} From ed00ec35f8e346ab32b485620dbf8e31456e91b4 Mon Sep 17 00:00:00 2001 From: Baptiste Leduc Date: Sun, 5 Jan 2020 21:00:27 +0100 Subject: [PATCH 22/38] Add MapperGeneratorMetadataFactory tests --- .../Component/AutoMapper/MapperMetadata.php | 24 +++- .../AutoMapper/Tests/AutoMapperBaseTest.php | 3 + .../Tests/AutoMapperNormalizerTest.php | 3 + .../AutoMapper/Tests/ContextTest.php | 3 + .../MapperGeneratorMetadataFactoryTest.php | 130 +++++++++++++++++- 5 files changed, 157 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Component/AutoMapper/MapperMetadata.php b/src/Symfony/Component/AutoMapper/MapperMetadata.php index 99db0007a747e..e819c7f429193 100644 --- a/src/Symfony/Component/AutoMapper/MapperMetadata.php +++ b/src/Symfony/Component/AutoMapper/MapperMetadata.php @@ -48,6 +48,8 @@ class MapperMetadata implements MapperGeneratorMetadataInterface private $attributeChecking; + private $targetReflectionClass = null; + public function __construct(MapperGeneratorMetadataRegistryInterface $metadataRegistry, MappingExtractorInterface $mappingExtractor, string $source, string $target, string $classPrefix = 'Mapper_') { $this->mappingExtractor = $mappingExtractor; @@ -60,6 +62,15 @@ public function __construct(MapperGeneratorMetadataRegistryInterface $metadataRe $this->attributeChecking = true; } + private function getCachedTargetReflectionClass(): \ReflectionClass + { + if (null === $this->targetReflectionClass) { + $this->targetReflectionClass = new \ReflectionClass($this->getTarget()); + } + + return $this->targetReflectionClass; + } + /** * {@inheritdoc} */ @@ -97,7 +108,7 @@ public function hasConstructor(): bool return false; } - $reflection = new \ReflectionClass($this->getTarget()); + $reflection = $this->getCachedTargetReflectionClass(); $constructor = $reflection->getConstructor(); if (null === $constructor) { @@ -133,9 +144,14 @@ public function hasConstructor(): bool */ public function isTargetCloneable(): bool { - $reflection = new \ReflectionClass($this->getTarget()); + try { + $reflection = $this->getCachedTargetReflectionClass(); - return $reflection->isCloneable() && !$reflection->hasMethod('__clone'); + return $reflection->isCloneable() && !$reflection->hasMethod('__clone'); + } catch (\ReflectionException $e) { + // if we have a \ReflectionException, then we can't clone target + return false; + } } /** @@ -173,7 +189,7 @@ public function getHash(): string } if (!\in_array($this->target, ['array', \stdClass::class], true)) { - $reflection = new \ReflectionClass($this->target); + $reflection = $this->getCachedTargetReflectionClass(); $hash .= filemtime($reflection->getFileName()); } diff --git a/src/Symfony/Component/AutoMapper/Tests/AutoMapperBaseTest.php b/src/Symfony/Component/AutoMapper/Tests/AutoMapperBaseTest.php index 6bca45d242980..a000d59add7c1 100644 --- a/src/Symfony/Component/AutoMapper/Tests/AutoMapperBaseTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/AutoMapperBaseTest.php @@ -22,6 +22,9 @@ use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; +/** + * @author Baptiste Leduc + */ abstract class AutoMapperBaseTest extends TestCase { /** @var AutoMapper */ diff --git a/src/Symfony/Component/AutoMapper/Tests/AutoMapperNormalizerTest.php b/src/Symfony/Component/AutoMapper/Tests/AutoMapperNormalizerTest.php index 190670fe2e848..4156a767e738e 100644 --- a/src/Symfony/Component/AutoMapper/Tests/AutoMapperNormalizerTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/AutoMapperNormalizerTest.php @@ -13,6 +13,9 @@ use Symfony\Component\AutoMapper\AutoMapperNormalizer; +/** + * @author Baptiste Leduc + */ class AutoMapperNormalizerTest extends AutoMapperBaseTest { /** @var AutoMapperNormalizer */ diff --git a/src/Symfony/Component/AutoMapper/Tests/ContextTest.php b/src/Symfony/Component/AutoMapper/Tests/ContextTest.php index 71d7c1e43b311..2a65f86426afb 100644 --- a/src/Symfony/Component/AutoMapper/Tests/ContextTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/ContextTest.php @@ -16,6 +16,9 @@ use Symfony\Component\AutoMapper\Exception\CircularReferenceException; use Symfony\Component\AutoMapper\Exception\NoConstructorArgumentFoundException; +/** + * @author Baptiste Leduc + */ class ContextTest extends TestCase { public function testIsAllowedAttribute(): void diff --git a/src/Symfony/Component/AutoMapper/Tests/MapperGeneratorMetadataFactoryTest.php b/src/Symfony/Component/AutoMapper/Tests/MapperGeneratorMetadataFactoryTest.php index e6b9f45c4caac..3ea0fdcb94da0 100644 --- a/src/Symfony/Component/AutoMapper/Tests/MapperGeneratorMetadataFactoryTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/MapperGeneratorMetadataFactoryTest.php @@ -11,8 +11,134 @@ namespace Symfony\Component\AutoMapper\Tests; -use PHPUnit\Framework\TestCase; +use Doctrine\Common\Annotations\AnnotationReader; +use Symfony\Component\AutoMapper\Extractor\FromSourceMappingExtractor; +use Symfony\Component\AutoMapper\Extractor\FromTargetMappingExtractor; +use Symfony\Component\AutoMapper\Extractor\PrivateReflectionExtractor; +use Symfony\Component\AutoMapper\Extractor\PropertyMapping; +use Symfony\Component\AutoMapper\Extractor\ReflectionExtractor; +use Symfony\Component\AutoMapper\Extractor\SourceTargetMappingExtractor; +use Symfony\Component\AutoMapper\MapperGeneratorMetadataFactory; +use Symfony\Component\AutoMapper\MapperGeneratorMetadataFactoryInterface; +use Symfony\Component\AutoMapper\Transformer\ArrayTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\BuiltinTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\ChainTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\DateTimeTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\MultipleTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\NullableTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\ObjectTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\UniqueTypeTransformerFactory; +use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; +use Symfony\Component\PropertyInfo\PropertyInfoExtractor; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; +use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; -class MapperGeneratorMetadataFactoryTest extends TestCase +/** + * @author Baptiste Leduc + */ +class MapperGeneratorMetadataFactoryTest extends AutoMapperBaseTest { + /** @var MapperGeneratorMetadataFactoryInterface */ + protected $factory; + + public function setUp(): void + { + parent::setUp(); + + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $reflectionExtractor = new PrivateReflectionExtractor(); + + $phpDocExtractor = new PhpDocExtractor(); + $propertyInfoExtractor = new PropertyInfoExtractor( + [$reflectionExtractor], + [$phpDocExtractor, $reflectionExtractor], + [$reflectionExtractor], + [$reflectionExtractor] + ); + + $accessorExtractor = new ReflectionExtractor(true); + $transformerFactory = new ChainTransformerFactory(); + $sourceTargetMappingExtractor = new SourceTargetMappingExtractor( + $propertyInfoExtractor, + $accessorExtractor, + $transformerFactory, + $classMetadataFactory + ); + + $fromTargetMappingExtractor = new FromTargetMappingExtractor( + $propertyInfoExtractor, + $accessorExtractor, + $transformerFactory, + $classMetadataFactory + ); + + $fromSourceMappingExtractor = new FromSourceMappingExtractor( + $propertyInfoExtractor, + $accessorExtractor, + $transformerFactory, + $classMetadataFactory + ); + + $this->factory = new MapperGeneratorMetadataFactory( + $sourceTargetMappingExtractor, + $fromSourceMappingExtractor, + $fromTargetMappingExtractor + ); + + $transformerFactory->addTransformerFactory(new MultipleTransformerFactory($transformerFactory)); + $transformerFactory->addTransformerFactory(new NullableTransformerFactory($transformerFactory)); + $transformerFactory->addTransformerFactory(new UniqueTypeTransformerFactory($transformerFactory)); + $transformerFactory->addTransformerFactory(new DateTimeTransformerFactory()); + $transformerFactory->addTransformerFactory(new BuiltinTransformerFactory()); + $transformerFactory->addTransformerFactory(new ArrayTransformerFactory($transformerFactory)); + $transformerFactory->addTransformerFactory(new ObjectTransformerFactory($this->autoMapper)); + } + + public function testCreateObjectToArray() + { + $userReflection = new \ReflectionClass(Fixtures\User::class); + + $metadata = $this->factory->create($this->autoMapper, Fixtures\User::class, 'array'); + self::assertFalse($metadata->hasConstructor()); + self::assertTrue($metadata->shouldCheckAttributes()); + self::assertFalse($metadata->isTargetCloneable()); + self::assertEquals(Fixtures\User::class, $metadata->getSource()); + self::assertEquals('array', $metadata->getTarget()); + self::assertCount(\count($userReflection->getProperties()), $metadata->getPropertiesMapping()); + self::assertInstanceOf(PropertyMapping::class, $metadata->getPropertyMapping('id')); + self::assertInstanceOf(PropertyMapping::class, $metadata->getPropertyMapping('name')); + self::assertInstanceOf(PropertyMapping::class, $metadata->getPropertyMapping('email')); + } + + public function testCreateArrayToObject() + { + $userReflection = new \ReflectionClass(Fixtures\User::class); + + $metadata = $this->factory->create($this->autoMapper, 'array', Fixtures\User::class); + self::assertTrue($metadata->hasConstructor()); + self::assertTrue($metadata->shouldCheckAttributes()); + self::assertTrue($metadata->isTargetCloneable()); + self::assertEquals('array', $metadata->getSource()); + self::assertEquals(Fixtures\User::class, $metadata->getTarget()); + self::assertCount(\count($userReflection->getProperties()), $metadata->getPropertiesMapping()); + self::assertInstanceOf(PropertyMapping::class, $metadata->getPropertyMapping('id')); + self::assertInstanceOf(PropertyMapping::class, $metadata->getPropertyMapping('name')); + self::assertInstanceOf(PropertyMapping::class, $metadata->getPropertyMapping('email')); + } + + public function testCreateWithBothObjects() + { + $userConstructorDTOReflection = new \ReflectionClass(Fixtures\UserConstructorDTO::class); + + $metadata = $this->factory->create($this->autoMapper, Fixtures\UserConstructorDTO::class, Fixtures\User::class); + self::assertTrue($metadata->hasConstructor()); + self::assertTrue($metadata->shouldCheckAttributes()); + self::assertTrue($metadata->isTargetCloneable()); + self::assertEquals(Fixtures\UserConstructorDTO::class, $metadata->getSource()); + self::assertEquals(Fixtures\User::class, $metadata->getTarget()); + self::assertCount(\count($userConstructorDTOReflection->getProperties()), $metadata->getPropertiesMapping()); + self::assertInstanceOf(PropertyMapping::class, $metadata->getPropertyMapping('id')); + self::assertInstanceOf(PropertyMapping::class, $metadata->getPropertyMapping('name')); + self::assertNull($metadata->getPropertyMapping('email')); + } } From ab82b9fd4990626e392065360d6590b601279481 Mon Sep 17 00:00:00 2001 From: Baptiste Leduc Date: Sun, 5 Jan 2020 22:26:56 +0100 Subject: [PATCH 23/38] Add FromSourceMappingExtractor tests --- .../FromSourceMappingExtractorTest.php | 146 ++++++++++++++++++ .../AutoMapper/Tests/Fixtures/Empty_.php | 16 ++ .../AutoMapper/Tests/Fixtures/Private_.php | 25 +++ .../MapperGeneratorMetadataFactoryTest.php | 6 +- 4 files changed, 190 insertions(+), 3 deletions(-) create mode 100644 src/Symfony/Component/AutoMapper/Tests/Extractor/FromSourceMappingExtractorTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Fixtures/Empty_.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Fixtures/Private_.php diff --git a/src/Symfony/Component/AutoMapper/Tests/Extractor/FromSourceMappingExtractorTest.php b/src/Symfony/Component/AutoMapper/Tests/Extractor/FromSourceMappingExtractorTest.php new file mode 100644 index 0000000000000..07fd4621aa89e --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Extractor/FromSourceMappingExtractorTest.php @@ -0,0 +1,146 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Extractor; + +use Doctrine\Common\Annotations\AnnotationReader; +use Symfony\Component\AutoMapper\Exception\InvalidMappingException; +use Symfony\Component\AutoMapper\Extractor\FromSourceMappingExtractor; +use Symfony\Component\AutoMapper\Extractor\PrivateReflectionExtractor; +use Symfony\Component\AutoMapper\Extractor\PropertyMapping; +use Symfony\Component\AutoMapper\Extractor\ReflectionExtractor; +use Symfony\Component\AutoMapper\MapperMetadata; +use Symfony\Component\AutoMapper\Tests\AutoMapperBaseTest; +use Symfony\Component\AutoMapper\Transformer\ArrayTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\BuiltinTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\ChainTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\DateTimeTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\MultipleTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\NullableTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\ObjectTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\UniqueTypeTransformerFactory; +use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; +use Symfony\Component\PropertyInfo\PropertyInfoExtractor; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; +use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; +use Symfony\Component\AutoMapper\Tests\Fixtures; + +/** + * @author Baptiste Leduc + */ +class FromSourceMappingExtractorTest extends AutoMapperBaseTest +{ + /** @var FromSourceMappingExtractor */ + protected $fromSourceMappingExtractor; + + public function setUp(): void + { + parent::setUp(); + $this->fromSourceMappingExtractorBootstrap(); + } + + private function fromSourceMappingExtractorBootstrap(bool $private = true): void + { + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $reflectionExtractor = $private ? new PrivateReflectionExtractor() : new \Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor(); + + $phpDocExtractor = new PhpDocExtractor(); + $propertyInfoExtractor = new PropertyInfoExtractor( + [$reflectionExtractor], + [$phpDocExtractor, $reflectionExtractor], + [$reflectionExtractor], + [$reflectionExtractor] + ); + + $accessorExtractor = new ReflectionExtractor($private); + $transformerFactory = new ChainTransformerFactory(); + + $this->fromSourceMappingExtractor = new FromSourceMappingExtractor( + $propertyInfoExtractor, + $accessorExtractor, + $transformerFactory, + $classMetadataFactory + ); + + $transformerFactory->addTransformerFactory(new MultipleTransformerFactory($transformerFactory)); + $transformerFactory->addTransformerFactory(new NullableTransformerFactory($transformerFactory)); + $transformerFactory->addTransformerFactory(new UniqueTypeTransformerFactory($transformerFactory)); + $transformerFactory->addTransformerFactory(new DateTimeTransformerFactory()); + $transformerFactory->addTransformerFactory(new BuiltinTransformerFactory()); + $transformerFactory->addTransformerFactory(new ArrayTransformerFactory($transformerFactory)); + $transformerFactory->addTransformerFactory(new ObjectTransformerFactory($this->autoMapper)); + } + + public function testWithTargetAsArray(): void + { + $userReflection = new \ReflectionClass(Fixtures\User::class); + $mapperMetadata = new MapperMetadata($this->autoMapper, $this->fromSourceMappingExtractor, Fixtures\User::class, 'array'); + $sourcePropertiesMapping = $this->fromSourceMappingExtractor->getPropertiesMapping($mapperMetadata); + + self::assertCount(\count($userReflection->getProperties()), $sourcePropertiesMapping); + /** @var PropertyMapping $propertyMapping */ + foreach ($sourcePropertiesMapping as $propertyMapping) { + self::assertTrue($userReflection->hasProperty($propertyMapping->getProperty())); + } + } + + public function testWithTargetAsStdClass(): void + { + $userReflection = new \ReflectionClass(Fixtures\User::class); + $mapperMetadata = new MapperMetadata($this->autoMapper, $this->fromSourceMappingExtractor, Fixtures\User::class, 'stdClass'); + $sourcePropertiesMapping = $this->fromSourceMappingExtractor->getPropertiesMapping($mapperMetadata); + + self::assertCount(\count($userReflection->getProperties()), $sourcePropertiesMapping); + /** @var PropertyMapping $propertyMapping */ + foreach ($sourcePropertiesMapping as $propertyMapping) { + self::assertTrue($userReflection->hasProperty($propertyMapping->getProperty())); + } + } + + public function testWithSourceAsEmpty(): void + { + $mapperMetadata = new MapperMetadata($this->autoMapper, $this->fromSourceMappingExtractor, Fixtures\Empty_::class, 'array'); + $sourcePropertiesMapping = $this->fromSourceMappingExtractor->getPropertiesMapping($mapperMetadata); + + self::assertCount(0, $sourcePropertiesMapping); + } + + public function testWithSourceAsPrivate(): void + { + $privateReflection = new \ReflectionClass(Fixtures\Private_::class); + $mapperMetadata = new MapperMetadata($this->autoMapper, $this->fromSourceMappingExtractor, Fixtures\Private_::class, 'array'); + $sourcePropertiesMapping = $this->fromSourceMappingExtractor->getPropertiesMapping($mapperMetadata); + self::assertCount(\count($privateReflection->getProperties()), $sourcePropertiesMapping); + + $this->fromSourceMappingExtractorBootstrap(false); + $mapperMetadata = new MapperMetadata($this->autoMapper, $this->fromSourceMappingExtractor, Fixtures\Private_::class, 'array'); + $sourcePropertiesMapping = $this->fromSourceMappingExtractor->getPropertiesMapping($mapperMetadata); + self::assertCount(0, $sourcePropertiesMapping); + } + + public function testWithSourceAsArray(): void + { + self::expectException(InvalidMappingException::class); + self::expectExceptionMessage('Only array or stdClass are accepted as a target'); + + $mapperMetadata = new MapperMetadata($this->autoMapper, $this->fromSourceMappingExtractor, 'array', Fixtures\User::class); + $this->fromSourceMappingExtractor->getPropertiesMapping($mapperMetadata); + } + + public function testWithSourceAsStdClass(): void + { + self::expectException(InvalidMappingException::class); + self::expectExceptionMessage('Only array or stdClass are accepted as a target'); + + $mapperMetadata = new MapperMetadata($this->autoMapper, $this->fromSourceMappingExtractor, 'stdClass', Fixtures\User::class); + $this->fromSourceMappingExtractor->getPropertiesMapping($mapperMetadata); + } +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Fixtures/Empty_.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/Empty_.php new file mode 100644 index 0000000000000..fd315c67ff037 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/Empty_.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Fixtures; + +class Empty_ +{ +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Fixtures/Private_.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/Private_.php new file mode 100644 index 0000000000000..2e39e855b0e3f --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/Private_.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Fixtures; + +class Private_ +{ + /** + * @var int + */ + private $id; + + public function __construct(int $id) + { + $this->id = $id; + } +} diff --git a/src/Symfony/Component/AutoMapper/Tests/MapperGeneratorMetadataFactoryTest.php b/src/Symfony/Component/AutoMapper/Tests/MapperGeneratorMetadataFactoryTest.php index 3ea0fdcb94da0..556f8bec40094 100644 --- a/src/Symfony/Component/AutoMapper/Tests/MapperGeneratorMetadataFactoryTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/MapperGeneratorMetadataFactoryTest.php @@ -94,7 +94,7 @@ public function setUp(): void $transformerFactory->addTransformerFactory(new ObjectTransformerFactory($this->autoMapper)); } - public function testCreateObjectToArray() + public function testCreateObjectToArray(): void { $userReflection = new \ReflectionClass(Fixtures\User::class); @@ -110,7 +110,7 @@ public function testCreateObjectToArray() self::assertInstanceOf(PropertyMapping::class, $metadata->getPropertyMapping('email')); } - public function testCreateArrayToObject() + public function testCreateArrayToObject(): void { $userReflection = new \ReflectionClass(Fixtures\User::class); @@ -126,7 +126,7 @@ public function testCreateArrayToObject() self::assertInstanceOf(PropertyMapping::class, $metadata->getPropertyMapping('email')); } - public function testCreateWithBothObjects() + public function testCreateWithBothObjects(): void { $userConstructorDTOReflection = new \ReflectionClass(Fixtures\UserConstructorDTO::class); From ee908a6cc90f8963d38bde149e223a2e3f2dec0c Mon Sep 17 00:00:00 2001 From: Baptiste Leduc Date: Sun, 5 Jan 2020 22:34:05 +0100 Subject: [PATCH 24/38] Add FromTargetMappingExtractor tests --- .../Extractor/FromTargetMappingExtractor.php | 2 +- .../FromSourceMappingExtractorTest.php | 2 +- .../FromTargetMappingExtractorTest.php | 146 ++++++++++++++++++ 3 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 src/Symfony/Component/AutoMapper/Tests/Extractor/FromTargetMappingExtractorTest.php diff --git a/src/Symfony/Component/AutoMapper/Extractor/FromTargetMappingExtractor.php b/src/Symfony/Component/AutoMapper/Extractor/FromTargetMappingExtractor.php index e3a8509b745b1..b2395fab9cda8 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/FromTargetMappingExtractor.php +++ b/src/Symfony/Component/AutoMapper/Extractor/FromTargetMappingExtractor.php @@ -46,7 +46,7 @@ public function __construct(PropertyInfoExtractorInterface $propertyInfoExtracto */ public function getPropertiesMapping(MapperMetadataInterface $mapperMetadata): array { - $targetProperties = array_unique($this->propertyInfoExtractor->getProperties($mapperMetadata->getTarget())); + $targetProperties = array_unique($this->propertyInfoExtractor->getProperties($mapperMetadata->getTarget()) ?? []); if (!\in_array($mapperMetadata->getSource(), self::ALLOWED_SOURCES, true)) { throw new InvalidMappingException('Only array or stdClass are accepted as a source'); diff --git a/src/Symfony/Component/AutoMapper/Tests/Extractor/FromSourceMappingExtractorTest.php b/src/Symfony/Component/AutoMapper/Tests/Extractor/FromSourceMappingExtractorTest.php index 07fd4621aa89e..ff2c2ca59bca3 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Extractor/FromSourceMappingExtractorTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/Extractor/FromSourceMappingExtractorTest.php @@ -19,6 +19,7 @@ use Symfony\Component\AutoMapper\Extractor\ReflectionExtractor; use Symfony\Component\AutoMapper\MapperMetadata; use Symfony\Component\AutoMapper\Tests\AutoMapperBaseTest; +use Symfony\Component\AutoMapper\Tests\Fixtures; use Symfony\Component\AutoMapper\Transformer\ArrayTransformerFactory; use Symfony\Component\AutoMapper\Transformer\BuiltinTransformerFactory; use Symfony\Component\AutoMapper\Transformer\ChainTransformerFactory; @@ -31,7 +32,6 @@ use Symfony\Component\PropertyInfo\PropertyInfoExtractor; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; -use Symfony\Component\AutoMapper\Tests\Fixtures; /** * @author Baptiste Leduc diff --git a/src/Symfony/Component/AutoMapper/Tests/Extractor/FromTargetMappingExtractorTest.php b/src/Symfony/Component/AutoMapper/Tests/Extractor/FromTargetMappingExtractorTest.php new file mode 100644 index 0000000000000..c9cb43490c587 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Extractor/FromTargetMappingExtractorTest.php @@ -0,0 +1,146 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Extractor; + +use Doctrine\Common\Annotations\AnnotationReader; +use Symfony\Component\AutoMapper\Exception\InvalidMappingException; +use Symfony\Component\AutoMapper\Extractor\FromTargetMappingExtractor; +use Symfony\Component\AutoMapper\Extractor\PrivateReflectionExtractor; +use Symfony\Component\AutoMapper\Extractor\PropertyMapping; +use Symfony\Component\AutoMapper\Extractor\ReflectionExtractor; +use Symfony\Component\AutoMapper\MapperMetadata; +use Symfony\Component\AutoMapper\Tests\AutoMapperBaseTest; +use Symfony\Component\AutoMapper\Tests\Fixtures; +use Symfony\Component\AutoMapper\Transformer\ArrayTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\BuiltinTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\ChainTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\DateTimeTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\MultipleTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\NullableTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\ObjectTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\UniqueTypeTransformerFactory; +use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; +use Symfony\Component\PropertyInfo\PropertyInfoExtractor; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; +use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; + +/** + * @author Baptiste Leduc + */ +class FromTargetMappingExtractorTest extends AutoMapperBaseTest +{ + /** @var FromTargetMappingExtractor */ + protected $fromTargetMappingExtractor; + + public function setUp(): void + { + parent::setUp(); + $this->fromTargetMappingExtractorBootstrap(); + } + + private function fromTargetMappingExtractorBootstrap(bool $private = true): void + { + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $reflectionExtractor = $private ? new PrivateReflectionExtractor() : new \Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor(); + + $phpDocExtractor = new PhpDocExtractor(); + $propertyInfoExtractor = new PropertyInfoExtractor( + [$reflectionExtractor], + [$phpDocExtractor, $reflectionExtractor], + [$reflectionExtractor], + [$reflectionExtractor] + ); + + $accessorExtractor = new ReflectionExtractor($private); + $transformerFactory = new ChainTransformerFactory(); + + $this->fromTargetMappingExtractor = new FromTargetMappingExtractor( + $propertyInfoExtractor, + $accessorExtractor, + $transformerFactory, + $classMetadataFactory + ); + + $transformerFactory->addTransformerFactory(new MultipleTransformerFactory($transformerFactory)); + $transformerFactory->addTransformerFactory(new NullableTransformerFactory($transformerFactory)); + $transformerFactory->addTransformerFactory(new UniqueTypeTransformerFactory($transformerFactory)); + $transformerFactory->addTransformerFactory(new DateTimeTransformerFactory()); + $transformerFactory->addTransformerFactory(new BuiltinTransformerFactory()); + $transformerFactory->addTransformerFactory(new ArrayTransformerFactory($transformerFactory)); + $transformerFactory->addTransformerFactory(new ObjectTransformerFactory($this->autoMapper)); + } + + public function testWithSourceAsArray(): void + { + $userReflection = new \ReflectionClass(Fixtures\User::class); + $mapperMetadata = new MapperMetadata($this->autoMapper, $this->fromTargetMappingExtractor, 'array', Fixtures\User::class); + $targetPropertiesMapping = $this->fromTargetMappingExtractor->getPropertiesMapping($mapperMetadata); + + self::assertCount(\count($userReflection->getProperties()), $targetPropertiesMapping); + /** @var PropertyMapping $propertyMapping */ + foreach ($targetPropertiesMapping as $propertyMapping) { + self::assertTrue($userReflection->hasProperty($propertyMapping->getProperty())); + } + } + + public function testWithSourceAsStdClass(): void + { + $userReflection = new \ReflectionClass(Fixtures\User::class); + $mapperMetadata = new MapperMetadata($this->autoMapper, $this->fromTargetMappingExtractor, 'stdClass', Fixtures\User::class); + $targetPropertiesMapping = $this->fromTargetMappingExtractor->getPropertiesMapping($mapperMetadata); + + self::assertCount(\count($userReflection->getProperties()), $targetPropertiesMapping); + /** @var PropertyMapping $propertyMapping */ + foreach ($targetPropertiesMapping as $propertyMapping) { + self::assertTrue($userReflection->hasProperty($propertyMapping->getProperty())); + } + } + + public function testWithTargetAsEmpty(): void + { + $mapperMetadata = new MapperMetadata($this->autoMapper, $this->fromTargetMappingExtractor, 'array', Fixtures\Empty_::class); + $targetPropertiesMapping = $this->fromTargetMappingExtractor->getPropertiesMapping($mapperMetadata); + + self::assertCount(0, $targetPropertiesMapping); + } + + public function testWithTargetAsPrivate(): void + { + $privateReflection = new \ReflectionClass(Fixtures\Private_::class); + $mapperMetadata = new MapperMetadata($this->autoMapper, $this->fromTargetMappingExtractor, 'array', Fixtures\Private_::class); + $targetPropertiesMapping = $this->fromTargetMappingExtractor->getPropertiesMapping($mapperMetadata); + self::assertCount(\count($privateReflection->getProperties()), $targetPropertiesMapping); + + $this->fromTargetMappingExtractorBootstrap(false); + $mapperMetadata = new MapperMetadata($this->autoMapper, $this->fromTargetMappingExtractor, 'array', Fixtures\Private_::class); + $targetPropertiesMapping = $this->fromTargetMappingExtractor->getPropertiesMapping($mapperMetadata); + self::assertCount(0, $targetPropertiesMapping); + } + + public function testWithTargetAsArray(): void + { + self::expectException(InvalidMappingException::class); + self::expectExceptionMessage('Only array or stdClass are accepted as a source'); + + $mapperMetadata = new MapperMetadata($this->autoMapper, $this->fromTargetMappingExtractor, Fixtures\User::class, 'array'); + $this->fromTargetMappingExtractor->getPropertiesMapping($mapperMetadata); + } + + public function testWithTargetAsStdClass(): void + { + self::expectException(InvalidMappingException::class); + self::expectExceptionMessage('Only array or stdClass are accepted as a source'); + + $mapperMetadata = new MapperMetadata($this->autoMapper, $this->fromTargetMappingExtractor, Fixtures\User::class, 'stdClass'); + $this->fromTargetMappingExtractor->getPropertiesMapping($mapperMetadata); + } +} From cd2d414c3d95b91931406315894891329c51d725 Mon Sep 17 00:00:00 2001 From: Baptiste Leduc Date: Mon, 6 Jan 2020 11:05:56 +0100 Subject: [PATCH 25/38] WIP PrivateReflectionExtractor tests --- .../PrivateReflectionExtractorTest.php | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/Symfony/Component/AutoMapper/Tests/Extractor/PrivateReflectionExtractorTest.php diff --git a/src/Symfony/Component/AutoMapper/Tests/Extractor/PrivateReflectionExtractorTest.php b/src/Symfony/Component/AutoMapper/Tests/Extractor/PrivateReflectionExtractorTest.php new file mode 100644 index 0000000000000..716cdb533de7f --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Extractor/PrivateReflectionExtractorTest.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Extractor; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\AutoMapper\Extractor\PrivateReflectionExtractor; +use Symfony\Component\AutoMapper\Tests\Fixtures; + +/** + * @author Baptiste Leduc + */ +class PrivateReflectionExtractorTest extends TestCase +{ + /** + * @var PrivateReflectionExtractor + */ + protected $privateReflectionExtractor; + + public function setUp(): void + { + $this->privateReflectionExtractor = new PrivateReflectionExtractor(); + } + + public function testProperties(): void + { + $userReflection = new \ReflectionClass(Fixtures\User::class); + $properties = $this->privateReflectionExtractor->getProperties(Fixtures\User::class); + + foreach ($userReflection->getProperties(\ReflectionProperty::IS_PRIVATE | \ReflectionProperty::IS_PROTECTED) as $reflectionProperty) { + self::assertTrue(in_array($reflectionProperty->getName(), $properties)); + } + } +} From cd85e2da404aa1cd98ba46bc4786d6f0e924d022 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Fri, 31 Jan 2020 21:47:15 +0100 Subject: [PATCH 26/38] Remove internal for generated mapper --- src/Symfony/Component/AutoMapper/Context.php | 262 ------------------ .../Component/AutoMapper/GeneratedMapper.php | 2 - ...{ContextTest.php => MapperContextTest.php} | 0 3 files changed, 264 deletions(-) delete mode 100644 src/Symfony/Component/AutoMapper/Context.php rename src/Symfony/Component/AutoMapper/Tests/{ContextTest.php => MapperContextTest.php} (100%) diff --git a/src/Symfony/Component/AutoMapper/Context.php b/src/Symfony/Component/AutoMapper/Context.php deleted file mode 100644 index 9dfdfe19325e9..0000000000000 --- a/src/Symfony/Component/AutoMapper/Context.php +++ /dev/null @@ -1,262 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\AutoMapper; - -use Symfony\Component\AutoMapper\Exception\CircularReferenceException; -use Symfony\Component\AutoMapper\Exception\NoConstructorArgumentFoundException; - -/** - * Context for mapping. - * - * Allows to customize how is done the mapping - * - * @author Joel Wurtz - */ -class Context extends \ArrayObject -{ - private $referenceRegistry = []; - - private $countReferenceRegistry = []; - - private $groups; - - private $depth; - - private $object; - - private $circularReferenceLimit; - - private $circularReferenceHandler; - - private $attributes; - - private $ignoredAttributes; - - private $constructorArguments = []; - - /** - * @param array|null $groups Groups to use for mapping - * @param array|null $attributes Attributes to use for mapping (exclude others) - * @param array|null $ignoredAttributes Attributes to exclude from mapping (include others) - */ - public function __construct(array $groups = null, array $attributes = null, array $ignoredAttributes = null) - { - parent::__construct(); - - $this->groups = $groups; - $this->depth = 0; - $this->attributes = $attributes; - $this->ignoredAttributes = $ignoredAttributes; - } - - /** - * Whether a reference has reached it's limit. - */ - public function shouldHandleCircularReference(string $reference, ?int $circularReferenceLimit = null): bool - { - if (!\array_key_exists($reference, $this->referenceRegistry)) { - return false; - } - - if (null === $circularReferenceLimit) { - $circularReferenceLimit = $this->circularReferenceLimit; - } - - if (null !== $circularReferenceLimit) { - return $this->countReferenceRegistry[$reference] >= $circularReferenceLimit; - } - - return true; - } - - /** - * Handle circular reference for a specific reference. - * - * By default will try to keep it and return the previous value - * - * @return mixed - */ - public function &handleCircularReference(string $reference, $object, ?int $circularReferenceLimit = null, callable $callback = null) - { - if (null === $callback) { - $callback = $this->circularReferenceHandler; - } - - if (null !== $callback) { - // Cannot directly return here, as we need to return by reference, and callback may not be declared as reference return - $value = $callback($object, $this); - - return $value; - } - - if (null === $circularReferenceLimit) { - $circularReferenceLimit = $this->circularReferenceLimit; - } - - if (null !== $circularReferenceLimit && $this->countReferenceRegistry[$reference] >= $circularReferenceLimit) { - throw new CircularReferenceException(sprintf('A circular reference has been detected when mapping the object of type "%s" (configured limit: %d)', \is_object($object) ? \get_class($object) : 'array', $circularReferenceLimit)); - } - - // When no limit defined return the object referenced - ++$this->countReferenceRegistry[$reference]; - - return $this->referenceRegistry[$reference]; - } - - /** - * Get groups for this context. - */ - public function getGroups(): ?array - { - return $this->groups; - } - - /** - * Get current depth. - */ - public function getDepth(): int - { - return $this->depth; - } - - /** - * Set object to populate (by-pass target construction). - */ - public function setObjectToPopulate($object) - { - $this->object = $object; - } - - /** - * Get object to populate. - */ - public function getObjectToPopulate() - { - $object = $this->object; - - if (null !== $object) { - $this->object = null; - } - - return $object; - } - - /** - * Set circular reference limit. - */ - public function setCircularReferenceLimit(?int $circularReferenceLimit): void - { - $this->circularReferenceLimit = $circularReferenceLimit; - } - - /** - * Set circular reference handler. - */ - public function setCircularReferenceHandler(?callable $circularReferenceHandler): void - { - $this->circularReferenceHandler = $circularReferenceHandler; - } - - /** - * Create a new context with a new reference. - */ - public function withReference($reference, &$object): self - { - $new = clone $this; - - $new->referenceRegistry[$reference] = &$object; - $new->countReferenceRegistry[$reference] = 1; - - return $new; - } - - /** - * Check whether an attribute is allowed to be mapped. - */ - public function isAllowedAttribute(string $attribute): bool - { - if (null !== $this->ignoredAttributes && \in_array($attribute, $this->ignoredAttributes, true)) { - return false; - } - - if (null === $this->attributes) { - return true; - } - - return \in_array($attribute, $this->attributes, true); - } - - /** - * Clone context with a incremented depth. - */ - public function withIncrementedDepth(): self - { - $new = clone $this; - ++$new->depth; - - return $new; - } - - /** - * Set the argument of a constructor for a specific class. - */ - public function setConstructorArgument(string $class, string $key, $value): void - { - if (!\array_key_exists($class, $this->constructorArguments)) { - $this->constructorArguments[$class] = []; - } - - $this->constructorArguments[$class][$key] = $value; - } - - /** - * Check wether an argument exist for the constructor for a specific class. - */ - public function hasConstructorArgument(string $class, string $key): bool - { - return \array_key_exists($key, $this->constructorArguments[$class] ?? []); - } - - /** - * Get constructor argument for a specific class. - */ - public function getConstructorArgument(string $class, string $key) - { - if (!\array_key_exists($key, $this->constructorArguments[$class] ?? [])) { - throw new NoConstructorArgumentFoundException(); - } - - return $this->constructorArguments[$class][$key]; - } - - /** - * Create a new cloned context, and reload attribute mapping for it. - */ - public function withNewContext(string $attribute): self - { - if (null === $this->attributes && null === $this->ignoredAttributes) { - return $this; - } - - $new = clone $this; - - if (null !== $this->ignoredAttributes && isset($this->ignoredAttributes[$attribute]) && \is_array($this->ignoredAttributes[$attribute])) { - $new->ignoredAttributes = $this->ignoredAttributes[$attribute]; - } - - if (null !== $this->attributes && isset($this->attributes[$attribute]) && \is_array($this->attributes[$attribute])) { - $new->attributes = $this->attributes[$attribute]; - } - - return $new; - } -} diff --git a/src/Symfony/Component/AutoMapper/GeneratedMapper.php b/src/Symfony/Component/AutoMapper/GeneratedMapper.php index 78c0b20ce1fa1..aa60d1a92d0d6 100644 --- a/src/Symfony/Component/AutoMapper/GeneratedMapper.php +++ b/src/Symfony/Component/AutoMapper/GeneratedMapper.php @@ -14,8 +14,6 @@ /** * Class derived for each generated mapper. * - * @internal - * * @author Joel Wurtz */ abstract class GeneratedMapper implements MapperInterface diff --git a/src/Symfony/Component/AutoMapper/Tests/ContextTest.php b/src/Symfony/Component/AutoMapper/Tests/MapperContextTest.php similarity index 100% rename from src/Symfony/Component/AutoMapper/Tests/ContextTest.php rename to src/Symfony/Component/AutoMapper/Tests/MapperContextTest.php From f74e22e0513fa8d2a78d284a62861e7e4e114da9 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Fri, 31 Jan 2020 22:11:54 +0100 Subject: [PATCH 27/38] Fix context rebase --- .../NoConstructorArgumentFoundException.php | 21 ----- .../Component/AutoMapper/MapperContext.php | 10 ++- .../AutoMapper/Tests/MapperContextTest.php | 76 +++++++++---------- 3 files changed, 42 insertions(+), 65 deletions(-) delete mode 100644 src/Symfony/Component/AutoMapper/Exception/NoConstructorArgumentFoundException.php diff --git a/src/Symfony/Component/AutoMapper/Exception/NoConstructorArgumentFoundException.php b/src/Symfony/Component/AutoMapper/Exception/NoConstructorArgumentFoundException.php deleted file mode 100644 index 4a186e180be0c..0000000000000 --- a/src/Symfony/Component/AutoMapper/Exception/NoConstructorArgumentFoundException.php +++ /dev/null @@ -1,21 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\AutoMapper\Exception; - -/** - * @expiremental in 4.3 - * - * @author Baptiste Leduc - */ -class NoConstructorArgumentFoundException extends \RuntimeException -{ -} diff --git a/src/Symfony/Component/AutoMapper/MapperContext.php b/src/Symfony/Component/AutoMapper/MapperContext.php index a88e1d0bc0e92..1fbb5f6bc3961 100644 --- a/src/Symfony/Component/AutoMapper/MapperContext.php +++ b/src/Symfony/Component/AutoMapper/MapperContext.php @@ -140,7 +140,7 @@ public static function shouldHandleCircularReference(array $context, string $ref * * @return mixed */ - public static function &handleCircularReference(array $context, string $reference, $object, ?int $circularReferenceLimit = null, callable $callback = null) + public static function &handleCircularReference(array &$context, string $reference, $object, ?int $circularReferenceLimit = null, callable $callback = null) { if (null === $callback) { $callback = $context[self::CIRCULAR_REFERENCE_HANDLER] ?? null; @@ -157,8 +157,12 @@ public static function &handleCircularReference(array $context, string $referenc $circularReferenceLimit = $context[self::CIRCULAR_REFERENCE_LIMIT] ?? null; } - if (null !== $circularReferenceLimit && $circularReferenceLimit <= ($context[self::CIRCULAR_COUNT_REFERENCE_REGISTRY][$reference] ?? 0)) { - throw new CircularReferenceException(sprintf('A circular reference has been detected when mapping the object of type "%s" (configured limit: %d)', \is_object($object) ? \get_class($object) : 'array', $circularReferenceLimit)); + if (null !== $circularReferenceLimit) { + if ($circularReferenceLimit <= ($context[self::CIRCULAR_COUNT_REFERENCE_REGISTRY][$reference] ?? 0)) { + throw new CircularReferenceException(sprintf('A circular reference has been detected when mapping the object of type "%s" (configured limit: %d)', \is_object($object) ? \get_class($object) : 'array', $circularReferenceLimit)); + } + + ++$context[self::CIRCULAR_COUNT_REFERENCE_REGISTRY][$reference]; } // When no limit defined return the object referenced diff --git a/src/Symfony/Component/AutoMapper/Tests/MapperContextTest.php b/src/Symfony/Component/AutoMapper/Tests/MapperContextTest.php index 2a65f86426afb..2289f2ea7623b 100644 --- a/src/Symfony/Component/AutoMapper/Tests/MapperContextTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/MapperContextTest.php @@ -12,100 +12,94 @@ namespace Symfony\Component\AutoMapper\Tests; use PHPUnit\Framework\TestCase; -use Symfony\Component\AutoMapper\Context; +use Symfony\Component\AutoMapper\MapperContext; use Symfony\Component\AutoMapper\Exception\CircularReferenceException; use Symfony\Component\AutoMapper\Exception\NoConstructorArgumentFoundException; /** * @author Baptiste Leduc */ -class ContextTest extends TestCase +class MapperContextTest extends TestCase { public function testIsAllowedAttribute(): void { - $context = new Context(null, ['id', 'age']); - self::assertTrue($context->isAllowedAttribute('id')); - self::assertFalse($context->isAllowedAttribute('name')); - self::assertTrue($context->isAllowedAttribute('age')); - - $context = new Context(null, null, ['name']); - self::assertTrue($context->isAllowedAttribute('id')); - self::assertFalse($context->isAllowedAttribute('name')); - self::assertTrue($context->isAllowedAttribute('age')); - - $context = new Context(null, ['id', 'age'], ['age']); - self::assertTrue($context->isAllowedAttribute('id')); - self::assertFalse($context->isAllowedAttribute('name')); - self::assertFalse($context->isAllowedAttribute('age')); + $context = new MapperContext(); + $context->setAllowedAttributes(['id', 'age']); + $context->setIgnoredAttributes(['age']); + + self::assertTrue(MapperContext::isAllowedAttribute($context->toArray(), 'id')); + self::assertFalse(MapperContext::isAllowedAttribute($context->toArray(), 'age')); + self::assertFalse(MapperContext::isAllowedAttribute($context->toArray(), 'name')); } public function testCircularReferenceLimit(): void { // with no circularReferenceLimit $object = new \stdClass(); - $context = new Context(); - $subContext = $context->withReference('reference', $object); - self::assertTrue($subContext->shouldHandleCircularReference('reference')); + $context = MapperContext::withReference([], 'reference', $object); + + self::assertTrue(MapperContext::shouldHandleCircularReference($context,'reference')); // with circularReferenceLimit $object = new \stdClass(); - $context = new Context(); + $context = new MapperContext(); $context->setCircularReferenceLimit(3); - $subContext = $context->withReference('reference', $object); + $context = MapperContext::withReference($context->toArray(), 'reference', $object); for ($i = 0; $i <= 2; ++$i) { if (2 === $i) { - self::assertTrue($subContext->shouldHandleCircularReference('reference')); + self::assertTrue(MapperContext::shouldHandleCircularReference($context,'reference')); break; } - self::assertFalse($subContext->shouldHandleCircularReference('reference')); + self::assertFalse(MapperContext::shouldHandleCircularReference($context,'reference')); // fake handleCircularReference to increment countReferenceRegistry - $subContext->handleCircularReference('reference', $object); + MapperContext::handleCircularReference($context,'reference', $object); } self::expectException(CircularReferenceException::class); self::expectExceptionMessage('A circular reference has been detected when mapping the object of type "stdClass" (configured limit: 3)'); - $subContext->handleCircularReference('reference', $object); + MapperContext::handleCircularReference($context,'reference', $object); } public function testCircularReferenceHandler(): void { $object = new \stdClass(); - $context = new Context(); + $context = new MapperContext(); $context->setCircularReferenceHandler(function ($object) { return $object; }); - $subContext = $context->withReference('reference', $object); - self::assertTrue($subContext->shouldHandleCircularReference('reference')); - self::assertEquals($object, $context->handleCircularReference('reference', $object)); + $context = MapperContext::withReference($context->toArray(),'reference', $object); + + self::assertTrue(MapperContext::shouldHandleCircularReference($context,'reference')); + self::assertEquals($object, MapperContext::handleCircularReference($context,'reference', $object)); } public function testConstructorArgument(): void { - $context = new Context(); + $context = new MapperContext(); $context->setConstructorArgument(Fixtures\User::class, 'id', 10); $context->setConstructorArgument(Fixtures\User::class, 'age', 50); - self::assertTrue($context->hasConstructorArgument(Fixtures\User::class, 'id')); - self::assertFalse($context->hasConstructorArgument(Fixtures\User::class, 'name')); - self::assertTrue($context->hasConstructorArgument(Fixtures\User::class, 'age')); + self::assertTrue(MapperContext::hasConstructorArgument($context->toArray(),Fixtures\User::class, 'id')); + self::assertFalse(MapperContext::hasConstructorArgument($context->toArray(),Fixtures\User::class, 'name')); + self::assertTrue(MapperContext::hasConstructorArgument($context->toArray(),Fixtures\User::class, 'age')); - self::assertEquals(10, $context->getConstructorArgument(Fixtures\User::class, 'id')); - self::assertEquals(50, $context->getConstructorArgument(Fixtures\User::class, 'age')); + self::assertEquals(10, MapperContext::getConstructorArgument($context->toArray(),Fixtures\User::class, 'id')); + self::assertEquals(50, MapperContext::getConstructorArgument($context->toArray(),Fixtures\User::class, 'age')); - self::expectException(NoConstructorArgumentFoundException::class); - $context->getConstructorArgument(Fixtures\User::class, 'name'); + self::assertNull(MapperContext::getConstructorArgument($context->toArray(),Fixtures\User::class, 'name')); } public function testGroups(): void { $expected = ['group1', 'group4']; - $context = new Context($expected); + $context = new MapperContext(); + $context->setGroups($expected); - self::assertEquals($expected, $context->getGroups()); - self::assertTrue(\in_array('group1', $context->getGroups())); - self::assertFalse(\in_array('group2', $context->getGroups())); + self::assertEquals($expected, $context->toArray()[MapperContext::GROUPS]); + self::assertContains('group1', $context->toArray()[MapperContext::GROUPS]); + self::assertNotContains('group2', $context->toArray()[MapperContext::GROUPS]); } } From ee671dd9db2fd5edd6185b43db1d16dfb7c625ab Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Fri, 31 Jan 2020 22:29:11 +0100 Subject: [PATCH 28/38] Add missing deps in dev --- src/Symfony/Component/AutoMapper/composer.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/AutoMapper/composer.json b/src/Symfony/Component/AutoMapper/composer.json index e455193d26312..4bc3ec4e5ae74 100644 --- a/src/Symfony/Component/AutoMapper/composer.json +++ b/src/Symfony/Component/AutoMapper/composer.json @@ -18,9 +18,11 @@ "require": { "php": "^7.1.3", "nikic/php-parser": "^4.0", - "symfony/property-info": "~3.4|~4.0" + "symfony/property-info": "~5.1" }, "require-dev": { + "doctrine/annotations": "~1.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0", "symfony/serializer": "^4.2" }, "suggest": { From 78ef805714cedcedbd07c28a543f8434d0642dc5 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Sat, 1 Feb 2020 00:44:28 +0100 Subject: [PATCH 29/38] Use property read / write info extractor --- .../Component/AutoMapper/AutoMapper.php | 24 ++- .../Extractor/AccessorExtractorInterface.php | 23 --- .../Extractor/FromSourceMappingExtractor.php | 9 +- .../Extractor/FromTargetMappingExtractor.php | 13 +- .../AutoMapper/Extractor/MappingExtractor.php | 61 ++++++- .../Extractor/MappingExtractorInterface.php | 2 +- .../Extractor/PrivateReflectionExtractor.php | 151 ------------------ .../AutoMapper/Extractor/PropertyMapping.php | 13 +- .../ReadAccessorExtractorInterface.php | 24 --- .../Extractor/ReflectionExtractor.php | 143 ----------------- .../SourceTargetMappingExtractor.php | 13 +- .../WriteMutatorExtractorInterface.php | 24 --- .../AutoMapper/Generator/Generator.php | 2 +- .../Component/AutoMapper/MapperMetadata.php | 3 +- .../FromSourceMappingExtractorTest.php | 17 +- .../FromTargetMappingExtractorTest.php | 15 +- .../PrivateReflectionExtractorTest.php | 42 ----- .../Extractor/ReflectionExtractorTest.php | 119 -------------- .../MapperGeneratorMetadataFactoryTest.php | 15 +- 19 files changed, 143 insertions(+), 570 deletions(-) delete mode 100644 src/Symfony/Component/AutoMapper/Extractor/AccessorExtractorInterface.php delete mode 100644 src/Symfony/Component/AutoMapper/Extractor/PrivateReflectionExtractor.php delete mode 100644 src/Symfony/Component/AutoMapper/Extractor/ReadAccessorExtractorInterface.php delete mode 100644 src/Symfony/Component/AutoMapper/Extractor/ReflectionExtractor.php delete mode 100644 src/Symfony/Component/AutoMapper/Extractor/WriteMutatorExtractorInterface.php delete mode 100644 src/Symfony/Component/AutoMapper/Tests/Extractor/PrivateReflectionExtractorTest.php delete mode 100644 src/Symfony/Component/AutoMapper/Tests/Extractor/ReflectionExtractorTest.php diff --git a/src/Symfony/Component/AutoMapper/AutoMapper.php b/src/Symfony/Component/AutoMapper/AutoMapper.php index a56b655604b4a..3c6969040ddb5 100644 --- a/src/Symfony/Component/AutoMapper/AutoMapper.php +++ b/src/Symfony/Component/AutoMapper/AutoMapper.php @@ -194,7 +194,19 @@ public static function create( )); } - $reflectionExtractor = $private ? new PrivateReflectionExtractor() : new ReflectionExtractor(); + $flags = ReflectionExtractor::ALLOW_PUBLIC; + + if ($private) { + $flags |= ReflectionExtractor::ALLOW_PROTECTED | ReflectionExtractor::ALLOW_PRIVATE; + } + + $reflectionExtractor = new ReflectionExtractor( + null, + null, + null, + true, + $flags + ); $phpDocExtractor = new PhpDocExtractor(); $propertyInfoExtractor = new PropertyInfoExtractor( @@ -204,18 +216,19 @@ public static function create( [$reflectionExtractor] ); - $accessorExtractor = new Extractor\ReflectionExtractor($private); $transformerFactory = new ChainTransformerFactory(); $sourceTargetMappingExtractor = new SourceTargetMappingExtractor( $propertyInfoExtractor, - $accessorExtractor, + $reflectionExtractor, + $reflectionExtractor, $transformerFactory, $classMetadataFactory ); $fromTargetMappingExtractor = new FromTargetMappingExtractor( $propertyInfoExtractor, - $accessorExtractor, + $reflectionExtractor, + $reflectionExtractor, $transformerFactory, $classMetadataFactory, $nameConverter @@ -223,7 +236,8 @@ public static function create( $fromSourceMappingExtractor = new FromSourceMappingExtractor( $propertyInfoExtractor, - $accessorExtractor, + $reflectionExtractor, + $reflectionExtractor, $transformerFactory, $classMetadataFactory, $nameConverter diff --git a/src/Symfony/Component/AutoMapper/Extractor/AccessorExtractorInterface.php b/src/Symfony/Component/AutoMapper/Extractor/AccessorExtractorInterface.php deleted file mode 100644 index 21a8bc74ed480..0000000000000 --- a/src/Symfony/Component/AutoMapper/Extractor/AccessorExtractorInterface.php +++ /dev/null @@ -1,23 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\AutoMapper\Extractor; - -/** - * Extracts accessor and mutator. - * - * @internal - * - * @author Joel Wurtz - */ -interface AccessorExtractorInterface extends ReadAccessorExtractorInterface, WriteMutatorExtractorInterface -{ -} diff --git a/src/Symfony/Component/AutoMapper/Extractor/FromSourceMappingExtractor.php b/src/Symfony/Component/AutoMapper/Extractor/FromSourceMappingExtractor.php index 6c87741c2652c..bfba50fb38c51 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/FromSourceMappingExtractor.php +++ b/src/Symfony/Component/AutoMapper/Extractor/FromSourceMappingExtractor.php @@ -15,6 +15,8 @@ use Symfony\Component\AutoMapper\MapperMetadataInterface; use Symfony\Component\AutoMapper\Transformer\TransformerFactoryInterface; use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface; use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; @@ -34,9 +36,9 @@ final class FromSourceMappingExtractor extends MappingExtractor private $nameConverter; - public function __construct(PropertyInfoExtractorInterface $propertyInfoExtractor, AccessorExtractorInterface $accessorExtractor, TransformerFactoryInterface $transformerFactory, ClassMetadataFactoryInterface $classMetadataFactory = null, AdvancedNameConverterInterface $nameConverter = null) + public function __construct(PropertyInfoExtractorInterface $propertyInfoExtractor, PropertyReadInfoExtractorInterface $readInfoExtractor, PropertyWriteInfoExtractorInterface $writeInfoExtractor, TransformerFactoryInterface $transformerFactory, ClassMetadataFactoryInterface $classMetadataFactory = null, AdvancedNameConverterInterface $nameConverter = null) { - parent::__construct($propertyInfoExtractor, $accessorExtractor, $transformerFactory, $classMetadataFactory); + parent::__construct($propertyInfoExtractor, $readInfoExtractor, $writeInfoExtractor, $transformerFactory, $classMetadataFactory); $this->nameConverter = $nameConverter; } @@ -85,6 +87,7 @@ public function getPropertiesMapping(MapperMetadataInterface $mapperMetadata): a $mapping[] = new PropertyMapping( $this->getReadAccessor($mapperMetadata->getSource(), $mapperMetadata->getTarget(), $property), $this->getWriteMutator($mapperMetadata->getSource(), $mapperMetadata->getTarget(), $property), + null, $transformer, $property, false, @@ -129,7 +132,7 @@ private function transformType(string $target, Type $type = null): ?Type /** * {@inheritdoc} */ - public function getWriteMutator(string $source, string $target, string $property): WriteMutator + public function getWriteMutator(string $source, string $target, string $property, array $context = []): WriteMutator { if (null !== $this->nameConverter) { $property = $this->nameConverter->normalize($property, $source, $target); diff --git a/src/Symfony/Component/AutoMapper/Extractor/FromTargetMappingExtractor.php b/src/Symfony/Component/AutoMapper/Extractor/FromTargetMappingExtractor.php index b2395fab9cda8..42c952521a804 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/FromTargetMappingExtractor.php +++ b/src/Symfony/Component/AutoMapper/Extractor/FromTargetMappingExtractor.php @@ -15,6 +15,8 @@ use Symfony\Component\AutoMapper\MapperMetadataInterface; use Symfony\Component\AutoMapper\Transformer\TransformerFactoryInterface; use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface; use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; @@ -34,9 +36,9 @@ final class FromTargetMappingExtractor extends MappingExtractor private $nameConverter; - public function __construct(PropertyInfoExtractorInterface $propertyInfoExtractor, AccessorExtractorInterface $accessorExtractor, TransformerFactoryInterface $transformerFactory, ClassMetadataFactoryInterface $classMetadataFactory = null, AdvancedNameConverterInterface $nameConverter = null) + public function __construct(PropertyInfoExtractorInterface $propertyInfoExtractor, PropertyReadInfoExtractorInterface $readInfoExtractor, PropertyWriteInfoExtractorInterface $writeInfoExtractor, TransformerFactoryInterface $transformerFactory, ClassMetadataFactoryInterface $classMetadataFactory = null, AdvancedNameConverterInterface $nameConverter = null) { - parent::__construct($propertyInfoExtractor, $accessorExtractor, $transformerFactory, $classMetadataFactory); + parent::__construct($propertyInfoExtractor, $readInfoExtractor, $writeInfoExtractor, $transformerFactory, $classMetadataFactory); $this->nameConverter = $nameConverter; } @@ -83,7 +85,12 @@ public function getPropertiesMapping(MapperMetadataInterface $mapperMetadata): a $mapping[] = new PropertyMapping( $this->getReadAccessor($mapperMetadata->getSource(), $mapperMetadata->getTarget(), $property), - $this->getWriteMutator($mapperMetadata->getSource(), $mapperMetadata->getTarget(), $property), + $this->getWriteMutator($mapperMetadata->getSource(), $mapperMetadata->getTarget(), $property, [ + 'enable_constructor_extraction' => false, + ]), + $this->getWriteMutator($mapperMetadata->getSource(), $mapperMetadata->getTarget(), $property, [ + 'enable_constructor_extraction' => true, + ]), $transformer, $property, true, diff --git a/src/Symfony/Component/AutoMapper/Extractor/MappingExtractor.php b/src/Symfony/Component/AutoMapper/Extractor/MappingExtractor.php index 3a38c950a77db..e806f933c2b1e 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/MappingExtractor.php +++ b/src/Symfony/Component/AutoMapper/Extractor/MappingExtractor.php @@ -13,6 +13,10 @@ use Symfony\Component\AutoMapper\Transformer\TransformerFactoryInterface; use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyReadInfo; +use Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyWriteInfo; +use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; /** @@ -26,14 +30,17 @@ abstract class MappingExtractor implements MappingExtractorInterface protected $transformerFactory; - protected $accessorExtractor; + protected $readInfoExtractor; + + protected $writeInfoExtractor; protected $classMetadataFactory; - public function __construct(PropertyInfoExtractorInterface $propertyInfoExtractor, AccessorExtractorInterface $accessorExtractor, TransformerFactoryInterface $transformerFactory, ClassMetadataFactoryInterface $classMetadataFactory = null) + public function __construct(PropertyInfoExtractorInterface $propertyInfoExtractor, PropertyReadInfoExtractorInterface $readInfoExtractor, PropertyWriteInfoExtractorInterface $writeInfoExtractor, TransformerFactoryInterface $transformerFactory, ClassMetadataFactoryInterface $classMetadataFactory = null) { $this->propertyInfoExtractor = $propertyInfoExtractor; - $this->accessorExtractor = $accessorExtractor; + $this->readInfoExtractor = $readInfoExtractor; + $this->writeInfoExtractor = $writeInfoExtractor; $this->transformerFactory = $transformerFactory; $this->classMetadataFactory = $classMetadataFactory; } @@ -43,15 +50,57 @@ public function __construct(PropertyInfoExtractorInterface $propertyInfoExtracto */ public function getReadAccessor(string $source, string $target, string $property): ?ReadAccessor { - return $this->accessorExtractor->getReadAccessor($source, $property); + $readInfo = $this->readInfoExtractor->getReadInfo($source, $property); + + if (null === $readInfo) { + return null; + } + + $type = ReadAccessor::TYPE_PROPERTY; + + if (PropertyReadInfo::TYPE_METHOD === $readInfo->getType()) { + $type = ReadAccessor::TYPE_METHOD; + } + + return new ReadAccessor( + $type, + $readInfo->getName(), + $readInfo->getVisibility() !== PropertyReadInfo::VISIBILITY_PUBLIC + ); } /** * {@inheritdoc} */ - public function getWriteMutator(string $source, string $target, string $property): ?WriteMutator + public function getWriteMutator(string $source, string $target, string $property, array $context = []): ?WriteMutator { - return $this->accessorExtractor->getWriteMutator($target, $property); + $writeInfo = $this->writeInfoExtractor->getWriteInfo($target, $property, $context); + + if (null === $writeInfo) { + return null; + } + + if (PropertyWriteInfo::TYPE_NONE === $writeInfo->getType()) { + return null; + } + + if (PropertyWriteInfo::TYPE_CONSTRUCTOR === $writeInfo->getType()) { + $parameter = new \ReflectionParameter([$target, '__construct'], $writeInfo->getName()); + + return new WriteMutator(WriteMutator::TYPE_CONSTRUCTOR, $writeInfo->getName(), false, $parameter); + } + + $type = WriteMutator::TYPE_PROPERTY; + + if (PropertyWriteInfo::TYPE_METHOD === $writeInfo->getType()) { + $type = WriteMutator::TYPE_METHOD; + } + + return new WriteMutator( + $type, + $writeInfo->getName(), + $writeInfo->getVisibility() !== PropertyReadInfo::VISIBILITY_PUBLIC + ); } protected function getMaxDepth($class, $property): ?int diff --git a/src/Symfony/Component/AutoMapper/Extractor/MappingExtractorInterface.php b/src/Symfony/Component/AutoMapper/Extractor/MappingExtractorInterface.php index 2645bb2601419..e6e3bd4e3857b 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/MappingExtractorInterface.php +++ b/src/Symfony/Component/AutoMapper/Extractor/MappingExtractorInterface.php @@ -37,5 +37,5 @@ public function getReadAccessor(string $source, string $target, string $property /** * Extracts write mutator for a given source, target and property. */ - public function getWriteMutator(string $source, string $target, string $property): ?WriteMutator; + public function getWriteMutator(string $source, string $target, string $property, array $context = []): ?WriteMutator; } diff --git a/src/Symfony/Component/AutoMapper/Extractor/PrivateReflectionExtractor.php b/src/Symfony/Component/AutoMapper/Extractor/PrivateReflectionExtractor.php deleted file mode 100644 index 79a5e93648412..0000000000000 --- a/src/Symfony/Component/AutoMapper/Extractor/PrivateReflectionExtractor.php +++ /dev/null @@ -1,151 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\AutoMapper\Extractor; - -use Symfony\Component\Inflector\Inflector; -use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; -use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface; -use Symfony\Component\PropertyInfo\PropertyListExtractorInterface; -use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; - -/** - * Extracts all (including private) data using the reflection API. - * - * @author Joel Wurtz - */ -final class PrivateReflectionExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface -{ - /** - * @internal - */ - public static $defaultMutatorPrefixes = ['add', 'remove', 'set']; - - /** - * @internal - */ - public static $defaultAccessorPrefixes = ['is', 'can', 'get']; - - /** - * @internal - */ - public static $defaultArrayMutatorPrefixes = ['add', 'remove']; - - private $mutatorPrefixes; - private $accessorPrefixes; - private $arrayMutatorPrefixes; - private $reflectionExtractor; - - public function __construct(array $mutatorPrefixes = null, array $accessorPrefixes = null, array $arrayMutatorPrefixes = null, bool $enableConstructorExtraction = true) - { - $this->mutatorPrefixes = $mutatorPrefixes ?? self::$defaultMutatorPrefixes; - $this->accessorPrefixes = $accessorPrefixes ?? self::$defaultAccessorPrefixes; - $this->arrayMutatorPrefixes = $arrayMutatorPrefixes ?? self::$defaultArrayMutatorPrefixes; - $this->reflectionExtractor = new ReflectionExtractor($mutatorPrefixes, $accessorPrefixes, $arrayMutatorPrefixes, $enableConstructorExtraction); - } - - /** - * {@inheritdoc} - */ - public function getProperties($class, array $context = []) - { - try { - $reflectionClass = new \ReflectionClass($class); - } catch (\ReflectionException $e) { - return; - } - - $propertyFlag = \ReflectionProperty::IS_PRIVATE | \ReflectionProperty::IS_PROTECTED; - $methodFlag = \ReflectionMethod::IS_PRIVATE | \ReflectionMethod::IS_PROTECTED; - - $reflectionProperties = $reflectionClass->getProperties($propertyFlag); - $properties = $this->reflectionExtractor->getProperties($class, $context); - - if (null === $properties) { - $properties = []; - } - - foreach ($reflectionProperties as $reflectionProperty) { - $properties[$reflectionProperty->name] = $reflectionProperty->name; - } - - foreach ($reflectionClass->getMethods($methodFlag) as $reflectionMethod) { - if ($reflectionMethod->isStatic()) { - continue; - } - - $propertyName = $this->getPropertyName($reflectionMethod->name, $reflectionProperties); - - if (!$propertyName || isset($properties[$propertyName])) { - continue; - } - - if (!$reflectionClass->hasProperty($propertyName) && !preg_match('/^[A-Z]{2,}/', $propertyName)) { - $propertyName = lcfirst($propertyName); - } - - $properties[$propertyName] = $propertyName; - } - - return $properties ? array_values($properties) : null; - } - - /** - * {@inheritdoc} - */ - public function getTypes($class, $property, array $context = []) - { - return $this->reflectionExtractor->getTypes($class, $property, $context); - } - - /** - * {@inheritdoc} - */ - public function isReadable($class, $property, array $context = []) - { - $refClass = new \ReflectionClass($class); - - return $refClass->hasProperty($property); - } - - /** - * {@inheritdoc} - */ - public function isWritable($class, $property, array $context = []) - { - $refClass = new \ReflectionClass($class); - - return $refClass->hasProperty($property); - } - - private function getPropertyName(string $methodName, array $reflectionProperties): ?string - { - $pattern = implode('|', array_merge($this->accessorPrefixes, $this->mutatorPrefixes)); - - if ('' !== $pattern && preg_match('/^('.$pattern.')(.+)$/i', $methodName, $matches)) { - if (!\in_array($matches[1], $this->arrayMutatorPrefixes)) { - return $matches[2]; - } - - foreach ($reflectionProperties as $reflectionProperty) { - foreach ((array) Inflector::singularize($reflectionProperty->name) as $name) { - if (strtolower($name) === strtolower($matches[2])) { - return $reflectionProperty->name; - } - } - } - - return $matches[2]; - } - - return null; - } -} diff --git a/src/Symfony/Component/AutoMapper/Extractor/PropertyMapping.php b/src/Symfony/Component/AutoMapper/Extractor/PropertyMapping.php index 25649867f6f03..4b2e1490b511c 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/PropertyMapping.php +++ b/src/Symfony/Component/AutoMapper/Extractor/PropertyMapping.php @@ -26,6 +26,8 @@ final class PropertyMapping private $writeMutator; + private $writeMutatorConstructor; + private $transformer; private $checkExists; @@ -40,7 +42,8 @@ final class PropertyMapping public function __construct( ReadAccessor $readAccessor, - WriteMutator $writeMutator, + ?WriteMutator $writeMutator, + ?WriteMutator $writeMutatorConstructor, TransformerInterface $transformer, string $property, bool $checkExists = false, @@ -50,6 +53,7 @@ public function __construct( ) { $this->readAccessor = $readAccessor; $this->writeMutator = $writeMutator; + $this->writeMutatorConstructor = $writeMutatorConstructor; $this->transformer = $transformer; $this->property = $property; $this->checkExists = $checkExists; @@ -63,11 +67,16 @@ public function getReadAccessor(): ReadAccessor return $this->readAccessor; } - public function getWriteMutator(): WriteMutator + public function getWriteMutator(): ?WriteMutator { return $this->writeMutator; } + public function getWriteMutatorConstructor(): ?WriteMutator + { + return $this->writeMutatorConstructor; + } + public function getTransformer(): TransformerInterface { return $this->transformer; diff --git a/src/Symfony/Component/AutoMapper/Extractor/ReadAccessorExtractorInterface.php b/src/Symfony/Component/AutoMapper/Extractor/ReadAccessorExtractorInterface.php deleted file mode 100644 index 422fc1490c2b4..0000000000000 --- a/src/Symfony/Component/AutoMapper/Extractor/ReadAccessorExtractorInterface.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\AutoMapper\Extractor; - -/** - * Extract read accessor for property of a class. - * - * @internal - * - * @author Joel Wurtz - */ -interface ReadAccessorExtractorInterface -{ - public function getReadAccessor(string $class, string $property): ?ReadAccessor; -} diff --git a/src/Symfony/Component/AutoMapper/Extractor/ReflectionExtractor.php b/src/Symfony/Component/AutoMapper/Extractor/ReflectionExtractor.php deleted file mode 100644 index 23f61621c375e..0000000000000 --- a/src/Symfony/Component/AutoMapper/Extractor/ReflectionExtractor.php +++ /dev/null @@ -1,143 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\AutoMapper\Extractor; - -/** - * Extracts accessor and mutator from reflection. - * - * @author Joel Wurtz - */ -final class ReflectionExtractor implements AccessorExtractorInterface -{ - private $allowPrivate; - - public function __construct($allowPrivate = false) - { - $this->allowPrivate = $allowPrivate; - } - - public function getReadAccessor(string $class, string $property): ?ReadAccessor - { - $reflClass = new \ReflectionClass($class); - $hasProperty = $reflClass->hasProperty($property); - $camelProp = $this->camelize($property); - $getter = 'get'.$camelProp; - $getsetter = lcfirst($camelProp); // jQuery style, e.g. read: last(), write: last($item) - $isser = 'is'.$camelProp; - $hasser = 'has'.$camelProp; - $accessPrivate = false; - - if ($reflClass->hasMethod($getter) && $reflClass->getMethod($getter)->isPublic()) { - $accessType = ReadAccessor::TYPE_METHOD; - $accessName = $getter; - } elseif ($reflClass->hasMethod($getsetter) && $reflClass->getMethod($getsetter)->isPublic()) { - $accessType = ReadAccessor::TYPE_METHOD; - $accessName = $getsetter; - } elseif ($reflClass->hasMethod($isser) && $reflClass->getMethod($isser)->isPublic()) { - $accessType = ReadAccessor::TYPE_METHOD; - $accessName = $isser; - } elseif ($reflClass->hasMethod($hasser) && $reflClass->getMethod($hasser)->isPublic()) { - $accessType = ReadAccessor::TYPE_METHOD; - $accessName = $hasser; - } elseif ($reflClass->hasMethod('__get') && $reflClass->getMethod('__get')->isPublic()) { - $accessType = ReadAccessor::TYPE_PROPERTY; - $accessName = $property; - } elseif ($hasProperty && $reflClass->getProperty($property)->isPublic()) { - $accessType = ReadAccessor::TYPE_PROPERTY; - $accessName = $property; - } elseif ($hasProperty && $this->allowPrivate && $reflClass->getProperty($property)) { - $accessType = ReadAccessor::TYPE_PROPERTY; - $accessName = $property; - $accessPrivate = true; - } else { - return null; - } - - return new ReadAccessor($accessType, $accessName, $accessPrivate); - } - - public function getWriteMutator(string $class, string $property, bool $allowConstruct = true): ?WriteMutator - { - $reflClass = new \ReflectionClass($class); - $hasProperty = $reflClass->hasProperty($property); - $camelized = $this->camelize($property); - $accessParameter = null; - $accessName = null; - $accessType = null; - $accessPrivate = false; - $constructor = $reflClass->getConstructor(); - - if (null !== $constructor) { - foreach ($constructor->getParameters() as $parameter) { - if ($parameter->getName() === $property) { - $accessParameter = $parameter; - } - } - } - - if (null === $accessType) { - $setter = 'set'.$camelized; - $getsetter = lcfirst($camelized); // jQuery style, e.g. read: last(), write: last($item) - - if (null !== $accessParameter && $allowConstruct) { - $accessType = WriteMutator::TYPE_CONSTRUCTOR; - $accessName = $property; - } elseif ($this->isMethodAccessible($reflClass, $setter, 1)) { - $accessType = WriteMutator::TYPE_METHOD; - $accessName = $setter; - } elseif ($this->isMethodAccessible($reflClass, $getsetter, 1)) { - $accessType = WriteMutator::TYPE_METHOD; - $accessName = $getsetter; - } elseif ($this->isMethodAccessible($reflClass, '__set', 2)) { - $accessType = WriteMutator::TYPE_PROPERTY; - $accessName = $property; - } elseif ($hasProperty && $reflClass->getProperty($property)->isPublic()) { - $accessType = WriteMutator::TYPE_PROPERTY; - $accessName = $property; - } elseif ($hasProperty && $this->allowPrivate && $reflClass->getProperty($property)) { - $accessType = WriteMutator::TYPE_PROPERTY; - $accessName = $property; - $accessPrivate = true; - } else { - return null; - } - } - - return new WriteMutator($accessType, $accessName, $accessPrivate, $accessParameter); - } - - /** - * Returns whether a method is public and has the number of required parameters. - */ - private function isMethodAccessible(\ReflectionClass $class, string $methodName, int $parameters): bool - { - if ($class->hasMethod($methodName)) { - $method = $class->getMethod($methodName); - - if ($method->isPublic() - && $method->getNumberOfRequiredParameters() <= $parameters - && $method->getNumberOfParameters() >= $parameters) { - return true; - } - } - - return false; - } - - /** - * Camelizes a given string. - */ - private function camelize(string $string): string - { - return str_replace(' ', '', ucwords(str_replace('_', ' ', $string))); - } -} diff --git a/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php b/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php index e52fd348a2a8c..a28d44a9263e5 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php +++ b/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php @@ -42,7 +42,9 @@ public function getPropertiesMapping(MapperMetadataInterface $mapperMetadata): a } if (\in_array($property, $targetProperties, true)) { - $targetMutatorConstruct = $this->accessorExtractor->getWriteMutator($mapperMetadata->getTarget(), $property, true); + $targetMutatorConstruct = $this->getWriteMutator($mapperMetadata->getSource(), $mapperMetadata->getTarget(), $property, [ + 'enable_constructor_extraction' => true, + ]); if ((null === $targetMutatorConstruct || null === $targetMutatorConstruct->getParameter()) && !$this->propertyInfoExtractor->isWritable($mapperMetadata->getTarget(), $property)) { continue; @@ -56,8 +58,10 @@ public function getPropertiesMapping(MapperMetadataInterface $mapperMetadata): a continue; } - $sourceAccessor = $this->accessorExtractor->getReadAccessor($mapperMetadata->getSource(), $property); - $targetMutator = $this->accessorExtractor->getWriteMutator($mapperMetadata->getTarget(), $property, false); + $sourceAccessor = $this->getReadAccessor($mapperMetadata->getSource(), $mapperMetadata->getTarget(), $property); + $targetMutator = $this->getWriteMutator($mapperMetadata->getSource(), $mapperMetadata->getTarget(), $property, [ + 'enable_constructor_extraction' => false, + ]); $maxDepthSource = $this->getMaxDepth($mapperMetadata->getSource(), $property); $maxDepthTarget = $this->getMaxDepth($mapperMetadata->getTarget(), $property); @@ -73,7 +77,8 @@ public function getPropertiesMapping(MapperMetadataInterface $mapperMetadata): a $mapping[] = new PropertyMapping( $sourceAccessor, - $targetMutator ?? $targetMutatorConstruct, + $targetMutator, + $targetMutatorConstruct->getType() === WriteMutator::TYPE_CONSTRUCTOR ? $targetMutatorConstruct : null, $transformer, $property, false, diff --git a/src/Symfony/Component/AutoMapper/Extractor/WriteMutatorExtractorInterface.php b/src/Symfony/Component/AutoMapper/Extractor/WriteMutatorExtractorInterface.php deleted file mode 100644 index 5b64918305c58..0000000000000 --- a/src/Symfony/Component/AutoMapper/Extractor/WriteMutatorExtractorInterface.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\AutoMapper\Extractor; - -/** - * Extracts write mutator for property of a class. - * - * @internal - * - * @author Joel Wurtz - */ -interface WriteMutatorExtractorInterface -{ - public function getWriteMutator(string $class, string $property, bool $allowConstructor = true): ?WriteMutator; -} diff --git a/src/Symfony/Component/AutoMapper/Generator/Generator.php b/src/Symfony/Component/AutoMapper/Generator/Generator.php index d889c49a033e6..41913d0eb587d 100644 --- a/src/Symfony/Component/AutoMapper/Generator/Generator.php +++ b/src/Symfony/Component/AutoMapper/Generator/Generator.php @@ -369,7 +369,7 @@ private function getCreateObjectStatements(MapperGeneratorMetadataInterface $map /** @var PropertyMapping $propertyMapping */ foreach ($propertiesMapping as $propertyMapping) { - if (null === ($parameter = $propertyMapping->getWriteMutator()->getParameter())) { + if (null === $propertyMapping->getWriteMutatorConstructor() || null === ($parameter = $propertyMapping->getWriteMutatorConstructor()->getParameter())) { continue; } diff --git a/src/Symfony/Component/AutoMapper/MapperMetadata.php b/src/Symfony/Component/AutoMapper/MapperMetadata.php index e819c7f429193..33a66d587b51e 100644 --- a/src/Symfony/Component/AutoMapper/MapperMetadata.php +++ b/src/Symfony/Component/AutoMapper/MapperMetadata.php @@ -119,7 +119,7 @@ public function hasConstructor(): bool $mandatoryParameters = []; foreach ($parameters as $parameter) { - if (!$parameter->isOptional()) { + if (!$parameter->isOptional() && !$parameter->allowsNull()) { $mandatoryParameters[] = $parameter; } } @@ -288,6 +288,7 @@ private function buildPropertyMapping() $this->propertiesMapping[$property] = new PropertyMapping( new ReadAccessor(ReadAccessor::TYPE_SOURCE, $property), $this->mappingExtractor->getWriteMutator($this->source, $this->target, $property), + null, new CallbackTransformer($property), $property, false diff --git a/src/Symfony/Component/AutoMapper/Tests/Extractor/FromSourceMappingExtractorTest.php b/src/Symfony/Component/AutoMapper/Tests/Extractor/FromSourceMappingExtractorTest.php index ff2c2ca59bca3..6428cb9804dd2 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Extractor/FromSourceMappingExtractorTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/Extractor/FromSourceMappingExtractorTest.php @@ -16,7 +16,6 @@ use Symfony\Component\AutoMapper\Extractor\FromSourceMappingExtractor; use Symfony\Component\AutoMapper\Extractor\PrivateReflectionExtractor; use Symfony\Component\AutoMapper\Extractor\PropertyMapping; -use Symfony\Component\AutoMapper\Extractor\ReflectionExtractor; use Symfony\Component\AutoMapper\MapperMetadata; use Symfony\Component\AutoMapper\Tests\AutoMapperBaseTest; use Symfony\Component\AutoMapper\Tests\Fixtures; @@ -29,6 +28,7 @@ use Symfony\Component\AutoMapper\Transformer\ObjectTransformerFactory; use Symfony\Component\AutoMapper\Transformer\UniqueTypeTransformerFactory; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; +use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\PropertyInfoExtractor; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; @@ -50,7 +50,14 @@ public function setUp(): void private function fromSourceMappingExtractorBootstrap(bool $private = true): void { $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); - $reflectionExtractor = $private ? new PrivateReflectionExtractor() : new \Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor(); + $flags = ReflectionExtractor::ALLOW_PUBLIC; + + if ($private) { + $flags |= ReflectionExtractor::ALLOW_PROTECTED | ReflectionExtractor::ALLOW_PRIVATE; + } + + $reflectionExtractor = new ReflectionExtractor(null, null, null, true, $flags); + $transformerFactory = new ChainTransformerFactory(); $phpDocExtractor = new PhpDocExtractor(); $propertyInfoExtractor = new PropertyInfoExtractor( @@ -60,12 +67,10 @@ private function fromSourceMappingExtractorBootstrap(bool $private = true): void [$reflectionExtractor] ); - $accessorExtractor = new ReflectionExtractor($private); - $transformerFactory = new ChainTransformerFactory(); - $this->fromSourceMappingExtractor = new FromSourceMappingExtractor( $propertyInfoExtractor, - $accessorExtractor, + $reflectionExtractor, + $reflectionExtractor, $transformerFactory, $classMetadataFactory ); diff --git a/src/Symfony/Component/AutoMapper/Tests/Extractor/FromTargetMappingExtractorTest.php b/src/Symfony/Component/AutoMapper/Tests/Extractor/FromTargetMappingExtractorTest.php index c9cb43490c587..3c77d97beea09 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Extractor/FromTargetMappingExtractorTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/Extractor/FromTargetMappingExtractorTest.php @@ -14,9 +14,7 @@ use Doctrine\Common\Annotations\AnnotationReader; use Symfony\Component\AutoMapper\Exception\InvalidMappingException; use Symfony\Component\AutoMapper\Extractor\FromTargetMappingExtractor; -use Symfony\Component\AutoMapper\Extractor\PrivateReflectionExtractor; use Symfony\Component\AutoMapper\Extractor\PropertyMapping; -use Symfony\Component\AutoMapper\Extractor\ReflectionExtractor; use Symfony\Component\AutoMapper\MapperMetadata; use Symfony\Component\AutoMapper\Tests\AutoMapperBaseTest; use Symfony\Component\AutoMapper\Tests\Fixtures; @@ -29,6 +27,7 @@ use Symfony\Component\AutoMapper\Transformer\ObjectTransformerFactory; use Symfony\Component\AutoMapper\Transformer\UniqueTypeTransformerFactory; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; +use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\PropertyInfoExtractor; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; @@ -50,7 +49,13 @@ public function setUp(): void private function fromTargetMappingExtractorBootstrap(bool $private = true): void { $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); - $reflectionExtractor = $private ? new PrivateReflectionExtractor() : new \Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor(); + $flags = ReflectionExtractor::ALLOW_PUBLIC; + + if ($private) { + $flags |= ReflectionExtractor::ALLOW_PROTECTED | ReflectionExtractor::ALLOW_PRIVATE; + } + + $reflectionExtractor = new ReflectionExtractor(null, null, null, true, $flags); $phpDocExtractor = new PhpDocExtractor(); $propertyInfoExtractor = new PropertyInfoExtractor( @@ -60,12 +65,12 @@ private function fromTargetMappingExtractorBootstrap(bool $private = true): void [$reflectionExtractor] ); - $accessorExtractor = new ReflectionExtractor($private); $transformerFactory = new ChainTransformerFactory(); $this->fromTargetMappingExtractor = new FromTargetMappingExtractor( $propertyInfoExtractor, - $accessorExtractor, + $reflectionExtractor, + $reflectionExtractor, $transformerFactory, $classMetadataFactory ); diff --git a/src/Symfony/Component/AutoMapper/Tests/Extractor/PrivateReflectionExtractorTest.php b/src/Symfony/Component/AutoMapper/Tests/Extractor/PrivateReflectionExtractorTest.php deleted file mode 100644 index 716cdb533de7f..0000000000000 --- a/src/Symfony/Component/AutoMapper/Tests/Extractor/PrivateReflectionExtractorTest.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\AutoMapper\Tests\Extractor; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\AutoMapper\Extractor\PrivateReflectionExtractor; -use Symfony\Component\AutoMapper\Tests\Fixtures; - -/** - * @author Baptiste Leduc - */ -class PrivateReflectionExtractorTest extends TestCase -{ - /** - * @var PrivateReflectionExtractor - */ - protected $privateReflectionExtractor; - - public function setUp(): void - { - $this->privateReflectionExtractor = new PrivateReflectionExtractor(); - } - - public function testProperties(): void - { - $userReflection = new \ReflectionClass(Fixtures\User::class); - $properties = $this->privateReflectionExtractor->getProperties(Fixtures\User::class); - - foreach ($userReflection->getProperties(\ReflectionProperty::IS_PRIVATE | \ReflectionProperty::IS_PROTECTED) as $reflectionProperty) { - self::assertTrue(in_array($reflectionProperty->getName(), $properties)); - } - } -} diff --git a/src/Symfony/Component/AutoMapper/Tests/Extractor/ReflectionExtractorTest.php b/src/Symfony/Component/AutoMapper/Tests/Extractor/ReflectionExtractorTest.php deleted file mode 100644 index 2edab913fabd6..0000000000000 --- a/src/Symfony/Component/AutoMapper/Tests/Extractor/ReflectionExtractorTest.php +++ /dev/null @@ -1,119 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\AutoMapper\Tests\Extractor; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\AutoMapper\Extractor\ReadAccessor; -use Symfony\Component\AutoMapper\Extractor\ReflectionExtractor; -use Symfony\Component\AutoMapper\Extractor\WriteMutator; -use Symfony\Component\AutoMapper\Tests\Fixtures\ReflectionExtractorTestFixture; - -class ReflectionExtractorTest extends TestCase -{ - /** @var ReflectionExtractor */ - private $reflectionExtractor; - - public function setUp(): void - { - $this->reflectionExtractor = new ReflectionExtractor(true); - } - - public function testReadAccessorGetter(): void - { - $accessor = $this->reflectionExtractor->getReadAccessor(ReflectionExtractorTestFixture::class, 'foo'); - - self::assertNotNull($accessor); - self::assertSame(ReadAccessor::TYPE_METHOD, $accessor->getType()); - self::assertSame('getFoo', $accessor->getName()); - self::assertFalse($accessor->isPrivate()); - } - - public function testReadAccessorGetterSetter(): void - { - $accessor = $this->reflectionExtractor->getReadAccessor(ReflectionExtractorTestFixture::class, 'bar'); - - self::assertNotNull($accessor); - self::assertSame(ReadAccessor::TYPE_METHOD, $accessor->getType()); - self::assertSame('bar', $accessor->getName()); - self::assertFalse($accessor->isPrivate()); - } - - public function testReadAccessorIsser(): void - { - $accessor = $this->reflectionExtractor->getReadAccessor(ReflectionExtractorTestFixture::class, 'baz'); - - self::assertNotNull($accessor); - self::assertSame(ReadAccessor::TYPE_METHOD, $accessor->getType()); - self::assertSame('isBaz', $accessor->getName()); - self::assertFalse($accessor->isPrivate()); - } - - public function testReadAccessorHasser(): void - { - $accessor = $this->reflectionExtractor->getReadAccessor(ReflectionExtractorTestFixture::class, 'foz'); - - self::assertNotNull($accessor); - self::assertSame(ReadAccessor::TYPE_METHOD, $accessor->getType()); - self::assertSame('hasFoz', $accessor->getName()); - self::assertFalse($accessor->isPrivate()); - } - - public function testReadAccessorMagicGet(): void - { - $accessor = $this->reflectionExtractor->getReadAccessor(ReflectionExtractorTestFixture::class, 'magicGet'); - - self::assertNotNull($accessor); - self::assertSame(ReadAccessor::TYPE_PROPERTY, $accessor->getType()); - self::assertSame('magicGet', $accessor->getName()); - self::assertFalse($accessor->isPrivate()); - } - - public function testWriteMutatorSetter(): void - { - $mutator = $this->reflectionExtractor->getWriteMutator(ReflectionExtractorTestFixture::class, 'foo'); - - self::assertNotNull($mutator); - self::assertSame(WriteMutator::TYPE_METHOD, $mutator->getType()); - self::assertSame('setFoo', $mutator->getName()); - self::assertFalse($mutator->isPrivate()); - } - - public function testWriteMutatorGetterSetter(): void - { - $mutator = $this->reflectionExtractor->getWriteMutator(ReflectionExtractorTestFixture::class, 'bar'); - - self::assertNotNull($mutator); - self::assertSame(WriteMutator::TYPE_METHOD, $mutator->getType()); - self::assertSame('bar', $mutator->getName()); - self::assertFalse($mutator->isPrivate()); - } - - public function testWriteMutatorMagicSet(): void - { - $mutator = $this->reflectionExtractor->getWriteMutator(ReflectionExtractorTestFixture::class, 'magicSet'); - - self::assertNotNull($mutator); - self::assertSame(WriteMutator::TYPE_PROPERTY, $mutator->getType()); - self::assertSame('magicSet', $mutator->getName()); - self::assertFalse($mutator->isPrivate()); - } - - public function testWriteMutatorConstructor(): void - { - $mutator = $this->reflectionExtractor->getWriteMutator(ReflectionExtractorTestFixture::class, 'propertyConstruct'); - - self::assertNotNull($mutator); - self::assertSame(WriteMutator::TYPE_CONSTRUCTOR, $mutator->getType()); - self::assertSame('propertyConstruct', $mutator->getName()); - self::assertFalse($mutator->isPrivate()); - } -} diff --git a/src/Symfony/Component/AutoMapper/Tests/MapperGeneratorMetadataFactoryTest.php b/src/Symfony/Component/AutoMapper/Tests/MapperGeneratorMetadataFactoryTest.php index 556f8bec40094..e131f0ba8e893 100644 --- a/src/Symfony/Component/AutoMapper/Tests/MapperGeneratorMetadataFactoryTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/MapperGeneratorMetadataFactoryTest.php @@ -14,9 +14,7 @@ use Doctrine\Common\Annotations\AnnotationReader; use Symfony\Component\AutoMapper\Extractor\FromSourceMappingExtractor; use Symfony\Component\AutoMapper\Extractor\FromTargetMappingExtractor; -use Symfony\Component\AutoMapper\Extractor\PrivateReflectionExtractor; use Symfony\Component\AutoMapper\Extractor\PropertyMapping; -use Symfony\Component\AutoMapper\Extractor\ReflectionExtractor; use Symfony\Component\AutoMapper\Extractor\SourceTargetMappingExtractor; use Symfony\Component\AutoMapper\MapperGeneratorMetadataFactory; use Symfony\Component\AutoMapper\MapperGeneratorMetadataFactoryInterface; @@ -29,6 +27,7 @@ use Symfony\Component\AutoMapper\Transformer\ObjectTransformerFactory; use Symfony\Component\AutoMapper\Transformer\UniqueTypeTransformerFactory; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; +use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\PropertyInfoExtractor; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; @@ -46,7 +45,7 @@ public function setUp(): void parent::setUp(); $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); - $reflectionExtractor = new PrivateReflectionExtractor(); + $reflectionExtractor = new ReflectionExtractor(null, null, null, true, ReflectionExtractor::ALLOW_PUBLIC | ReflectionExtractor::ALLOW_PROTECTED | ReflectionExtractor::ALLOW_PRIVATE); $phpDocExtractor = new PhpDocExtractor(); $propertyInfoExtractor = new PropertyInfoExtractor( @@ -56,25 +55,27 @@ public function setUp(): void [$reflectionExtractor] ); - $accessorExtractor = new ReflectionExtractor(true); $transformerFactory = new ChainTransformerFactory(); $sourceTargetMappingExtractor = new SourceTargetMappingExtractor( $propertyInfoExtractor, - $accessorExtractor, + $reflectionExtractor, + $reflectionExtractor, $transformerFactory, $classMetadataFactory ); $fromTargetMappingExtractor = new FromTargetMappingExtractor( $propertyInfoExtractor, - $accessorExtractor, + $reflectionExtractor, + $reflectionExtractor, $transformerFactory, $classMetadataFactory ); $fromSourceMappingExtractor = new FromSourceMappingExtractor( $propertyInfoExtractor, - $accessorExtractor, + $reflectionExtractor, + $reflectionExtractor, $transformerFactory, $classMetadataFactory ); From 1e4bd5e17b2cdbcf96fc982d4e4f8559754282aa Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Sat, 1 Feb 2020 00:54:22 +0100 Subject: [PATCH 30/38] Fix cs --- src/Symfony/Component/AutoMapper/AutoMapper.php | 1 - .../Component/AutoMapper/Extractor/MappingExtractor.php | 4 ++-- .../AutoMapper/Extractor/SourceTargetMappingExtractor.php | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/AutoMapper/AutoMapper.php b/src/Symfony/Component/AutoMapper/AutoMapper.php index 3c6969040ddb5..d0d898cba633b 100644 --- a/src/Symfony/Component/AutoMapper/AutoMapper.php +++ b/src/Symfony/Component/AutoMapper/AutoMapper.php @@ -16,7 +16,6 @@ use Symfony\Component\AutoMapper\Exception\NoMappingFoundException; use Symfony\Component\AutoMapper\Extractor\FromSourceMappingExtractor; use Symfony\Component\AutoMapper\Extractor\FromTargetMappingExtractor; -use Symfony\Component\AutoMapper\Extractor\PrivateReflectionExtractor; use Symfony\Component\AutoMapper\Extractor\SourceTargetMappingExtractor; use Symfony\Component\AutoMapper\Generator\Generator; use Symfony\Component\AutoMapper\Loader\ClassLoaderInterface; diff --git a/src/Symfony/Component/AutoMapper/Extractor/MappingExtractor.php b/src/Symfony/Component/AutoMapper/Extractor/MappingExtractor.php index e806f933c2b1e..60c46a3eaee34 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/MappingExtractor.php +++ b/src/Symfony/Component/AutoMapper/Extractor/MappingExtractor.php @@ -65,7 +65,7 @@ public function getReadAccessor(string $source, string $target, string $property return new ReadAccessor( $type, $readInfo->getName(), - $readInfo->getVisibility() !== PropertyReadInfo::VISIBILITY_PUBLIC + PropertyReadInfo::VISIBILITY_PUBLIC !== $readInfo->getVisibility() ); } @@ -99,7 +99,7 @@ public function getWriteMutator(string $source, string $target, string $property return new WriteMutator( $type, $writeInfo->getName(), - $writeInfo->getVisibility() !== PropertyReadInfo::VISIBILITY_PUBLIC + PropertyReadInfo::VISIBILITY_PUBLIC !== $writeInfo->getVisibility() ); } diff --git a/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php b/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php index a28d44a9263e5..c4ad3b4ebdf47 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php +++ b/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php @@ -78,7 +78,7 @@ public function getPropertiesMapping(MapperMetadataInterface $mapperMetadata): a $mapping[] = new PropertyMapping( $sourceAccessor, $targetMutator, - $targetMutatorConstruct->getType() === WriteMutator::TYPE_CONSTRUCTOR ? $targetMutatorConstruct : null, + WriteMutator::TYPE_CONSTRUCTOR === $targetMutatorConstruct->getType() ? $targetMutatorConstruct : null, $transformer, $property, false, From 20a37dfc70894ad5a3c5c2e38dc15733f4c3fbf4 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Sun, 2 Feb 2020 20:21:36 +0100 Subject: [PATCH 31/38] Add tests and date time mutable / immutable transformations --- .../AutoMapper/Extractor/ReadAccessor.php | 15 - .../SourceTargetMappingExtractor.php | 7 +- .../AutoMapper/Extractor/WriteMutator.php | 14 - .../Component/AutoMapper/MapperMetadata.php | 6 +- .../AutoMapper/Tests/AutoMapperTest.php | 123 ++++++++ .../AutoMapper/Tests/Fixtures/Address.php | 8 - .../AutoMapper/Tests/Fixtures/AddressBar.php | 20 ++ .../AutoMapper/Tests/Fixtures/AddressFoo.php | 20 ++ .../Tests/Fixtures/AddressNoTypes.php | 17 ++ .../Tests/Fixtures/AddressNotWritable.php | 25 ++ .../AutoMapper/Tests/Fixtures/CircularBar.php | 11 + .../AutoMapper/Tests/Fixtures/CircularBaz.php | 11 + .../AutoMapper/Tests/Fixtures/CircularFoo.php | 11 + .../AutoMapper/Tests/Fixtures/CityFoo.php | 8 + .../Tests/Fixtures/FooNoProperties.php | 7 + .../AutoMapper/Tests/Fixtures/User.php | 2 +- .../Tests/Fixtures/UserConstructorDTO.php | 11 + .../AutoMapper/Tests/MapperContextTest.php | 37 +++ .../MapperGeneratorMetadataFactoryTest.php | 10 +- .../ArrayTransformerFactoryTest.php | 49 +++ .../Transformer/ArrayTransformerTest.php | 12 + .../BuiltinTransformerFactoryTest.php | 47 +++ .../Transformer/BuiltinTransformerTest.php | 281 ++++++++++++++++++ .../Transformer/CallbackTransformerTest.php | 24 ++ .../ChainTransformerFactoryTest.php | 37 +++ .../Tests/Transformer/CopyTransformerTest.php | 11 + ...eTimeImmutableToMutableTransformerTest.php | 45 +++ ...eTimeMutableToImmutableTransformerTest.php | 45 +++ .../DateTimeToStringTransformerTest.php | 22 ++ .../DateTimeTransformerFactoryTest.php | 68 +++++ .../Transformer/EvalTransformerTrait.php | 53 ++++ .../MultipleTransformerFactoryTest.php | 62 ++++ .../Transformer/MultipleTransformerTest.php | 26 ++ .../NullableTransformerFactoryTest.php | 75 +++++ .../Transformer/NullableTransformerTest.php | 26 ++ .../ObjectTransformerFactoryTest.php | 59 ++++ .../Transformer/ObjectTransformerTest.php | 33 ++ .../StringToDateTimeTransformerTest.php | 35 +++ .../UniqueTypeTransformerFactoryTest.php | 52 ++++ .../Transformer/BuiltinTransformer.php | 8 +- .../DateTimeImmutableToMutableTransformer.php | 41 +++ .../DateTimeMutableToImmutableTransformer.php | 41 +++ .../DateTimeTransformerFactory.php | 10 +- .../Transformer/MultipleTransformer.php | 7 +- .../MultipleTransformerFactory.php | 23 +- .../NullableTransformerFactory.php | 4 - .../Transformer/ObjectTransformerFactory.php | 2 +- 47 files changed, 1484 insertions(+), 77 deletions(-) create mode 100644 src/Symfony/Component/AutoMapper/Tests/Fixtures/AddressBar.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Fixtures/AddressFoo.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Fixtures/AddressNoTypes.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Fixtures/AddressNotWritable.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Fixtures/CircularBar.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Fixtures/CircularBaz.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Fixtures/CircularFoo.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Fixtures/CityFoo.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Fixtures/FooNoProperties.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Transformer/DateTimeImmutableToMutableTransformerTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Transformer/DateTimeMutableToImmutableTransformerTest.php create mode 100644 src/Symfony/Component/AutoMapper/Tests/Transformer/EvalTransformerTrait.php create mode 100644 src/Symfony/Component/AutoMapper/Transformer/DateTimeImmutableToMutableTransformer.php create mode 100644 src/Symfony/Component/AutoMapper/Transformer/DateTimeMutableToImmutableTransformer.php diff --git a/src/Symfony/Component/AutoMapper/Extractor/ReadAccessor.php b/src/Symfony/Component/AutoMapper/Extractor/ReadAccessor.php index 93ee7a5615acf..f8bf79de81399 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/ReadAccessor.php +++ b/src/Symfony/Component/AutoMapper/Extractor/ReadAccessor.php @@ -44,21 +44,6 @@ public function __construct(int $type, string $name, $private = false) $this->private = $private; } - public function getType(): int - { - return $this->type; - } - - public function getName(): string - { - return $this->name; - } - - public function isPrivate(): bool - { - return $this->private; - } - /** * Get AST expression for reading property from an input. * diff --git a/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php b/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php index c4ad3b4ebdf47..e83a31fdfec0f 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php +++ b/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php @@ -27,13 +27,16 @@ class SourceTargetMappingExtractor extends MappingExtractor */ public function getPropertiesMapping(MapperMetadataInterface $mapperMetadata): array { - $sourceProperties = array_unique($this->propertyInfoExtractor->getProperties($mapperMetadata->getSource())); - $targetProperties = array_unique($this->propertyInfoExtractor->getProperties($mapperMetadata->getTarget())); + $sourceProperties = $this->propertyInfoExtractor->getProperties($mapperMetadata->getSource()); + $targetProperties = $this->propertyInfoExtractor->getProperties($mapperMetadata->getTarget()); if (null === $sourceProperties || null === $targetProperties) { return []; } + $sourceProperties = array_unique($sourceProperties ?? []); + $targetProperties = array_unique($targetProperties ?? []); + $mapping = []; foreach ($sourceProperties as $property) { diff --git a/src/Symfony/Component/AutoMapper/Extractor/WriteMutator.php b/src/Symfony/Component/AutoMapper/Extractor/WriteMutator.php index de42362738c8b..3c20bf955a818 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/WriteMutator.php +++ b/src/Symfony/Component/AutoMapper/Extractor/WriteMutator.php @@ -49,16 +49,6 @@ public function getType(): int return $this->type; } - public function getName(): string - { - return $this->name; - } - - public function isPrivate(): bool - { - return $this->private; - } - /** * Get AST expression for writing from a value to an output. * @@ -97,10 +87,6 @@ public function getExpression(Expr\Variable $output, Expr $value, bool $byRef = return new Expr\Assign(new Expr\ArrayDimFetch($output, new Scalar\String_($this->name)), $value); } - if (self::TYPE_CONSTRUCTOR === $this->type) { - return null; - } - throw new CompileException('Invalid accessor for write expression'); } diff --git a/src/Symfony/Component/AutoMapper/MapperMetadata.php b/src/Symfony/Component/AutoMapper/MapperMetadata.php index 33a66d587b51e..410bd2654cc31 100644 --- a/src/Symfony/Component/AutoMapper/MapperMetadata.php +++ b/src/Symfony/Component/AutoMapper/MapperMetadata.php @@ -88,11 +88,7 @@ public function getPropertiesMapping(): array */ public function getPropertyMapping(string $property): ?PropertyMapping { - if (null === $this->propertiesMapping) { - $this->buildPropertyMapping(); - } - - return $this->propertiesMapping[$property] ?? null; + return $this->getPropertiesMapping()[$property] ?? null; } /** diff --git a/src/Symfony/Component/AutoMapper/Tests/AutoMapperTest.php b/src/Symfony/Component/AutoMapper/Tests/AutoMapperTest.php index 604d7df8d97c5..eaacab9571abd 100644 --- a/src/Symfony/Component/AutoMapper/Tests/AutoMapperTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/AutoMapperTest.php @@ -75,6 +75,29 @@ public function testAutoMapperFromArray(): void self::assertEquals(1987, $userDto->createdAt->format('Y')); } + public function testAutoMapperFromArrayCustomDateTime(): void + { + $dateTime = \DateTime::createFromFormat(\DateTime::RFC3339, '1987-04-30T06:00:00Z'); + $customFormat = 'U'; + $user = [ + 'id' => 1, + 'address' => [ + 'city' => 'Toulon', + ], + 'createdAt' => $dateTime->format($customFormat), + ]; + + $autoMapper = AutoMapper::create(true, $this->loader, null, 'CustomDateTime_'); + $configuration = $autoMapper->getMetadata('array', Fixtures\UserDTO::class); + $configuration->setDateTimeFormat($customFormat); + + /** @var Fixtures\UserDTO $userDto */ + $userDto = $autoMapper->map($user, Fixtures\UserDTO::class); + + self::assertInstanceOf(Fixtures\UserDTO::class, $userDto); + self::assertEquals($dateTime->format($customFormat), $userDto->createdAt->format($customFormat)); + } + public function testAutoMapperToArray(): void { $address = new Fixtures\Address(); @@ -114,6 +137,62 @@ public function testAutoMapperToStdObject(): void self::assertEquals(1, $user->id); } + public function testNotReadable(): void + { + $autoMapper = AutoMapper::create(false, $this->loader, null, 'NotReadable_'); + $address = new Fixtures\Address(); + $address->setCity('test'); + + $addressArray = $autoMapper->map($address, 'array'); + + self::assertIsArray($addressArray); + self::assertArrayNotHasKey('city', $addressArray); + + $addressMapped = $autoMapper->map($address, Fixtures\Address::class); + + self::assertInstanceOf(Fixtures\Address::class, $addressMapped); + + $property = (new \ReflectionClass($addressMapped))->getProperty('city'); + $property->setAccessible(true); + + $city = $property->getValue($addressMapped); + + self::assertNull($city); + } + + public function testNoTypes(): void + { + $autoMapper = AutoMapper::create(false, $this->loader, null, 'NotReadable_'); + $address = new Fixtures\AddressNoTypes(); + $address->city = 'test'; + + $addressArray = $autoMapper->map($address, 'array'); + + self::assertIsArray($addressArray); + self::assertArrayNotHasKey('city', $addressArray); + } + + public function testNoTransformer(): void + { + $addressFoo = new Fixtures\AddressFoo(); + $addressFoo->city = new Fixtures\CityFoo(); + $addressFoo->city->name = 'test'; + + $addressBar = $this->autoMapper->map($addressFoo, Fixtures\AddressBar::class); + + self::assertInstanceOf(Fixtures\AddressBar::class, $addressBar); + self::assertNull($addressBar->city); + } + + public function testNoProperties(): void + { + $noProperties = new Fixtures\FooNoProperties(); + $noPropertiesMapped = $this->autoMapper->map($noProperties, Fixtures\FooNoProperties::class); + + self::assertInstanceOf(Fixtures\FooNoProperties::class, $noPropertiesMapped); + self::assertNotSame($noProperties, $noPropertiesMapped); + } + public function testGroupsSourceTarget(): void { $foo = new Fixtures\Foo(); @@ -205,6 +284,28 @@ public function testDeepCloningArray(): void self::assertSame($newNode, $newNode['parent']['parent']['parent']); } + public function testCircularReferenceDeep(): void + { + $foo = new Fixtures\CircularFoo(); + $bar = new Fixtures\CircularBar(); + $baz = new Fixtures\CircularBaz(); + + $foo->bar = $bar; + $bar->baz = $baz; + $baz->foo = $foo; + + + $newFoo = $this->autoMapper->map($foo, Fixtures\CircularFoo::class); + + self::assertNotSame($foo, $newFoo); + self::assertNotNull($newFoo->bar); + self::assertNotSame($bar, $newFoo->bar); + self::assertNotNull($newFoo->bar->baz); + self::assertNotSame($baz, $newFoo->bar->baz); + self::assertNotNull($newFoo->bar->baz->foo); + self::assertSame($newFoo, $newFoo->bar->baz->foo); + } + public function testCircularReferenceArray(): void { $nodeA = new Fixtures\Node(); @@ -248,6 +349,28 @@ public function testConstructor(): void self::assertSame('10', $userDto->getId()); self::assertSame('foo', $userDto->getName()); self::assertSame(3, $userDto->getAge()); + self::assertTrue($userDto->getConstructor()); + } + + public function testConstructorNotAllowed(): void + { + $autoMapper = AutoMapper::create(true, $this->loader, null, 'NotAllowedMapper_'); + $configuration = $autoMapper->getMetadata(Fixtures\UserDTO::class, Fixtures\UserConstructorDTO::class); + $configuration->setConstructorAllowed(false); + + $user = new Fixtures\UserDTO(); + $user->id = 10; + $user->setName('foo'); + $user->age = 3; + + /** @var Fixtures\UserConstructorDTO $userDto */ + $userDto = $autoMapper->map($user, Fixtures\UserConstructorDTO::class); + + self::assertInstanceOf(Fixtures\UserConstructorDTO::class, $userDto); + self::assertSame('10', $userDto->getId()); + self::assertSame('foo', $userDto->getName()); + self::assertSame(3, $userDto->getAge()); + self::assertFalse($userDto->getConstructor()); } public function testConstructorWithDefault(): void diff --git a/src/Symfony/Component/AutoMapper/Tests/Fixtures/Address.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/Address.php index 32eac16125d7c..39eada6e322a3 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Fixtures/Address.php +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/Address.php @@ -18,14 +18,6 @@ class Address */ private $city; - /** - * @return string - */ - public function getCity(): ?string - { - return $this->city; - } - /** * @param string $city */ diff --git a/src/Symfony/Component/AutoMapper/Tests/Fixtures/AddressBar.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/AddressBar.php new file mode 100644 index 0000000000000..253b797892033 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/AddressBar.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Fixtures; + +class AddressBar +{ + /** + * @var string|null + */ + public $city; +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Fixtures/AddressFoo.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/AddressFoo.php new file mode 100644 index 0000000000000..777c22851197e --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/AddressFoo.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Fixtures; + +class AddressFoo +{ + /** + * @var CityFoo + */ + public $city; +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Fixtures/AddressNoTypes.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/AddressNoTypes.php new file mode 100644 index 0000000000000..7c237b472e704 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/AddressNoTypes.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Fixtures; + +class AddressNoTypes +{ + public $city; +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Fixtures/AddressNotWritable.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/AddressNotWritable.php new file mode 100644 index 0000000000000..565337c36229b --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/AddressNotWritable.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Fixtures; + +class AddressNotWritable +{ + /** + * @var string|null + */ + private $city; + + public function getCity(): ?string + { + return $this->city; + } +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Fixtures/CircularBar.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/CircularBar.php new file mode 100644 index 0000000000000..97ce56eacdc2f --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/CircularBar.php @@ -0,0 +1,11 @@ +id = $id; $this->name = $name; $this->age = $age; + $this->constructor = true; } /** @@ -57,4 +63,9 @@ public function getAge() { return $this->age; } + + public function getConstructor(): bool + { + return $this->constructor; + } } diff --git a/src/Symfony/Component/AutoMapper/Tests/MapperContextTest.php b/src/Symfony/Component/AutoMapper/Tests/MapperContextTest.php index 2289f2ea7623b..ccba9759324dd 100644 --- a/src/Symfony/Component/AutoMapper/Tests/MapperContextTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/MapperContextTest.php @@ -102,4 +102,41 @@ public function testGroups(): void self::assertContains('group1', $context->toArray()[MapperContext::GROUPS]); self::assertNotContains('group2', $context->toArray()[MapperContext::GROUPS]); } + + public function testTargetToPopulate(): void + { + $object = new \stdClass(); + $context = new MapperContext(); + $context->setTargetToPopulate($object); + + self::assertSame($object, $context->toArray()[MapperContext::TARGET_TO_POPULATE]); + } + + public function testWithNewContextIgnoredAttributesNested(): void + { + $context = [ + MapperContext::IGNORED_ATTRIBUTES => [ + 'foo' => ['bar'], + 'baz', + ] + ]; + + $newContext = MapperContext::withNewContext($context, 'foo'); + + self::assertEquals(['bar'], $newContext[MapperContext::IGNORED_ATTRIBUTES]); + } + + public function testWithNewContextAllowedAttributesNested(): void + { + $context = [ + MapperContext::ALLOWED_ATTRIBUTES => [ + 'foo' => ['bar'], + 'baz', + ] + ]; + + $newContext = MapperContext::withNewContext($context, 'foo'); + + self::assertEquals(['bar'], $newContext[MapperContext::ALLOWED_ATTRIBUTES]); + } } diff --git a/src/Symfony/Component/AutoMapper/Tests/MapperGeneratorMetadataFactoryTest.php b/src/Symfony/Component/AutoMapper/Tests/MapperGeneratorMetadataFactoryTest.php index e131f0ba8e893..8457f929091b9 100644 --- a/src/Symfony/Component/AutoMapper/Tests/MapperGeneratorMetadataFactoryTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/MapperGeneratorMetadataFactoryTest.php @@ -129,17 +129,21 @@ public function testCreateArrayToObject(): void public function testCreateWithBothObjects(): void { - $userConstructorDTOReflection = new \ReflectionClass(Fixtures\UserConstructorDTO::class); - $metadata = $this->factory->create($this->autoMapper, Fixtures\UserConstructorDTO::class, Fixtures\User::class); self::assertTrue($metadata->hasConstructor()); self::assertTrue($metadata->shouldCheckAttributes()); self::assertTrue($metadata->isTargetCloneable()); self::assertEquals(Fixtures\UserConstructorDTO::class, $metadata->getSource()); self::assertEquals(Fixtures\User::class, $metadata->getTarget()); - self::assertCount(\count($userConstructorDTOReflection->getProperties()), $metadata->getPropertiesMapping()); self::assertInstanceOf(PropertyMapping::class, $metadata->getPropertyMapping('id')); self::assertInstanceOf(PropertyMapping::class, $metadata->getPropertyMapping('name')); self::assertNull($metadata->getPropertyMapping('email')); } + + public function testHasNotConstructor(): void + { + $metadata = $this->factory->create($this->autoMapper, 'array', Fixtures\UserDTO::class); + + self::assertFalse($metadata->hasConstructor()); + } } diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/ArrayTransformerFactoryTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/ArrayTransformerFactoryTest.php index 6cd4f4859ba5e..a7aa12fb0c597 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Transformer/ArrayTransformerFactoryTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/ArrayTransformerFactoryTest.php @@ -12,7 +12,56 @@ namespace Symfony\Component\AutoMapper\Tests\Transformer; use PHPUnit\Framework\TestCase; +use Symfony\Component\AutoMapper\MapperMetadata; +use Symfony\Component\AutoMapper\Transformer\ArrayTransformer; +use Symfony\Component\AutoMapper\Transformer\ArrayTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\ChainTransformerFactory; +use Symfony\Component\PropertyInfo\Type; class ArrayTransformerFactoryTest extends TestCase { + public function testGetTransformer(): void + { + $chainFactory = new ChainTransformerFactory(); + $factory = new ArrayTransformerFactory($chainFactory); + $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); + + $transformer = $factory->getTransformer([new Type('array', false, null, true)], [new Type('array', false, null, true)], $mapperMetadata); + + self::assertInstanceOf(ArrayTransformer::class, $transformer); + } + + public function testNoTransformerTargetNoCollection(): void + { + $chainFactory = new ChainTransformerFactory(); + $factory = new ArrayTransformerFactory($chainFactory); + $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); + + $transformer = $factory->getTransformer([new Type('array', false, null, true)], [new Type('string')], $mapperMetadata); + + self::assertNull($transformer); + } + + public function testNoTransformerSourceNoCollection(): void + { + $chainFactory = new ChainTransformerFactory(); + $factory = new ArrayTransformerFactory($chainFactory); + $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); + + $transformer = $factory->getTransformer([new Type('string')], [new Type('array', false, null, true)], $mapperMetadata); + + self::assertNull($transformer); + } + + public function testNoTransformerIfNoSubTypeTransformerNoCollection(): void + { + $chainFactory = new ChainTransformerFactory(); + $factory = new ArrayTransformerFactory($chainFactory); + $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); + + $stringType = new Type('string'); + $transformer = $factory->getTransformer([new Type('array', false, null, true, null, $stringType)], [new Type('array', false, null, true, null, $stringType)], $mapperMetadata); + + self::assertNull($transformer); + } } diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/ArrayTransformerTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/ArrayTransformerTest.php index 27ba919bef990..ae64b6df05041 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Transformer/ArrayTransformerTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/ArrayTransformerTest.php @@ -12,7 +12,19 @@ namespace Symfony\Component\AutoMapper\Tests\Transformer; use PHPUnit\Framework\TestCase; +use Symfony\Component\AutoMapper\Transformer\ArrayTransformer; +use Symfony\Component\AutoMapper\Transformer\BuiltinTransformer; +use Symfony\Component\PropertyInfo\Type; class ArrayTransformerTest extends TestCase { + use EvalTransformerTrait; + + public function testArrayToArray(): void + { + $transformer = new ArrayTransformer(new BuiltinTransformer(new Type('string'), [new Type('string')])); + $output = $this->evalTransformer($transformer, ['test']); + + self::assertEquals(['test'], $output); + } } diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/BuiltinTransformerFactoryTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/BuiltinTransformerFactoryTest.php index 919bdbd1a8ecf..3088f36daebda 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Transformer/BuiltinTransformerFactoryTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/BuiltinTransformerFactoryTest.php @@ -12,7 +12,54 @@ namespace Symfony\Component\AutoMapper\Tests\Transformer; use PHPUnit\Framework\TestCase; +use Symfony\Component\AutoMapper\MapperMetadata; +use Symfony\Component\AutoMapper\Transformer\BuiltinTransformer; +use Symfony\Component\AutoMapper\Transformer\BuiltinTransformerFactory; +use Symfony\Component\PropertyInfo\Type; class BuiltinTransformerFactoryTest extends TestCase { + public function testGetTransformer(): void + { + $factory = new BuiltinTransformerFactory(); + $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); + + $transformer = $factory->getTransformer([new Type('string')], [new Type('string')], $mapperMetadata); + + self::assertInstanceOf(BuiltinTransformer::class, $transformer); + + $transformer = $factory->getTransformer([new Type('bool')], [new Type('string')], $mapperMetadata); + + self::assertInstanceOf(BuiltinTransformer::class, $transformer); + } + + public function testNoTransformer(): void + { + $factory = new BuiltinTransformerFactory(); + $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); + + $transformer = $factory->getTransformer([], [new Type('string')], $mapperMetadata); + + self::assertNull($transformer); + + $transformer = $factory->getTransformer(null, [new Type('string')], $mapperMetadata); + + self::assertNull($transformer); + + $transformer = $factory->getTransformer(['test'], [new Type('string')], $mapperMetadata); + + self::assertNull($transformer); + + $transformer = $factory->getTransformer([new Type('string'), new Type('string')], [new Type('string')], $mapperMetadata); + + self::assertNull($transformer); + + $transformer = $factory->getTransformer([new Type('array')], [new Type('string')], $mapperMetadata); + + self::assertNull($transformer); + + $transformer = $factory->getTransformer([new Type('object')], [new Type('string')], $mapperMetadata); + + self::assertNull($transformer); + } } diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/BuiltinTransformerTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/BuiltinTransformerTest.php index 99c91862bf825..9f73772fbd505 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Transformer/BuiltinTransformerTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/BuiltinTransformerTest.php @@ -12,7 +12,288 @@ namespace Symfony\Component\AutoMapper\Tests\Transformer; use PHPUnit\Framework\TestCase; +use Symfony\Component\AutoMapper\Transformer\BuiltinTransformer; +use Symfony\Component\PropertyInfo\Type; class BuiltinTransformerTest extends TestCase { + use EvalTransformerTrait; + + public function testStringToString() + { + $transformer = new BuiltinTransformer(new Type('string'), [new Type('string')]); + $output = $this->evalTransformer($transformer, 'foo'); + + self::assertSame('foo', $output); + } + + public function testStringToArray() + { + $transformer = new BuiltinTransformer(new Type('string'), [new Type('array')]); + $output = $this->evalTransformer($transformer, 'foo'); + + self::assertSame(['foo'], $output); + } + + public function testStringToIterable() + { + $transformer = new BuiltinTransformer(new Type('string'), [new Type('iterable')]); + $output = $this->evalTransformer($transformer, 'foo'); + + self::assertSame(['foo'], $output); + } + + public function testStringToFloat() + { + $transformer = new BuiltinTransformer(new Type('string'), [new Type('float')]); + $output = $this->evalTransformer($transformer, '12.2'); + + self::assertSame(12.2, $output); + } + + public function testStringToInt() + { + $transformer = new BuiltinTransformer(new Type('string'), [new Type('int')]); + $output = $this->evalTransformer($transformer, '12'); + + self::assertSame(12, $output); + } + + public function testStringToBool() + { + $transformer = new BuiltinTransformer(new Type('string'), [new Type('bool')]); + $output = $this->evalTransformer($transformer, 'foo'); + + self::assertSame(true, $output); + + $output = $this->evalTransformer($transformer, ''); + + self::assertSame(false, $output); + } + + public function testBoolToInt() + { + $transformer = new BuiltinTransformer(new Type('bool'), [new Type('int')]); + $output = $this->evalTransformer($transformer, true); + + self::assertSame(1, $output); + + $output = $this->evalTransformer($transformer, false); + + self::assertSame(0, $output); + } + + public function testBoolToString() + { + $transformer = new BuiltinTransformer(new Type('bool'), [new Type('string')]); + + $output = $this->evalTransformer($transformer, true); + + self::assertSame('1', $output); + + $output = $this->evalTransformer($transformer, false); + + self::assertSame('', $output); + } + + public function testBoolToFloat() + { + $transformer = new BuiltinTransformer(new Type('bool'), [new Type('float')]); + + $output = $this->evalTransformer($transformer, true); + + self::assertSame(1.0, $output); + + $output = $this->evalTransformer($transformer, false); + + self::assertSame(0.0, $output); + } + + public function testBoolToArray() + { + $transformer = new BuiltinTransformer(new Type('bool'), [new Type('array')]); + + $output = $this->evalTransformer($transformer, true); + + self::assertSame([true], $output); + + $output = $this->evalTransformer($transformer, false); + + self::assertSame([false], $output); + } + + public function testBoolToIterable() + { + $transformer = new BuiltinTransformer(new Type('bool'), [new Type('iterable')]); + + $output = $this->evalTransformer($transformer, true); + + self::assertSame([true], $output); + + $output = $this->evalTransformer($transformer, false); + + self::assertSame([false], $output); + } + + public function testBoolToBool() + { + $transformer = new BuiltinTransformer(new Type('bool'), [new Type('bool')]); + + $output = $this->evalTransformer($transformer, true); + + self::assertSame(true, $output); + + $output = $this->evalTransformer($transformer, false); + + self::assertSame(false, $output); + } + + public function testFloatToString() + { + $transformer = new BuiltinTransformer(new Type('float'), [new Type('string')]); + + $output = $this->evalTransformer($transformer, 12.23); + + self::assertSame('12.23', $output); + } + + public function testFloatToInt() + { + $transformer = new BuiltinTransformer(new Type('float'), [new Type('int')]); + + $output = $this->evalTransformer($transformer, 12.23); + + self::assertSame(12, $output); + } + + public function testFloatToBool() + { + $transformer = new BuiltinTransformer(new Type('float'), [new Type('bool')]); + + $output = $this->evalTransformer($transformer, 12.23); + + self::assertSame(true, $output); + + $output = $this->evalTransformer($transformer, 0.0); + + self::assertSame(false, $output); + } + + public function testFloatToArray() + { + $transformer = new BuiltinTransformer(new Type('float'), [new Type('array')]); + + $output = $this->evalTransformer($transformer, 12.23); + + self::assertSame([12.23], $output); + } + + public function testFloatToIterable() + { + $transformer = new BuiltinTransformer(new Type('float'), [new Type('iterable')]); + + $output = $this->evalTransformer($transformer, 12.23); + + self::assertSame([12.23], $output); + } + + public function testFloatToFloat() + { + $transformer = new BuiltinTransformer(new Type('float'), [new Type('float')]); + + $output = $this->evalTransformer($transformer, 12.23); + + self::assertSame(12.23, $output); + } + + public function testIntToInt() + { + $transformer = new BuiltinTransformer(new Type('int'), [new Type('int')]); + + $output = $this->evalTransformer($transformer, 12); + + self::assertSame(12, $output); + } + + public function testIntToFloat() + { + $transformer = new BuiltinTransformer(new Type('int'), [new Type('float')]); + + $output = $this->evalTransformer($transformer, 12); + + self::assertSame(12.0, $output); + } + + public function testIntToString() + { + $transformer = new BuiltinTransformer(new Type('int'), [new Type('string')]); + + $output = $this->evalTransformer($transformer, 12); + + self::assertSame('12', $output); + } + + public function testIntToBool() + { + $transformer = new BuiltinTransformer(new Type('int'), [new Type('bool')]); + + $output = $this->evalTransformer($transformer, 12); + + self::assertSame(true, $output); + + $output = $this->evalTransformer($transformer, 0); + + self::assertSame(false, $output); + } + + public function testIntToArray() + { + $transformer = new BuiltinTransformer(new Type('int'), [new Type('array')]); + + $output = $this->evalTransformer($transformer, 12); + + self::assertSame([12], $output); + } + + public function testIntToIterable() + { + $transformer = new BuiltinTransformer(new Type('int'), [new Type('iterable')]); + + $output = $this->evalTransformer($transformer, 12); + + self::assertSame([12], $output); + } + + public function testIterableToArray() + { + $transformer = new BuiltinTransformer(new Type('iterable'), [new Type('array')]); + + $closure = function () { + yield 1; + yield 2; + }; + + $output = $this->evalTransformer($transformer, $closure()); + + self::assertSame([1, 2], $output); + } + + public function testArrayToIterable() + { + $transformer = new BuiltinTransformer(new Type('array'), [new Type('iterable')]); + $output = $this->evalTransformer($transformer, [1, 2]); + + self::assertSame([1, 2], $output); + } + + public function testToUnknowCast() + { + $transformer = new BuiltinTransformer(new Type('callable'), [new Type('string')]); + + $output = $this->evalTransformer($transformer, function ($test) { + return $test; + }); + + self::assertIsCallable($output); + } } diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/CallbackTransformerTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/CallbackTransformerTest.php index 8ac3dbd4d3360..d643597233b66 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Transformer/CallbackTransformerTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/CallbackTransformerTest.php @@ -12,7 +12,31 @@ namespace Symfony\Component\AutoMapper\Tests\Transformer; use PHPUnit\Framework\TestCase; +use Symfony\Component\AutoMapper\Transformer\CallbackTransformer; class CallbackTransformerTest extends TestCase { + use EvalTransformerTrait; + + public function testCallbackTransform() + { + $transformer = new CallbackTransformer('test'); + $function = $this->createTransformerFunction($transformer); + $class = new class () { + public $callbacks; + + public function __construct() + { + $this->callbacks['test'] = function ($input) { + return 'output'; + }; + } + }; + + $transform = \Closure::bind($function, $class); + + $output = $transform('input'); + + self::assertEquals('output', $output); + } } diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/ChainTransformerFactoryTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/ChainTransformerFactoryTest.php index fa77f2b7b7053..66cb86a94e98c 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Transformer/ChainTransformerFactoryTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/ChainTransformerFactoryTest.php @@ -12,7 +12,44 @@ namespace Symfony\Component\AutoMapper\Tests\Transformer; use PHPUnit\Framework\TestCase; +use Symfony\Component\AutoMapper\MapperMetadata; +use Symfony\Component\AutoMapper\Transformer\ChainTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\CopyTransformer; +use Symfony\Component\AutoMapper\Transformer\TransformerFactoryInterface; class ChainTransformerFactoryTest extends TestCase { + public function testGetTransformer() + { + $chainTransformerFactory = new ChainTransformerFactory(); + $transformer = new CopyTransformer(); + $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); + $subTransformer = $this + ->getMockBuilder(TransformerFactoryInterface::class) + ->getMock() + ; + + $subTransformer->expects($this->any())->method('getTransformer')->willReturn($transformer); + $chainTransformerFactory->addTransformerFactory($subTransformer); + + $transformerReturned = $chainTransformerFactory->getTransformer([], [], $mapperMetadata); + + self::assertSame($transformer, $transformerReturned); + } + public function testNoTransformer() + { + $chainTransformerFactory = new ChainTransformerFactory(); + $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); + $subTransformer = $this + ->getMockBuilder(TransformerFactoryInterface::class) + ->getMock() + ; + + $subTransformer->expects($this->any())->method('getTransformer')->willReturn(null); + $chainTransformerFactory->addTransformerFactory($subTransformer); + + $transformerReturned = $chainTransformerFactory->getTransformer([], [], $mapperMetadata); + + self::assertNull($transformerReturned); + } } diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/CopyTransformerTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/CopyTransformerTest.php index b0ce1b5ebcef1..f0dd3fc7686df 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Transformer/CopyTransformerTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/CopyTransformerTest.php @@ -12,7 +12,18 @@ namespace Symfony\Component\AutoMapper\Tests\Transformer; use PHPUnit\Framework\TestCase; +use Symfony\Component\AutoMapper\Transformer\CopyTransformer; class CopyTransformerTest extends TestCase { + use EvalTransformerTrait; + + public function testCopyTransformer() + { + $transformer = new CopyTransformer(); + + $output = $this->evalTransformer($transformer, 'foo'); + + self::assertSame('foo', $output); + } } diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/DateTimeImmutableToMutableTransformerTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/DateTimeImmutableToMutableTransformerTest.php new file mode 100644 index 0000000000000..766ed0d8de915 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/DateTimeImmutableToMutableTransformerTest.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Transformer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\AutoMapper\Transformer\DateTimeImmutableToMutableTransformer; + +class DateTimeImmutableToMutableTransformerTest extends TestCase +{ + use EvalTransformerTrait; + + public function testDateTimeImmutableTransformer() + { + $transformer = new DateTimeImmutableToMutableTransformer(); + + $date = new \DateTimeImmutable(); + $output = $this->evalTransformer($transformer, $date); + + self::assertInstanceOf(\DateTime::class, $output); + self::assertSame($date->format(\DateTime::RFC3339), $output->format(\DateTime::RFC3339)); + } + + public function testAssignByRef() + { + $transformer = new DateTimeImmutableToMutableTransformer(); + + self::assertFalse($transformer->assignByRef()); + } + + public function testEmptyDependencies() + { + $transformer = new DateTimeImmutableToMutableTransformer(); + + self::assertEmpty($transformer->getDependencies()); + } +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/DateTimeMutableToImmutableTransformerTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/DateTimeMutableToImmutableTransformerTest.php new file mode 100644 index 0000000000000..01f907aece0c2 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/DateTimeMutableToImmutableTransformerTest.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\AutoMapper\Tests\Transformer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\AutoMapper\Transformer\DateTimeMutableToImmutableTransformer; + +class DateTimeMutableToImmutableTransformerTest extends TestCase +{ + use EvalTransformerTrait; + + public function testDateTimeImmutableTransformer() + { + $transformer = new DateTimeMutableToImmutableTransformer(); + + $date = new \DateTime(); + $output = $this->evalTransformer($transformer, $date); + + self::assertInstanceOf(\DateTimeImmutable::class, $output); + self::assertSame($date->format(\DateTime::RFC3339), $output->format(\DateTime::RFC3339)); + } + + public function testAssignByRef() + { + $transformer = new DateTimeMutableToImmutableTransformer(); + + self::assertFalse($transformer->assignByRef()); + } + + public function testEmptyDependencies() + { + $transformer = new DateTimeMutableToImmutableTransformer(); + + self::assertEmpty($transformer->getDependencies()); + } +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/DateTimeToStringTransformerTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/DateTimeToStringTransformerTest.php index 8a362817fa6db..7d776d3715c5f 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Transformer/DateTimeToStringTransformerTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/DateTimeToStringTransformerTest.php @@ -12,7 +12,29 @@ namespace Symfony\Component\AutoMapper\Tests\Transformer; use PHPUnit\Framework\TestCase; +use Symfony\Component\AutoMapper\Transformer\DateTimeToStringTansformer; class DateTimeToStringTransformerTest extends TestCase { + use EvalTransformerTrait; + + public function testDateTimeTransformer() + { + $transformer = new DateTimeToStringTansformer(); + + $date = new \DateTime(); + $output = $this->evalTransformer($transformer, new \DateTime()); + + self::assertSame($date->format(\DateTime::RFC3339), $output); + } + + public function testDateTimeTransformerCustomFormat() + { + $transformer = new DateTimeToStringTansformer(\DateTime::COOKIE); + + $date = new \DateTime(); + $output = $this->evalTransformer($transformer, new \DateTime()); + + self::assertSame($date->format(\DateTime::COOKIE), $output); + } } diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/DateTimeTransformerFactoryTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/DateTimeTransformerFactoryTest.php index b0514b45464a9..fa8462843cba5 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Transformer/DateTimeTransformerFactoryTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/DateTimeTransformerFactoryTest.php @@ -12,7 +12,75 @@ namespace Symfony\Component\AutoMapper\Tests\Transformer; use PHPUnit\Framework\TestCase; +use Symfony\Component\AutoMapper\MapperMetadata; +use Symfony\Component\AutoMapper\Transformer\CopyTransformer; +use Symfony\Component\AutoMapper\Transformer\DateTimeImmutableToMutableTransformer; +use Symfony\Component\AutoMapper\Transformer\DateTimeMutableToImmutableTransformer; +use Symfony\Component\AutoMapper\Transformer\DateTimeToStringTansformer; +use Symfony\Component\AutoMapper\Transformer\DateTimeTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\StringToDateTimeTransformer; +use Symfony\Component\PropertyInfo\Type; class DateTimeTransformerFactoryTest extends TestCase { + public function testGetTransformer() + { + $factory = new DateTimeTransformerFactory(); + $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); + + $transformer = $factory->getTransformer([new Type('object', false, \DateTime::class)], [new Type('object', false, \DateTime::class)], $mapperMetadata); + + self::assertNotNull($transformer); + self::assertInstanceOf(CopyTransformer::class, $transformer); + + $transformer = $factory->getTransformer([new Type('object', false, \DateTime::class)], [new Type('string')], $mapperMetadata); + + self::assertNotNull($transformer); + self::assertInstanceOf(DateTimeToStringTansformer::class, $transformer); + + $transformer = $factory->getTransformer([new Type('string')], [new Type('object', false, \DateTime::class)], $mapperMetadata); + + self::assertNotNull($transformer); + self::assertInstanceOf(StringToDateTimeTransformer::class, $transformer); + } + + public function testGetTransformerImmutable() + { + $factory = new DateTimeTransformerFactory(); + $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); + + $transformer = $factory->getTransformer([new Type('object', false, \DateTimeImmutable::class)], [new Type('object', false, \DateTime::class)], $mapperMetadata); + + self::assertNotNull($transformer); + self::assertInstanceOf(DateTimeImmutableToMutableTransformer::class, $transformer); + } + + public function testGetTransformerMutable() + { + $factory = new DateTimeTransformerFactory(); + $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); + + $transformer = $factory->getTransformer([new Type('object', false, \DateTime::class)], [new Type('object', false, \DateTimeImmutable::class)], $mapperMetadata); + + self::assertNotNull($transformer); + self::assertInstanceOf(DateTimeMutableToImmutableTransformer::class, $transformer); + } + + public function testNoTransformer() + { + $factory = new DateTimeTransformerFactory(); + $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); + + $transformer = $factory->getTransformer([new Type('string')], [new Type('string')], $mapperMetadata); + + self::assertNull($transformer); + + $transformer = $factory->getTransformer([new Type('object', false, \DateTime::class)], [new Type('bool')], $mapperMetadata); + + self::assertNull($transformer); + + $transformer = $factory->getTransformer([new Type('bool')], [new Type('object', false, \DateTime::class)], $mapperMetadata); + + self::assertNull($transformer); + } } diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/EvalTransformerTrait.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/EvalTransformerTrait.php new file mode 100644 index 0000000000000..cbbcf1f8efd72 --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/EvalTransformerTrait.php @@ -0,0 +1,53 @@ +getUniqueName('input'); + $inputExpr = new Expr\Variable($inputName); + + [$outputExpr, $stmts] = $transformer->transform($inputExpr, $propertyMapping, $variableScope); + + $stmts[] = new Stmt\Return_($outputExpr); + + $functionExpr = new Expr\Closure([ + 'stmts' => $stmts, + 'params' => [new Param($inputExpr), new Param(new Expr\Variable('context'), new Expr\Array_())] + ]); + + $printer = new Standard(); + $code = $printer->prettyPrint([new Stmt\Return_($functionExpr)]); + + return eval($code); + } + + private function evalTransformer(TransformerInterface $transformer, $input, PropertyMapping $propertyMapping = null) + { + $function = $this->createTransformerFunction($transformer, $propertyMapping); + + return $function($input); + } +} diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/MultipleTransformerFactoryTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/MultipleTransformerFactoryTest.php index c47906a60f1b2..c8c94c75fa575 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Transformer/MultipleTransformerFactoryTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/MultipleTransformerFactoryTest.php @@ -12,7 +12,69 @@ namespace Symfony\Component\AutoMapper\Tests\Transformer; use PHPUnit\Framework\TestCase; +use Symfony\Component\AutoMapper\MapperMetadata; +use Symfony\Component\AutoMapper\Transformer\BuiltinTransformer; +use Symfony\Component\AutoMapper\Transformer\BuiltinTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\ChainTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\MultipleTransformer; +use Symfony\Component\AutoMapper\Transformer\MultipleTransformerFactory; +use Symfony\Component\PropertyInfo\Type; class MultipleTransformerFactoryTest extends TestCase { + public function testGetTransformer() + { + $chainFactory = new ChainTransformerFactory(); + $factory = new MultipleTransformerFactory($chainFactory); + + $chainFactory->addTransformerFactory($factory); + $chainFactory->addTransformerFactory(new BuiltinTransformerFactory()); + + $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); + + $transformer = $factory->getTransformer([new Type('string'), new Type('int')], [], $mapperMetadata); + + self::assertNotNull($transformer); + self::assertInstanceOf(MultipleTransformer::class, $transformer); + + $transformer = $factory->getTransformer([new Type('string'), new Type('object')], [], $mapperMetadata); + + self::assertNotNull($transformer); + self::assertInstanceOf(BuiltinTransformer::class, $transformer); + } + + public function testNoTransformerIfNoSubTransformer() + { + $chainFactory = new ChainTransformerFactory(); + $factory = new MultipleTransformerFactory($chainFactory); + + $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); + + $transformer = $factory->getTransformer([new Type('string'), new Type('int')], [], $mapperMetadata); + + self::assertNull($transformer); + } + + public function testNoTransformer() + { + $chainFactory = new ChainTransformerFactory(); + $factory = new MultipleTransformerFactory($chainFactory); + + $chainFactory->addTransformerFactory($factory); + $chainFactory->addTransformerFactory(new BuiltinTransformerFactory()); + + $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); + + $transformer = $factory->getTransformer(null, null, $mapperMetadata); + + self::assertNull($transformer); + + $transformer = $factory->getTransformer([], null, $mapperMetadata); + + self::assertNull($transformer); + + $transformer = $factory->getTransformer([new Type('string')], null, $mapperMetadata); + + self::assertNull($transformer); + } } diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/MultipleTransformerTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/MultipleTransformerTest.php index 12629d374755d..7e1582c1180f3 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Transformer/MultipleTransformerTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/MultipleTransformerTest.php @@ -12,7 +12,33 @@ namespace Symfony\Component\AutoMapper\Tests\Transformer; use PHPUnit\Framework\TestCase; +use Symfony\Component\AutoMapper\Transformer\BuiltinTransformer; +use Symfony\Component\AutoMapper\Transformer\MultipleTransformer; +use Symfony\Component\PropertyInfo\Type; class MultipleTransformerTest extends TestCase { + use EvalTransformerTrait; + + public function testMultipleTransformer() + { + $transformer = new MultipleTransformer([ + [ + 'transformer' => new BuiltinTransformer(new Type('string'), [new Type('int')]), + 'type' => new Type('string'), + ], + [ + 'transformer' => new BuiltinTransformer(new Type('int'), [new Type('string')]), + 'type' => new Type('int'), + ], + ]); + + $output = $this->evalTransformer($transformer, '12'); + + self::assertSame(12, $output); + + $output = $this->evalTransformer($transformer, 12); + + self::assertSame('12', $output); + } } diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/NullableTransformerFactoryTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/NullableTransformerFactoryTest.php index dc8851800173b..dc39c38935f87 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Transformer/NullableTransformerFactoryTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/NullableTransformerFactoryTest.php @@ -12,7 +12,82 @@ namespace Symfony\Component\AutoMapper\Tests\Transformer; use PHPUnit\Framework\TestCase; +use Symfony\Component\AutoMapper\MapperMetadata; +use Symfony\Component\AutoMapper\Transformer\BuiltinTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\ChainTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\NullableTransformer; +use Symfony\Component\AutoMapper\Transformer\NullableTransformerFactory; +use Symfony\Component\PropertyInfo\Type; class NullableTransformerFactoryTest extends TestCase { + private $isTargetNullableProperty; + + public function setUp(): void + { + $this->isTargetNullableProperty = (new \ReflectionClass(NullableTransformer::class))->getProperty('isTargetNullable'); + $this->isTargetNullableProperty->setAccessible(true); + } + + public function testGetTransformer(): void + { + $chainFactory = new ChainTransformerFactory(); + $factory = new NullableTransformerFactory($chainFactory); + + $chainFactory->addTransformerFactory($factory); + $chainFactory->addTransformerFactory(new BuiltinTransformerFactory()); + $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); + + $transformer = $factory->getTransformer([new Type('string', true)], [new Type('string')], $mapperMetadata); + + self::assertNotNull($transformer); + self::assertInstanceOf(NullableTransformer::class, $transformer); + self::assertFalse($this->isTargetNullableProperty->getValue($transformer)); + + $transformer = $factory->getTransformer([new Type('string', true)], [new Type('string', true)], $mapperMetadata); + + self::assertNotNull($transformer); + self::assertInstanceOf(NullableTransformer::class, $transformer); + self::assertTrue($this->isTargetNullableProperty->getValue($transformer)); + + $transformer = $factory->getTransformer([new Type('string', true)], [new Type('string'), new Type('int', true)], $mapperMetadata); + + self::assertNotNull($transformer); + self::assertInstanceOf(NullableTransformer::class, $transformer); + self::assertTrue($this->isTargetNullableProperty->getValue($transformer)); + + $transformer = $factory->getTransformer([new Type('string', true)], [new Type('string'), new Type('int')], $mapperMetadata); + + self::assertNotNull($transformer); + self::assertInstanceOf(NullableTransformer::class, $transformer); + self::assertFalse($this->isTargetNullableProperty->getValue($transformer)); + } + + public function testNullTransformerIfSourceTypeNotNullable(): void + { + $chainFactory = new ChainTransformerFactory(); + $factory = new NullableTransformerFactory($chainFactory); + + $chainFactory->addTransformerFactory($factory); + $chainFactory->addTransformerFactory(new BuiltinTransformerFactory()); + $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); + + $transformer = $factory->getTransformer([new Type('string')], [new Type('string')], $mapperMetadata); + + self::assertNull($transformer); + } + + public function testNullTransformerIfMultipleSource(): void + { + $chainFactory = new ChainTransformerFactory(); + $factory = new NullableTransformerFactory($chainFactory); + + $chainFactory->addTransformerFactory($factory); + $chainFactory->addTransformerFactory(new BuiltinTransformerFactory()); + $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); + + $transformer = $factory->getTransformer([new Type('string', true), new Type('string')], [new Type('string')], $mapperMetadata); + + self::assertNull($transformer); + } } diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/NullableTransformerTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/NullableTransformerTest.php index e4a70b1b5b153..6750e6d64d656 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Transformer/NullableTransformerTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/NullableTransformerTest.php @@ -12,7 +12,33 @@ namespace Symfony\Component\AutoMapper\Tests\Transformer; use PHPUnit\Framework\TestCase; +use Symfony\Component\AutoMapper\Transformer\BuiltinTransformer; +use Symfony\Component\AutoMapper\Transformer\NullableTransformer; +use Symfony\Component\PropertyInfo\Type; class NullableTransformerTest extends TestCase { + use EvalTransformerTrait; + + public function testNullTransformerTargetNullable() + { + $transformer = new NullableTransformer(new BuiltinTransformer(new Type('string'), [new Type('string', true)]), true); + + $output = $this->evalTransformer($transformer, 'foo'); + + self::assertSame('foo', $output); + + $output = $this->evalTransformer($transformer, null); + + self::assertNull($output); + } + + public function testNullTransformerTargetNotNullable() + { + $transformer = new NullableTransformer(new BuiltinTransformer(new Type('string'), [new Type('string')]), false); + + $output = $this->evalTransformer($transformer, 'foo'); + + self::assertSame('foo', $output); + } } diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/ObjectTransformerFactoryTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/ObjectTransformerFactoryTest.php index 31938a252e172..f8a64f324862b 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Transformer/ObjectTransformerFactoryTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/ObjectTransformerFactoryTest.php @@ -12,7 +12,66 @@ namespace Symfony\Component\AutoMapper\Tests\Transformer; use PHPUnit\Framework\TestCase; +use Symfony\Component\AutoMapper\AutoMapperRegistryInterface; +use Symfony\Component\AutoMapper\MapperMetadata; +use Symfony\Component\AutoMapper\Transformer\ObjectTransformer; +use Symfony\Component\AutoMapper\Transformer\ObjectTransformerFactory; +use Symfony\Component\PropertyInfo\Type; class ObjectTransformerFactoryTest extends TestCase { + public function testGetTransformer(): void + { + $autoMapperRegistry = $this->getMockBuilder(AutoMapperRegistryInterface::class)->getMock(); + $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); + $factory = new ObjectTransformerFactory($autoMapperRegistry); + + $autoMapperRegistry + ->expects($this->any()) + ->method('hasMapper') + ->willReturn(true) + ; + + $transformer = $factory->getTransformer([new Type('object', false, \stdClass::class)], [new Type('object', false, \stdClass::class)], $mapperMetadata); + + self::assertNotNull($transformer); + self::assertInstanceOf(ObjectTransformer::class, $transformer); + + $transformer = $factory->getTransformer([new Type('array')], [new Type('object', false, \stdClass::class)], $mapperMetadata); + + self::assertNotNull($transformer); + self::assertInstanceOf(ObjectTransformer::class, $transformer); + + $transformer = $factory->getTransformer([new Type('object', false, \stdClass::class)], [new Type('array')], $mapperMetadata); + + self::assertNotNull($transformer); + self::assertInstanceOf(ObjectTransformer::class, $transformer); + } + + public function testNoTransformer(): void + { + $autoMapperRegistry = $this->getMockBuilder(AutoMapperRegistryInterface::class)->getMock(); + $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); + $factory = new ObjectTransformerFactory($autoMapperRegistry); + + $transformer = $factory->getTransformer([], [], $mapperMetadata); + + self::assertNull($transformer); + + $transformer = $factory->getTransformer([new Type('object')], [], $mapperMetadata); + + self::assertNull($transformer); + + $transformer = $factory->getTransformer([], [new Type('object')], $mapperMetadata); + + self::assertNull($transformer); + + $transformer = $factory->getTransformer([new Type('object'), new Type('object')], [new Type('object')], $mapperMetadata); + + self::assertNull($transformer); + + $transformer = $factory->getTransformer([new Type('object')], [new Type('object'), new Type('object')], $mapperMetadata); + + self::assertNull($transformer); + } } diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/ObjectTransformerTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/ObjectTransformerTest.php index 016aa9e92f177..ec15600f5c61d 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Transformer/ObjectTransformerTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/ObjectTransformerTest.php @@ -12,7 +12,40 @@ namespace Symfony\Component\AutoMapper\Tests\Transformer; use PHPUnit\Framework\TestCase; +use Symfony\Component\AutoMapper\Transformer\ObjectTransformer; +use Symfony\Component\PropertyInfo\Type; class ObjectTransformerTest extends TestCase { + use EvalTransformerTrait; + + public function testObjectTransformer() + { + $transformer = new ObjectTransformer(new Type('object', false, Foo::class), new Type('object', false, Foo::class)); + + $function = $this->createTransformerFunction($transformer); + $class = new class () { + public $mappers; + + public function __construct() + { + $this->mappers['Mapper_' . Foo::class . '_' . Foo::class] = new class () { + public function map() + { + return new Foo(); + } + }; + } + }; + + $transform = \Closure::bind($function, $class); + $output = $transform(new Foo()); + + self::assertNotNull($output); + self::assertInstanceOf(Foo::class, $output); + } +} + +class Foo { + public $bar; } diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/StringToDateTimeTransformerTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/StringToDateTimeTransformerTest.php index 9fcb1b328cec3..98473d98eecd4 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Transformer/StringToDateTimeTransformerTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/StringToDateTimeTransformerTest.php @@ -12,7 +12,42 @@ namespace Symfony\Component\AutoMapper\Tests\Transformer; use PHPUnit\Framework\TestCase; +use Symfony\Component\AutoMapper\Transformer\DateTimeToStringTansformer; +use Symfony\Component\AutoMapper\Transformer\StringToDateTimeTransformer; class StringToDateTimeTransformerTest extends TestCase { + use EvalTransformerTrait; + + public function testDateTimeTransformer() + { + $transformer = new StringToDateTimeTransformer(\DateTime::class); + + $date = new \DateTime(); + $output = $this->evalTransformer($transformer, $date->format(\DateTime::RFC3339)); + + self::assertInstanceOf(\DateTime::class, $output); + self::assertSame($date->format(\DateTime::RFC3339), $output->format(\DateTime::RFC3339)); + } + + public function testDateTimeTransformerCustomFormat() + { + $transformer = new StringToDateTimeTransformer(\DateTime::class, \DateTime::COOKIE); + + $date = new \DateTime(); + $output = $this->evalTransformer($transformer, $date->format(\DateTime::COOKIE)); + + self::assertInstanceOf(\DateTime::class, $output); + self::assertSame($date->format(\DateTime::RFC3339), $output->format(\DateTime::RFC3339)); + } + + public function testDateTimeTransformerImmutable() + { + $transformer = new StringToDateTimeTransformer(\DateTimeImmutable::class, \DateTime::COOKIE); + + $date = new \DateTime(); + $output = $this->evalTransformer($transformer, $date->format(\DateTime::COOKIE)); + + self::assertInstanceOf(\DateTimeImmutable::class, $output); + } } diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/UniqueTypeTransformerFactoryTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/UniqueTypeTransformerFactoryTest.php index 8dc3fb2542a83..0b31d0ec22642 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Transformer/UniqueTypeTransformerFactoryTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/UniqueTypeTransformerFactoryTest.php @@ -12,7 +12,59 @@ namespace Symfony\Component\AutoMapper\Tests\Transformer; use PHPUnit\Framework\TestCase; +use Symfony\Component\AutoMapper\MapperMetadata; +use Symfony\Component\AutoMapper\Transformer\BuiltinTransformer; +use Symfony\Component\AutoMapper\Transformer\BuiltinTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\ChainTransformerFactory; +use Symfony\Component\AutoMapper\Transformer\UniqueTypeTransformerFactory; +use Symfony\Component\PropertyInfo\Type; class UniqueTypeTransformerFactoryTest extends TestCase { + public function testGetTransformer(): void + { + $chainFactory = new ChainTransformerFactory(); + $factory = new UniqueTypeTransformerFactory($chainFactory); + + $chainFactory->addTransformerFactory($factory); + $chainFactory->addTransformerFactory(new BuiltinTransformerFactory()); + + $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); + + $transformer = $factory->getTransformer([new Type('string')], [new Type('string'), new Type('string')], $mapperMetadata); + + self::assertNotNull($transformer); + self::assertInstanceOf(BuiltinTransformer::class, $transformer); + } + + public function testNullTransformer(): void + { + $chainFactory = new ChainTransformerFactory(); + $factory = new UniqueTypeTransformerFactory($chainFactory); + + $chainFactory->addTransformerFactory($factory); + $chainFactory->addTransformerFactory(new BuiltinTransformerFactory()); + + $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); + + $transformer = $factory->getTransformer(null, [], $mapperMetadata); + + self::assertNull($transformer); + + $transformer = $factory->getTransformer([], [], $mapperMetadata); + + self::assertNull($transformer); + + $transformer = $factory->getTransformer([new Type('string')], [], $mapperMetadata); + + self::assertNull($transformer); + + $transformer = $factory->getTransformer([new Type('string'), new Type('string')], [], $mapperMetadata); + + self::assertNull($transformer); + + $transformer = $factory->getTransformer([new Type('string')], [new Type('string')], $mapperMetadata); + + self::assertNull($transformer); + } } diff --git a/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformer.php index a35543e1ca76c..294482083ee9a 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformer.php @@ -53,9 +53,7 @@ final class BuiltinTransformer implements TransformerInterface Type::BUILTIN_TYPE_ITERABLE => [ Type::BUILTIN_TYPE_ARRAY => 'fromIteratorToArray', ], - Type::BUILTIN_TYPE_ARRAY => [ - Type::BUILTIN_TYPE_ITERABLE => null, - ], + Type::BUILTIN_TYPE_ARRAY => [], Type::BUILTIN_TYPE_STRING => [ Type::BUILTIN_TYPE_ARRAY => 'toArray', Type::BUILTIN_TYPE_ITERABLE => 'toArray', @@ -89,13 +87,13 @@ public function transform(Expr $input, PropertyMapping $propertyMapping, UniqueV }, $this->targetTypes); // Source type is in target => no cast - if (\in_array($this->sourceType->getBuiltinType(), $targetTypes)) { + if (\in_array($this->sourceType->getBuiltinType(), $targetTypes, true)) { return [$input, []]; } // Cast needed foreach (self::CAST_MAPPING[$this->sourceType->getBuiltinType()] as $castType => $castMethod) { - if (\in_array($castType, $targetTypes)) { + if (\in_array($castType, $targetTypes, true)) { if (method_exists($this, $castMethod)) { return [$this->$castMethod($input), []]; } diff --git a/src/Symfony/Component/AutoMapper/Transformer/DateTimeImmutableToMutableTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/DateTimeImmutableToMutableTransformer.php new file mode 100644 index 0000000000000..519403d326c0e --- /dev/null +++ b/src/Symfony/Component/AutoMapper/Transformer/DateTimeImmutableToMutableTransformer.php @@ -0,0 +1,41 @@ +getBuiltinType()) { - return false; - } - if (\DateTime::class !== $type->getClassName() && !is_subclass_of($type->getClassName(), \DateTime::class)) { return false; } diff --git a/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformer.php index 330b5172fde6c..bd4b5b2518b30 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformer.php @@ -46,12 +46,9 @@ final class MultipleTransformer implements TransformerInterface private $transformers = []; - public function addTransformer(TransformerInterface $transformer, Type $sourceType) + public function __construct(array $transformers) { - $this->transformers[] = [ - 'transformer' => $transformer, - 'type' => $sourceType, - ]; + $this->transformers = $transformers; } /** diff --git a/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformerFactory.php index 686f93b2f10c1..1cff5b66c2051 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformerFactory.php +++ b/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformerFactory.php @@ -32,18 +32,29 @@ public function __construct(ChainTransformerFactory $chainTransformerFactory) */ public function getTransformer(?array $sourcesTypes, ?array $targetTypes, MapperMetadataInterface $mapperMetadata): ?TransformerInterface { - if (null === $sourcesTypes || 0 === \count($sourcesTypes)) { + if (null === $sourcesTypes || \count($sourcesTypes) <= 1) { return null; } - if (\count($sourcesTypes) > 1) { - $transformer = new MultipleTransformer(); + $transformers = []; - foreach ($sourcesTypes as $sourceType) { - $transformer->addTransformer($this->chainTransformerFactory->getTransformer([$sourceType], $targetTypes, $mapperMetadata), $sourceType); + foreach ($sourcesTypes as $sourceType) { + $transformer = $this->chainTransformerFactory->getTransformer([$sourceType], $targetTypes, $mapperMetadata); + + if (null !== $transformer) { + $transformers[] = [ + 'transformer' => $transformer, + 'type' => $sourceType + ]; } + } + + if (\count($transformers) > 1) { + return new MultipleTransformer($transformers); + } - return $transformer; + if (\count($transformers) === 1) { + return $transformers[0]['transformer']; } return null; diff --git a/src/Symfony/Component/AutoMapper/Transformer/NullableTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/NullableTransformerFactory.php index 491c00afdcc32..0a8e8cc9a2ac6 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/NullableTransformerFactory.php +++ b/src/Symfony/Component/AutoMapper/Transformer/NullableTransformerFactory.php @@ -42,10 +42,6 @@ public function getTransformer(?array $sourcesTypes, ?array $targetTypes, Mapper /** @var Type $propertyType */ $propertyType = $sourcesTypes[0]; - if (null === $propertyType) { - var_dump($sourcesTypes); - } - if (!$propertyType->isNullable()) { return null; } diff --git a/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformerFactory.php index f63c02abef9f7..034fb34790da7 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformerFactory.php +++ b/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformerFactory.php @@ -50,7 +50,7 @@ protected function createTransformer(Type $sourceType, Type $targetType, MapperM $targetTypeName = $targetType->getClassName(); } - if ($this->autoMapper->hasMapper($sourceTypeName, $targetTypeName)) { + if (null !== $sourceTypeName && null !== $targetTypeName && $this->autoMapper->hasMapper($sourceTypeName, $targetTypeName)) { return new ObjectTransformer($sourceType, $targetType); } From af1fdf25b4c03fb1c9a5847ed0af43f906f3fc01 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Sun, 2 Feb 2020 20:29:42 +0100 Subject: [PATCH 32/38] Fix header, expiremental in 5.1 --- src/Symfony/Component/AutoMapper/AutoMapper.php | 2 +- .../Component/AutoMapper/AutoMapperInterface.php | 2 +- .../AutoMapper/AutoMapperNormalizer.php | 2 +- .../Exception/CircularReferenceException.php | 2 +- .../AutoMapper/Exception/CompileException.php | 2 +- .../Exception/InvalidMappingException.php | 2 +- .../Exception/NoMappingFoundException.php | 2 +- .../Extractor/FromSourceMappingExtractor.php | 2 +- .../Extractor/FromTargetMappingExtractor.php | 2 +- .../AutoMapper/Extractor/PropertyMapping.php | 2 +- .../AutoMapper/Extractor/ReadAccessor.php | 2 ++ .../Extractor/SourceTargetMappingExtractor.php | 2 +- .../AutoMapper/Extractor/WriteMutator.php | 2 ++ .../Component/AutoMapper/GeneratedMapper.php | 2 ++ .../Component/AutoMapper/Generator/Generator.php | 2 +- .../AutoMapper/Loader/ClassLoaderInterface.php | 2 ++ .../Component/AutoMapper/Loader/EvalLoader.php | 2 +- .../Component/AutoMapper/Loader/FileLoader.php | 2 +- .../Component/AutoMapper/MapperContext.php | 2 ++ .../MapperGeneratorMetadataFactory.php | 2 +- .../MapperGeneratorMetadataFactoryInterface.php | 2 +- .../Component/AutoMapper/MapperMetadata.php | 2 +- .../AutoMapper/Tests/Fixtures/CircularBar.php | 9 ++++++++- .../AutoMapper/Tests/Fixtures/CircularBaz.php | 9 ++++++++- .../AutoMapper/Tests/Fixtures/CircularFoo.php | 9 ++++++++- .../AutoMapper/Tests/Fixtures/CityFoo.php | 9 +++++++++ .../Tests/Fixtures/FooNoProperties.php | 9 +++++++++ .../Tests/Transformer/EvalTransformerTrait.php | 9 +++++++++ .../StringToDateTimeTransformerTest.php | 1 - .../AutoMapper/Transformer/ArrayTransformer.php | 2 +- .../Transformer/ArrayTransformerFactory.php | 2 +- .../Transformer/BuiltinTransformer.php | 2 +- .../Transformer/BuiltinTransformerFactory.php | 2 +- .../Transformer/CallbackTransformer.php | 2 +- .../Transformer/ChainTransformerFactory.php | 2 +- .../AutoMapper/Transformer/CopyTransformer.php | 2 +- .../DateTimeImmutableToMutableTransformer.php | 16 ++++++++++++++++ .../DateTimeMutableToImmutableTransformer.php | 16 ++++++++++++++++ .../Transformer/DateTimeToStringTansformer.php | 2 +- .../Transformer/DateTimeTransformerFactory.php | 2 +- .../Transformer/MultipleTransformer.php | 2 +- .../Transformer/MultipleTransformerFactory.php | 2 +- .../Transformer/NullableTransformer.php | 2 +- .../Transformer/NullableTransformerFactory.php | 2 +- .../AutoMapper/Transformer/ObjectTransformer.php | 2 +- .../Transformer/ObjectTransformerFactory.php | 2 +- .../Transformer/StringToDateTimeTransformer.php | 2 +- .../Transformer/TransformerFactoryInterface.php | 2 +- .../Transformer/TransformerInterface.php | 2 +- .../Transformer/UniqueTypeTransformerFactory.php | 2 ++ 50 files changed, 130 insertions(+), 39 deletions(-) diff --git a/src/Symfony/Component/AutoMapper/AutoMapper.php b/src/Symfony/Component/AutoMapper/AutoMapper.php index d0d898cba633b..8aae90ab13e5a 100644 --- a/src/Symfony/Component/AutoMapper/AutoMapper.php +++ b/src/Symfony/Component/AutoMapper/AutoMapper.php @@ -39,7 +39,7 @@ /** * Maps a source data structure (object or array) to a target one. * - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/AutoMapperInterface.php b/src/Symfony/Component/AutoMapper/AutoMapperInterface.php index 3e805f7d1686c..facf2b704bcd2 100644 --- a/src/Symfony/Component/AutoMapper/AutoMapperInterface.php +++ b/src/Symfony/Component/AutoMapper/AutoMapperInterface.php @@ -14,7 +14,7 @@ /** * An auto mapper has the role of mapping a source to a target. * - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php b/src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php index ac27d2b4494a4..47c297fe16399 100644 --- a/src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php +++ b/src/Symfony/Component/AutoMapper/AutoMapperNormalizer.php @@ -18,7 +18,7 @@ /** * Bridge for symfony/serializer. * - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Exception/CircularReferenceException.php b/src/Symfony/Component/AutoMapper/Exception/CircularReferenceException.php index 1334b22a5d352..6e0e0852357b1 100644 --- a/src/Symfony/Component/AutoMapper/Exception/CircularReferenceException.php +++ b/src/Symfony/Component/AutoMapper/Exception/CircularReferenceException.php @@ -12,7 +12,7 @@ namespace Symfony\Component\AutoMapper\Exception; /** - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Exception/CompileException.php b/src/Symfony/Component/AutoMapper/Exception/CompileException.php index 3064f389edc99..4f2db214b1774 100644 --- a/src/Symfony/Component/AutoMapper/Exception/CompileException.php +++ b/src/Symfony/Component/AutoMapper/Exception/CompileException.php @@ -12,7 +12,7 @@ namespace Symfony\Component\AutoMapper\Exception; /** - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Exception/InvalidMappingException.php b/src/Symfony/Component/AutoMapper/Exception/InvalidMappingException.php index 68e57093edb7d..f741bd68a4a85 100644 --- a/src/Symfony/Component/AutoMapper/Exception/InvalidMappingException.php +++ b/src/Symfony/Component/AutoMapper/Exception/InvalidMappingException.php @@ -12,7 +12,7 @@ namespace Symfony\Component\AutoMapper\Exception; /** - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Exception/NoMappingFoundException.php b/src/Symfony/Component/AutoMapper/Exception/NoMappingFoundException.php index e92f5b4ecc489..b9ffe095c696f 100644 --- a/src/Symfony/Component/AutoMapper/Exception/NoMappingFoundException.php +++ b/src/Symfony/Component/AutoMapper/Exception/NoMappingFoundException.php @@ -12,7 +12,7 @@ namespace Symfony\Component\AutoMapper\Exception; /** - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Extractor/FromSourceMappingExtractor.php b/src/Symfony/Component/AutoMapper/Extractor/FromSourceMappingExtractor.php index bfba50fb38c51..a732d131b4636 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/FromSourceMappingExtractor.php +++ b/src/Symfony/Component/AutoMapper/Extractor/FromSourceMappingExtractor.php @@ -26,7 +26,7 @@ * * Can use a NameConverter to use specific properties name in the target * - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Extractor/FromTargetMappingExtractor.php b/src/Symfony/Component/AutoMapper/Extractor/FromTargetMappingExtractor.php index 42c952521a804..e861cf2439f0e 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/FromTargetMappingExtractor.php +++ b/src/Symfony/Component/AutoMapper/Extractor/FromTargetMappingExtractor.php @@ -26,7 +26,7 @@ * * Can use a NameConverter to use specific properties name in the source * - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Extractor/PropertyMapping.php b/src/Symfony/Component/AutoMapper/Extractor/PropertyMapping.php index 4b2e1490b511c..2cda5745cd370 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/PropertyMapping.php +++ b/src/Symfony/Component/AutoMapper/Extractor/PropertyMapping.php @@ -16,7 +16,7 @@ /** * Property mapping. * - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Extractor/ReadAccessor.php b/src/Symfony/Component/AutoMapper/Extractor/ReadAccessor.php index f8bf79de81399..138e9aebca0fd 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/ReadAccessor.php +++ b/src/Symfony/Component/AutoMapper/Extractor/ReadAccessor.php @@ -22,6 +22,8 @@ /** * Read accessor tell how to read from a property. * + * @expiremental in 5.1 + * * @author Joel Wurtz */ final class ReadAccessor diff --git a/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php b/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php index e83a31fdfec0f..7276a451a8b5a 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php +++ b/src/Symfony/Component/AutoMapper/Extractor/SourceTargetMappingExtractor.php @@ -16,7 +16,7 @@ /** * Extracts mapping between two objects, only gives properties that have the same name. * - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Extractor/WriteMutator.php b/src/Symfony/Component/AutoMapper/Extractor/WriteMutator.php index 3c20bf955a818..3b16999cdaec5 100644 --- a/src/Symfony/Component/AutoMapper/Extractor/WriteMutator.php +++ b/src/Symfony/Component/AutoMapper/Extractor/WriteMutator.php @@ -22,6 +22,8 @@ /** * Writes mutator tell how to write to a property. * + * @expiremental in 5.1 + * * @author Joel Wurtz */ final class WriteMutator diff --git a/src/Symfony/Component/AutoMapper/GeneratedMapper.php b/src/Symfony/Component/AutoMapper/GeneratedMapper.php index aa60d1a92d0d6..11a4cc58a6cc7 100644 --- a/src/Symfony/Component/AutoMapper/GeneratedMapper.php +++ b/src/Symfony/Component/AutoMapper/GeneratedMapper.php @@ -14,6 +14,8 @@ /** * Class derived for each generated mapper. * + * @expiremental in 5.1 + * * @author Joel Wurtz */ abstract class GeneratedMapper implements MapperInterface diff --git a/src/Symfony/Component/AutoMapper/Generator/Generator.php b/src/Symfony/Component/AutoMapper/Generator/Generator.php index 41913d0eb587d..18cf48e7c57fa 100644 --- a/src/Symfony/Component/AutoMapper/Generator/Generator.php +++ b/src/Symfony/Component/AutoMapper/Generator/Generator.php @@ -31,7 +31,7 @@ /** * Generates code for a mapping class. * - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Loader/ClassLoaderInterface.php b/src/Symfony/Component/AutoMapper/Loader/ClassLoaderInterface.php index cd995ccf5d4ba..c70070f1400b4 100644 --- a/src/Symfony/Component/AutoMapper/Loader/ClassLoaderInterface.php +++ b/src/Symfony/Component/AutoMapper/Loader/ClassLoaderInterface.php @@ -15,6 +15,8 @@ /** * Loads (require) a mapping given metadata. + * + * @expiremental in 5.1 */ interface ClassLoaderInterface { diff --git a/src/Symfony/Component/AutoMapper/Loader/EvalLoader.php b/src/Symfony/Component/AutoMapper/Loader/EvalLoader.php index b48001b6cb6f8..88ca4e68a1f32 100644 --- a/src/Symfony/Component/AutoMapper/Loader/EvalLoader.php +++ b/src/Symfony/Component/AutoMapper/Loader/EvalLoader.php @@ -18,7 +18,7 @@ /** * Use eval to load mappers. * - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Loader/FileLoader.php b/src/Symfony/Component/AutoMapper/Loader/FileLoader.php index 48f7f69c80858..cfd5ccd148e16 100644 --- a/src/Symfony/Component/AutoMapper/Loader/FileLoader.php +++ b/src/Symfony/Component/AutoMapper/Loader/FileLoader.php @@ -18,7 +18,7 @@ /** * Use file system to load mapper, and persist them using a registry. * - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/MapperContext.php b/src/Symfony/Component/AutoMapper/MapperContext.php index 1fbb5f6bc3961..12027c278778c 100644 --- a/src/Symfony/Component/AutoMapper/MapperContext.php +++ b/src/Symfony/Component/AutoMapper/MapperContext.php @@ -18,6 +18,8 @@ * * Allows to customize how is done the mapping * + * @expiremental in 5.1 + * * @author Joel Wurtz */ class MapperContext diff --git a/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactory.php b/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactory.php index d013085e74391..ce6724dca90c4 100644 --- a/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactory.php +++ b/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactory.php @@ -18,7 +18,7 @@ /** * Metadata factory, used to autoregistering new mapping without creating them. * - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactoryInterface.php b/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactoryInterface.php index 0818529deb894..b80084ec81873 100644 --- a/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactoryInterface.php +++ b/src/Symfony/Component/AutoMapper/MapperGeneratorMetadataFactoryInterface.php @@ -14,7 +14,7 @@ /** * Metadata factory, used to autoregistering new mapping without creating them. * - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/MapperMetadata.php b/src/Symfony/Component/AutoMapper/MapperMetadata.php index 410bd2654cc31..b294cf894cd26 100644 --- a/src/Symfony/Component/AutoMapper/MapperMetadata.php +++ b/src/Symfony/Component/AutoMapper/MapperMetadata.php @@ -20,7 +20,7 @@ /** * Mapper metadata. * - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Tests/Fixtures/CircularBar.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/CircularBar.php index 97ce56eacdc2f..4c03f031ae326 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Fixtures/CircularBar.php +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/CircularBar.php @@ -1,9 +1,16 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ namespace Symfony\Component\AutoMapper\Tests\Fixtures; - class CircularBar { /** @var CircularBaz */ diff --git a/src/Symfony/Component/AutoMapper/Tests/Fixtures/CircularBaz.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/CircularBaz.php index fe56fb314c78b..8711dc8b751a0 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Fixtures/CircularBaz.php +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/CircularBaz.php @@ -1,9 +1,16 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ namespace Symfony\Component\AutoMapper\Tests\Fixtures; - class CircularBaz { /** @var CircularFoo */ diff --git a/src/Symfony/Component/AutoMapper/Tests/Fixtures/CircularFoo.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/CircularFoo.php index 5f6ab432cdc3f..3b68c49b55871 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Fixtures/CircularFoo.php +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/CircularFoo.php @@ -1,9 +1,16 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ namespace Symfony\Component\AutoMapper\Tests\Fixtures; - class CircularFoo { /** @var CircularBar */ diff --git a/src/Symfony/Component/AutoMapper/Tests/Fixtures/CityFoo.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/CityFoo.php index 754ac4ceba000..99a61b2264bed 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Fixtures/CityFoo.php +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/CityFoo.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\AutoMapper\Tests\Fixtures; class CityFoo diff --git a/src/Symfony/Component/AutoMapper/Tests/Fixtures/FooNoProperties.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/FooNoProperties.php index 17e96862d2b91..a0c1292b7c274 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Fixtures/FooNoProperties.php +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/FooNoProperties.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\AutoMapper\Tests\Fixtures; class FooNoProperties diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/EvalTransformerTrait.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/EvalTransformerTrait.php index cbbcf1f8efd72..fd846b86e712b 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Transformer/EvalTransformerTrait.php +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/EvalTransformerTrait.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\AutoMapper\Tests\Transformer; use PhpParser\Node\Expr; diff --git a/src/Symfony/Component/AutoMapper/Tests/Transformer/StringToDateTimeTransformerTest.php b/src/Symfony/Component/AutoMapper/Tests/Transformer/StringToDateTimeTransformerTest.php index 98473d98eecd4..c19964323d72b 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Transformer/StringToDateTimeTransformerTest.php +++ b/src/Symfony/Component/AutoMapper/Tests/Transformer/StringToDateTimeTransformerTest.php @@ -12,7 +12,6 @@ namespace Symfony\Component\AutoMapper\Tests\Transformer; use PHPUnit\Framework\TestCase; -use Symfony\Component\AutoMapper\Transformer\DateTimeToStringTansformer; use Symfony\Component\AutoMapper\Transformer\StringToDateTimeTransformer; class StringToDateTimeTransformerTest extends TestCase diff --git a/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformer.php index e7a8af745d135..659fc9c666009 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformer.php @@ -19,7 +19,7 @@ /** * Transformer array decorator. * - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformerFactory.php index cc4a926ea5303..1c14023280d9f 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformerFactory.php +++ b/src/Symfony/Component/AutoMapper/Transformer/ArrayTransformerFactory.php @@ -17,7 +17,7 @@ /** * Create a decorated transformer to handle array type. * - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformer.php index 294482083ee9a..bf4f53e3c1643 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformer.php @@ -22,7 +22,7 @@ /** * Built in transformer to handle PHP scalar types. * - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformerFactory.php index 8bc03b397bb8a..c751b393adec3 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformerFactory.php +++ b/src/Symfony/Component/AutoMapper/Transformer/BuiltinTransformerFactory.php @@ -15,7 +15,7 @@ use Symfony\Component\PropertyInfo\Type; /** - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/CallbackTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/CallbackTransformer.php index 891e2f93360ec..693ff9a3ef524 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/CallbackTransformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/CallbackTransformer.php @@ -20,7 +20,7 @@ /** * Handle custom callback transformation. * - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/ChainTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/ChainTransformerFactory.php index 7571d14ecc368..5b40373bfec03 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/ChainTransformerFactory.php +++ b/src/Symfony/Component/AutoMapper/Transformer/ChainTransformerFactory.php @@ -14,7 +14,7 @@ use Symfony\Component\AutoMapper\MapperMetadataInterface; /** - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/CopyTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/CopyTransformer.php index 35503d3a8e047..683554b57bd93 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/CopyTransformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/CopyTransformer.php @@ -18,7 +18,7 @@ /** * Does not do any transformation, output = input. * - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/DateTimeImmutableToMutableTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/DateTimeImmutableToMutableTransformer.php index 519403d326c0e..18eb7e6f88650 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/DateTimeImmutableToMutableTransformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/DateTimeImmutableToMutableTransformer.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\AutoMapper\Transformer; use PhpParser\Node\Arg; @@ -8,6 +17,13 @@ use Symfony\Component\AutoMapper\Extractor\PropertyMapping; use Symfony\Component\AutoMapper\Generator\UniqueVariableScope; +/** + * @expiremental in 5.1 + * + * Transform DateTimeImmutable to DateTime. + * + * @author Joel Wurtz + */ final class DateTimeImmutableToMutableTransformer implements TransformerInterface { /** diff --git a/src/Symfony/Component/AutoMapper/Transformer/DateTimeMutableToImmutableTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/DateTimeMutableToImmutableTransformer.php index 024bbd18278ca..682ac787d4016 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/DateTimeMutableToImmutableTransformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/DateTimeMutableToImmutableTransformer.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\AutoMapper\Transformer; use PhpParser\Node\Arg; @@ -8,6 +17,13 @@ use Symfony\Component\AutoMapper\Extractor\PropertyMapping; use Symfony\Component\AutoMapper\Generator\UniqueVariableScope; +/** + * @expiremental in 5.1 + * + * Transform DateTime to DateTimeImmutable. + * + * @author Joel Wurtz + */ final class DateTimeMutableToImmutableTransformer implements TransformerInterface { /** diff --git a/src/Symfony/Component/AutoMapper/Transformer/DateTimeToStringTansformer.php b/src/Symfony/Component/AutoMapper/Transformer/DateTimeToStringTansformer.php index 1b98be6468516..615429df4123b 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/DateTimeToStringTansformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/DateTimeToStringTansformer.php @@ -20,7 +20,7 @@ /** * Transform a \DateTimeInterface object to a string. * - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/DateTimeTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/DateTimeTransformerFactory.php index 90bd07641e798..4509b807b5d86 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/DateTimeTransformerFactory.php +++ b/src/Symfony/Component/AutoMapper/Transformer/DateTimeTransformerFactory.php @@ -15,7 +15,7 @@ use Symfony\Component\PropertyInfo\Type; /** - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformer.php index bd4b5b2518b30..8703a482e3bb5 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformer.php @@ -25,7 +25,7 @@ * Decorate transformers with condition to handle property with multiples source types * It will always use the first target type possible for transformation * - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformerFactory.php index 1cff5b66c2051..72e28ab432f26 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformerFactory.php +++ b/src/Symfony/Component/AutoMapper/Transformer/MultipleTransformerFactory.php @@ -14,7 +14,7 @@ use Symfony\Component\AutoMapper\MapperMetadataInterface; /** - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/NullableTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/NullableTransformer.php index 3bfbef33edc20..e880bd5601181 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/NullableTransformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/NullableTransformer.php @@ -20,7 +20,7 @@ /** * Tansformer decorator to handle null values. * - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/NullableTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/NullableTransformerFactory.php index 0a8e8cc9a2ac6..b2e43c7b7383b 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/NullableTransformerFactory.php +++ b/src/Symfony/Component/AutoMapper/Transformer/NullableTransformerFactory.php @@ -15,7 +15,7 @@ use Symfony\Component\PropertyInfo\Type; /** - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformer.php index 9d0628969497d..12d848a0cf425 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformer.php @@ -23,7 +23,7 @@ /** * Transform to an object which can be mapped by AutoMapper (sub mapping). * - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformerFactory.php index 034fb34790da7..909dacccb0c4b 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformerFactory.php +++ b/src/Symfony/Component/AutoMapper/Transformer/ObjectTransformerFactory.php @@ -16,7 +16,7 @@ use Symfony\Component\PropertyInfo\Type; /** - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/StringToDateTimeTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/StringToDateTimeTransformer.php index 91b100aed7412..870f9fc0afc81 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/StringToDateTimeTransformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/StringToDateTimeTransformer.php @@ -21,7 +21,7 @@ /** * Transform a string to a \DateTimeInterface object. * - * @expiremental in 4.3 + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/TransformerFactoryInterface.php b/src/Symfony/Component/AutoMapper/Transformer/TransformerFactoryInterface.php index 4e7d2eed920d3..61c5809cdb8f7 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/TransformerFactoryInterface.php +++ b/src/Symfony/Component/AutoMapper/Transformer/TransformerFactoryInterface.php @@ -17,7 +17,7 @@ /** * Create transformer. * - * @internal + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/TransformerInterface.php b/src/Symfony/Component/AutoMapper/Transformer/TransformerInterface.php index adbe6b050e7de..3eb4ff4fada10 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/TransformerInterface.php +++ b/src/Symfony/Component/AutoMapper/Transformer/TransformerInterface.php @@ -19,7 +19,7 @@ /** * Transformer tell how to transform a property mapping. * - * @internal + * @expiremental in 5.1 * * @author Joel Wurtz */ diff --git a/src/Symfony/Component/AutoMapper/Transformer/UniqueTypeTransformerFactory.php b/src/Symfony/Component/AutoMapper/Transformer/UniqueTypeTransformerFactory.php index 54be1581874fd..9d985961ba463 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/UniqueTypeTransformerFactory.php +++ b/src/Symfony/Component/AutoMapper/Transformer/UniqueTypeTransformerFactory.php @@ -16,6 +16,8 @@ /** * Reduce array of type to only one type on source and target. * + * @expiremental in 5.1 + * * @author Joel Wurtz */ final class UniqueTypeTransformerFactory implements TransformerFactoryInterface From c07edd2c7267e3cd2d48b7f4a7e81e84bee7524c Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Sun, 2 Feb 2020 20:30:01 +0100 Subject: [PATCH 33/38] Fix php version --- src/Symfony/Component/AutoMapper/composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/AutoMapper/composer.json b/src/Symfony/Component/AutoMapper/composer.json index 4bc3ec4e5ae74..ee72bdde5fe6a 100644 --- a/src/Symfony/Component/AutoMapper/composer.json +++ b/src/Symfony/Component/AutoMapper/composer.json @@ -16,7 +16,7 @@ } ], "require": { - "php": "^7.1.3", + "php": "^7.2.5", "nikic/php-parser": "^4.0", "symfony/property-info": "~5.1" }, From ad190a27fcfd490578101731723be879610a958e Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Sun, 2 Feb 2020 20:37:11 +0100 Subject: [PATCH 34/38] Fix createFromImmutable not available in php 7.2 --- .../Transformer/DateTimeImmutableToMutableTransformer.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/AutoMapper/Transformer/DateTimeImmutableToMutableTransformer.php b/src/Symfony/Component/AutoMapper/Transformer/DateTimeImmutableToMutableTransformer.php index 18eb7e6f88650..d0ecd25bb73d9 100644 --- a/src/Symfony/Component/AutoMapper/Transformer/DateTimeImmutableToMutableTransformer.php +++ b/src/Symfony/Component/AutoMapper/Transformer/DateTimeImmutableToMutableTransformer.php @@ -14,6 +14,7 @@ use PhpParser\Node\Arg; use PhpParser\Node\Expr; use PhpParser\Node\Name; +use PhpParser\Node\Scalar\String_; use Symfony\Component\AutoMapper\Extractor\PropertyMapping; use Symfony\Component\AutoMapper\Generator\UniqueVariableScope; @@ -32,8 +33,11 @@ final class DateTimeImmutableToMutableTransformer implements TransformerInterfac public function transform(Expr $input, PropertyMapping $propertyMapping, UniqueVariableScope $uniqueVariableScope): array { return [ - new Expr\StaticCall(new Name\FullyQualified(\DateTime::class), 'createFromImmutable', [ - new Arg($input) + new Expr\StaticCall(new Name\FullyQualified(\DateTime::class), 'createFromFormat', [ + new Arg(new String_(\DateTime::RFC3339)), + new Arg(new Expr\MethodCall($input, 'format', [ + new Arg(new String_(\DateTime::RFC3339)), + ])), ]), [] ]; From 9c3cb23f2a053146ba811bfd3ef5a63fd39b4b5f Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Sun, 2 Feb 2020 20:59:55 +0100 Subject: [PATCH 35/38] Fix test --- .../Component/AutoMapper/Tests/Fixtures/UserConstructorDTO.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/AutoMapper/Tests/Fixtures/UserConstructorDTO.php b/src/Symfony/Component/AutoMapper/Tests/Fixtures/UserConstructorDTO.php index 8a63a4714f7c8..6227159d5bd36 100644 --- a/src/Symfony/Component/AutoMapper/Tests/Fixtures/UserConstructorDTO.php +++ b/src/Symfony/Component/AutoMapper/Tests/Fixtures/UserConstructorDTO.php @@ -57,7 +57,7 @@ public function getName(): ?string } /** - * @return int + * @return int|null */ public function getAge() { From fecf949ffbcb3dea2d279eb8a37201fafc6d5d66 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Mon, 3 Feb 2020 23:18:56 +0100 Subject: [PATCH 36/38] Better conditions on automapper --- src/Symfony/Component/AutoMapper/AutoMapper.php | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/Symfony/Component/AutoMapper/AutoMapper.php b/src/Symfony/Component/AutoMapper/AutoMapper.php index 8aae90ab13e5a..606b5b2172cc8 100644 --- a/src/Symfony/Component/AutoMapper/AutoMapper.php +++ b/src/Symfony/Component/AutoMapper/AutoMapper.php @@ -86,7 +86,7 @@ public function getMapper(string $source, string $target): MapperInterface return $this->mapperRegistry[$className]; } - if (!class_exists($className)) { + if (!\class_exists($className)) { $this->classLoader->loadClass($metadata); } @@ -122,9 +122,7 @@ public function map($sourceData, $targetData, array $context = []) if (\is_object($sourceData)) { $source = \get_class($sourceData); - } - - if (\is_array($sourceData)) { + } elseif (\is_array($sourceData)) { $source = 'array'; } @@ -135,14 +133,10 @@ public function map($sourceData, $targetData, array $context = []) if (\is_object($targetData)) { $target = \get_class($targetData); $context[MapperContext::TARGET_TO_POPULATE] = $targetData; - } - - if (\is_array($targetData)) { + } elseif (\is_array($targetData)) { $target = 'array'; $context[MapperContext::TARGET_TO_POPULATE] = $targetData; - } - - if (\is_string($targetData)) { + } elseif (\is_string($targetData)) { $target = $targetData; } From ad0f4871d693d917699042de982c40eeda98359f Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Mon, 3 Feb 2020 23:23:35 +0100 Subject: [PATCH 37/38] Remove bad deps on rebase --- composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/composer.json b/composer.json index 95df7b2e9d76b..2a15956313430 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,6 @@ "ext-xml": "*", "doctrine/event-manager": "~1.0", "doctrine/persistence": "^1.3", - "fig/link-util": "^1.0", "nikic/php-parser": "^4.0", "twig/twig": "^2.10|^3.0", "psr/cache": "~1.0", From 5ba8171091f8cfc6958be75df9c625f7ecae528b Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Mon, 3 Feb 2020 23:26:51 +0100 Subject: [PATCH 38/38] Fix class exists --- src/Symfony/Component/AutoMapper/AutoMapper.php | 2 +- src/Symfony/Component/AutoMapper/CHANGELOG.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/AutoMapper/AutoMapper.php b/src/Symfony/Component/AutoMapper/AutoMapper.php index 606b5b2172cc8..d30fb4602f6ce 100644 --- a/src/Symfony/Component/AutoMapper/AutoMapper.php +++ b/src/Symfony/Component/AutoMapper/AutoMapper.php @@ -86,7 +86,7 @@ public function getMapper(string $source, string $target): MapperInterface return $this->mapperRegistry[$className]; } - if (!\class_exists($className)) { + if (!class_exists($className)) { $this->classLoader->loadClass($metadata); } diff --git a/src/Symfony/Component/AutoMapper/CHANGELOG.md b/src/Symfony/Component/AutoMapper/CHANGELOG.md index 69abaa606de85..9f62849f35bd8 100644 --- a/src/Symfony/Component/AutoMapper/CHANGELOG.md +++ b/src/Symfony/Component/AutoMapper/CHANGELOG.md @@ -1,7 +1,7 @@ CHANGELOG ========= -4.3.0 +5.1.0 ----- * Initial release