diff --git a/CHANGELOG.md b/CHANGELOG.md index 87555d5..24430da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * feat: Uses transformer class names as service IDs for easier decoration. * refactor: Move mapping logic in `ObjectToObjectTransformer` to its own service. +* refactor: Move more mapping logic to `ObjectMappingResolver`. ## 0.5.12 diff --git a/config/services.php b/config/services.php index ea9be9e..65a83a7 100644 --- a/config/services.php +++ b/config/services.php @@ -136,9 +136,6 @@ $services ->set(ObjectToObjectTransformer::class) ->args([ - '$propertyListExtractor' => service('rekalogika.mapper.property_info'), - '$propertyTypeExtractor' => service('rekalogika.mapper.property_info'), - '$propertyInitializableExtractor' => service('rekalogika.mapper.property_info'), '$propertyAccessor' => service('property_accessor'), '$typeResolver' => service('rekalogika.mapper.type_resolver'), '$objectMappingResolver' => service('rekalogika.mapper.object_mapping_resolver'), @@ -194,6 +191,8 @@ ->args([ service('rekalogika.mapper.property_info'), service('rekalogika.mapper.property_info'), + service('rekalogika.mapper.property_info'), + service('rekalogika.mapper.property_info'), ]); # transformer registry diff --git a/src/MapperFactory/MapperFactory.php b/src/MapperFactory/MapperFactory.php index fdeb321..0d9ac00 100644 --- a/src/MapperFactory/MapperFactory.php +++ b/src/MapperFactory/MapperFactory.php @@ -54,12 +54,10 @@ use Symfony\Component\PropertyAccess\PropertyAccessorInterface; use Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; -use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface; use Symfony\Component\PropertyInfo\PropertyInfoCacheExtractor; use Symfony\Component\PropertyInfo\PropertyInfoExtractor; use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface; -use Symfony\Component\PropertyInfo\PropertyListExtractorInterface; use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; @@ -183,21 +181,6 @@ private function getPropertyInfoExtractor(): PropertyInfoExtractorInterface&Prop return $this->propertyInfoExtractor; } - private function getPropertyInitializableExtractor(): PropertyInitializableExtractorInterface - { - return $this->getPropertyInfoExtractor(); - } - - private function getPropertyAccessExtractor(): PropertyAccessExtractorInterface - { - return $this->getPropertyInfoExtractor(); - } - - private function getPropertyListExtractor(): PropertyListExtractorInterface - { - return $this->getPropertyInfoExtractor(); - } - // // concrete services // @@ -275,9 +258,6 @@ protected function getObjectToObjectTransformer(): TransformerInterface { if (null === $this->objectToObjectTransformer) { $this->objectToObjectTransformer = new ObjectToObjectTransformer( - $this->getPropertyListExtractor(), - $this->getPropertyInfoExtractor(), - $this->getPropertyInitializableExtractor(), $this->getPropertyAccessor(), $this->getTypeResolver(), $this->getObjectMappingResolver(), @@ -411,8 +391,10 @@ protected function getObjectMappingResolver(): ObjectMappingResolverInterface { if (null === $this->objectMappingResolver) { $this->objectMappingResolver = new ObjectMappingResolver( - $this->getPropertyAccessExtractor(), - $this->getPropertyListExtractor(), + $this->getPropertyInfoExtractor(), + $this->getPropertyInfoExtractor(), + $this->getPropertyInfoExtractor(), + $this->getPropertyInfoExtractor(), ); } diff --git a/src/Transformer/ObjectMappingResolver/Contracts/ConstructorMapping.php b/src/Transformer/ObjectMappingResolver/Contracts/ConstructorMapping.php new file mode 100644 index 0000000..2d0b3c8 --- /dev/null +++ b/src/Transformer/ObjectMappingResolver/Contracts/ConstructorMapping.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +namespace Rekalogika\Mapper\Transformer\ObjectMappingResolver\Contracts; + +use Symfony\Component\PropertyInfo\Type; + +final class ConstructorMapping +{ + /** + * @var array $targetTypes + */ + private array $targetTypes; + + /** + * @param array $targetTypes + */ + public function __construct( + private ?string $sourceProperty, + private string $targetProperty, + array $targetTypes, + ) { + $this->targetTypes = array_values($targetTypes); + } + + public function getSourceProperty(): ?string + { + return $this->sourceProperty; + } + + public function getTargetProperty(): string + { + return $this->targetProperty; + } + + /** + * @return array + */ + public function getTargetTypes(): array + { + return $this->targetTypes; + } +} diff --git a/src/Transformer/ObjectMappingResolver/Contracts/ObjectMapping.php b/src/Transformer/ObjectMappingResolver/Contracts/ObjectMapping.php index 326ce1c..6dcb711 100644 --- a/src/Transformer/ObjectMappingResolver/Contracts/ObjectMapping.php +++ b/src/Transformer/ObjectMappingResolver/Contracts/ObjectMapping.php @@ -13,35 +13,45 @@ namespace Rekalogika\Mapper\Transformer\ObjectMappingResolver\Contracts; -use Symfony\Component\PropertyInfo\Type; - final class ObjectMapping { /** - * @param array $propertyMapping + * @param class-string $sourceClass + * @param class-string $targetClass + * @param array $propertyMapping + * @param array $constructorMapping */ public function __construct( - private Type $sourceType, - private Type $targetType, + private string $sourceClass, + private string $targetClass, private array $propertyMapping, + private array $constructorMapping, ) { } - public function getSourceType(): Type + /** + * @return array + */ + public function getPropertyMapping(): array + { + return $this->propertyMapping; + } + + /** + * @return array + */ + public function getConstructorMapping(): array { - return $this->sourceType; + return $this->constructorMapping; } - public function getTargetType(): Type + public function getSourceClass(): string { - return $this->targetType; + return $this->sourceClass; } - /** - * @return \Traversable - */ - public function getPropertyMapping(): \Traversable + public function getTargetClass(): string { - yield from $this->propertyMapping; + return $this->targetClass; } } diff --git a/src/Transformer/ObjectMappingResolver/Contracts/ObjectMappingEntry.php b/src/Transformer/ObjectMappingResolver/Contracts/ObjectMappingEntry.php deleted file mode 100644 index 907363d..0000000 --- a/src/Transformer/ObjectMappingResolver/Contracts/ObjectMappingEntry.php +++ /dev/null @@ -1,33 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE file - * that was distributed with this source code. - */ - -namespace Rekalogika\Mapper\Transformer\ObjectMappingResolver\Contracts; - -final class ObjectMappingEntry -{ - public function __construct( - private string $sourcePath, - private string $targetPath, - ) { - } - - public function getSourcePath(): string - { - return $this->sourcePath; - } - - public function getTargetPath(): string - { - return $this->targetPath; - } -} diff --git a/src/Transformer/ObjectMappingResolver/Contracts/ObjectMappingResolverInterface.php b/src/Transformer/ObjectMappingResolver/Contracts/ObjectMappingResolverInterface.php index bd89ab7..d25be96 100644 --- a/src/Transformer/ObjectMappingResolver/Contracts/ObjectMappingResolverInterface.php +++ b/src/Transformer/ObjectMappingResolver/Contracts/ObjectMappingResolverInterface.php @@ -14,13 +14,16 @@ namespace Rekalogika\Mapper\Transformer\ObjectMappingResolver\Contracts; use Rekalogika\Mapper\Context\Context; -use Symfony\Component\PropertyInfo\Type; interface ObjectMappingResolverInterface { + /** + * @param class-string $sourceClass + * @param class-string $targetClass + */ public function resolveObjectMapping( - Type $sourceType, - Type $targetType, + string $sourceClass, + string $targetClass, Context $context ): ObjectMapping; } diff --git a/src/Transformer/ObjectMappingResolver/Contracts/PropertyMapping.php b/src/Transformer/ObjectMappingResolver/Contracts/PropertyMapping.php new file mode 100644 index 0000000..c3ece60 --- /dev/null +++ b/src/Transformer/ObjectMappingResolver/Contracts/PropertyMapping.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +namespace Rekalogika\Mapper\Transformer\ObjectMappingResolver\Contracts; + +use Symfony\Component\PropertyInfo\Type; + +final class PropertyMapping +{ + /** + * @var array $targetTypes + */ + private array $targetTypes; + + /** + * @param array $targetTypes + */ + public function __construct( + private string $sourceProperty, + private string $targetProperty, + array $targetTypes, + ) { + $this->targetTypes = array_values($targetTypes); + } + + public function getSourceProperty(): string + { + return $this->sourceProperty; + } + + public function getTargetProperty(): string + { + return $this->targetProperty; + } + + /** + * @return array + */ + public function getTargetTypes(): array + { + return $this->targetTypes; + } +} diff --git a/src/Transformer/ObjectMappingResolver/ObjectMappingResolver.php b/src/Transformer/ObjectMappingResolver/ObjectMappingResolver.php index 71748c0..4a0a530 100644 --- a/src/Transformer/ObjectMappingResolver/ObjectMappingResolver.php +++ b/src/Transformer/ObjectMappingResolver/ObjectMappingResolver.php @@ -15,107 +15,176 @@ use Rekalogika\Mapper\Context\Context; use Rekalogika\Mapper\Exception\InvalidArgumentException; +use Rekalogika\Mapper\Transformer\ObjectMappingResolver\Contracts\ConstructorMapping; use Rekalogika\Mapper\Transformer\ObjectMappingResolver\Contracts\ObjectMapping; -use Rekalogika\Mapper\Transformer\ObjectMappingResolver\Contracts\ObjectMappingEntry; use Rekalogika\Mapper\Transformer\ObjectMappingResolver\Contracts\ObjectMappingResolverInterface; +use Rekalogika\Mapper\Transformer\ObjectMappingResolver\Contracts\PropertyMapping; use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface; use Symfony\Component\PropertyInfo\PropertyListExtractorInterface; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; final class ObjectMappingResolver implements ObjectMappingResolverInterface { public function __construct( private PropertyAccessExtractorInterface $propertyAccessExtractor, private PropertyListExtractorInterface $propertyListExtractor, + private PropertyInitializableExtractorInterface $propertyInitializableExtractor, + private PropertyTypeExtractorInterface $propertyTypeExtractor, ) { } public function resolveObjectMapping( - Type $sourceType, - Type $targetType, + string $sourceClass, + string $targetClass, Context $context ): ObjectMapping { - $sourceProperties = $this->listSourceAttributes($sourceType, $context); + + // queries + + $readableSourceProperties = $this + ->listReadableSourceProperties($sourceClass, $context); $writableTargetProperties = $this - ->listTargetWritableAttributes($targetType, $context); + ->listTargetWritableProperties($targetClass, $context); + $initializableTargetProperties = $this + ->listTargetInitializableProperties($targetClass, $context); - $propertiesToMap = array_intersect($sourceProperties, $writableTargetProperties); + // process properties mapping - $results = []; + $propertiesToMap = array_intersect($readableSourceProperties, $writableTargetProperties); + + $propertyResults = []; foreach ($propertiesToMap as $property) { - $results[] = new ObjectMappingEntry( - $property, - $property, + $sourceProperty = $property; + $targetProperty = $property; + + /// + + $targetPropertyTypes = $this->propertyTypeExtractor + ->getTypes($targetClass, $targetProperty); + + if (null === $targetPropertyTypes || count($targetPropertyTypes) === 0) { + throw new InvalidArgumentException( + sprintf( + 'Cannot get type of target property "%s::$%s".', + $targetClass, + $targetProperty + ), + context: $context + ); + } + + $propertyResults[] = new PropertyMapping( + $sourceProperty, + $targetProperty, + $targetPropertyTypes, + ); + } + + // process source properties to target constructor mapping + + $initializableResults = []; + + foreach ($initializableTargetProperties as $property) { + $sourceProperty = $property; + $targetProperty = $property; + + /// + + if (!in_array($property, $readableSourceProperties)) { + $sourceProperty = null; + } + + $targetPropertyTypes = $this->propertyTypeExtractor + ->getTypes($targetClass, $targetProperty); + + if (null === $targetPropertyTypes || count($targetPropertyTypes) === 0) { + throw new InvalidArgumentException( + sprintf( + 'Cannot get type of target property "%s::$%s".', + $targetClass, + $targetProperty + ), + context: $context + ); + } + + $initializableResults[] = new ConstructorMapping( + $sourceProperty, + $targetProperty, + $targetPropertyTypes, ); } return new ObjectMapping( - $sourceType, - $targetType, - $results, + $sourceClass, + $targetClass, + $propertyResults, + $initializableResults, ); } /** + * @param class-string $class * @return array - * @todo cache result */ - protected function listSourceAttributes( - Type $sourceType, + private function listReadableSourceProperties( + string $class, Context $context ): array { - $class = $sourceType->getClassName(); - - if (null === $class) { - throw new InvalidArgumentException('Cannot get class name from source type.', context: $context); - } - - $attributes = $this->propertyListExtractor->getProperties($class); + $properties = $this->propertyListExtractor->getProperties($class) ?? []; - if (null === $attributes) { - throw new InvalidArgumentException(sprintf('Cannot get properties from source class "%s".', $class), context: $context); - } - - $readableAttributes = []; + $readableProperties = []; - foreach ($attributes as $attribute) { - if ($this->propertyAccessExtractor->isReadable($class, $attribute)) { - $readableAttributes[] = $attribute; + foreach ($properties as $property) { + if ($this->propertyAccessExtractor->isReadable($class, $property)) { + $readableProperties[] = $property; } } - return $readableAttributes; + return $readableProperties; } /** + * @param class-string $class * @return array - * @todo cache result */ - protected function listTargetWritableAttributes( - Type $targetType, + private function listTargetWritableProperties( + string $class, Context $context ): array { - $class = $targetType->getClassName(); + $properties = $this->propertyListExtractor->getProperties($class) ?? []; - if (null === $class) { - throw new InvalidArgumentException('Cannot get class name from source type.', context: $context); + $writableProperties = []; + + foreach ($properties as $property) { + if ($this->propertyAccessExtractor->isWritable($class, $property)) { + $writableProperties[] = $property; + } } - $attributes = $this->propertyListExtractor->getProperties($class); + return $writableProperties; + } - if (null === $attributes) { - throw new InvalidArgumentException(sprintf('Cannot get properties from target class "%s".', $class), context: $context); - } + /** + * @param class-string $class + * @return array + */ + private function listTargetInitializableProperties( + string $class, + Context $context + ): array { + $properties = $this->propertyListExtractor->getProperties($class) ?? []; - $writableAttributes = []; + $initializableProperties = []; - foreach ($attributes as $attribute) { - if ($this->propertyAccessExtractor->isWritable($class, $attribute)) { - $writableAttributes[] = $attribute; + foreach ($properties as $property) { + if ($this->propertyInitializableExtractor->isInitializable($class, $property)) { + $initializableProperties[] = $property; } } - return $writableAttributes; + return $initializableProperties; } } diff --git a/src/Transformer/ObjectToObjectTransformer.php b/src/Transformer/ObjectToObjectTransformer.php index 2952fbf..14fa43c 100644 --- a/src/Transformer/ObjectToObjectTransformer.php +++ b/src/Transformer/ObjectToObjectTransformer.php @@ -27,6 +27,7 @@ use Rekalogika\Mapper\Transformer\Exception\NotAClassException; use Rekalogika\Mapper\Transformer\Exception\UnableToReadException; use Rekalogika\Mapper\Transformer\Exception\UnableToWriteException; +use Rekalogika\Mapper\Transformer\ObjectMappingResolver\Contracts\ObjectMapping; use Rekalogika\Mapper\Transformer\ObjectMappingResolver\Contracts\ObjectMappingResolverInterface; use Rekalogika\Mapper\TypeResolver\TypeResolverInterface; use Rekalogika\Mapper\Util\TypeCheck; @@ -36,9 +37,6 @@ use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException; use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; -use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface; -use Symfony\Component\PropertyInfo\PropertyListExtractorInterface; -use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; use Symfony\Component\PropertyInfo\Type; final class ObjectToObjectTransformer implements TransformerInterface, MainTransformerAwareInterface @@ -46,9 +44,6 @@ final class ObjectToObjectTransformer implements TransformerInterface, MainTrans use MainTransformerAwareTrait; public function __construct( - private PropertyListExtractorInterface $propertyListExtractor, - private PropertyTypeExtractorInterface $propertyTypeExtractor, - private PropertyInitializableExtractorInterface $propertyInitializableExtractor, private PropertyAccessorInterface $propertyAccessor, private TypeResolverInterface $typeResolver, private ObjectMappingResolverInterface $objectMappingResolver, @@ -66,13 +61,20 @@ public function transform( throw new InvalidArgumentException('Target type must not be null.', context: $context); } - // get source object & class + // verify source if (!is_object($source)) { throw new InvalidArgumentException(sprintf('The source must be an object, "%s" given.', get_debug_type($source)), context: $context); } $sourceType = $this->typeResolver->guessTypeFromVariable($source); + $sourceClass = $sourceType->getClassName(); + + if (null === $sourceClass || !\class_exists($sourceClass)) { + throw new InvalidArgumentException("Cannot get the class name for the source type.", context: $context); + } + + // verify target $targetClass = $targetType->getClassName(); @@ -90,10 +92,15 @@ public function transform( return $source; } + // resolve object mapping + + $objectMapping = $this->objectMappingResolver + ->resolveObjectMapping($sourceClass, $targetClass, $context); + // initialize target if (null === $target) { - $target = $this->instantiateTarget($source, $targetType, $context); + $target = $this->instantiateTarget($source, $targetType, $objectMapping, $context); } else { if (!is_object($target)) { throw new InvalidArgumentException(sprintf('The target must be an object, "%s" given.', get_debug_type($target)), context: $context); @@ -105,111 +112,76 @@ public function transform( $context(ObjectCache::class) ->saveTarget($source, $targetType, $target, $context); - // resolve object mapping - - $objectMapping = $this->objectMappingResolver - ->resolveObjectMapping($sourceType, $targetType, $context); - // map properties foreach ($objectMapping->getPropertyMapping() as $propertyMapping) { - assert(is_object($target)); - - $sourcePropertyName = $propertyMapping->getSourcePath(); - $targetPropertyName = $propertyMapping->getTargetPath(); + $sourcePropertyName = $propertyMapping->getSourceProperty(); + $targetPropertyName = $propertyMapping->getTargetProperty(); + $targetTypes = $propertyMapping->getTargetTypes(); - /** @var mixed */ - $targetPropertyValue = $this->resolveTargetPropertyValue( - source: $source, - target: $target, - sourcePropertyName: $sourcePropertyName, - targetPropertyName: $targetPropertyName, - targetClass: $targetClass, - context: $context, - path: $propertyMapping->getTargetPath(), - ); + // get the value of the source property try { - $this->propertyAccessor - ->setValue($target, $targetPropertyName, $targetPropertyValue); + /** @var mixed */ + $sourcePropertyValue = $this->propertyAccessor + ->getValue($source, $sourcePropertyName); + } catch (NoSuchPropertyException $e) { + $sourcePropertyValue = null; + } catch (UninitializedPropertyException $e) { + continue; } catch (AccessException | UnexpectedTypeException $e) { - throw new UnableToWriteException($source, $target, $target, $targetPropertyName, $e, context: $context); + $sourcePropertyValue = null; } - } - - return $target; - } - - /** - * @param class-string $targetClass - * @return mixed - */ - private function resolveTargetPropertyValue( - object $source, - ?object $target, - string $sourcePropertyName, - string $targetPropertyName, - string $targetClass, - Context $context, - string $path, - ): mixed { - /** @var array|null */ - $targetPropertyTypes = $this->propertyTypeExtractor->getTypes($targetClass, $targetPropertyName); - if (null === $targetPropertyTypes || count($targetPropertyTypes) === 0) { - throw new InvalidArgumentException(sprintf('Cannot get type of target property "%s::$%s".', $targetClass, $targetPropertyName), context: $context); - } + // get the value of the target property - try { - /** @var mixed */ - $sourcePropertyValue = $this->propertyAccessor - ->getValue($source, $sourcePropertyName); - } catch (NoSuchPropertyException $e) { - throw new IncompleteConstructorArgument($source, $targetClass, $sourcePropertyName, $e, context: $context); - } catch (UninitializedPropertyException $e) { - $sourcePropertyValue = null; - } catch (AccessException | UnexpectedTypeException $e) { - throw new UnableToReadException($source, $target, $source, $sourcePropertyName, $e, context: $context); - } - - if ($target !== null) { try { /** @var mixed */ $targetPropertyValue = $this->propertyAccessor ->getValue($target, $targetPropertyName); - } catch (NoSuchPropertyException $e) { - throw new IncompleteConstructorArgument($source, $targetClass, $targetPropertyName, $e, context: $context); } catch (UninitializedPropertyException $e) { $targetPropertyValue = null; - } catch (AccessException | UnexpectedTypeException $e) { - throw new UnableToReadException($source, $target, $target, $targetPropertyName, $e, context: $context); + } catch (NoSuchPropertyException | AccessException | UnexpectedTypeException $e) { + $targetPropertyValue = null; } - } else { - $targetPropertyValue = null; - } - /** @var mixed */ - $targetPropertyValue = $this->getMainTransformer()->transform( - source: $sourcePropertyValue, - target: $targetPropertyValue, - targetTypes: $targetPropertyTypes, - context: $context, - path: $path, - ); + // transform the value - return $targetPropertyValue; - } + /** @var mixed */ + $targetPropertyValue = $this->getMainTransformer()->transform( + source: $sourcePropertyValue, + target: $targetPropertyValue, + targetTypes: $targetTypes, + context: $context, + path: $targetPropertyName, + ); - public function getSupportedTransformation(): iterable - { - yield new TypeMapping(TypeFactory::object(), TypeFactory::object(), true); + try { + $this->propertyAccessor + ->setValue($target, $targetPropertyName, $targetPropertyValue); + } catch (AccessException | UnexpectedTypeException $e) { + throw new UnableToWriteException( + $source, + $target, + $target, + $targetPropertyName, + $e, + context: $context + ); + } + } + + return $target; } protected function instantiateTarget( object $source, Type $targetType, + ObjectMapping $objectMapping, Context $context ): object { + // check if class is valid & instantiable + $targetClass = $targetType->getClassName(); if (null === $targetClass || !\class_exists($targetClass)) { @@ -222,25 +194,51 @@ protected function instantiateTarget( throw new ClassNotInstantiableException($targetClass, context: $context); } - $initializableTargetProperties = $this - ->listTargetInitializableAttributes($targetClass, $context); + // gets the mapping and loop over the mapping + + $initializableMappings = $objectMapping->getConstructorMapping(); $constructorArguments = []; - foreach ($initializableTargetProperties as $propertyName) { + foreach ($initializableMappings as $mapping) { + $sourcePropertyName = $mapping->getSourceProperty(); + $targetPropertyName = $mapping->getTargetProperty(); + $targetTypes = $mapping->getTargetTypes(); + + if ($sourcePropertyName === null) { + throw new IncompleteConstructorArgument($source, $targetClass, $targetPropertyName, context: $context); + } + + // get the value of the source property + + try { + /** @var mixed */ + $sourcePropertyValue = $this->propertyAccessor + ->getValue($source, $sourcePropertyName); + } catch (NoSuchPropertyException $e) { + // if source property is not found, then it is an error + throw new IncompleteConstructorArgument($source, $targetClass, $sourcePropertyName, $e, context: $context); + } catch (UninitializedPropertyException $e) { + // if source property is unset, we skip it + continue; + } catch (AccessException | UnexpectedTypeException $e) { + // otherwise, it is an error + throw new UnableToReadException($source, null, $source, $sourcePropertyName, $e, context: $context); + } + + // transform the value + /** @var mixed */ - $targetPropertyValue = $this->resolveTargetPropertyValue( - source: $source, + $targetPropertyValue = $this->getMainTransformer()->transform( + source: $sourcePropertyValue, target: null, - sourcePropertyName: $propertyName, - targetPropertyName: $propertyName, - targetClass: $targetClass, + targetTypes: $targetTypes, context: $context, - path: $propertyName, + path: $targetPropertyName, ); /** @psalm-suppress MixedAssignment */ - $constructorArguments[$propertyName] = $targetPropertyValue; + $constructorArguments[$targetPropertyName] = $targetPropertyValue; } try { @@ -250,29 +248,8 @@ protected function instantiateTarget( } } - /** - * @param class-string $class - * @return array - * @todo cache result - */ - protected function listTargetInitializableAttributes( - string $class, - Context $context - ): array { - $attributes = $this->propertyListExtractor->getProperties($class); - - if (null === $attributes) { - throw new InvalidArgumentException(sprintf('Cannot get properties from target class "%s".', $class), context: $context); - } - - $initializableAttributes = []; - - foreach ($attributes as $attribute) { - if ($this->propertyInitializableExtractor->isInitializable($class, $attribute)) { - $initializableAttributes[] = $attribute; - } - } - - return $initializableAttributes; + public function getSupportedTransformation(): iterable + { + yield new TypeMapping(TypeFactory::object(), TypeFactory::object(), true); } }