From d7c32df59e938b67eaebbaed01efbe50b676552a Mon Sep 17 00:00:00 2001 From: Priyadi Iman Nurcahyo <1102197+priyadi@users.noreply.github.com> Date: Thu, 11 Jan 2024 13:07:31 +0700 Subject: [PATCH] TypeUtil refactor; add ObjectCacheFactory --- CHANGELOG.md | 2 ++ composer.json | 4 ++- config/services.php | 18 ++++++++++- phpstan.neon.dist | 1 + src/Exception/CircularReferenceException.php | 4 +-- .../InvalidTypeInArgumentException.php | 25 +++++++++++++++ .../MapperReturnsUnexpectedValueException.php | 31 +++++++++++++++++++ .../MissingMemberKeyTypeException.php | 4 +-- .../MissingMemberValueTypeException.php | 4 +-- ...ableToFindSuitableTransformerException.php | 8 ++--- src/MainTransformer.php | 8 +++-- src/Mapper.php | 8 ++--- src/MapperFactory.php | 27 ++++++++++++++-- src/Mapping/MappingFactory.php | 20 ++++++------ src/{Model => ObjectCache}/ObjectCache.php | 20 ++++++------ src/ObjectCache/ObjectCacheFactory.php | 29 +++++++++++++++++ .../ObjectCacheFactoryInterface.php | 19 ++++++++++++ src/Transformer/ArrayToObjectTransformer.php | 3 +- src/Transformer/ObjectToObjectTransformer.php | 6 ++-- .../TraversableToArrayAccessTransformer.php | 13 ++++++-- .../TraversableToTraversableTransformer.php | 10 ++++-- src/TypeResolver/TypeResolver.php | 5 +++ src/TypeResolver/TypeResolverInterface.php | 2 ++ src/Util/TypeCheck.php | 5 --- src/Util/TypeUtil.php | 18 +++++++++++ tests/UnitTest/Model/ObjectCacheTest.php | 5 ++- 26 files changed, 245 insertions(+), 54 deletions(-) create mode 100644 src/Exception/InvalidTypeInArgumentException.php create mode 100644 src/Exception/MapperReturnsUnexpectedValueException.php rename src/{Model => ObjectCache}/ObjectCache.php (88%) create mode 100644 src/ObjectCache/ObjectCacheFactory.php create mode 100644 src/ObjectCache/ObjectCacheFactoryInterface.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 6780a1b..543dfc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## 0.5.3 * Use `MappingFactoryInterface` everywhere instead of `MappingFactory` +* Move some `TypeUtil` methods to `TypeResolver` for optimization opportunities +* use `ObjectCacheFactory` to generate `ObjectCache` instances ## 0.5.2 diff --git a/composer.json b/composer.json index def6bba..30058a9 100644 --- a/composer.json +++ b/composer.json @@ -43,7 +43,9 @@ "symfony/http-kernel": "^6.4 || ^7.0", "symfony/phpunit-bridge": "^6.4 || ^7.0", "symfony/var-dumper": "^6.4 || ^7.0", - "vimeo/psalm": "^5.18" + "vimeo/psalm": "^5.18", + "dave-liddament/php-language-extensions": "^0.6.0", + "dave-liddament/phpstan-php-language-extensions": "^0.5.0" }, "autoload": { "psr-4": { diff --git a/config/services.php b/config/services.php index e2650a8..6cf9278 100644 --- a/config/services.php +++ b/config/services.php @@ -18,6 +18,7 @@ use Rekalogika\Mapper\MapperInterface; use Rekalogika\Mapper\Mapping\MappingFactory; use Rekalogika\Mapper\Mapping\MappingFactoryInterface; +use Rekalogika\Mapper\ObjectCache\ObjectCacheFactory; use Rekalogika\Mapper\Transformer\ArrayToObjectTransformer; use Rekalogika\Mapper\Transformer\DateTimeTransformer; use Rekalogika\Mapper\Transformer\NullTransformer; @@ -96,10 +97,16 @@ $services ->set('rekalogika.mapper.transformer.traversable_to_arrayaccess', TraversableToArrayAccessTransformer::class) + ->args([ + '$objectCacheFactory' => service('rekalogika.mapper.object_cache_factory'), + ]) ->tag('rekalogika.mapper.transformer', ['priority' => -750]); $services ->set('rekalogika.mapper.transformer.traversable_to_traversable', TraversableToTraversableTransformer::class) + ->args([ + '$objectCacheFactory' => service('rekalogika.mapper.object_cache_factory'), + ]) ->tag('rekalogika.mapper.transformer', ['priority' => -800]); $services @@ -121,6 +128,7 @@ '$propertyAccessExtractor' => service('rekalogika.mapper.property_info'), '$propertyAccessor' => service('property_accessor'), '$typeResolver' => service('rekalogika.mapper.type_resolver'), + '$objectCacheFactory' => service('rekalogika.mapper.object_cache_factory'), ]) ->tag('rekalogika.mapper.transformer', ['priority' => -950]); @@ -132,13 +140,20 @@ $services ->set('rekalogika.mapper.mapping_factory', MappingFactory::class) - ->args([tagged_iterator('rekalogika.mapper.transformer', 'key')]); + ->args([ + tagged_iterator('rekalogika.mapper.transformer', 'key'), + service('rekalogika.mapper.type_resolver') + ]); $services ->alias(MappingFactoryInterface::class, 'rekalogika.mapper.mapping_factory'); # other services + $services + ->set('rekalogika.mapper.object_cache_factory', ObjectCacheFactory::class) + ->args([service('rekalogika.mapper.type_resolver')]); + $services ->set('rekalogika.mapper.type_resolver', TypeResolver::class); @@ -148,6 +163,7 @@ '$transformersLocator' => tagged_locator('rekalogika.mapper.transformer'), '$typeResolver' => service('rekalogika.mapper.type_resolver'), '$mappingFactory' => service('rekalogika.mapper.mapping_factory'), + '$objectCacheFactory' => service('rekalogika.mapper.object_cache_factory'), ]); $services diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 127470b..54446af 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -14,3 +14,4 @@ includes: - vendor/phpstan/phpstan-deprecation-rules/rules.neon - vendor/bnf/phpstan-psr-container/extension.neon - vendor/ekino/phpstan-banned-code/extension.neon + - vendor/dave-liddament/phpstan-php-language-extensions/extension.neon diff --git a/src/Exception/CircularReferenceException.php b/src/Exception/CircularReferenceException.php index f82000d..a709ccb 100644 --- a/src/Exception/CircularReferenceException.php +++ b/src/Exception/CircularReferenceException.php @@ -13,7 +13,7 @@ namespace Rekalogika\Mapper\Exception; -use Rekalogika\Mapper\Util\TypeCheck; +use Rekalogika\Mapper\Util\TypeUtil; use Symfony\Component\PropertyInfo\Type; class CircularReferenceException extends \RuntimeException implements ExceptionInterface @@ -24,7 +24,7 @@ public function __construct(mixed $source, Type $targetType) sprintf( 'Circular reference detected when trying to get the object of type "%s" transformed to "%s"', \get_debug_type($source), - TypeCheck::getDebugType($targetType) + TypeUtil::getDebugType($targetType) ) ); } diff --git a/src/Exception/InvalidTypeInArgumentException.php b/src/Exception/InvalidTypeInArgumentException.php new file mode 100644 index 0000000..2525c09 --- /dev/null +++ b/src/Exception/InvalidTypeInArgumentException.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 Rekalogika\Mapper\Exception; + +use Rekalogika\Mapper\Util\TypeUtil; +use Symfony\Component\PropertyInfo\Type; + +class InvalidTypeInArgumentException extends InvalidArgumentException +{ + public function __construct(string $printfMessage, Type $expectedType) + { + parent::__construct(sprintf($printfMessage, TypeUtil::getDebugType($expectedType))); + } +} diff --git a/src/Exception/MapperReturnsUnexpectedValueException.php b/src/Exception/MapperReturnsUnexpectedValueException.php new file mode 100644 index 0000000..7ca567a --- /dev/null +++ b/src/Exception/MapperReturnsUnexpectedValueException.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 Rekalogika\Mapper\Exception; + +use Rekalogika\Mapper\Util\TypeUtil; +use Symfony\Component\PropertyInfo\Type; + +class MapperReturnsUnexpectedValueException extends UnexpectedValueException +{ + public function __construct(Type|null $type, mixed $target) + { + $message = sprintf( + 'Mapper returns unexpected value. Expected type "%s", but got "%s"', + $type === null ? 'unknown' : TypeUtil::getTypeString($type), + get_debug_type($target), + ); + + parent::__construct($message); + } +} diff --git a/src/Exception/MissingMemberKeyTypeException.php b/src/Exception/MissingMemberKeyTypeException.php index 303b3a3..8a51c92 100644 --- a/src/Exception/MissingMemberKeyTypeException.php +++ b/src/Exception/MissingMemberKeyTypeException.php @@ -13,13 +13,13 @@ namespace Rekalogika\Mapper\Exception; -use Rekalogika\Mapper\Util\TypeCheck; +use Rekalogika\Mapper\Util\TypeUtil; use Symfony\Component\PropertyInfo\Type; class MissingMemberKeyTypeException extends MissingMemberTypeException { public function __construct(Type $sourceType, Type $targetType) { - 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.', TypeCheck::getDebugType($sourceType), TypeCheck::getDebugType($targetType))); + 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 3f13530..e43270a 100644 --- a/src/Exception/MissingMemberValueTypeException.php +++ b/src/Exception/MissingMemberValueTypeException.php @@ -13,13 +13,13 @@ namespace Rekalogika\Mapper\Exception; -use Rekalogika\Mapper\Util\TypeCheck; +use Rekalogika\Mapper\Util\TypeUtil; use Symfony\Component\PropertyInfo\Type; class MissingMemberValueTypeException extends MissingMemberTypeException { public function __construct(Type $sourceType, Type $targetType) { - 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.', TypeCheck::getDebugType($sourceType), TypeCheck::getDebugType($targetType))); + 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 4125497..88f35ff 100644 --- a/src/Exception/UnableToFindSuitableTransformerException.php +++ b/src/Exception/UnableToFindSuitableTransformerException.php @@ -13,7 +13,7 @@ namespace Rekalogika\Mapper\Exception; -use Rekalogika\Mapper\Util\TypeCheck; +use Rekalogika\Mapper\Util\TypeUtil; use Symfony\Component\PropertyInfo\Type; class UnableToFindSuitableTransformerException extends NotMappableValueException @@ -25,11 +25,11 @@ class UnableToFindSuitableTransformerException extends NotMappableValueException public function __construct(Type $sourceType, Type|array $targetType) { if (is_array($targetType)) { - $targetType = implode(', ', array_map(fn (Type $type) => TypeCheck::getDebugType($type), $targetType)); + $targetType = implode(', ', array_map(fn (Type $type) => TypeUtil::getDebugType($type), $targetType)); } else { - $targetType = TypeCheck::getDebugType($targetType); + $targetType = TypeUtil::getDebugType($targetType); } - parent::__construct(sprintf('Unable to map the value "%s" to "%s"', TypeCheck::getDebugType($sourceType), $targetType)); + parent::__construct(sprintf('Unable to map the value "%s" to "%s"', TypeUtil::getDebugType($sourceType), $targetType)); } } diff --git a/src/MainTransformer.php b/src/MainTransformer.php index 5a99d94..d8471fe 100644 --- a/src/MainTransformer.php +++ b/src/MainTransformer.php @@ -22,7 +22,8 @@ use Rekalogika\Mapper\Mapping\MappingEntry; use Rekalogika\Mapper\Mapping\MappingFactoryInterface; use Rekalogika\Mapper\Model\MixedType; -use Rekalogika\Mapper\Model\ObjectCache; +use Rekalogika\Mapper\ObjectCache\ObjectCache; +use Rekalogika\Mapper\ObjectCache\ObjectCacheFactoryInterface; use Rekalogika\Mapper\TypeResolver\TypeResolverInterface; use Rekalogika\Mapper\Util\TypeUtil; use Symfony\Component\PropertyInfo\Type; @@ -34,7 +35,8 @@ class MainTransformer implements MainTransformerInterface public function __construct( private ContainerInterface $transformersLocator, private TypeResolverInterface $typeResolver, - private MappingFactoryInterface $mappingFactory + private MappingFactoryInterface $mappingFactory, + private ObjectCacheFactoryInterface $objectCacheFactory, ) { } @@ -76,7 +78,7 @@ public function transform( // get object cache if (!isset($context[self::OBJECT_CACHE])) { - $objectCache = new ObjectCache(); + $objectCache = $this->objectCacheFactory->createObjectCache(); $context[self::OBJECT_CACHE] = $objectCache; } else { /** @var ObjectCache */ diff --git a/src/Mapper.php b/src/Mapper.php index 1243966..8a05f7f 100644 --- a/src/Mapper.php +++ b/src/Mapper.php @@ -14,10 +14,10 @@ namespace Rekalogika\Mapper; use Rekalogika\Mapper\Contracts\MainTransformerInterface; +use Rekalogika\Mapper\Exception\MapperReturnsUnexpectedValueException; use Rekalogika\Mapper\Exception\UnexpectedValueException; use Rekalogika\Mapper\Model\MixedType; use Rekalogika\Mapper\Util\TypeFactory; -use Rekalogika\Mapper\Util\TypeUtil; use Symfony\Component\PropertyInfo\Type; final class Mapper implements MapperInterface @@ -52,7 +52,7 @@ class_exists($target) } elseif (is_object($target)) { /** @var object $target */ $targetClass = $target::class; - $targetType = null; + $targetType = TypeFactory::objectOfClass($targetClass); } else { $targetClass = null; $targetType = TypeFactory::fromBuiltIn($target); @@ -83,7 +83,7 @@ class_exists($target) } } - if ($targetType !== null && ($targetKeyType !== null || $targetValueType !== null)) { + if ($targetKeyType !== null || $targetValueType !== null) { $targetType = new Type( builtinType: $targetType->getBuiltinType(), nullable: $targetType->isNullable(), @@ -129,6 +129,6 @@ class: $targetType->getClassName(), return $target; } - throw new UnexpectedValueException(sprintf('The transformer did not return the variable of expected type, expecting "%s", returned "%s".', $targetType !== null ? TypeUtil::getTypeString($targetType) : 'unknown', get_debug_type($target))); + throw new MapperReturnsUnexpectedValueException($targetType, $target); } } diff --git a/src/MapperFactory.php b/src/MapperFactory.php index f053a2b..94ed1fa 100644 --- a/src/MapperFactory.php +++ b/src/MapperFactory.php @@ -20,6 +20,8 @@ use Rekalogika\Mapper\Mapping\MappingFactory; use Rekalogika\Mapper\Mapping\MappingFactoryInterface; use Rekalogika\Mapper\Model\ServiceLocator; +use Rekalogika\Mapper\ObjectCache\ObjectCacheFactory; +use Rekalogika\Mapper\ObjectCache\ObjectCacheFactoryInterface; use Rekalogika\Mapper\Transformer\ArrayToObjectTransformer; use Rekalogika\Mapper\Transformer\DateTimeTransformer; use Rekalogika\Mapper\Transformer\NullTransformer; @@ -77,6 +79,7 @@ class MapperFactory private ?MainTransformer $mainTransformer = null; private ?MapperInterface $mapper = null; private ?MappingFactoryInterface $mappingFactory = null; + private ?ObjectCacheFactoryInterface $objectCacheFactory = null; private ?MappingCommand $mappingCommand = null; private ?TryCommand $tryCommand = null; @@ -234,6 +237,7 @@ protected function getObjectToObjectTransformer(): TransformerInterface $this->getPropertyAccessExtractor(), $this->getPropertyAccessor(), $this->getTypeResolver(), + $this->getObjectCacheFactory(), ); } @@ -301,7 +305,10 @@ protected function getDateTimeTransformer(): TransformerInterface protected function getTraversableToArrayAccessTransformer(): TransformerInterface { if (null === $this->traversableToArrayAccessTransformer) { - $this->traversableToArrayAccessTransformer = new TraversableToArrayAccessTransformer(); + $this->traversableToArrayAccessTransformer = + new TraversableToArrayAccessTransformer( + $this->getObjectCacheFactory() + ); } return $this->traversableToArrayAccessTransformer; @@ -310,7 +317,10 @@ protected function getTraversableToArrayAccessTransformer(): TransformerInterfac protected function getTraversableToTraversableTransformer(): TransformerInterface { if (null === $this->traversableToTraversableTransformer) { - $this->traversableToTraversableTransformer = new TraversableToTraversableTransformer(); + $this->traversableToTraversableTransformer = + new TraversableToTraversableTransformer( + $this->getObjectCacheFactory() + ); } return $this->traversableToTraversableTransformer; @@ -370,6 +380,7 @@ protected function getMainTransformer(): MainTransformer $this->getTransformersLocator(), $this->getTypeResolver(), $this->getMappingFactory(), + $this->getObjectCacheFactory(), ); } @@ -380,13 +391,23 @@ protected function getMappingFactory(): MappingFactoryInterface { if (null === $this->mappingFactory) { $this->mappingFactory = new MappingFactory( - $this->getTransformersIterator() + $this->getTransformersIterator(), + $this->getTypeResolver(), ); } return $this->mappingFactory; } + protected function getObjectCacheFactory(): ObjectCacheFactoryInterface + { + if (null === $this->objectCacheFactory) { + $this->objectCacheFactory = new ObjectCacheFactory($this->getTypeResolver()); + } + + return $this->objectCacheFactory; + } + // // command // diff --git a/src/Mapping/MappingFactory.php b/src/Mapping/MappingFactory.php index 5131b1d..2bcf52d 100644 --- a/src/Mapping/MappingFactory.php +++ b/src/Mapping/MappingFactory.php @@ -14,7 +14,7 @@ namespace Rekalogika\Mapper\Mapping; use Rekalogika\Mapper\Contracts\TransformerInterface; -use Rekalogika\Mapper\Util\TypeUtil; +use Rekalogika\Mapper\TypeResolver\TypeResolverInterface; /** * Initialize transformer mappings @@ -26,14 +26,16 @@ final class MappingFactory implements MappingFactoryInterface /** * @param iterable $transformers */ - public function __construct(private iterable $transformers) - { + public function __construct( + private iterable $transformers, + private TypeResolverInterface $typeResolver + ) { } public function getMapping(): Mapping { if ($this->mapping === null) { - $this->mapping = self::createMapping($this->transformers); + $this->mapping = $this->createMapping($this->transformers); } return $this->mapping; @@ -43,18 +45,18 @@ public function getMapping(): Mapping * @param iterable $transformers * @return Mapping */ - private static function createMapping(iterable $transformers): Mapping + private function createMapping(iterable $transformers): Mapping { $mapping = new Mapping(); foreach ($transformers as $id => $transformer) { - self::addMapping($mapping, $id, $transformer); + $this->addMapping($mapping, $id, $transformer); } return $mapping; } - private static function addMapping( + private function addMapping( Mapping $mapping, string $id, TransformerInterface $transformer @@ -65,8 +67,8 @@ private static function addMapping( foreach ($sourceTypes as $sourceType) { foreach ($targetTypes as $targetType) { - $sourceTypeString = TypeUtil::getTypeString($sourceType); - $targetTypeString = TypeUtil::getTypeString($targetType); + $sourceTypeString = $this->typeResolver->getTypeString($sourceType); + $targetTypeString = $this->typeResolver->getTypeString($targetType); $mapping->addEntry( id: $id, diff --git a/src/Model/ObjectCache.php b/src/ObjectCache/ObjectCache.php similarity index 88% rename from src/Model/ObjectCache.php rename to src/ObjectCache/ObjectCache.php index 684ee7a..c8e0b3f 100644 --- a/src/Model/ObjectCache.php +++ b/src/ObjectCache/ObjectCache.php @@ -11,11 +11,12 @@ * that was distributed with this source code. */ -namespace Rekalogika\Mapper\Model; +namespace Rekalogika\Mapper\ObjectCache; use Rekalogika\Mapper\Exception\CachedTargetObjectNotFoundException; use Rekalogika\Mapper\Exception\CircularReferenceException; use Rekalogika\Mapper\Exception\LogicException; +use Rekalogika\Mapper\TypeResolver\TypeResolverInterface; use Rekalogika\Mapper\Util\TypeUtil; use Symfony\Component\PropertyInfo\Type; @@ -31,8 +32,9 @@ final class ObjectCache */ private \SplObjectStorage $preCache; - public function __construct() - { + public function __construct( + private TypeResolverInterface $typeResolver + ) { $this->cache = new \SplObjectStorage(); $this->preCache = new \SplObjectStorage(); } @@ -66,7 +68,7 @@ public function preCache(mixed $source, Type $targetType): void throw new LogicException('Target type must be simple type'); } - $targetTypeString = TypeUtil::getTypeString($targetType); + $targetTypeString = $this->typeResolver->getTypeString($targetType); if (!isset($this->preCache[$source])) { /** @var \ArrayObject */ @@ -87,7 +89,7 @@ private function isPreCached(mixed $source, Type $targetType): bool throw new LogicException('Target type must be simple type'); } - $targetTypeString = TypeUtil::getTypeString($targetType); + $targetTypeString = $this->typeResolver->getTypeString($targetType); return isset($this->preCache[$source][$targetTypeString]); } @@ -102,7 +104,7 @@ private function removePrecache(mixed $source, Type $targetType): void throw new LogicException('Target type must be simple type'); } - $targetTypeString = TypeUtil::getTypeString($targetType); + $targetTypeString = $this->typeResolver->getTypeString($targetType); if (isset($this->preCache[$source][$targetTypeString])) { unset($this->preCache[$source][$targetTypeString]); @@ -123,7 +125,7 @@ public function containsTarget(mixed $source, Type $targetType): bool throw new LogicException('Target type must be simple type'); } - $targetTypeString = TypeUtil::getTypeString($targetType); + $targetTypeString = $this->typeResolver->getTypeString($targetType); return isset($this->cache[$source][$targetTypeString]); } @@ -146,7 +148,7 @@ public function getTarget(mixed $source, Type $targetType): mixed throw new LogicException('Target type must be simple type'); } - $targetTypeString = TypeUtil::getTypeString($targetType); + $targetTypeString = $this->typeResolver->getTypeString($targetType); /** @var object */ return $this->cache[$source][$targetTypeString] @@ -166,7 +168,7 @@ public function saveTarget( return; } - $targetTypeString = TypeUtil::getTypeString($targetType); + $targetTypeString = $this->typeResolver->getTypeString($targetType); if (isset($this->cache[$source][$targetTypeString])) { throw new LogicException(sprintf( diff --git a/src/ObjectCache/ObjectCacheFactory.php b/src/ObjectCache/ObjectCacheFactory.php new file mode 100644 index 0000000..971f66f --- /dev/null +++ b/src/ObjectCache/ObjectCacheFactory.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +namespace Rekalogika\Mapper\ObjectCache; + +use Rekalogika\Mapper\TypeResolver\TypeResolverInterface; + +final class ObjectCacheFactory implements ObjectCacheFactoryInterface +{ + public function __construct( + private TypeResolverInterface $typeResolver + ) { + } + + public function createObjectCache(): ObjectCache + { + return new ObjectCache($this->typeResolver); + } +} diff --git a/src/ObjectCache/ObjectCacheFactoryInterface.php b/src/ObjectCache/ObjectCacheFactoryInterface.php new file mode 100644 index 0000000..0dd4dbe --- /dev/null +++ b/src/ObjectCache/ObjectCacheFactoryInterface.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +namespace Rekalogika\Mapper\ObjectCache; + +interface ObjectCacheFactoryInterface +{ + public function createObjectCache(): ObjectCache; +} diff --git a/src/Transformer/ArrayToObjectTransformer.php b/src/Transformer/ArrayToObjectTransformer.php index 881eb60..606c499 100644 --- a/src/Transformer/ArrayToObjectTransformer.php +++ b/src/Transformer/ArrayToObjectTransformer.php @@ -16,6 +16,7 @@ use Rekalogika\Mapper\Contracts\TransformerInterface; use Rekalogika\Mapper\Contracts\TypeMapping; use Rekalogika\Mapper\Exception\InvalidArgumentException; +use Rekalogika\Mapper\Exception\InvalidTypeInArgumentException; use Rekalogika\Mapper\Util\TypeCheck; use Rekalogika\Mapper\Util\TypeFactory; use Symfony\Component\PropertyInfo\Type; @@ -46,7 +47,7 @@ public function transform( } if (!TypeCheck::isObject($targetType)) { - throw new InvalidArgumentException(sprintf('Target type must be an object, "%s" given', TypeCheck::getDebugType($targetType))); + throw new InvalidTypeInArgumentException('Target type must be an object, "%s" given', $targetType); } if ($target !== null) { diff --git a/src/Transformer/ObjectToObjectTransformer.php b/src/Transformer/ObjectToObjectTransformer.php index 1d3e20c..6c756f5 100644 --- a/src/Transformer/ObjectToObjectTransformer.php +++ b/src/Transformer/ObjectToObjectTransformer.php @@ -20,7 +20,8 @@ use Rekalogika\Mapper\Exception\CachedTargetObjectNotFoundException; use Rekalogika\Mapper\Exception\InvalidArgumentException; use Rekalogika\Mapper\MainTransformer; -use Rekalogika\Mapper\Model\ObjectCache; +use Rekalogika\Mapper\ObjectCache\ObjectCache; +use Rekalogika\Mapper\ObjectCache\ObjectCacheFactoryInterface; use Rekalogika\Mapper\TypeResolver\TypeResolverInterface; use Rekalogika\Mapper\Util\TypeCheck; use Rekalogika\Mapper\Util\TypeFactory; @@ -42,6 +43,7 @@ public function __construct( private PropertyAccessExtractorInterface $propertyAccessExtractor, private PropertyAccessorInterface $propertyAccessor, private TypeResolverInterface $typeResolver, + private ObjectCacheFactoryInterface $objectCacheFactory, ) { } @@ -55,7 +57,7 @@ public function transform( // get object cache if (!isset($context[MainTransformer::OBJECT_CACHE])) { - $objectCache = new ObjectCache(); + $objectCache = $this->objectCacheFactory->createObjectCache(); $context[MainTransformer::OBJECT_CACHE] = $objectCache; } else { /** @var ObjectCache */ diff --git a/src/Transformer/TraversableToArrayAccessTransformer.php b/src/Transformer/TraversableToArrayAccessTransformer.php index 752c803..d38486d 100644 --- a/src/Transformer/TraversableToArrayAccessTransformer.php +++ b/src/Transformer/TraversableToArrayAccessTransformer.php @@ -21,10 +21,12 @@ use Rekalogika\Mapper\Contracts\TypeMapping; use Rekalogika\Mapper\Exception\CachedTargetObjectNotFoundException; use Rekalogika\Mapper\Exception\InvalidArgumentException; +use Rekalogika\Mapper\Exception\InvalidTypeInArgumentException; use Rekalogika\Mapper\Exception\MissingMemberKeyTypeException; use Rekalogika\Mapper\Exception\MissingMemberValueTypeException; use Rekalogika\Mapper\MainTransformer; -use Rekalogika\Mapper\Model\ObjectCache; +use Rekalogika\Mapper\ObjectCache\ObjectCache; +use Rekalogika\Mapper\ObjectCache\ObjectCacheFactoryInterface; use Rekalogika\Mapper\Util\TypeCheck; use Rekalogika\Mapper\Util\TypeFactory; use Symfony\Component\PropertyInfo\Type; @@ -33,6 +35,11 @@ final class TraversableToArrayAccessTransformer implements TransformerInterface, { use MainTransformerAwareTrait; + public function __construct( + private ObjectCacheFactoryInterface $objectCacheFactory, + ) { + } + public function transform( mixed $source, mixed $target, @@ -43,7 +50,7 @@ public function transform( // get object cache if (!isset($context[MainTransformer::OBJECT_CACHE])) { - $objectCache = new ObjectCache(); + $objectCache = $this->objectCacheFactory->createObjectCache(); $context[MainTransformer::OBJECT_CACHE] = $objectCache; } else { /** @var ObjectCache */ @@ -179,7 +186,7 @@ private function instantiateArrayAccessOrArray( $class = $targetType->getClassName(); if ($class === null) { - throw new InvalidArgumentException(sprintf('Target must be an instance of "\ArrayAccess" or "array", "%s" given', TypeCheck::getDebugType($targetType))); + throw new InvalidTypeInArgumentException('Target must be an instance of "\ArrayAccess" or "array, "%s" given', $targetType); } if (!class_exists($class) && !\interface_exists($class)) { diff --git a/src/Transformer/TraversableToTraversableTransformer.php b/src/Transformer/TraversableToTraversableTransformer.php index fd7fe06..0e2e753 100644 --- a/src/Transformer/TraversableToTraversableTransformer.php +++ b/src/Transformer/TraversableToTraversableTransformer.php @@ -22,7 +22,8 @@ use Rekalogika\Mapper\Exception\MissingMemberKeyTypeException; use Rekalogika\Mapper\Exception\MissingMemberValueTypeException; use Rekalogika\Mapper\MainTransformer; -use Rekalogika\Mapper\Model\ObjectCache; +use Rekalogika\Mapper\ObjectCache\ObjectCache; +use Rekalogika\Mapper\ObjectCache\ObjectCacheFactoryInterface; use Rekalogika\Mapper\Util\TypeCheck; use Rekalogika\Mapper\Util\TypeFactory; use Symfony\Component\PropertyInfo\Type; @@ -31,6 +32,11 @@ final class TraversableToTraversableTransformer implements TransformerInterface, { use MainTransformerAwareTrait; + public function __construct( + private ObjectCacheFactoryInterface $objectCacheFactory, + ) { + } + public function transform( mixed $source, mixed $target, @@ -41,7 +47,7 @@ public function transform( // get object cache if (!isset($context[MainTransformer::OBJECT_CACHE])) { - $objectCache = new ObjectCache(); + $objectCache = $this->objectCacheFactory->createObjectCache(); $context[MainTransformer::OBJECT_CACHE] = $objectCache; } else { /** @var ObjectCache */ diff --git a/src/TypeResolver/TypeResolver.php b/src/TypeResolver/TypeResolver.php index de3b904..3015094 100644 --- a/src/TypeResolver/TypeResolver.php +++ b/src/TypeResolver/TypeResolver.php @@ -61,6 +61,11 @@ public static function guessTypeFromVariable(mixed $variable): Type )); } + public function getTypeString(Type|MixedType $type): string + { + return TypeUtil::getTypeString($type); + } + public function getApplicableTypeStrings(array|Type|MixedType $type): array { if ($type instanceof MixedType) { diff --git a/src/TypeResolver/TypeResolverInterface.php b/src/TypeResolver/TypeResolverInterface.php index dc04672..9013dfc 100644 --- a/src/TypeResolver/TypeResolverInterface.php +++ b/src/TypeResolver/TypeResolverInterface.php @@ -23,6 +23,8 @@ interface TypeResolverInterface */ public static function guessTypeFromVariable(mixed $variable): Type; + public function getTypeString(Type|MixedType $type): string; + /** * Example: If the variable type is * 'IteratorAggregate>', then this method diff --git a/src/Util/TypeCheck.php b/src/Util/TypeCheck.php index 05c140e..a8bbaec 100644 --- a/src/Util/TypeCheck.php +++ b/src/Util/TypeCheck.php @@ -23,11 +23,6 @@ private function __construct() { } - public static function getDebugType(Type $type): string - { - return TypeUtil::getTypeString($type); - } - /** * Checks if the name is a valid class, interface, or enum * diff --git a/src/Util/TypeUtil.php b/src/Util/TypeUtil.php index c142b74..88ddfab 100644 --- a/src/Util/TypeUtil.php +++ b/src/Util/TypeUtil.php @@ -13,8 +13,14 @@ namespace Rekalogika\Mapper\Util; +use DaveLiddament\PhpLanguageExtensions\Friend; +use DaveLiddament\PhpLanguageExtensions\NamespaceVisibility; use Rekalogika\Mapper\Exception\InvalidArgumentException; +use Rekalogika\Mapper\Exception\MapperReturnsUnexpectedValueException; use Rekalogika\Mapper\Model\MixedType; +use Rekalogika\Mapper\Tests\UnitTest\Util\TypeUtil2Test; +use Rekalogika\Mapper\Tests\UnitTest\Util\TypeUtilTest; +use Rekalogika\Mapper\TypeResolver\TypeResolver; use Symfony\Component\PropertyInfo\Type; class TypeUtil @@ -218,10 +224,21 @@ class: $type->getClassName(), return $permutations; } + #[NamespaceVisibility(namespace: 'Rekalogika\Mapper\Exception')] + public static function getDebugType(Type $type): string + { + return TypeUtil::getTypeString($type); + } + /** * @param Type|MixedType $type * @return string */ + #[Friend( + TypeResolver::class, + MapperReturnsUnexpectedValueException::class, + TypeUtilTest::class + )] public static function getTypeString(Type|MixedType $type): string { if ($type instanceof MixedType) { @@ -270,6 +287,7 @@ public static function getTypeString(Type|MixedType $type): string /** * @return array */ + #[Friend(TypeResolver::class, TypeUtil2Test::class)] public static function getAllTypeStrings( Type $type, bool $withParents = false diff --git a/tests/UnitTest/Model/ObjectCacheTest.php b/tests/UnitTest/Model/ObjectCacheTest.php index a668f56..c57f911 100644 --- a/tests/UnitTest/Model/ObjectCacheTest.php +++ b/tests/UnitTest/Model/ObjectCacheTest.php @@ -14,13 +14,16 @@ namespace Rekalogika\Mapper\Tests\UnitTest\Model; use PHPUnit\Framework\TestCase; +use Rekalogika\Mapper\ObjectCache\ObjectCache; +use Rekalogika\Mapper\TypeResolver\TypeResolver; use Rekalogika\Mapper\Util\TypeFactory; class ObjectCacheTest extends TestCase { public function testObjectCache(): void { - $objectCache = new \Rekalogika\Mapper\Model\ObjectCache(); + $typeResolver = new TypeResolver(); + $objectCache = new ObjectCache($typeResolver); $source = new \stdClass(); $this->assertFalse($objectCache->containsTarget($source, TypeFactory::int()));