From d6feb052035233cdbb2d2e87729d9dfdc20b9ad7 Mon Sep 17 00:00:00 2001 From: Priyadi Iman Nurcahyo <1102197+priyadi@users.noreply.github.com> Date: Sat, 13 Jan 2024 11:15:07 +0700 Subject: [PATCH] refactor: Mover more logic to `TransformerRegistry`. --- src/Contracts/MainTransformerInterface.php | 4 +- src/Contracts/TransformerInterface.php | 2 +- .../MissingMemberKeyTypeException.php | 7 ++- .../MissingMemberValueTypeException.php | 7 ++- ...ableToFindSuitableTransformerException.php | 15 +++--- src/MainTransformer.php | 51 +++++++++++-------- src/Mapper.php | 2 +- src/MethodMapper/ClassMethodTransformer.php | 2 +- src/Transformer/ArrayToObjectTransformer.php | 2 +- src/Transformer/CopyTransformer.php | 2 +- src/Transformer/DateTimeTransformer.php | 2 +- src/Transformer/NullTransformer.php | 2 +- src/Transformer/ObjectToArrayTransformer.php | 2 +- src/Transformer/ObjectToObjectTransformer.php | 4 +- src/Transformer/ObjectToStringTransformer.php | 2 +- src/Transformer/ScalarToScalarTransformer.php | 2 +- .../StringToBackedEnumTransformer.php | 2 +- .../TraversableToArrayAccessTransformer.php | 6 +-- .../TraversableToTraversableTransformer.php | 6 +-- src/TransformerRegistry/SearchResult.php | 33 ++++++++++++ src/TransformerRegistry/SearchResultEntry.php | 43 ++++++++++++++++ .../TransformerRegistry.php | 40 ++++++++++----- .../TransformerRegistryInterface.php | 20 ++++---- src/Util/TypeUtil.php | 19 ++++++- .../MoneyToMoneyDtoTransformer.php | 2 +- 25 files changed, 199 insertions(+), 80 deletions(-) create mode 100644 src/TransformerRegistry/SearchResult.php create mode 100644 src/TransformerRegistry/SearchResultEntry.php diff --git a/src/Contracts/MainTransformerInterface.php b/src/Contracts/MainTransformerInterface.php index ca37a5b..cb1bdd0 100644 --- a/src/Contracts/MainTransformerInterface.php +++ b/src/Contracts/MainTransformerInterface.php @@ -18,13 +18,13 @@ interface MainTransformerInterface { /** - * @param array $targetType If provided, it will be used instead of guessing the type + * @param array $targetTypes If provided, it will be used instead of guessing the type * @param array $context */ public function transform( mixed $source, mixed $target, - array $targetType, + array $targetTypes, array $context ): mixed; } diff --git a/src/Contracts/TransformerInterface.php b/src/Contracts/TransformerInterface.php index ba73109..2a7099b 100644 --- a/src/Contracts/TransformerInterface.php +++ b/src/Contracts/TransformerInterface.php @@ -34,7 +34,7 @@ interface TransformerInterface public function transform( mixed $source, mixed $target, - Type $sourceType, + ?Type $sourceType, ?Type $targetType, array $context ): mixed; diff --git a/src/Exception/MissingMemberKeyTypeException.php b/src/Exception/MissingMemberKeyTypeException.php index 8a51c92..a408786 100644 --- a/src/Exception/MissingMemberKeyTypeException.php +++ b/src/Exception/MissingMemberKeyTypeException.php @@ -13,13 +13,18 @@ namespace Rekalogika\Mapper\Exception; +use Rekalogika\Mapper\Contracts\MixedType; use Rekalogika\Mapper\Util\TypeUtil; use Symfony\Component\PropertyInfo\Type; class MissingMemberKeyTypeException extends MissingMemberTypeException { - public function __construct(Type $sourceType, Type $targetType) + public function __construct(?Type $sourceType, Type $targetType) { + if (null === $sourceType) { + $sourceType = MixedType::instance(); + } + parent::__construct(sprintf('Trying to map collection type "%s" to "%s", but the source member key is not the simple array-key type, and the target does not have the type information about the key of its child members. Usually you can fix this by adding a PHPdoc to the property containing the collection type.', TypeUtil::getDebugType($sourceType), TypeUtil::getDebugType($targetType))); } } diff --git a/src/Exception/MissingMemberValueTypeException.php b/src/Exception/MissingMemberValueTypeException.php index e43270a..4b48549 100644 --- a/src/Exception/MissingMemberValueTypeException.php +++ b/src/Exception/MissingMemberValueTypeException.php @@ -13,13 +13,18 @@ namespace Rekalogika\Mapper\Exception; +use Rekalogika\Mapper\Contracts\MixedType; use Rekalogika\Mapper\Util\TypeUtil; use Symfony\Component\PropertyInfo\Type; class MissingMemberValueTypeException extends MissingMemberTypeException { - public function __construct(Type $sourceType, Type $targetType) + public function __construct(?Type $sourceType, Type $targetType) { + if (null === $sourceType) { + $sourceType = MixedType::instance(); + } + parent::__construct(sprintf('Trying to map collection type "%s" to "%s", but the target does not have the type information about the value of its child members. Usually you can fix this by adding a PHPdoc to the property containing the collection type.', TypeUtil::getDebugType($sourceType), TypeUtil::getDebugType($targetType))); } } diff --git a/src/Exception/UnableToFindSuitableTransformerException.php b/src/Exception/UnableToFindSuitableTransformerException.php index 75bef34..e62b452 100644 --- a/src/Exception/UnableToFindSuitableTransformerException.php +++ b/src/Exception/UnableToFindSuitableTransformerException.php @@ -20,17 +20,14 @@ class UnableToFindSuitableTransformerException extends NotMappableValueException { /** - * @param Type $sourceType - * @param Type|array $targetType + * @param array $sourceTypes + * @param array $targetTypes */ - public function __construct(Type $sourceType, Type|MixedType|array $targetType) + public function __construct(array $sourceTypes, array $targetTypes) { - if (is_array($targetType)) { - $targetType = implode(', ', array_map(fn (Type|MixedType $type) => TypeUtil::getDebugType($type), $targetType)); - } else { - $targetType = TypeUtil::getDebugType($targetType); - } + $sourceTypes = TypeUtil::getDebugType($sourceTypes); + $targetTypes = TypeUtil::getDebugType($targetTypes); - parent::__construct(sprintf('Unable to map the value "%s" to "%s"', TypeUtil::getDebugType($sourceType), $targetType)); + parent::__construct(sprintf('Unable to find a suitable transformer for mapping the source types "%s" to the target types "%s"', $sourceTypes, $sourceTypes)); } } diff --git a/src/MainTransformer.php b/src/MainTransformer.php index b0ec284..d316216 100644 --- a/src/MainTransformer.php +++ b/src/MainTransformer.php @@ -74,17 +74,17 @@ public static function getObjectCache( public function transform( mixed $source, mixed $target, - array $targetType, + array $targetTypes, array $context ): mixed { // if targettype is not provided, guess it from target // if the target is also missing then the target is mixed - if (count($targetType) === 0) { + if (count($targetTypes) === 0) { if ($target === null) { - $targetType = [MixedType::instance()]; + $targetTypes = [MixedType::instance()]; } else { - $targetType = [$this->typeResolver->guessTypeFromVariable($target)]; + $targetTypes = [$this->typeResolver->guessTypeFromVariable($target)]; } } @@ -99,8 +99,8 @@ public function transform( $simpleTargetTypes = []; - foreach ($targetType as $singleTargetType) { - foreach ($this->typeResolver->getSimpleTypes($singleTargetType) as $simpleType) { + foreach ($targetTypes as $targetType) { + foreach ($this->typeResolver->getSimpleTypes($targetType) as $simpleType) { $simpleTargetTypes[] = $simpleType; } } @@ -111,26 +111,33 @@ public function transform( // iterate simple target types and find the suitable transformer - foreach ($simpleTargetTypes as $singleTargetType) { - $transformers = $this->transformerRegistry - ->findBySourceAndTargetType($sourceType, $singleTargetType); + $searchResult = $this->transformerRegistry + ->findBySourceAndTargetTypes( + [$sourceType], + $simpleTargetTypes + ); - foreach ($transformers as $transformer) { - $transformer = $this->processTransformer($transformer); + foreach ($searchResult as $searchEntry) { + $transformer = $this->processTransformer($searchEntry->getTransformer()); - /** @var mixed */ - $result = $transformer->transform( - source: $source, - target: $target, - sourceType: $sourceType, - targetType: $singleTargetType instanceof MixedType ? null : $singleTargetType, - context: $context - ); + $sourceType = $searchEntry->getSourceType(); + $sourceTypeForTransformer = $sourceType instanceof MixedType ? null : $sourceType; - return $result; - } + $targetTypes = $searchEntry->getTargetType(); + $targetTypeForTransformer = $targetTypes instanceof MixedType ? null : $targetTypes; + + /** @var mixed */ + $result = $transformer->transform( + source: $source, + target: $target, + sourceType: $sourceTypeForTransformer, + targetType: $targetTypeForTransformer, + context: $context + ); + + return $result; } - throw new UnableToFindSuitableTransformerException($sourceType, $targetType); + throw new UnableToFindSuitableTransformerException([$sourceType], $simpleTargetTypes); } } diff --git a/src/Mapper.php b/src/Mapper.php index ae5437b..821246c 100644 --- a/src/Mapper.php +++ b/src/Mapper.php @@ -52,7 +52,7 @@ public function map(mixed $source, object|string $target, array $context = []): $target = $this->transformer->transform( source: $source, target: $target, - targetType: [$targetType], + targetTypes: [$targetType], context: $context ); diff --git a/src/MethodMapper/ClassMethodTransformer.php b/src/MethodMapper/ClassMethodTransformer.php index 2fc8fb9..9f362cd 100644 --- a/src/MethodMapper/ClassMethodTransformer.php +++ b/src/MethodMapper/ClassMethodTransformer.php @@ -38,7 +38,7 @@ public function __construct( public function transform( mixed $source, mixed $target, - Type $sourceType, + ?Type $sourceType, ?Type $targetType, array $context ): mixed { diff --git a/src/Transformer/ArrayToObjectTransformer.php b/src/Transformer/ArrayToObjectTransformer.php index 1de4327..a3c6f78 100644 --- a/src/Transformer/ArrayToObjectTransformer.php +++ b/src/Transformer/ArrayToObjectTransformer.php @@ -38,7 +38,7 @@ public function __construct( public function transform( mixed $source, mixed $target, - Type $sourceType, + ?Type $sourceType, ?Type $targetType, array $context ): mixed { diff --git a/src/Transformer/CopyTransformer.php b/src/Transformer/CopyTransformer.php index 09950d4..7a157b8 100644 --- a/src/Transformer/CopyTransformer.php +++ b/src/Transformer/CopyTransformer.php @@ -23,7 +23,7 @@ final class CopyTransformer implements TransformerInterface public function transform( mixed $source, mixed $target, - Type $sourceType, + ?Type $sourceType, ?Type $targetType, array $context ): mixed { diff --git a/src/Transformer/DateTimeTransformer.php b/src/Transformer/DateTimeTransformer.php index 14091a1..09008dd 100644 --- a/src/Transformer/DateTimeTransformer.php +++ b/src/Transformer/DateTimeTransformer.php @@ -29,7 +29,7 @@ final class DateTimeTransformer implements TransformerInterface public function transform( mixed $source, mixed $target, - Type $sourceType, + ?Type $sourceType, ?Type $targetType, array $context ): mixed { diff --git a/src/Transformer/NullTransformer.php b/src/Transformer/NullTransformer.php index fb94f9b..daa0099 100644 --- a/src/Transformer/NullTransformer.php +++ b/src/Transformer/NullTransformer.php @@ -25,7 +25,7 @@ final class NullTransformer implements TransformerInterface public function transform( mixed $source, mixed $target, - Type $sourceType, + ?Type $sourceType, ?Type $targetType, array $context ): mixed { diff --git a/src/Transformer/ObjectToArrayTransformer.php b/src/Transformer/ObjectToArrayTransformer.php index 6d552fd..5395d89 100644 --- a/src/Transformer/ObjectToArrayTransformer.php +++ b/src/Transformer/ObjectToArrayTransformer.php @@ -35,7 +35,7 @@ public function __construct( public function transform( mixed $source, mixed $target, - Type $sourceType, + ?Type $sourceType, ?Type $targetType, array $context ): mixed { diff --git a/src/Transformer/ObjectToObjectTransformer.php b/src/Transformer/ObjectToObjectTransformer.php index ece5743..889836a 100644 --- a/src/Transformer/ObjectToObjectTransformer.php +++ b/src/Transformer/ObjectToObjectTransformer.php @@ -54,7 +54,7 @@ public function __construct( public function transform( mixed $source, mixed $target, - Type $sourceType, + ?Type $sourceType, ?Type $targetType, array $context ): mixed { @@ -169,7 +169,7 @@ private function resolveTargetPropertyValue( $targetPropertyValue = $this->mainTransformer?->transform( source: $sourcePropertyValue, target: $targetPropertyValue, - targetType: $targetPropertyTypes, + targetTypes: $targetPropertyTypes, context: $context ); diff --git a/src/Transformer/ObjectToStringTransformer.php b/src/Transformer/ObjectToStringTransformer.php index 72161d8..9801c32 100644 --- a/src/Transformer/ObjectToStringTransformer.php +++ b/src/Transformer/ObjectToStringTransformer.php @@ -24,7 +24,7 @@ final class ObjectToStringTransformer implements TransformerInterface public function transform( mixed $source, mixed $target, - Type $sourceType, + ?Type $sourceType, ?Type $targetType, array $context ): mixed { diff --git a/src/Transformer/ScalarToScalarTransformer.php b/src/Transformer/ScalarToScalarTransformer.php index 24ebae7..54b79b4 100644 --- a/src/Transformer/ScalarToScalarTransformer.php +++ b/src/Transformer/ScalarToScalarTransformer.php @@ -25,7 +25,7 @@ final class ScalarToScalarTransformer implements TransformerInterface public function transform( mixed $source, mixed $target, - Type $sourceType, + ?Type $sourceType, ?Type $targetType, array $context ): mixed { diff --git a/src/Transformer/StringToBackedEnumTransformer.php b/src/Transformer/StringToBackedEnumTransformer.php index d48dd67..b97eae0 100644 --- a/src/Transformer/StringToBackedEnumTransformer.php +++ b/src/Transformer/StringToBackedEnumTransformer.php @@ -24,7 +24,7 @@ final class StringToBackedEnumTransformer implements TransformerInterface public function transform( mixed $source, mixed $target, - Type $sourceType, + ?Type $sourceType, ?Type $targetType, array $context ): mixed { diff --git a/src/Transformer/TraversableToArrayAccessTransformer.php b/src/Transformer/TraversableToArrayAccessTransformer.php index ef489d4..f60e69f 100644 --- a/src/Transformer/TraversableToArrayAccessTransformer.php +++ b/src/Transformer/TraversableToArrayAccessTransformer.php @@ -41,7 +41,7 @@ public function __construct( public function transform( mixed $source, mixed $target, - Type $sourceType, + ?Type $sourceType, ?Type $targetType, array $context ): mixed { @@ -121,7 +121,7 @@ public function transform( $targetMemberKey = $this->getMainTransformer()->transform( source: $sourceMemberKey, target: null, - targetType: $targetMemberKeyType, + targetTypes: $targetMemberKeyType, context: $context, ); } @@ -145,7 +145,7 @@ public function transform( $targetMemberValue = $this->getMainTransformer()->transform( source: $sourceMemberValue, target: $targetMemberValue, - targetType: $targetMemberValueType, + targetTypes: $targetMemberValueType, context: $context, ); diff --git a/src/Transformer/TraversableToTraversableTransformer.php b/src/Transformer/TraversableToTraversableTransformer.php index 9fe98fd..1d60a04 100644 --- a/src/Transformer/TraversableToTraversableTransformer.php +++ b/src/Transformer/TraversableToTraversableTransformer.php @@ -40,7 +40,7 @@ public function __construct( public function transform( mixed $source, mixed $target, - Type $sourceType, + ?Type $sourceType, ?Type $targetType, array $context ): mixed { @@ -132,7 +132,7 @@ public function transform( $targetPropertyKey = $this->getMainTransformer()->transform( source: $sourcePropertyKey, target: null, - targetType: $targetMemberKeyType, + targetTypes: $targetMemberKeyType, context: $context, ); } @@ -144,7 +144,7 @@ public function transform( $targetPropertyValue = $this->getMainTransformer()->transform( source: $sourcePropertyValue, target: null, - targetType: $targetMemberValueType, + targetTypes: $targetMemberValueType, context: $context, ); diff --git a/src/TransformerRegistry/SearchResult.php b/src/TransformerRegistry/SearchResult.php new file mode 100644 index 0000000..8363407 --- /dev/null +++ b/src/TransformerRegistry/SearchResult.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 Rekalogika\Mapper\TransformerRegistry; + +/** + * @implements \IteratorAggregate + */ +class SearchResult implements \IteratorAggregate +{ + /** + * @param \Traversable $entries + */ + public function __construct( + private \Traversable $entries + ) { + } + + public function getIterator(): \Traversable + { + return $this->entries; + } +} diff --git a/src/TransformerRegistry/SearchResultEntry.php b/src/TransformerRegistry/SearchResultEntry.php new file mode 100644 index 0000000..e68c2f5 --- /dev/null +++ b/src/TransformerRegistry/SearchResultEntry.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +namespace Rekalogika\Mapper\TransformerRegistry; + +use Rekalogika\Mapper\Contracts\MixedType; +use Rekalogika\Mapper\Contracts\TransformerInterface; +use Symfony\Component\PropertyInfo\Type; + +class SearchResultEntry +{ + public function __construct( + private Type|MixedType $sourceType, + private Type|MixedType $targetType, + private TransformerInterface $transformer, + ) { + } + + public function getSourceType(): Type|MixedType + { + return $this->sourceType; + } + + public function getTargetType(): Type|MixedType + { + return $this->targetType; + } + + public function getTransformer(): TransformerInterface + { + return $this->transformer; + } +} diff --git a/src/TransformerRegistry/TransformerRegistry.php b/src/TransformerRegistry/TransformerRegistry.php index a038694..57698e4 100644 --- a/src/TransformerRegistry/TransformerRegistry.php +++ b/src/TransformerRegistry/TransformerRegistry.php @@ -45,19 +45,31 @@ public function get(string $id): TransformerInterface return $transformer; } - // public function findBySourceAndTargetTypes( - // iterable $sourceTypes, - // iterable $targetTypes, - // ): iterable { - // foreach ($sourceTypes as $sourceType) { - // foreach ($targetTypes as $targetType) { - // yield from $this->findBySourceAndTargetType( - // $sourceType, - // $targetType - // ); - // } - // } - // } + public function findBySourceAndTargetTypes( + iterable $sourceTypes, + iterable $targetTypes, + ): SearchResult { + $result = (function () use ($sourceTypes, $targetTypes) { + foreach ($sourceTypes as $sourceType) { + foreach ($targetTypes as $targetType) { + $transformers = $this->findBySourceAndTargetType( + $sourceType, + $targetType + ); + + foreach ($transformers as $transformer) { + yield new SearchResultEntry( + $sourceType, + $targetType, + $transformer + ); + } + } + } + })(); + + return new SearchResult($result); + } public function findBySourceAndTargetType( Type|MixedType $sourceType, @@ -67,7 +79,7 @@ public function findBySourceAndTargetType( foreach ($mapping as $item) { $id = $item->getId(); - yield $this->get($id); + yield $id => $this->get($id); } } diff --git a/src/TransformerRegistry/TransformerRegistryInterface.php b/src/TransformerRegistry/TransformerRegistryInterface.php index dc76798..81e3746 100644 --- a/src/TransformerRegistry/TransformerRegistryInterface.php +++ b/src/TransformerRegistry/TransformerRegistryInterface.php @@ -22,20 +22,20 @@ interface TransformerRegistryInterface { public function get(string $id): TransformerInterface; - // /** - // * @param iterable $sourceTypes - // * @param iterable $targetTypes - // * @return iterable - // */ - // public function findBySourceAndTargetTypes( - // iterable $sourceTypes, - // iterable $targetTypes, - // ): iterable; + /** + * @param iterable $sourceTypes + * @param iterable $targetTypes + * @return SearchResult + */ + public function findBySourceAndTargetTypes( + iterable $sourceTypes, + iterable $targetTypes, + ): SearchResult; /** * @param Type|MixedType $sourceType * @param Type|MixedType $targetType - * @return iterable + * @return iterable */ public function findBySourceAndTargetType( Type|MixedType $sourceType, diff --git a/src/Util/TypeUtil.php b/src/Util/TypeUtil.php index 40322f6..a40a53d 100644 --- a/src/Util/TypeUtil.php +++ b/src/Util/TypeUtil.php @@ -227,13 +227,30 @@ class: $type->getClassName(), return $permutations; } + /** + * @param null|Type|MixedType|array $type + * @return string + */ #[NamespaceVisibility(namespace: 'Rekalogika\Mapper\Exception')] - public static function getDebugType(null|Type|MixedType $type): string + public static function getDebugType(null|Type|MixedType|array $type): string { if ($type === null) { return 'null'; } + if (is_array($type)) { + if (count($type) === 0) { + return 'mixed'; + } + + $typeStrings = []; + foreach ($type as $t) { + $typeStrings[] = TypeUtil::getTypeString($t); + } + + return implode('|', $typeStrings); + } + return TypeUtil::getTypeString($type); } diff --git a/tests/Fixtures/Transformer/MoneyToMoneyDtoTransformer.php b/tests/Fixtures/Transformer/MoneyToMoneyDtoTransformer.php index 25199c4..c159a73 100644 --- a/tests/Fixtures/Transformer/MoneyToMoneyDtoTransformer.php +++ b/tests/Fixtures/Transformer/MoneyToMoneyDtoTransformer.php @@ -60,7 +60,7 @@ public function getSupportedTransformation(): iterable public function transform( mixed $source, mixed $target, - Type $sourceType, + ?Type $sourceType, ?Type $targetType, array $context ): mixed {