diff --git a/CHANGELOG.md b/CHANGELOG.md index 51408862..f5d69400 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ * feat: Constructor arguments * test: Custom transformer * refactor: Move `MixedType` to contracts +* refactor: Move standalone `MapperFactory` under MapperFactory namespace +* refactor: Simplify `MapperInterface` +* test: Fix tests due to refactor +* refactor: Move deprecated facade to Facade namespace ## 0.5.3 diff --git a/src/Exception/NonSimpleTypeException.php b/src/Exception/NonSimpleTypeException.php new file mode 100644 index 00000000..df561523 --- /dev/null +++ b/src/Exception/NonSimpleTypeException.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 NonSimpleTypeException extends UnexpectedValueException +{ + public function __construct(Type $type) + { + parent::__construct(sprintf('Expected a simple type, got non-simple type "%s".', TypeUtil::getDebugType($type))); + } +} diff --git a/src/Facade/AllPurposeMapper.php b/src/Facade/AllPurposeMapper.php new file mode 100644 index 00000000..d01b3244 --- /dev/null +++ b/src/Facade/AllPurposeMapper.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +namespace Rekalogika\Mapper\Facade; + +use Rekalogika\Mapper\Contracts\MainTransformerInterface; +use Rekalogika\Mapper\Contracts\MixedType; +use Rekalogika\Mapper\Exception\MapperReturnsUnexpectedValueException; +use Rekalogika\Mapper\Exception\UnexpectedValueException; +use Rekalogika\Mapper\Util\TypeFactory; +use Symfony\Component\PropertyInfo\Type; + +final class AllPurposeMapper implements AllPurposeMapperInterface +{ + /** + * Informs the key and value type of the member of the target collection. + */ + public const TARGET_KEY_TYPE = 'target_key_type'; + public const TARGET_VALUE_TYPE = 'target_value_type'; + + public function __construct( + private MainTransformerInterface $transformer, + ) { + } + + public function map(mixed $source, mixed $target, array $context = []): mixed + { + $originalTarget = $target; + + if ( + is_string($target) + && ( + class_exists($target) + || interface_exists($target) + || enum_exists($target) + ) + ) { + /** @var class-string $target */ + $targetClass = $target; + $targetType = TypeFactory::objectOfClass($targetClass); + $target = null; + } elseif (is_object($target)) { + /** @var object $target */ + $targetClass = $target::class; + $targetType = TypeFactory::objectOfClass($targetClass); + } else { + $targetClass = null; + $targetType = TypeFactory::fromBuiltIn($target); + $target = null; + } + + /** @var ?string */ + $contextTargetKeyType = $context[self::TARGET_KEY_TYPE] ?? null; + /** @var ?string */ + $contextTargetValueType = $context[self::TARGET_VALUE_TYPE] ?? null; + unset($context[self::TARGET_KEY_TYPE]); + unset($context[self::TARGET_VALUE_TYPE]); + + $targetKeyType = null; + $targetValueType = null; + + if ($contextTargetKeyType) { + $targetKeyType = TypeFactory::fromString($contextTargetKeyType); + if ($targetKeyType instanceof MixedType) { + $targetKeyType = null; + } + } + + if ($contextTargetValueType) { + $targetValueType = TypeFactory::fromString($contextTargetValueType); + if ($targetValueType instanceof MixedType) { + $targetValueType = null; + } + } + + if ($targetKeyType !== null || $targetValueType !== null) { + $targetType = new Type( + builtinType: $targetType->getBuiltinType(), + nullable: $targetType->isNullable(), + class: $targetType->getClassName(), + collection: true, + collectionKeyType: $targetKeyType, + collectionValueType: $targetValueType, + ); + } + + /** @var mixed */ + $target = $this->transformer->transform( + source: $source, + target: $target, + targetType: $targetType, + context: $context + ); + + if (is_object($target) && is_string($targetClass)) { + if (!is_a($target, $targetClass)) { + throw new UnexpectedValueException(sprintf('The transformer did not return the variable of expected class, expecting "%s", returned "%s".', $targetClass, get_debug_type($target))); + } + return $target; + } + + if ($originalTarget === 'string' && is_string($target)) { + return $target; + } + + if ($originalTarget === 'int' && is_int($target)) { + return $target; + } + + if ($originalTarget === 'float' && is_float($target)) { + return $target; + } + + if ($originalTarget === 'bool' && is_bool($target)) { + return $target; + } + + if ($originalTarget === 'array' && is_array($target)) { + return $target; + } + + throw new MapperReturnsUnexpectedValueException($targetType, $target); + } +} diff --git a/src/Facade/AllPurposeMapperInterface.php b/src/Facade/AllPurposeMapperInterface.php new file mode 100644 index 00000000..3b73bf93 --- /dev/null +++ b/src/Facade/AllPurposeMapperInterface.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 Rekalogika\Mapper\Facade; + +use Rekalogika\Mapper\Exception\CircularReferenceException; +use Rekalogika\Mapper\Exception\ExceptionInterface; +use Rekalogika\Mapper\Exception\InvalidArgumentException; +use Rekalogika\Mapper\Exception\LogicException; + +interface AllPurposeMapperInterface +{ + /** + * @template T of object + * @param class-string|T|"int"|"string"|"float"|"bool"|"array" $target + * @param array $context + * @return ($target is class-string|T ? T : ($target is "int" ? int : ($target is "string" ? string : ($target is "float" ? float : ($target is "bool" ? bool : ($target is "array" ? array : mixed )))))) + * @throws InvalidArgumentException + * @throws CircularReferenceException + * @throws LogicException + * @throws ExceptionInterface + */ + public function map(mixed $source, mixed $target, array $context = []): mixed; +} diff --git a/src/Mapper.php b/src/Mapper.php index 5a770172..6438ad0d 100644 --- a/src/Mapper.php +++ b/src/Mapper.php @@ -14,84 +14,38 @@ namespace Rekalogika\Mapper; use Rekalogika\Mapper\Contracts\MainTransformerInterface; -use Rekalogika\Mapper\Contracts\MixedType; -use Rekalogika\Mapper\Exception\MapperReturnsUnexpectedValueException; use Rekalogika\Mapper\Exception\UnexpectedValueException; use Rekalogika\Mapper\Util\TypeFactory; -use Symfony\Component\PropertyInfo\Type; final class Mapper implements MapperInterface { - /** - * Informs the key and value type of the member of the target collection. - */ - public const TARGET_KEY_TYPE = 'target_key_type'; - public const TARGET_VALUE_TYPE = 'target_value_type'; - public function __construct( private MainTransformerInterface $transformer, ) { } - public function map(mixed $source, mixed $target, array $context = []): mixed + /** + * @template T of object + * @param class-string|T $target + * @param array $context + * @return T + */ + public function map(mixed $source, object|string $target, array $context = []): object { - $originalTarget = $target; - - if ( - is_string($target) - && ( - class_exists($target) - || interface_exists($target) - || enum_exists($target) - ) - ) { - /** @var class-string $target */ + if (is_string($target)) { $targetClass = $target; + if ( + !class_exists($targetClass) + && !\interface_exists($targetClass) + ) { + throw new UnexpectedValueException(sprintf('The target class "%s" does not exist.', $targetClass)); + } $targetType = TypeFactory::objectOfClass($targetClass); $target = null; - } elseif (is_object($target)) { - /** @var object $target */ - $targetClass = $target::class; - $targetType = TypeFactory::objectOfClass($targetClass); } else { - $targetClass = null; - $targetType = TypeFactory::fromBuiltIn($target); - $target = null; - } - - /** @var ?string */ - $contextTargetKeyType = $context[self::TARGET_KEY_TYPE] ?? null; - /** @var ?string */ - $contextTargetValueType = $context[self::TARGET_VALUE_TYPE] ?? null; - unset($context[self::TARGET_KEY_TYPE]); - unset($context[self::TARGET_VALUE_TYPE]); - - $targetKeyType = null; - $targetValueType = null; - - if ($contextTargetKeyType) { - $targetKeyType = TypeFactory::fromString($contextTargetKeyType); - if ($targetKeyType instanceof MixedType) { - $targetKeyType = null; - } - } - - if ($contextTargetValueType) { - $targetValueType = TypeFactory::fromString($contextTargetValueType); - if ($targetValueType instanceof MixedType) { - $targetValueType = null; - } - } - - if ($targetKeyType !== null || $targetValueType !== null) { - $targetType = new Type( - builtinType: $targetType->getBuiltinType(), - nullable: $targetType->isNullable(), - class: $targetType->getClassName(), - collection: true, - collectionKeyType: $targetKeyType, - collectionValueType: $targetValueType, - ); + /** @var T $target */ + $targetClass = get_class($target); + $targetType = TypeFactory::objectOfClass($targetClass); } /** @var mixed */ @@ -102,33 +56,14 @@ class: $targetType->getClassName(), context: $context ); - if (is_object($target) && is_string($targetClass)) { - if (!is_a($target, $targetClass)) { - throw new UnexpectedValueException(sprintf('The transformer did not return the variable of expected class, expecting "%s", returned "%s".', $targetClass, get_debug_type($target))); - } - return $target; - } - - if ($originalTarget === 'string' && is_string($target)) { - return $target; - } - - if ($originalTarget === 'int' && is_int($target)) { - return $target; - } - - if ($originalTarget === 'float' && is_float($target)) { - return $target; - } - - if ($originalTarget === 'bool' && is_bool($target)) { - return $target; + if ($target === null) { + throw new UnexpectedValueException(sprintf('The mapper returned null, expecting "%s".', $targetClass)); } - if ($originalTarget === 'array' && is_array($target)) { - return $target; + if (!is_object($target) || !is_a($target, $targetClass)) { + throw new UnexpectedValueException(sprintf('The mapper did not return the variable of expected class, expecting "%s", returned "%s".', $targetClass, get_debug_type($target))); } - throw new MapperReturnsUnexpectedValueException($targetType, $target); + return $target; } } diff --git a/src/MapperInterface.php b/src/MapperInterface.php index 027c29ad..455d0d30 100644 --- a/src/MapperInterface.php +++ b/src/MapperInterface.php @@ -13,22 +13,13 @@ namespace Rekalogika\Mapper; -use Rekalogika\Mapper\Exception\CircularReferenceException; -use Rekalogika\Mapper\Exception\ExceptionInterface; -use Rekalogika\Mapper\Exception\InvalidArgumentException; -use Rekalogika\Mapper\Exception\LogicException; - interface MapperInterface { /** * @template T of object - * @param class-string|T|"int"|"string"|"float"|"bool"|"array" $target + * @param class-string|T $target * @param array $context - * @return ($target is class-string|T ? T : ($target is "int" ? int : ($target is "string" ? string : ($target is "float" ? float : ($target is "bool" ? bool : ($target is "array" ? array : mixed )))))) - * @throws InvalidArgumentException - * @throws CircularReferenceException - * @throws LogicException - * @throws ExceptionInterface + * @return T */ - public function map(mixed $source, mixed $target, array $context = []): mixed; + public function map(mixed $source, object|string $target, array $context = []): mixed; } diff --git a/src/ObjectCache/ObjectCache.php b/src/ObjectCache/ObjectCache.php index 3cc7e869..60b9c9cc 100644 --- a/src/ObjectCache/ObjectCache.php +++ b/src/ObjectCache/ObjectCache.php @@ -16,6 +16,7 @@ use Rekalogika\Mapper\Exception\CachedTargetObjectNotFoundException; use Rekalogika\Mapper\Exception\CircularReferenceException; use Rekalogika\Mapper\Exception\LogicException; +use Rekalogika\Mapper\Exception\NonSimpleTypeException; use Rekalogika\Mapper\TypeResolver\TypeResolverInterface; use Symfony\Component\PropertyInfo\Type; @@ -43,6 +44,13 @@ private function isBlacklisted(mixed $source): bool return $source instanceof \DateTimeInterface; } + private function assertSimpleType(Type $type): void + { + if (!$this->typeResolver->isSimpleType($type)) { + throw new NonSimpleTypeException($type); + } + } + /** * Precaching indicates we want to cache the target, but haven't done so * yet. If the object is still in precached status, obtaining it from the @@ -63,9 +71,7 @@ public function preCache(mixed $source, Type $targetType): void return; } - if (!$this->typeResolver->isSimpleType($targetType)) { - throw new LogicException('Target type must be simple type'); - } + $this->assertSimpleType($targetType); $targetTypeString = $this->typeResolver->getTypeString($targetType); @@ -84,9 +90,7 @@ private function isPreCached(mixed $source, Type $targetType): bool return false; } - if (!$this->typeResolver->isSimpleType($targetType)) { - throw new LogicException('Target type must be simple type'); - } + $this->assertSimpleType($targetType); $targetTypeString = $this->typeResolver->getTypeString($targetType); @@ -99,9 +103,7 @@ private function removePrecache(mixed $source, Type $targetType): void return; } - if (!$this->typeResolver->isSimpleType($targetType)) { - throw new LogicException('Target type must be simple type'); - } + $this->assertSimpleType($targetType); $targetTypeString = $this->typeResolver->getTypeString($targetType); @@ -120,9 +122,7 @@ public function containsTarget(mixed $source, Type $targetType): bool return false; } - if (!$this->typeResolver->isSimpleType($targetType)) { - throw new LogicException('Target type must be simple type'); - } + $this->assertSimpleType($targetType); $targetTypeString = $this->typeResolver->getTypeString($targetType); @@ -143,9 +143,7 @@ public function getTarget(mixed $source, Type $targetType): mixed throw new CachedTargetObjectNotFoundException(); } - if (!$this->typeResolver->isSimpleType($targetType)) { - throw new LogicException('Target type must be simple type'); - } + $this->assertSimpleType($targetType); $targetTypeString = $this->typeResolver->getTypeString($targetType); diff --git a/src/Util/TypeUtil.php b/src/Util/TypeUtil.php index f7a3cd64..1c5f8c97 100644 --- a/src/Util/TypeUtil.php +++ b/src/Util/TypeUtil.php @@ -49,16 +49,21 @@ public static function isSimpleType(Type $type): bool $keyTypes = $type->getCollectionKeyTypes(); $valueTypes = $type->getCollectionValueTypes(); - if (count($keyTypes) != 1) { + if (count($keyTypes) > 1) { return false; } - if (count($valueTypes) != 1) { + if (count($valueTypes) > 1) { return false; } - return self::isSimpleType($keyTypes[0]) - && self::isSimpleType($valueTypes[0]); + $keyTypeIsSimple = count($keyTypes) === 0 + || self::isSimpleType($keyTypes[0]); + + $valueTypeIsSimple = count($valueTypes) === 0 + || self::isSimpleType($valueTypes[0]); + + return $keyTypeIsSimple && $valueTypeIsSimple; } return true; diff --git a/tests/Fixtures/ArrayLikeDto/ObjectWithArrayAccessPropertyWithoutTypeHintDto.php b/tests/Fixtures/ArrayLikeDto/ObjectWithArrayAccessPropertyWithoutTypeHintDto.php new file mode 100644 index 00000000..1d2a05a8 --- /dev/null +++ b/tests/Fixtures/ArrayLikeDto/ObjectWithArrayAccessPropertyWithoutTypeHintDto.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +namespace Rekalogika\Mapper\Tests\Fixtures\ArrayLikeDto; + +class ObjectWithArrayAccessPropertyWithoutTypeHintDto +{ + /** + * @var \ArrayAccess + */ + public \ArrayAccess $property; + + public function __construct() + { + $this->property = new \ArrayObject(); + } +} diff --git a/tests/Fixtures/ArrayLikeDto/ObjectWithArrayPropertyWithoutTypeHintDto.php b/tests/Fixtures/ArrayLikeDto/ObjectWithArrayPropertyWithoutTypeHintDto.php new file mode 100644 index 00000000..8ee5a9d2 --- /dev/null +++ b/tests/Fixtures/ArrayLikeDto/ObjectWithArrayPropertyWithoutTypeHintDto.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +namespace Rekalogika\Mapper\Tests\Fixtures\ArrayLikeDto; + +class ObjectWithArrayPropertyWithoutTypeHintDto +{ + /** + * @var array + */ + public array $property = []; +} diff --git a/tests/Fixtures/ClassImplementingStringable.php b/tests/Fixtures/EnumAndStringable/ObjectImplementingStringable.php similarity index 74% rename from tests/Fixtures/ClassImplementingStringable.php rename to tests/Fixtures/EnumAndStringable/ObjectImplementingStringable.php index b97b6ec9..51f3de09 100644 --- a/tests/Fixtures/ClassImplementingStringable.php +++ b/tests/Fixtures/EnumAndStringable/ObjectImplementingStringable.php @@ -11,9 +11,9 @@ * that was distributed with this source code. */ -namespace Rekalogika\Mapper\Tests\Fixtures; +namespace Rekalogika\Mapper\Tests\Fixtures\EnumAndStringable; -class ClassImplementingStringable implements \Stringable +class ObjectImplementingStringable implements \Stringable { public function __toString(): string { diff --git a/tests/Fixtures/EnumAndStringable/ObjectWithEnumStringableProperty.php b/tests/Fixtures/EnumAndStringable/ObjectWithEnumStringableProperty.php new file mode 100644 index 00000000..1fe713aa --- /dev/null +++ b/tests/Fixtures/EnumAndStringable/ObjectWithEnumStringableProperty.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 Rekalogika\Mapper\Tests\Fixtures\EnumAndStringable; + +class ObjectWithEnumStringableProperty +{ + public ObjectImplementingStringable $stringable; + public SomeBackedEnum $backedEnum = SomeBackedEnum::Foo; + public SomeEnum $unitEnum = SomeEnum::Foo; + + public function __construct() + { + $this->stringable = new ObjectImplementingStringable(); + } +} diff --git a/tests/Fixtures/SomeBackedEnum.php b/tests/Fixtures/EnumAndStringable/SomeBackedEnum.php similarity index 85% rename from tests/Fixtures/SomeBackedEnum.php rename to tests/Fixtures/EnumAndStringable/SomeBackedEnum.php index 1635a58d..95854009 100644 --- a/tests/Fixtures/SomeBackedEnum.php +++ b/tests/Fixtures/EnumAndStringable/SomeBackedEnum.php @@ -11,7 +11,7 @@ * that was distributed with this source code. */ -namespace Rekalogika\Mapper\Tests\Fixtures; +namespace Rekalogika\Mapper\Tests\Fixtures\EnumAndStringable; enum SomeBackedEnum: string { diff --git a/tests/Fixtures/SomeEnum.php b/tests/Fixtures/EnumAndStringable/SomeEnum.php similarity index 84% rename from tests/Fixtures/SomeEnum.php rename to tests/Fixtures/EnumAndStringable/SomeEnum.php index 9e900e24..cb24d57a 100644 --- a/tests/Fixtures/SomeEnum.php +++ b/tests/Fixtures/EnumAndStringable/SomeEnum.php @@ -11,7 +11,7 @@ * that was distributed with this source code. */ -namespace Rekalogika\Mapper\Tests\Fixtures; +namespace Rekalogika\Mapper\Tests\Fixtures\EnumAndStringable; enum SomeEnum { diff --git a/tests/Fixtures/EnumAndStringableDto/ObjectWithEnumStringablePropertyDto.php b/tests/Fixtures/EnumAndStringableDto/ObjectWithEnumStringablePropertyDto.php new file mode 100644 index 00000000..44c7d186 --- /dev/null +++ b/tests/Fixtures/EnumAndStringableDto/ObjectWithEnumStringablePropertyDto.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 Rekalogika\Mapper\Tests\Fixtures\EnumAndStringableDto; + +class ObjectWithEnumStringablePropertyDto +{ + public ?string $stringable = null; + public ?string $backedEnum = null; + public ?string $unitEnum = null; +} diff --git a/tests/IntegrationTest/ArrayAndObjectMappingTest.php b/tests/IntegrationTest/ArrayAndObjectMappingTest.php index 6f624c42..3a2664ca 100644 --- a/tests/IntegrationTest/ArrayAndObjectMappingTest.php +++ b/tests/IntegrationTest/ArrayAndObjectMappingTest.php @@ -14,7 +14,6 @@ namespace Rekalogika\Mapper\Tests\IntegrationTest; use Rekalogika\Mapper\Tests\Common\AbstractIntegrationTest; -use Rekalogika\Mapper\Tests\Fixtures\Scalar\ObjectWithScalarProperties; use Rekalogika\Mapper\Tests\Fixtures\Scalar\ObjectWithScalarPropertiesDto; class ArrayAndObjectMappingTest extends AbstractIntegrationTest @@ -35,16 +34,16 @@ public function testArrayToObject(): void $this->assertEquals(1.1, $dto->d); } - public function testObjectToArray(): void - { - $class = new ObjectWithScalarProperties(); + // public function testObjectToArray(): void + // { + // $class = new ObjectWithScalarProperties(); - $array = $this->mapper->map($class, 'array'); + // $array = $this->mapper->map($class, 'array'); - $this->assertEquals(1, $array['a'] ?? null); - $this->assertEquals('string', $array['b'] ?? null); - $this->assertEquals(true, $array['c'] ?? null); - $this->assertEquals(1.1, $array['d'] ?? null); - } + // $this->assertEquals(1, $array['a'] ?? null); + // $this->assertEquals('string', $array['b'] ?? null); + // $this->assertEquals(true, $array['c'] ?? null); + // $this->assertEquals(1.1, $array['d'] ?? null); + // } } diff --git a/tests/IntegrationTest/ObjectEnumStringMappingTest.php b/tests/IntegrationTest/ObjectEnumStringMappingTest.php index 170f3ac0..233bf4db 100644 --- a/tests/IntegrationTest/ObjectEnumStringMappingTest.php +++ b/tests/IntegrationTest/ObjectEnumStringMappingTest.php @@ -14,33 +14,18 @@ namespace Rekalogika\Mapper\Tests\IntegrationTest; use Rekalogika\Mapper\Tests\Common\AbstractIntegrationTest; -use Rekalogika\Mapper\Tests\Fixtures\ClassImplementingStringable; -use Rekalogika\Mapper\Tests\Fixtures\SomeBackedEnum; -use Rekalogika\Mapper\Tests\Fixtures\SomeEnum; +use Rekalogika\Mapper\Tests\Fixtures\EnumAndStringable\ObjectWithEnumStringableProperty; +use Rekalogika\Mapper\Tests\Fixtures\EnumAndStringableDto\ObjectWithEnumStringablePropertyDto; class ObjectEnumStringMappingTest extends AbstractIntegrationTest { - public function testObjectToString(): void + public function testToString(): void { - $object = new ClassImplementingStringable(); - $result = $this->mapper->map($object, 'string'); + $object = new ObjectWithEnumStringableProperty(); + $result = $this->mapper->map($object, ObjectWithEnumStringablePropertyDto::class); - $this->assertSame('foo', $result); - } - - public function testBackedEnumToString(): void - { - $enum = SomeBackedEnum::Foo; - $result = $this->mapper->map($enum, 'string'); - - $this->assertSame('foo', $result); - } - - public function testUnitEnumToString(): void - { - $enum = SomeEnum::Foo; - $result = $this->mapper->map($enum, 'string'); - - $this->assertSame('Foo', $result); + $this->assertEquals('foo', $result->stringable); + $this->assertEquals('foo', $result->backedEnum); + $this->assertEquals('Foo', $result->unitEnum); } } diff --git a/tests/IntegrationTest/TraversableToArrayAccessMappingTest.php b/tests/IntegrationTest/TraversableToArrayAccessMappingTest.php index 65941f27..a4389b29 100644 --- a/tests/IntegrationTest/TraversableToArrayAccessMappingTest.php +++ b/tests/IntegrationTest/TraversableToArrayAccessMappingTest.php @@ -16,7 +16,6 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Rekalogika\Mapper\Exception\MissingMemberValueTypeException; -use Rekalogika\Mapper\Mapper; use Rekalogika\Mapper\Tests\Common\AbstractIntegrationTest; use Rekalogika\Mapper\Tests\Fixtures\ArrayLike\ObjectWithArrayProperty; use Rekalogika\Mapper\Tests\Fixtures\ArrayLike\ObjectWithArrayPropertyWithStringKey; @@ -24,84 +23,38 @@ use Rekalogika\Mapper\Tests\Fixtures\ArrayLikeDto\ObjectWithArrayAccessPropertyDto; use Rekalogika\Mapper\Tests\Fixtures\ArrayLikeDto\ObjectWithArrayPropertyDto; use Rekalogika\Mapper\Tests\Fixtures\ArrayLikeDto\ObjectWithArrayPropertyDtoWithIntKey; +use Rekalogika\Mapper\Tests\Fixtures\ArrayLikeDto\ObjectWithArrayPropertyWithoutTypeHintDto; use Rekalogika\Mapper\Tests\Fixtures\ArrayLikeDto\ObjectWithCollectionPropertyDto; use Rekalogika\Mapper\Tests\Fixtures\Scalar\ObjectWithScalarPropertiesDto; class TraversableToArrayAccessMappingTest extends AbstractIntegrationTest { + /** + * @todo array with mixed target type means we should just copy the source + */ public function testFailedArrayToArray(): void { - $source = [ - 'a' => 1, - 'b' => "string", - 'c' => true, - 'd' => 1.1, - ]; + $source = new ObjectWithArrayProperty(); // target array does not have information about the type of its // elements $this->expectException(MissingMemberValueTypeException::class); - $result = $this->mapper->map($source, 'array'); + $result = $this->mapper->map($source, ObjectWithArrayPropertyWithoutTypeHintDto::class); } + /** + * @todo array with mixed target type means we should just copy the source + */ public function testFailedTraversableToArrayAccess(): void { - $source = [ - 'a' => 1, - 'b' => "string", - 'c' => true, - 'd' => 1.1, - ]; + $source = new ObjectWithTraversableProperties(); // cannot do a direct mapping from array to \ArrayAccess because // it does not have information about the type of its elements $this->expectException(MissingMemberValueTypeException::class); - - $result = $this->mapper->map( - $source, - \ArrayObject::class - ); - } - - public function testArrayOfArrayToArrayOfDto(): void - { - $source = [ - [ - 'a' => 1, - 'b' => "foo", - 'c' => true, - 'd' => 1.1, - ], - [ - 'a' => 2, - 'b' => "bar", - 'c' => false, - 'd' => 0.1, - ], - ]; - - $result = $this->mapper->map($source, 'array', [ - Mapper::TARGET_KEY_TYPE => 'string', - Mapper::TARGET_VALUE_TYPE => ObjectWithScalarPropertiesDto::class, - ]); - - // @phpstan-ignore-next-line - $first = $result[0]; - $this->assertInstanceOf(ObjectWithScalarPropertiesDto::class, $first); - $this->assertSame(1, $first->a); - $this->assertSame("foo", $first->b); - $this->assertTrue($first->c); - $this->assertSame(1.1, $first->d); - - // @phpstan-ignore-next-line - $second = $result[1]; - $this->assertInstanceOf(ObjectWithScalarPropertiesDto::class, $second); - $this->assertSame(2, $second->a); - $this->assertSame("bar", $second->b); - $this->assertFalse($second->c); - $this->assertSame(0.1, $second->d); + $this->mapper->map($source, ObjectWithArrayPropertyWithoutTypeHintDto::class); } // diff --git a/tests/UnitTest/Util/TypeCheckTest.php b/tests/UnitTest/Util/TypeCheckTest.php index 136d4a2e..2145a968 100644 --- a/tests/UnitTest/Util/TypeCheckTest.php +++ b/tests/UnitTest/Util/TypeCheckTest.php @@ -14,8 +14,8 @@ namespace Rekalogika\Mapper\Tests\UnitTest\Util; use PHPUnit\Framework\TestCase; -use Rekalogika\Mapper\Tests\Fixtures\SomeBackedEnum; -use Rekalogika\Mapper\Tests\Fixtures\SomeEnum; +use Rekalogika\Mapper\Tests\Fixtures\EnumAndStringable\SomeBackedEnum; +use Rekalogika\Mapper\Tests\Fixtures\EnumAndStringable\SomeEnum; use Rekalogika\Mapper\Util\TypeCheck; use Rekalogika\Mapper\Util\TypeFactory;