From 5a35aa00087742cf82ecb91d6b7a43e4db5b85dc Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Thu, 27 Nov 2025 11:41:58 +0100 Subject: [PATCH 1/9] feat(upgrade): upgrade deps, remove deprecated code --- bootstrap.php | 3 + composer.json | 67 ++++++++--------- phpunit.xml | 17 ++--- src/AutoMapper.php | 7 -- src/Metadata/MetadataFactory.php | 24 +----- .../AutoMapperExtension.php | 8 -- .../DependencyInjection/Configuration.php | 1 - tests/AutoMapperBuilder.php | 4 - tests/AutoMapperTest.php | 65 ++-------------- tests/AutoMapperWithCustomTransformerTest.php | 7 +- tests/Bundle/ApiPlatformTest.php | 38 ++-------- .../AutoMapperExtensionTest.php | 5 +- tests/Bundle/NormalizerTest.php | 6 ++ .../App/Api/Processor/BookProcessor.php | 2 +- .../Resources/config/packages/doctrine.yaml | 1 - tests/Bundle/ServiceInstantiationTest.php | 14 ++-- tests/Fixtures/MapTo/FooMapTo.php | 1 + .../Transformer/ArrayToMoneyTransformer.php | 43 ----------- .../Transformer/MoneyToArrayTransformer.php | 40 ---------- .../Transformer/MoneyToMoneyTransformer.php | 45 ----------- .../Transformer/MoneyTransformerFactory.php | 75 ------------------- tests/Fixtures/User.php | 1 + tests/MapperContextTest.php | 5 +- tests/Normalizer/AutoMapperNormalizerTest.php | 2 - .../Features/CallbacksTestTrait.php | 33 +++----- .../Features/CircularReferenceTestTrait.php | 7 +- .../SkipUninitializedValuesTestTrait.php | 7 +- 27 files changed, 101 insertions(+), 427 deletions(-) create mode 100644 bootstrap.php delete mode 100644 tests/Fixtures/Transformer/ArrayToMoneyTransformer.php delete mode 100644 tests/Fixtures/Transformer/MoneyToArrayTransformer.php delete mode 100644 tests/Fixtures/Transformer/MoneyToMoneyTransformer.php delete mode 100644 tests/Fixtures/Transformer/MoneyTransformerFactory.php diff --git a/bootstrap.php b/bootstrap.php new file mode 100644 index 00000000..caea0ed4 --- /dev/null +++ b/bootstrap.php @@ -0,0 +1,3 @@ + - - - - - ./tests - + src - + + + + + diff --git a/src/AutoMapper.php b/src/AutoMapper.php index f8c618d0..963fd3fb 100644 --- a/src/AutoMapper.php +++ b/src/AutoMapper.php @@ -149,13 +149,8 @@ public static function create( ?ExpressionLanguageProvider $expressionLanguageProvider = null, EventDispatcherInterface $eventDispatcher = new EventDispatcher(), iterable $providers = [], - bool $removeDefaultProperties = false, ?ObjectManager $objectManager = null, ): AutoMapperInterface { - if (\count($transformerFactories) > 0) { - trigger_deprecation('jolicode/automapper', '9.0', 'The "$transformerFactories" property will be removed in version 10.0, AST transformer factories must be included within AutoMapper.', __METHOD__); - } - if (class_exists(AttributeLoader::class)) { $loaderClass = new AttributeLoader(); } elseif (class_exists(AnnotationReader::class) && class_exists(AnnotationLoader::class)) { @@ -195,12 +190,10 @@ public static function create( $customTransformerRegistry, $metadataRegistry, $classDiscriminatorResolver, - $transformerFactories, $classMetadataFactory, $nameConverter, $expressionLanguage, $eventDispatcher, - $removeDefaultProperties, $objectManager, ); diff --git a/src/Metadata/MetadataFactory.php b/src/Metadata/MetadataFactory.php index bd09d55c..8e616398 100644 --- a/src/Metadata/MetadataFactory.php +++ b/src/Metadata/MetadataFactory.php @@ -77,11 +77,7 @@ public function __construct( private readonly EventDispatcherInterface $eventDispatcher, public readonly MetadataRegistry $metadataRegistry, private readonly ClassDiscriminatorResolver $classDiscriminatorResolver, - private readonly bool $removeDefaultProperties = false, ) { - if (!$this->removeDefaultProperties) { - trigger_deprecation('jolicode/automapper', '9.4', 'Not removing default properties is deprecated, pass this parameter to true and add necessary attributes if needed', __CLASS__); - } } /** @@ -221,12 +217,10 @@ private function createGeneratorMetadata(MapperMetadata $mapperMetadata): Genera foreach ($mapperEvent->properties as $propertyEvent) { $this->eventDispatcher->dispatch($propertyEvent); - if ($this->removeDefaultProperties) { - foreach ($propertyEvents as $propertyEventExisting) { - if ($propertyEventExisting->source->property === $propertyEvent->source->property && $propertyEventExisting->isFromDefaultExtractor && !$propertyEventExisting->ignored) { - $propertyEventExisting->ignored = true; - $propertyEventExisting->ignoreReason = 'Default property is ignored because a custom property is defined.'; - } + foreach ($propertyEvents as $propertyEventExisting) { + if ($propertyEventExisting->source->property === $propertyEvent->source->property && $propertyEventExisting->isFromDefaultExtractor && !$propertyEventExisting->ignored) { + $propertyEventExisting->ignored = true; + $propertyEventExisting->ignoreReason = 'Default property is ignored because a custom property is defined.'; } } @@ -344,20 +338,15 @@ private function createGeneratorMetadata(MapperMetadata $mapperMetadata): Genera ); } - /** - * @param TransformerFactoryInterface[] $transformerFactories - */ public static function create( Configuration $configuration, PropertyTransformerRegistry $customTransformerRegistry, MetadataRegistry $metadataRegistry, ClassDiscriminatorResolver $classDiscriminatorResolver, - array $transformerFactories = [], ?ClassMetadataFactory $classMetadataFactory = null, AdvancedNameConverterInterface|NameConverterInterface|null $nameConverter = null, ExpressionLanguage $expressionLanguage = new ExpressionLanguage(), EventDispatcherInterface $eventDispatcher = new EventDispatcher(), - bool $removeDefaultProperties = false, ?ObjectManager $objectManager = null, ): self { // Create property info extractors @@ -416,10 +405,6 @@ public static function create( $factories[] = new SymfonyUidTransformerFactory(); } - foreach ($transformerFactories as $transformerFactory) { - $factories[] = $transformerFactory; - } - $transformerFactory = new ChainTransformerFactory($factories); $sourceTargetMappingExtractor = new SourceTargetMappingExtractor( @@ -452,7 +437,6 @@ public static function create( $eventDispatcher, $metadataRegistry, $classDiscriminatorResolver, - $removeDefaultProperties, ); } } diff --git a/src/Symfony/Bundle/DependencyInjection/AutoMapperExtension.php b/src/Symfony/Bundle/DependencyInjection/AutoMapperExtension.php index c317d528..1d805161 100644 --- a/src/Symfony/Bundle/DependencyInjection/AutoMapperExtension.php +++ b/src/Symfony/Bundle/DependencyInjection/AutoMapperExtension.php @@ -74,14 +74,6 @@ public function load(array $configs, ContainerBuilder $container): void ))) ; - if (!$config['remove_default_properties']) { - trigger_deprecation('jolicode/automapper', '9.4', 'Not removing default properties is deprecated and will be always true in future versions, set \'remove_default_properties\' config parameter to true, and check that your mapping is correct.', __CLASS__); - } - - $container->getDefinition(MetadataFactory::class) - ->setArgument('$removeDefaultProperties', $config['remove_default_properties']) - ; - if ($config['map_private_properties']) { $container->getDefinition('automapper.property_info.reflection_extractor') ->replaceArgument('$accessFlags', ReflectionExtractor::ALLOW_PUBLIC | ReflectionExtractor::ALLOW_PRIVATE | ReflectionExtractor::ALLOW_PROTECTED); diff --git a/src/Symfony/Bundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/DependencyInjection/Configuration.php index ba7f0f16..8f866869 100644 --- a/src/Symfony/Bundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/DependencyInjection/Configuration.php @@ -32,7 +32,6 @@ public function getConfigTreeBuilder(): TreeBuilder ->booleanNode('auto_register')->defaultTrue()->end() ->booleanNode('map_private_properties')->defaultTrue()->end() ->booleanNode('allow_readonly_target_to_populate')->defaultFalse()->end() - ->booleanNode('remove_default_properties')->defaultFalse()->end() ->arrayNode('normalizer') ->children() ->booleanNode('enabled')->defaultFalse()->end() diff --git a/tests/AutoMapperBuilder.php b/tests/AutoMapperBuilder.php index 85c7dc14..fa51d1ea 100644 --- a/tests/AutoMapperBuilder.php +++ b/tests/AutoMapperBuilder.php @@ -23,13 +23,11 @@ public static function buildAutoMapper( bool $mapPrivatePropertiesAndMethod = false, ConstructorStrategy $constructorStrategy = ConstructorStrategy::AUTO, string $classPrefix = 'Mapper_', - array $transformerFactories = [], array $propertyTransformers = [], array $providers = [], string $dateTimeFormat = \DateTimeInterface::RFC3339, ?ExpressionLanguageProvider $expressionLanguageProvider = null, EventDispatcherInterface $eventDispatcher = new EventDispatcher(), - bool $removeDefaultProperties = false, ?ObjectManager $objectManager = null, ): AutoMapper { $skipCacheRemove = $_SERVER['SKIP_CACHE_REMOVE'] ?? false; @@ -50,12 +48,10 @@ classPrefix: $classPrefix, return AutoMapper::create( $configuration, cacheDirectory: __DIR__ . '/cache/', - transformerFactories: $transformerFactories, propertyTransformers: $propertyTransformers, expressionLanguageProvider: $expressionLanguageProvider, eventDispatcher: $eventDispatcher, providers: $providers, - removeDefaultProperties: $removeDefaultProperties, objectManager: $objectManager, ); } diff --git a/tests/AutoMapperTest.php b/tests/AutoMapperTest.php index c0c5a57f..64969510 100644 --- a/tests/AutoMapperTest.php +++ b/tests/AutoMapperTest.php @@ -39,13 +39,12 @@ use AutoMapper\Tests\Fixtures\HasDateTimeWithNullValue; use AutoMapper\Tests\Fixtures\IntDTO; use AutoMapper\Tests\Fixtures\ObjectWithDateTime; -use AutoMapper\Tests\Fixtures\Order; use AutoMapper\Tests\Fixtures\PetOwner; use AutoMapper\Tests\Fixtures\PetOwnerWithConstructorArguments; use AutoMapper\Tests\Fixtures\Post; use AutoMapper\Tests\Fixtures\SourceForConstructorWithDefaultValues; -use AutoMapper\Tests\Fixtures\Transformer\MoneyTransformerFactory; use AutoMapper\Tests\Fixtures\Uninitialized; +use PHPUnit\Framework\Attributes\DataProvider; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\Finder\Finder; use Symfony\Component\HttpKernel\Kernel; @@ -748,57 +747,6 @@ public function testWithMixedArray(): void self::assertSame(['foo' => 'bar'], $dto->getProperties()); } - public function testCustomTransformerFromArrayToObject(): void - { - $this->autoMapper = AutoMapperBuilder::buildAutoMapper(mapPrivatePropertiesAndMethod: true, transformerFactories: [new MoneyTransformerFactory()]); - - $data = [ - 'id' => 4582, - 'price' => [ - 'amount' => 1000, - 'currency' => 'EUR', - ], - ]; - $order = $this->autoMapper->map($data, Order::class); - - self::assertInstanceOf(Order::class, $order); - self::assertInstanceOf(\Money\Money::class, $order->price); - self::assertEquals(1000, $order->price->getAmount()); - self::assertEquals('EUR', $order->price->getCurrency()->getCode()); - } - - public function testCustomTransformerFromObjectToArray(): void - { - $this->autoMapper = AutoMapperBuilder::buildAutoMapper(transformerFactories: [new MoneyTransformerFactory()]); - - $order = new Order(); - $order->id = 4582; - $order->price = new \Money\Money(1000, new \Money\Currency('EUR')); - $data = $this->autoMapper->map($order, 'array'); - - self::assertIsArray($data); - self::assertEquals(4582, $data['id']); - self::assertIsArray($data['price']); - self::assertEquals(1000, $data['price']['amount']); - self::assertEquals('EUR', $data['price']['currency']); - } - - public function testCustomTransformerFromObjectToObject(): void - { - $this->autoMapper = AutoMapperBuilder::buildAutoMapper(transformerFactories: [new MoneyTransformerFactory()]); - - $order = new Order(); - $order->id = 4582; - $order->price = new \Money\Money(1000, new \Money\Currency('EUR')); - $newOrder = new Order(); - $newOrder = $this->autoMapper->map($order, $newOrder); - - self::assertInstanceOf(Order::class, $newOrder); - self::assertInstanceOf(\Money\Money::class, $newOrder->price); - self::assertEquals(1000, $newOrder->price->getAmount()); - self::assertEquals('EUR', $newOrder->price->getCurrency()->getCode()); - } - public function testAdderAndRemoverWithClass(): void { $this->autoMapper = AutoMapperBuilder::buildAutoMapper(mapPrivatePropertiesAndMethod: true); @@ -950,9 +898,7 @@ public function testTargetReadonlyClassAllowed(): void self::assertEquals('city', $toPopulate->city); } - /** - * @dataProvider provideReadonly - */ + #[DataProvider('provideReadonly')] public function testReadonly(string $addressWithReadonlyClass): void { $this->autoMapper = AutoMapperBuilder::buildAutoMapper(allowReadOnlyTargetToPopulate: true, mapPrivatePropertiesAndMethod: true); @@ -1025,6 +971,7 @@ public function testDateTimeFormatCanBeConfiguredFromContext(): void * * @dataProvider dateTimeMappingProvider */ + #[DataProvider('dateTimeMappingProvider')] public function testDateTimeMapping( string $from, string $to, @@ -1044,7 +991,7 @@ public function testDateTimeMapping( /** * @return iterable */ - public function dateTimeMappingProvider(): iterable + public static function dateTimeMappingProvider(): iterable { $classes = [ HasDateTime::class, @@ -1337,9 +1284,7 @@ public function testMapCollectionToArray(): void self::assertIsString($userDatas[1]['createdAt']); } - /** - * @dataProvider provideAutoMapperFixturesTests - */ + #[DataProvider('provideAutoMapperFixturesTests')] public function testAutoMapperFixtures(string $mapFile, string $directory): void { try { diff --git a/tests/AutoMapperWithCustomTransformerTest.php b/tests/AutoMapperWithCustomTransformerTest.php index f1c6434f..892609ab 100644 --- a/tests/AutoMapperWithCustomTransformerTest.php +++ b/tests/AutoMapperWithCustomTransformerTest.php @@ -21,6 +21,7 @@ use AutoMapper\Tests\Fixtures\Transformer\CustomTransformer\TransformerWithDependency; use AutoMapper\Tests\Fixtures\User; use AutoMapper\Tests\Fixtures\UserDTO; +use PHPUnit\Framework\Attributes\DataProvider; class AutoMapperWithCustomTransformerTest extends AutoMapperTestCase { @@ -80,9 +81,7 @@ public function testFromTargetCanUseCustomTransformer(): void ); } - /** - * @dataProvider providerFromSourceToTargetCanUseCustomTransformer - */ + #[DataProvider('providerFromSourceToTargetCanUseCustomTransformer')] public function testFromSourceToTargetCanUseCustomTransformer(string|object $target): void { $this->autoMapper = AutoMapperBuilder::buildAutoMapper(mapPrivatePropertiesAndMethod: true, classPrefix: 'SourceTargetCustomTransformer_', propertyTransformers: [ @@ -98,7 +97,7 @@ public function testFromSourceToTargetCanUseCustomTransformer(string|object $tar self::assertEquals('name DTO from custom property transformer', $mappedUser->name); } - public function providerFromSourceToTargetCanUseCustomTransformer(): iterable + public static function providerFromSourceToTargetCanUseCustomTransformer(): iterable { yield 'class name' => [User::class]; yield 'object' => [self::createUser()]; diff --git a/tests/Bundle/ApiPlatformTest.php b/tests/Bundle/ApiPlatformTest.php index 78c2e93f..e9364bf0 100644 --- a/tests/Bundle/ApiPlatformTest.php +++ b/tests/Bundle/ApiPlatformTest.php @@ -21,38 +21,8 @@ protected function setUp(): void self::bootKernel(); } - public function testGetBookCollectionOnApip3(): void + public function testGetBookCollectionOnApip(): void { - if (version_compare(\Composer\InstalledVersions::getVersion('api-platform/core'), '4', '>=')) { - $this->markTestSkipped('This test requires api-platform/core 3'); - } - - $response = static::createClient()->request('GET', '/books.jsonld'); - - $this->assertResponseIsSuccessful(); - $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); - - $this->assertJsonContains([ - '@context' => '/contexts/Book', - '@id' => '/books', - '@type' => 'hydra:Collection', - 'hydra:totalItems' => 1, - ]); - - $this->assertCount(1, $response->toArray()['hydra:member']); - $this->assertArraySubset([ - '@type' => 'Book', - '@id' => '/books/1', - 'reviews' => [], - ], $response->toArray()['hydra:member'][0]); - } - - public function testGetBookCollectionOnApip4(): void - { - if (version_compare(\Composer\InstalledVersions::getVersion('api-platform/core'), '4', '<')) { - $this->markTestSkipped('This test requires api-platform/core 4'); - } - $response = static::createClient()->request('GET', '/books.jsonld'); $this->assertResponseIsSuccessful(); @@ -133,4 +103,10 @@ public function testUpdateBook(): void 'title' => 'updated title', ]); } + + protected function tearDown(): void + { + parent::tearDown(); + restore_exception_handler(); + } } diff --git a/tests/Bundle/DependencyInjection/AutoMapperExtensionTest.php b/tests/Bundle/DependencyInjection/AutoMapperExtensionTest.php index c286976b..6c2a83bb 100644 --- a/tests/Bundle/DependencyInjection/AutoMapperExtensionTest.php +++ b/tests/Bundle/DependencyInjection/AutoMapperExtensionTest.php @@ -8,6 +8,7 @@ use AutoMapper\Loader\FileReloadStrategy; use AutoMapper\Symfony\Bundle\DependencyInjection\AutoMapperExtension; use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractExtensionTestCase; +use PHPUnit\Framework\Attributes\DataProvider; /** * @author Nicolas PHILIPPE @@ -21,9 +22,7 @@ protected function setUp(): void $this->container->setParameter('kernel.environment', 'prod'); } - /** - * @dataProvider provideReloadStrategyConfiguration - */ + #[DataProvider('provideReloadStrategyConfiguration')] public function testItCorrectlyConfiguresReloadStrategy(array $config, bool $debug, FileReloadStrategy $expectedValue): void { $this->container->setParameter('kernel.debug', $debug); diff --git a/tests/Bundle/NormalizerTest.php b/tests/Bundle/NormalizerTest.php index 45402f2e..db63c317 100644 --- a/tests/Bundle/NormalizerTest.php +++ b/tests/Bundle/NormalizerTest.php @@ -84,4 +84,10 @@ public function testAddressDTONormalizer(): void self::assertEquals($address, $denormalized); } + + protected function tearDown(): void + { + parent::tearDown(); + restore_exception_handler(); + } } diff --git a/tests/Bundle/Resources/App/Api/Processor/BookProcessor.php b/tests/Bundle/Resources/App/Api/Processor/BookProcessor.php index 35a3a8ac..09668c49 100644 --- a/tests/Bundle/Resources/App/Api/Processor/BookProcessor.php +++ b/tests/Bundle/Resources/App/Api/Processor/BookProcessor.php @@ -11,7 +11,7 @@ final readonly class BookProcessor implements ProcessorInterface { - public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []) + public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): ?Book { if (!$data instanceof Book) { return null; diff --git a/tests/Bundle/Resources/config/packages/doctrine.yaml b/tests/Bundle/Resources/config/packages/doctrine.yaml index 4cb5a8e6..6599ba0b 100644 --- a/tests/Bundle/Resources/config/packages/doctrine.yaml +++ b/tests/Bundle/Resources/config/packages/doctrine.yaml @@ -6,7 +6,6 @@ doctrine: path: '%kernel.project_dir%/var/data.db' default_connection: default orm: - auto_generate_proxy_classes: true naming_strategy: doctrine.orm.naming_strategy.underscore auto_mapping: true mappings: diff --git a/tests/Bundle/ServiceInstantiationTest.php b/tests/Bundle/ServiceInstantiationTest.php index 69e90ec9..5fae7454 100644 --- a/tests/Bundle/ServiceInstantiationTest.php +++ b/tests/Bundle/ServiceInstantiationTest.php @@ -20,6 +20,7 @@ use AutoMapper\Tests\Bundle\Resources\App\Entity\SomeEnum; use AutoMapper\Tests\Bundle\Resources\App\Entity\User; use AutoMapper\Tests\Bundle\Resources\App\Entity\UserDTO; +use PHPUnit\Framework\Attributes\DataProvider; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpFoundation\Request; @@ -135,11 +136,7 @@ public function testMapToClassWithPrivateProperty(): void ); } - /** - * This test validates that PropertyInfoPass is correctly applied. - * - * @dataProvider mapFromClassWithPrivatePropertyProvider - */ + #[DataProvider('mapFromClassWithPrivatePropertyProvider')] public function testMapFromClassWithPrivateProperty(array $kernelOptions, array $expected): void { static::bootKernel($kernelOptions); @@ -194,7 +191,6 @@ public function testMapTo(): void $this->assertArrayNotHasKey('bar', $bar); $this->assertArrayNotHasKey('a', $bar); $this->assertSame('foo', $bar['baz']); - $this->assertSame('foo', $bar['foo']); $this->assertSame('transformFromIsCallable_foo', $bar['transformFromIsCallable']); $this->assertSame('transformFromStringInstance_foo', $bar['transformFromStringInstance']); $this->assertSame('transformFromStringStatic_foo', $bar['transformFromStringStatic']); @@ -282,4 +278,10 @@ protected static function createKernel(array $options = []): KernelInterface { return new AppKernel('test', false, $options['additionalConfigFile'] ?? null); } + + protected function tearDown(): void + { + parent::tearDown(); + restore_exception_handler(); + } } diff --git a/tests/Fixtures/MapTo/FooMapTo.php b/tests/Fixtures/MapTo/FooMapTo.php index bc245ea8..74dd4f31 100644 --- a/tests/Fixtures/MapTo/FooMapTo.php +++ b/tests/Fixtures/MapTo/FooMapTo.php @@ -19,6 +19,7 @@ public function __construct( #[MapTo('array', property: 'transformFromStringStatic', transformer: 'transformFromStringStatic')] #[MapTo('array', property: 'transformFromCustomTransformerService', transformer: TransformerWithDependency::class)] #[MapTo('array', property: 'transformFromExpressionLanguage', transformer: "source.foo === 'foo' ? 'transformed' : 'not transformed'")] + #[MapTo('array', property: 'foo')] public string $foo, ) { } diff --git a/tests/Fixtures/Transformer/ArrayToMoneyTransformer.php b/tests/Fixtures/Transformer/ArrayToMoneyTransformer.php deleted file mode 100644 index d9c7b2cf..00000000 --- a/tests/Fixtures/Transformer/ArrayToMoneyTransformer.php +++ /dev/null @@ -1,43 +0,0 @@ - - */ -final class ArrayToMoneyTransformer implements TransformerInterface -{ - public function transform(Expr $input, Expr $target, PropertyMetadata $propertyMapping, UniqueVariableScope $uniqueVariableScope, Expr\Variable $source, ?Expr $existingValue = null): array - { - return [new Expr\New_(new Name\FullyQualified(Money::class), [ - new Arg(new Expr\ArrayDimFetch($input, new String_('amount'))), - new Arg(new Expr\New_(new Name\FullyQualified(Currency::class), [ - new Arg(new Expr\ArrayDimFetch($input, new String_('currency'))), - ])), - ]), []]; - } - - public function getDependencies(): array - { - return []; - } - - public function assignByRef(): bool - { - return false; - } -} diff --git a/tests/Fixtures/Transformer/MoneyToArrayTransformer.php b/tests/Fixtures/Transformer/MoneyToArrayTransformer.php deleted file mode 100644 index 74b5929d..00000000 --- a/tests/Fixtures/Transformer/MoneyToArrayTransformer.php +++ /dev/null @@ -1,40 +0,0 @@ - - */ -final class MoneyToArrayTransformer implements TransformerInterface -{ - public function transform(Expr $input, Expr $target, PropertyMetadata $propertyMapping, UniqueVariableScope $uniqueVariableScope, Expr\Variable $source, ?Expr $existingValue = null): array - { - $moneyVar = new Expr\Variable($uniqueVariableScope->getUniqueName('money')); - - return [$moneyVar, [ - new Expression(new Expr\Assign(new Expr\ArrayDimFetch($moneyVar, new String_('amount')), new Expr\MethodCall($input, 'getAmount'))), - new Expression(new Expr\Assign(new Expr\ArrayDimFetch($moneyVar, new String_('currency')), new Expr\MethodCall(new Expr\MethodCall($input, 'getCurrency'), 'getCode'))), - ]]; - } - - public function getDependencies(): array - { - return []; - } - - public function assignByRef(): bool - { - return false; - } -} diff --git a/tests/Fixtures/Transformer/MoneyToMoneyTransformer.php b/tests/Fixtures/Transformer/MoneyToMoneyTransformer.php deleted file mode 100644 index 264e610b..00000000 --- a/tests/Fixtures/Transformer/MoneyToMoneyTransformer.php +++ /dev/null @@ -1,45 +0,0 @@ - - */ -final class MoneyToMoneyTransformer implements TransformerInterface -{ - public function transform(Expr $input, Expr $target, PropertyMetadata $propertyMapping, UniqueVariableScope $uniqueVariableScope, Expr\Variable $source, ?Expr $existingValue = null): array - { - return [ - new Expr\New_(new Name\FullyQualified(Money::class), [ - new Arg(new Expr\MethodCall($input, 'getAmount')), - new Arg(new Expr\New_(new Name\FullyQualified(Currency::class), [ - new Arg(new Expr\MethodCall(new Expr\MethodCall($input, 'getCurrency'), 'getCode')), - ])), - ]), - [], - ]; - } - - public function getDependencies(): array - { - return []; - } - - public function assignByRef(): bool - { - return false; - } -} diff --git a/tests/Fixtures/Transformer/MoneyTransformerFactory.php b/tests/Fixtures/Transformer/MoneyTransformerFactory.php deleted file mode 100644 index 1df0476a..00000000 --- a/tests/Fixtures/Transformer/MoneyTransformerFactory.php +++ /dev/null @@ -1,75 +0,0 @@ - - */ -final class MoneyTransformerFactory extends AbstractUniqueTypeTransformerFactory -{ - protected function createTransformer(Type $sourceType, Type $targetType, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface - { - $isSourceMoney = $this->isMoneyType($sourceType); - $isTargetMoney = $this->isMoneyType($targetType); - - if ($isSourceMoney && $isTargetMoney) { - return $this->createTransformerForSourceAndTarget(); - } - - if ($isSourceMoney && !$isTargetMoney) { - return $this->createTransformerForSource($targetType); - } - - if ($isTargetMoney && !$isSourceMoney) { - return $this->createTransformerForTarget($sourceType); - } - - return null; - } - - protected function createTransformerForSource(Type $targetType): ?TransformerInterface - { - if (Type::BUILTIN_TYPE_ARRAY === $targetType->getBuiltinType()) { - return new MoneyToArrayTransformer(); - } - - return null; - } - - protected function createTransformerForTarget(Type $sourceType): ?TransformerInterface - { - if (Type::BUILTIN_TYPE_ARRAY === $sourceType->getBuiltinType()) { - return new ArrayToMoneyTransformer(); - } - - return null; - } - - protected function createTransformerForSourceAndTarget(): TransformerInterface - { - return new MoneyToMoneyTransformer(); - } - - private function isMoneyType(Type $type): bool - { - if (Type::BUILTIN_TYPE_OBJECT !== $type->getBuiltinType()) { - return false; - } - - if (Money::class !== $type->getClassName() && !is_subclass_of($type->getClassName(), Money::class)) { - return false; - } - - return true; - } -} diff --git a/tests/Fixtures/User.php b/tests/Fixtures/User.php index 45834223..75ee75cd 100644 --- a/tests/Fixtures/User.php +++ b/tests/Fixtures/User.php @@ -12,6 +12,7 @@ class User * @var int */ #[MapTo(target: 'array', property: '_id')] + #[MapTo(target: 'array', property: 'id')] private $id; /** diff --git a/tests/MapperContextTest.php b/tests/MapperContextTest.php index 7ab95f72..a0de72a4 100644 --- a/tests/MapperContextTest.php +++ b/tests/MapperContextTest.php @@ -8,6 +8,7 @@ use AutoMapper\Exception\InvalidArgumentException; use AutoMapper\MapperContext; use AutoMapper\Tests\Fixtures\UserDTO; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; /** @@ -150,9 +151,7 @@ public function testSkipNullValues(): void self::assertFalse(MapperContext::isAllowedAttribute($context, 'id', function () use ($data) { return !isset($data->id) && null === $data->id; }, false)); } - /** - * @dataProvider forcedTimeZoneProvider - */ + #[DataProvider('forcedTimeZoneProvider')] public function testItCanGetTimeZone(array $context, ?\DateTimeZone $expectedTimeZone): void { self::assertEquals( diff --git a/tests/Normalizer/AutoMapperNormalizerTest.php b/tests/Normalizer/AutoMapperNormalizerTest.php index c54ec819..50a615bc 100644 --- a/tests/Normalizer/AutoMapperNormalizerTest.php +++ b/tests/Normalizer/AutoMapperNormalizerTest.php @@ -81,12 +81,10 @@ public function testNormalizeNoDefaultProperties(): void $normalizer = new AutoMapperNormalizer(AutoMapperBuilder::buildAutoMapper( mapPrivatePropertiesAndMethod: true, classPrefix: 'AutoMapperNoDefaultProperties_', - removeDefaultProperties: true )); $normalized = $normalizer->normalize($object); self::assertIsArray($normalized); - self::assertArrayNotHasKey('id', $normalized); self::assertEquals($expected['id'], $normalized['_id']); self::assertEquals($expected['name'], $normalized['name']); self::assertEquals($expected['age'], $normalized['age']); diff --git a/tests/Normalizer/Features/CallbacksTestTrait.php b/tests/Normalizer/Features/CallbacksTestTrait.php index 92c81980..48b87442 100644 --- a/tests/Normalizer/Features/CallbacksTestTrait.php +++ b/tests/Normalizer/Features/CallbacksTestTrait.php @@ -13,6 +13,7 @@ namespace AutoMapper\Tests\Normalizer\Features; +use PHPUnit\Framework\Attributes\DataProvider; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\PropertyInfoExtractor; @@ -28,9 +29,7 @@ abstract protected function getNormalizerForCallbacks(): NormalizerInterface; abstract protected function getNormalizerForCallbacksWithPropertyTypeExtractor(): NormalizerInterface; - /** - * @dataProvider provideNormalizeCallbacks - */ + #[DataProvider('provideNormalizeCallbacks')] public function testNormalizeCallbacks($callbacks, $valueBar, $result) { self::markTestSkipped('Callbacks not supported.'); @@ -43,9 +42,7 @@ public function testNormalizeCallbacks($callbacks, $valueBar, $result) $this->assertSame($result, $normalizer->normalize($obj, 'any', ['callbacks' => $callbacks])); } - /** - * @dataProvider provideNormalizeCallbacks - */ + #[DataProvider('provideNormalizeCallbacks')] public function testNormalizeCallbacksWithTypedProperty($callbacks, $valueBar, $result) { self::markTestSkipped('Callbacks not supported.'); @@ -58,9 +55,7 @@ public function testNormalizeCallbacksWithTypedProperty($callbacks, $valueBar, $ $this->assertSame($result, $normalizer->normalize($obj, 'any', ['callbacks' => $callbacks])); } - /** - * @dataProvider provideDenormalizeCallbacks - */ + #[DataProvider('provideDenormalizeCallbacks')] public function testDenormalizeCallbacks($callbacks, $valueBar, $result) { self::markTestSkipped('Callbacks not supported.'); @@ -72,9 +67,7 @@ public function testDenormalizeCallbacks($callbacks, $valueBar, $result) $this->assertEquals($result, $obj); } - /** - * @dataProvider providerDenormalizeCallbacksWithTypedProperty - */ + #[DataProvider('providerDenormalizeCallbacksWithTypedProperty')] public function testDenormalizeCallbacksWithTypedProperty($callbacks, $valueBar, $result) { self::markTestSkipped('Callbacks not supported.'); @@ -86,9 +79,7 @@ public function testDenormalizeCallbacksWithTypedProperty($callbacks, $valueBar, $this->assertEquals($result, $obj); } - /** - * @dataProvider providerDenormalizeCallbacksWithTypedProperty - */ + #[DataProvider('providerDenormalizeCallbacksWithTypedProperty')] public function testDenormalizeCallbacksWithNoConstructorArgument($callbacks, $valueBar, $result) { self::markTestSkipped('Callbacks not supported.'); @@ -106,9 +97,7 @@ public function __construct() $this->assertEquals($result->getBar(), $obj->getBar()); } - /** - * @dataProvider provideInvalidCallbacks - */ + #[DataProvider('provideInvalidCallbacks')] public function testUncallableCallbacks($callbacks) { self::markTestSkipped('Callbacks not supported.'); @@ -122,7 +111,7 @@ public function testUncallableCallbacks($callbacks) $normalizer->normalize($obj, null, ['callbacks' => $callbacks]); } - public function provideNormalizeCallbacks() + public static function provideNormalizeCallbacks() { return [ 'Change a string' => [ @@ -184,7 +173,7 @@ public function provideNormalizeCallbacks() ]; } - public function provideDenormalizeCallbacks(): array + public static function provideDenormalizeCallbacks(): array { return [ 'Change a string' => [ @@ -246,7 +235,7 @@ public function provideDenormalizeCallbacks(): array ]; } - public function providerDenormalizeCallbacksWithTypedProperty(): array + public static function providerDenormalizeCallbacksWithTypedProperty(): array { return [ 'Change a typed string' => [ @@ -276,7 +265,7 @@ public function providerDenormalizeCallbacksWithTypedProperty(): array ]; } - public function provideInvalidCallbacks() + public static function provideInvalidCallbacks(): array { return [ [['bar' => null]], diff --git a/tests/Normalizer/Features/CircularReferenceTestTrait.php b/tests/Normalizer/Features/CircularReferenceTestTrait.php index 10f103d4..26514e14 100644 --- a/tests/Normalizer/Features/CircularReferenceTestTrait.php +++ b/tests/Normalizer/Features/CircularReferenceTestTrait.php @@ -13,6 +13,7 @@ namespace AutoMapper\Tests\Normalizer\Features; +use PHPUnit\Framework\Attributes\DataProvider; use Symfony\Component\Serializer\Exception\CircularReferenceException; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; @@ -25,7 +26,7 @@ abstract protected function getNormalizerForCircularReference(): NormalizerInter abstract protected function getSelfReferencingModel(); - public function provideUnableToNormalizeCircularReference(): array + public static function provideUnableToNormalizeCircularReference(): array { return [ [[], 1], @@ -33,9 +34,7 @@ public function provideUnableToNormalizeCircularReference(): array ]; } - /** - * @dataProvider provideUnableToNormalizeCircularReference - */ + #[DataProvider('provideUnableToNormalizeCircularReference')] public function testUnableToNormalizeCircularReference(array $context, int $expectedLimit) { $normalizer = $this->getNormalizerForCircularReference(); diff --git a/tests/Normalizer/Features/SkipUninitializedValuesTestTrait.php b/tests/Normalizer/Features/SkipUninitializedValuesTestTrait.php index d213f03c..abed8a6c 100644 --- a/tests/Normalizer/Features/SkipUninitializedValuesTestTrait.php +++ b/tests/Normalizer/Features/SkipUninitializedValuesTestTrait.php @@ -13,6 +13,7 @@ namespace AutoMapper\Tests\Normalizer\Features; +use PHPUnit\Framework\Attributes\DataProvider; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; /** @@ -22,9 +23,7 @@ trait SkipUninitializedValuesTestTrait { abstract protected function getNormalizerForSkipUninitializedValues(): NormalizerInterface; - /** - * @dataProvider skipUninitializedValuesFlagProvider - */ + #[DataProvider('skipUninitializedValuesFlagProvider')] public function testSkipUninitializedValues(array $context) { self::markTestSkipped('Uninitialized properties are not supported yet'); @@ -45,7 +44,7 @@ public function testSkipUninitializedValues(array $context) $this->assertSame('value', $objectToPopulate->getUninitialized()); } - public function skipUninitializedValuesFlagProvider(): iterable + public static function skipUninitializedValuesFlagProvider(): iterable { yield 'passed manually' => [['skip_uninitialized_values' => true, 'groups' => ['foo']]]; yield 'using default context value' => [['groups' => ['foo']]]; From 15886f7a64912153709f3743ba1229f4526a956f Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Thu, 27 Nov 2025 17:59:53 +0100 Subject: [PATCH 2/9] feat(type): migrate to use typeinfo component --- .php-cs-fixer.php | 2 +- bootstrap.php | 2 +- composer.json | 1 + docs/mapping/transformer.md | 351 +++++++++--------- src/Event/PropertyMetadataEvent.php | 5 +- src/Event/SourcePropertyMetadata.php | 5 +- src/Event/TargetPropertyMetadata.php | 3 +- src/EventListener/MapToContextListener.php | 3 +- src/Extractor/FromSourceMappingExtractor.php | 102 ++--- src/Extractor/FromTargetMappingExtractor.php | 93 +++-- src/Extractor/GetTypeTrait.php | 181 --------- src/Extractor/MappingExtractor.php | 7 +- src/Extractor/MappingExtractorInterface.php | 7 +- src/Extractor/ReadAccessor.php | 75 ---- src/Extractor/ReadWriteTypeExtractor.php | 38 -- .../SourceTargetMappingExtractor.php | 10 +- src/Extractor/WriteMutator.php | 82 ---- src/Metadata/MetadataFactory.php | 49 ++- src/Metadata/PropertyMetadata.php | 1 - src/Metadata/SourcePropertyMetadata.php | 14 + src/Metadata/TargetPropertyMetadata.php | 16 + src/Metadata/TypesMatching.php | 64 ---- .../Bundle/Command/DebugMapperCommand.php | 6 +- .../AutoMapperExtension.php | 1 - .../Bundle/Resources/config/metadata.php | 12 +- .../Bundle/Resources/config/property_info.php | 44 ++- .../Bundle/Resources/config/transformers.php | 16 +- src/Transformer/AbstractArrayTransformer.php | 8 +- .../AbstractUniqueTypeTransformerFactory.php | 40 -- .../ArrayToDoctrineCollectionTransformer.php | 6 +- src/Transformer/ArrayTransformerFactory.php | 55 +-- src/Transformer/BuiltinTransformer.php | 134 ++++--- src/Transformer/BuiltinTransformerFactory.php | 30 +- src/Transformer/ChainTransformerFactory.php | 7 +- src/Transformer/CopyTransformerFactory.php | 5 +- .../DateTimeTransformerFactory.php | 49 +-- src/Transformer/DictionaryTransformer.php | 31 +- .../DoctrineCollectionTransformerFactory.php | 31 +- src/Transformer/EnumTransformerFactory.php | 26 +- src/Transformer/MixedTransformerFactory.php | 37 ++ src/Transformer/MultipleTransformer.php | 22 +- .../MultipleTransformerFactory.php | 14 +- .../NullableTargetTransformerFactory.php | 40 ++ src/Transformer/NullableTransformer.php | 7 +- .../NullableTransformerFactory.php | 41 +- src/Transformer/ObjectTransformer.php | 37 +- src/Transformer/ObjectTransformerFactory.php | 64 ++-- .../PropertyTransformerFactory.php | 5 +- .../PropertyTransformerRegistry.php | 5 +- .../PropertyTransformerSupportInterface.php | 4 +- .../SymfonyUidTransformerFactory.php | 14 +- .../TransformerFactoryInterface.php | 4 +- .../UniqueTypeTransformerFactory.php | 19 +- tests/AutoMapperTest.php | 54 +-- .../DifferentSetterGetterType/map.php | 9 +- tests/Bundle/ApiPlatformTest.php | 14 + .../Resources/App/Service/IdNameConverter.php | 48 +-- .../App/Service/YearOfBirthTransformer.php | 3 +- tests/Bundle/Resources/config/bundles.php | 9 +- .../Resources/config/packages/framework.yaml | 3 +- tests/Doctrine/DoctrineTest.php | 4 +- .../FromSourceMappingExtractorTest.php | 2 + .../FromTargetMappingExtractorTest.php | 2 + .../FromSourceCustomModelTransformer.php | 13 +- .../FromSourceCustomPropertyTransformer.php | 3 +- .../FromTargetCustomModelTransformer.php | 28 +- .../FromTargetCustomPropertyTransformer.php | 3 +- ...dFromSourcePropertyPriorityTransformer.php | 3 +- .../SourceTargetCustomModelTransformer.php | 27 +- .../SourceTargetCustomPropertyTransformer.php | 3 +- ...etMultiFieldsCustomPropertyTransformer.php | 3 +- .../TransformerWithDependency.php | 3 +- tests/Metadata/MetadataFactoryTest.php | 6 + .../Features/IgnoredAttributesTestTrait.php | 4 +- .../ArrayTransformerFactoryTest.php | 42 +-- tests/Transformer/ArrayTransformerTest.php | 4 +- .../BuiltinTransformerFactoryTest.php | 45 +-- tests/Transformer/BuiltinTransformerTest.php | 56 +-- .../ChainTransformerFactoryTest.php | 7 +- .../DateTimeTransformerFactoryTest.php | 59 ++- tests/Transformer/EvalTransformerTrait.php | 3 - .../MultipleTransformerFactoryTest.php | 26 +- tests/Transformer/MultipleTransformerTest.php | 10 +- .../NullableTransformerFactoryTest.php | 45 +-- tests/Transformer/NullableTransformerTest.php | 6 +- .../ObjectTransformerFactoryTest.php | 51 +-- tests/Transformer/ObjectTransformerTest.php | 4 +- .../SymfonyUidTransformerFactoryTest.php | 17 +- .../UniqueTypeTransformerFactoryTest.php | 28 +- 89 files changed, 1097 insertions(+), 1475 deletions(-) delete mode 100644 src/Extractor/GetTypeTrait.php delete mode 100644 src/Extractor/ReadWriteTypeExtractor.php delete mode 100644 src/Metadata/TypesMatching.php delete mode 100644 src/Transformer/AbstractUniqueTypeTransformerFactory.php create mode 100644 src/Transformer/MixedTransformerFactory.php create mode 100644 src/Transformer/NullableTargetTransformerFactory.php diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index 7c3661ac..371f105b 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -4,7 +4,7 @@ ->in(__DIR__ . '/src') ->in(__DIR__ . '/tests') ->append([__DIR__ . '/castor.php']) - ->exclude(['cache', 'Bundle/Resources/var', 'Bundles/Resources/config']) + ->exclude(['cache', 'Bundle/Resources/var', 'Bundle/Resources/config/reference.php']) ; return (new PhpCsFixer\Config()) diff --git a/bootstrap.php b/bootstrap.php index caea0ed4..a9c3c986 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -1,3 +1,3 @@ [!NOTE] -> In standalone mode we do not provide any functions to the expression language. -> However we are interested in adding some functions to the expression language in the future. If you have some use -> cases that you would like to see covered, please open an issue on the GitHub repository. - -### Using a callable - -```php -class Source -{ - #[MapTo(transformer: 'strtoupper')] - public string $property; -} -``` - -In this case it will use the `strtoupper` PHP function to transform the value of the `property` property. - -### Using a static callback - -```php -class Source -{ - #[MapTo(transformer: [self::class, 'transform'])] - public string $property; - - public static function transform(string $value, Source $source, array $context): string - { - return strtoupper($value); - } -} -``` - -The callback will receive, the value of the source property, the whole `source` object and the `context` array. - -### Creating a custom transformer - -> [!WARNING] -> Custom transformers are experimental and may change in the future. - -You can also create a custom transformer by implementing the `PropertyTransformerInterface` interface. -This can be useful if you need external dependencies or if you want to reuse the transformer in multiple places. - -```php -namespace App\Transformer; - -use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerInterface; - -class UrlTransformer implements PropertyTransformerInterface -{ - public function __construct(private UrlGeneratorInterface $urlGenerator) - { - } - - public function transform(mixed $value, object|array $source, array $context): mixed - { - return $this->urlGenerator->generate('my_route', ['id' => $value]); - } -} -``` - -You will need to register the transformer in the `AutoMapper` instance. - -```php -use AutoMapper\AutoMapper; - -$autoMapper = AutoMapper::create(propertyTransformers: [new UrlTransformer($urlGenerator)]); -``` - -> [!NOTE] -> When using the Symfony Bundle version of the AutoMapper, you can use the `automapper.property_transformer` tag to -> register the transformer. -> -> If you have autoconfiguration enabled, you do not need to register the transformer manually as the tag will be -> automatically added. - -Then you can use it in the `transformer` argument. - -```php -use App\Transformer\UrlTransformer; - -class Source -{ - #[MapTo(property: 'url', transformer: UrlTransformer::class)] - public int $id; -} -``` - -> [!NOTE] -> When using the Symfony Bundle version of the AutoMapper, the transformer will be the service id, which may be different -> from the class name. - -### Automatically apply custom transformers - -You may want to automatically apply a custom transformer given a specific condition. -In order to do so, you can implement the `PropertyTransformerSupportInterface` interface. - -```php -namespace App\Transformer; - -use AutoMapper\Metadata\MapperMetadata; -use AutoMapper\Metadata\SourcePropertyMetadata; -use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; -use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerInterface; -use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerSupportInterface; - -class UrlTransformer implements PropertyTransformerInterface, PropertyTransformerSupportInterface -{ - public function __construct(private UrlGeneratorInterface $urlGenerator) - { - } - - public function supports(TypesMatching $types, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): bool - { - $sourceUniqueType = $types->getSourceUniqueType(); - - if (null === $sourceUniqueType) { - return false; - } - - return $sourceUniqueType->getBuiltinType() === 'int' && $source->property === 'id' && $target->property === 'url'; - } - - public function transform(mixed $value, object|array $source, array $context): mixed - { - return $this->urlGenerator->generate('my_route', ['id' => $value]); - } -} -``` - -In this case every transformation where the source `id` property is an `int` and the target property is named `url` -will use the `UrlTransformer`. There is no need to specify the transformer in the `#[MapTo]` or `#[MapFrom]` attribute. - -### Prioritize transformers - -If you have multiple transformers that can be applied to the same transformation, you can prioritize them by using the -`PrioritizedPropertyTransformerInterface` interface. - -```php -namespace App\Transformer; - -class UrlTransformer implements PropertyTransformerInterface, PropertyTransformerSupportInterface, PrioritizedPropertyTransformerInterface -{ - // ... - - public function getPriority(): int - { - return 10; - } -} -``` - -When multiple transformers can be applied, the one with the highest priority will be used. +# Transformer + +Transformers are the way the AutoMapper transforms the value from a `source` property to a `target` property. + +By default, it tries to find the best transformer given the type of the `source` and `target` properties. + +In some cases you may want to use a custom transformer. This can be done by using the `transformer` parameter of the +`#[MapTo]` or `#[MapFrom]` attributes. + +Like the `if` argument, the `transformer` argument can accept several types of values. + +### Expression language + +You can use the Symfony Expression Language to define the transformer. +In this context the `source` object is available as `source` and the `context` array is available as `context`. + +```php +class Source +{ + #[MapTo(transformer: "source.property === 'foo' ? 'bar' : 'baz'")] + public string $property; +} +``` + +If you use the Bundle version of the AutoMapper, there is also [additional functions available](../bundle/expression-language.md). + +> [!NOTE] +> In standalone mode we do not provide any functions to the expression language. +> However we are interested in adding some functions to the expression language in the future. If you have some use +> cases that you would like to see covered, please open an issue on the GitHub repository. + +### Using a callable + +```php +class Source +{ + #[MapTo(transformer: 'strtoupper')] + public string $property; +} +``` + +In this case it will use the `strtoupper` PHP function to transform the value of the `property` property. + +### Using a static callback + +```php +class Source +{ + #[MapTo(transformer: [self::class, 'transform'])] + public string $property; + + public static function transform(string $value, Source $source, array $context): string + { + return strtoupper($value); + } +} +``` + +The callback will receive, the value of the source property, the whole `source` object and the `context` array. + +### Creating a custom transformer + +> [!WARNING] +> Custom transformers are experimental and may change in the future. + +You can also create a custom transformer by implementing the `PropertyTransformerInterface` interface. +This can be useful if you need external dependencies or if you want to reuse the transformer in multiple places. + +```php +namespace App\Transformer; + +use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerInterface; + +class UrlTransformer implements PropertyTransformerInterface +{ + public function __construct(private UrlGeneratorInterface $urlGenerator) + { + } + + public function transform(mixed $value, object|array $source, array $context): mixed + { + return $this->urlGenerator->generate('my_route', ['id' => $value]); + } +} +``` + +You will need to register the transformer in the `AutoMapper` instance. + +```php +use AutoMapper\AutoMapper; + +$autoMapper = AutoMapper::create(propertyTransformers: [new UrlTransformer($urlGenerator)]); +``` + +> [!NOTE] +> When using the Symfony Bundle version of the AutoMapper, you can use the `automapper.property_transformer` tag to +> register the transformer. +> +> If you have autoconfiguration enabled, you do not need to register the transformer manually as the tag will be +> automatically added. + +Then you can use it in the `transformer` argument. + +```php +use App\Transformer\UrlTransformer; + +class Source +{ + #[MapTo(property: 'url', transformer: UrlTransformer::class)] + public int $id; +} +``` + +> [!NOTE] +> When using the Symfony Bundle version of the AutoMapper, the transformer will be the service id, which may be different +> from the class name. + +### Automatically apply custom transformers + +You may want to automatically apply a custom transformer given a specific condition. +In order to do so, you can implement the `PropertyTransformerSupportInterface` interface. + +```php +namespace App\Transformer; + +use AutoMapper\Metadata\MapperMetadata; +use AutoMapper\Metadata\SourcePropertyMetadata; +use AutoMapper\Metadata\TargetPropertyMetadata; +use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerInterface; +use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerSupportInterface; + +class UrlTransformer implements PropertyTransformerInterface, PropertyTransformerSupportInterface +{ + public function __construct(private UrlGeneratorInterface $urlGenerator) + { + } + + public function supports(TypesMatching $types, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): bool + { + return $source->type->isIdentifiedBy(TypeIdentifier::INT && $source->property === 'id' && $target->property === 'url'; + } + + public function transform(mixed $value, object|array $source, array $context): mixed + { + return $this->urlGenerator->generate('my_route', ['id' => $value]); + } +} +``` + +In this case every transformation where the source `id` property is an `int` and the target property is named `url` +will use the `UrlTransformer`. There is no need to specify the transformer in the `#[MapTo]` or `#[MapFrom]` attribute. + +### Prioritize transformers + +If you have multiple transformers that can be applied to the same transformation, you can prioritize them by using the +`PrioritizedPropertyTransformerInterface` interface. + +```php +namespace App\Transformer; + +class UrlTransformer implements PropertyTransformerInterface, PropertyTransformerSupportInterface, PrioritizedPropertyTransformerInterface +{ + // ... + + public function getPriority(): int + { + return 10; + } +} +``` + +When multiple transformers can be applied, the one with the highest priority will be used. diff --git a/src/Event/PropertyMetadataEvent.php b/src/Event/PropertyMetadataEvent.php index 2668b39c..308adc55 100644 --- a/src/Event/PropertyMetadataEvent.php +++ b/src/Event/PropertyMetadataEvent.php @@ -5,8 +5,8 @@ namespace AutoMapper\Event; use AutoMapper\Metadata\MapperMetadata; -use AutoMapper\Metadata\TypesMatching; use AutoMapper\Transformer\TransformerInterface; +use Symfony\Component\TypeInfo\Type; /** * @internal @@ -20,7 +20,8 @@ public function __construct( public readonly MapperMetadata $mapperMetadata, public readonly SourcePropertyMetadata $source, public readonly TargetPropertyMetadata $target, - public ?TypesMatching $types = null, + public ?Type $sourceType = null, + public ?Type $targetType = null, public ?int $maxDepth = null, public ?TransformerInterface $transformer = null, public ?string $dateTimeFormat = null, diff --git a/src/Event/SourcePropertyMetadata.php b/src/Event/SourcePropertyMetadata.php index 90c01058..d77a2030 100644 --- a/src/Event/SourcePropertyMetadata.php +++ b/src/Event/SourcePropertyMetadata.php @@ -5,7 +5,7 @@ namespace AutoMapper\Event; use AutoMapper\Extractor\ReadAccessor; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; /** * Source Property metadata for event. @@ -17,17 +17,16 @@ final class SourcePropertyMetadata { /** - * @param Type[]|null $types * @param string[]|null $groups */ public function __construct( public string $property, - public ?array $types = null, public ?ReadAccessor $accessor = null, public ?bool $checkExists = null, public bool $extractGroupsIfNull = true, public ?array $groups = null, public ?string $dateTimeFormat = null, + public ?Type $type = null, ) { } } diff --git a/src/Event/TargetPropertyMetadata.php b/src/Event/TargetPropertyMetadata.php index f3c290ee..b7822fae 100644 --- a/src/Event/TargetPropertyMetadata.php +++ b/src/Event/TargetPropertyMetadata.php @@ -6,7 +6,7 @@ use AutoMapper\Extractor\ReadAccessor; use AutoMapper\Extractor\WriteMutator; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; /** * Target Property metadata for event. @@ -30,6 +30,7 @@ public function __construct( public bool $extractGroupsIfNull = true, public ?array $groups = null, public ?string $dateTimeFormat = null, + public ?Type $type = null, ) { } } diff --git a/src/EventListener/MapToContextListener.php b/src/EventListener/MapToContextListener.php index 2aad9904..fc0ee447 100644 --- a/src/EventListener/MapToContextListener.php +++ b/src/EventListener/MapToContextListener.php @@ -9,6 +9,7 @@ use AutoMapper\Extractor\ReadAccessor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\PropertyReadInfo; +use Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface; /** * @internal @@ -16,7 +17,7 @@ final readonly class MapToContextListener { public function __construct( - private ReflectionExtractor $extractor, + private PropertyReadInfoExtractorInterface $extractor, ) { } diff --git a/src/Extractor/FromSourceMappingExtractor.php b/src/Extractor/FromSourceMappingExtractor.php index 8d010325..98c0b32a 100644 --- a/src/Extractor/FromSourceMappingExtractor.php +++ b/src/Extractor/FromSourceMappingExtractor.php @@ -6,8 +6,7 @@ use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; /** * Mapping extracted only from source, useful when not having metadata on the target for dynamic data like array, \stdClass, ... @@ -20,61 +19,80 @@ */ final class FromSourceMappingExtractor extends MappingExtractor { - public function getTypes(string $source, SourcePropertyMetadata $sourceProperty, string $target, TargetPropertyMetadata $targetProperty, bool $extractTypesFromGetter): TypesMatching + public function getTypes(string $source, SourcePropertyMetadata $sourceProperty, string $target, TargetPropertyMetadata $targetProperty, bool $extractTypesFromGetter): array { - $types = new TypesMatching(); - $sourceTypes = $this->propertyInfoExtractor->getTypes($source, $sourceProperty->property, [ - ReadWriteTypeExtractor::READ_ACCESSOR => $sourceProperty->accessor, - ]) ?? [new Type(Type::BUILTIN_TYPE_NULL)]; + $sourceType = $this->sourceTypeExtractor->getType($source, $sourceProperty->property) ?? Type::mixed(); + $targetType = $this->transformSourceType($target, $sourceType); - foreach ($sourceTypes as $type) { - $targetType = $this->transformType($target, $type); - - if ($targetType) { - $types[$type] = [$targetType]; - } - } - - return $types; + return [$sourceType, $targetType ?? Type::mixed()]; } - private function transformType(string $target, ?Type $type = null): ?Type + private function transformSourceType(string $target, ?Type $type = null): ?Type { if (null === $type) { return null; } - $builtinType = $type->getBuiltinType(); - $className = $type->getClassName(); - $collection = $type->isCollection(); + if ($type instanceof Type\NullableType) { + $wrappedType = $this->transformSourceType($target, $type->getWrappedType()); + + if (null === $wrappedType) { + return null; + } + + return new Type\NullableType($wrappedType); + } + + if ($type instanceof Type\UnionType) { + $types = []; + + foreach ($type->getTypes() as $subType) { + $transformedType = $this->transformSourceType($target, $subType); + + if ($transformedType) { + $types[] = $transformedType; + } + } + + return new Type\UnionType(...$types); + } + + if ($type instanceof Type\IntersectionType) { + $types = []; + + foreach ($type->getTypes() as $subType) { + $transformedType = $this->transformSourceType($target, $subType); + + if ($transformedType) { + $types[] = $transformedType; + } + } + + return new Type\IntersectionType(...$types); + } + + if ($type instanceof Type\CollectionType) { + $keyType = $this->transformSourceType($target, $type->getCollectionKeyType()); + $valueType = $this->transformSourceType($target, $type->getCollectionValueType()); + + return Type::array($valueType, $keyType, $type->isList()); + } - if (Type::BUILTIN_TYPE_OBJECT === $type->getBuiltinType() && \stdClass::class !== $type->getClassName()) { - $builtinType = 'array' === $target ? Type::BUILTIN_TYPE_ARRAY : Type::BUILTIN_TYPE_OBJECT; - $className = 'array' === $target ? null : \stdClass::class; + // maybe check for collection ? + if ($type instanceof Type\ObjectType && \Generator::class === $type->getClassName()) { + return Type::array(); } - // Use array for generator - if (Type::BUILTIN_TYPE_OBJECT === $type->getBuiltinType() && \Generator::class === $type->getClassName()) { - $builtinType = Type::BUILTIN_TYPE_ARRAY; - $className = null; - $collection = true; + // Transform datetime to string + if ($type instanceof Type\ObjectType && $type->getClassName() !== null && (\DateTimeInterface::class === $type->getClassName() || is_subclass_of($type->getClassName(), \DateTimeInterface::class))) { + return Type::string(); } - // Use string for datetime - if (Type::BUILTIN_TYPE_OBJECT === $type->getBuiltinType() && $type->getClassName() !== null && (\DateTimeInterface::class === $type->getClassName() || is_subclass_of($type->getClassName(), \DateTimeInterface::class))) { - $builtinType = 'string'; + // Transform objects to array or \stdClass given the target + if ($type instanceof Type\ObjectType && \stdClass::class !== $type->getClassName()) { + return $target === 'array' ? Type::arrayShape([]) : Type::object(\stdClass::class); } - $collectionKeyTypes = $type->getCollectionKeyTypes(); - $collectionValueTypes = $type->getCollectionValueTypes(); - - return new Type( - $builtinType, - $type->isNullable(), - $className, - $collection, - $this->transformType($target, $collectionKeyTypes[0] ?? null), - $this->transformType($target, $collectionValueTypes[0] ?? null) - ); + return $type; } } diff --git a/src/Extractor/FromTargetMappingExtractor.php b/src/Extractor/FromTargetMappingExtractor.php index 9b4e0193..d940b481 100644 --- a/src/Extractor/FromTargetMappingExtractor.php +++ b/src/Extractor/FromTargetMappingExtractor.php @@ -6,8 +6,7 @@ use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; /** * Mapping extracted only from target, useful when not having metadata on the source for dynamic data like array, \stdClass, ... @@ -20,53 +19,75 @@ */ final class FromTargetMappingExtractor extends MappingExtractor { - public function getTypes(string $source, SourcePropertyMetadata $sourceProperty, string $target, TargetPropertyMetadata $targetProperty, bool $extractTypesFromGetter): TypesMatching + public function getTypes(string $source, SourcePropertyMetadata $sourceProperty, string $target, TargetPropertyMetadata $targetProperty, bool $extractTypesFromGetter): array { - $types = new TypesMatching(); - $targetTypes = $this->propertyInfoExtractor->getTypes($target, $targetProperty->property, [ - ReadWriteTypeExtractor::WRITE_MUTATOR => $targetProperty->writeMutator, - ReadWriteTypeExtractor::EXTRACT_TYPE_FROM_GETTER => $extractTypesFromGetter, - ]) ?? []; + $targetType = $this->targetTypeExtractor->getType($target, $targetProperty->property) ?? Type::mixed(); + $sourceType = $this->transformTargetType($source, $targetType); - foreach ($targetTypes as $type) { - $sourceType = $this->transformType($source, $type); - - if ($sourceType) { - $types[$sourceType] = [$type]; - } - } - - return $types; + return [$sourceType ?? Type::mixed(), $targetType]; } - private function transformType(string $source, ?Type $type = null): ?Type + private function transformTargetType(string $source, ?Type $type = null): ?Type { if (null === $type) { return null; } - $builtinType = $type->getBuiltinType(); - $className = $type->getClassName(); + if ($type instanceof Type\NullableType) { + $wrappedType = $this->transformTargetType($source, $type->getWrappedType()); + + if (null === $wrappedType) { + return null; + } + + return new Type\NullableType($wrappedType); + } + + if ($type instanceof Type\UnionType) { + $types = []; + + foreach ($type->getTypes() as $subType) { + $transformedType = $this->transformTargetType($source, $subType); + + if ($transformedType) { + $types[] = $transformedType; + } + } + + return new Type\UnionType(...$types); + } + + if ($type instanceof Type\IntersectionType) { + $types = []; + + foreach ($type->getTypes() as $subType) { + $transformedType = $this->transformTargetType($source, $subType); + + if ($transformedType) { + $types[] = $transformedType; + } + } + + return new Type\IntersectionType(...$types); + } + + if ($type instanceof Type\CollectionType) { + $keyType = $this->transformTargetType($source, $type->getCollectionKeyType()); + $valueType = $this->transformTargetType($source, $type->getCollectionValueType()); + + return Type::array($valueType, $keyType, $type->isList()); + } - if (Type::BUILTIN_TYPE_OBJECT === $type->getBuiltinType() && \stdClass::class !== $type->getClassName()) { - $builtinType = 'array' === $source ? Type::BUILTIN_TYPE_ARRAY : Type::BUILTIN_TYPE_OBJECT; - $className = 'array' === $source ? null : \stdClass::class; + // Transform datetime to string + if ($type instanceof Type\ObjectType && $type->getClassName() !== null && (\DateTimeInterface::class === $type->getClassName() || is_subclass_of($type->getClassName(), \DateTimeInterface::class))) { + return Type::string(); } - if (Type::BUILTIN_TYPE_OBJECT === $type->getBuiltinType() && $type->getClassName() !== null && (\DateTimeInterface::class === $type->getClassName() || is_subclass_of($type->getClassName(), \DateTimeInterface::class))) { - $builtinType = 'string'; + // Transform objects to array or \stdClass given the target + if ($type instanceof Type\ObjectType && \stdClass::class !== $type->getClassName()) { + return $source === 'array' ? Type::arrayShape([]) : Type::object(\stdClass::class); } - $collectionKeyTypes = $type->getCollectionKeyTypes(); - $collectionValueTypes = $type->getCollectionValueTypes(); - - return new Type( - $builtinType, - $type->isNullable(), - $className, - $type->isCollection(), - $this->transformType($source, $collectionKeyTypes[0] ?? null), - $this->transformType($source, $collectionValueTypes[0] ?? null) - ); + return $type; } } diff --git a/src/Extractor/GetTypeTrait.php b/src/Extractor/GetTypeTrait.php deleted file mode 100644 index 9ec08091..00000000 --- a/src/Extractor/GetTypeTrait.php +++ /dev/null @@ -1,181 +0,0 @@ -tokenize($rawDocNode)); - $docNode = $phpDocParser->parse($tokens); - $tokens->consumeTokenType(Lexer::TOKEN_END); - $nameScope = $this->createNameScope($class, $declaringClass); - - $types = []; - - foreach ($docNode->getTagsByName($tagName) as $tagDocNode) { - if ($tagDocNode->value instanceof InvalidTagValueNode) { - continue; - } - - if ( - $tagDocNode->value instanceof ParamTagValueNode - && $tagName === '@param' - && $tagDocNode->value->parameterName !== '$' . $property - ) { - continue; - } - - foreach ($phpStanTypeHelper->getTypes($tagDocNode->value, $nameScope) as $type) { - switch ($type->getClassName()) { - case 'self': - case 'static': - $resolvedClass = $class; - break; - - case 'parent': - if (false !== $resolvedClass = $parentClass ??= get_parent_class($class)) { - break; - } - - // no break - default: - $types[] = $type; - continue 2; - } - - $types[] = new Type(Type::BUILTIN_TYPE_OBJECT, $type->isNullable(), $resolvedClass, $type->isCollection(), $type->getCollectionKeyTypes(), $type->getCollectionValueTypes()); - } - } - - if (!isset($types[0])) { - return null; - } - - return $types; - } - - /** - * @param \ReflectionClass $declaringClass - * - * @return Type[] - */ - private function extractFromReflectionType(\ReflectionType $reflectionType, \ReflectionClass $declaringClass): array - { - $types = []; - $nullable = $reflectionType->allowsNull(); - - foreach (($reflectionType instanceof \ReflectionUnionType || $reflectionType instanceof \ReflectionIntersectionType) ? $reflectionType->getTypes() : [$reflectionType] as $type) { - if (!$type instanceof \ReflectionNamedType) { - // Nested composite types are not supported yet. - return []; - } - - $phpTypeOrClass = $type->getName(); - if ('null' === $phpTypeOrClass || 'mixed' === $phpTypeOrClass || 'never' === $phpTypeOrClass) { - continue; - } - - if (Type::BUILTIN_TYPE_ARRAY === $phpTypeOrClass) { - $types[] = new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true); - } elseif ('void' === $phpTypeOrClass) { - $types[] = new Type(Type::BUILTIN_TYPE_NULL, $nullable); - } elseif ($type->isBuiltin()) { - $types[] = new Type($phpTypeOrClass, $nullable); - } else { - $types[] = new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $this->resolveTypeName($phpTypeOrClass, $declaringClass)); - } - } - - return $types; - } - - /** - * @param \ReflectionClass $declaringClass - */ - private function resolveTypeName(string $name, \ReflectionClass $declaringClass): string - { - if ('self' === $lcName = strtolower($name)) { - return $declaringClass->name; - } - if ('parent' === $lcName && $parent = $declaringClass->getParentClass()) { - return $parent->name; - } - - return $name; - } - - /** - * @phpstan-ignore class.notFound - */ - private function createNameScope(string $class, string $declaringClass): NameScope|TypeContext - { - // For Symfony <=7.2. To remove when we drop support for Symfony <= 7.2 - if (class_exists(NameScopeFactory::class)) { - static $nameScopeFactory = new NameScopeFactory(); - - return $nameScopeFactory->create($class, $declaringClass); - } - - static $typeContextFactory = new TypeContextFactory(); - - return $typeContextFactory->createFromClassName($class, $declaringClass); - } -} diff --git a/src/Extractor/MappingExtractor.php b/src/Extractor/MappingExtractor.php index 70fc4173..a26bd38d 100644 --- a/src/Extractor/MappingExtractor.php +++ b/src/Extractor/MappingExtractor.php @@ -6,9 +6,10 @@ use AutoMapper\Configuration; use AutoMapper\Event\PropertyMetadataEvent; -use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyListExtractorInterface; use Symfony\Component\PropertyInfo\PropertyReadInfo; use Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; use Symfony\Component\PropertyInfo\PropertyWriteInfo; use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface; @@ -21,9 +22,11 @@ abstract class MappingExtractor implements MappingExtractorInterface { public function __construct( protected readonly Configuration $configuration, - protected readonly PropertyInfoExtractorInterface $propertyInfoExtractor, + protected readonly PropertyListExtractorInterface $propertyInfoExtractor, protected readonly PropertyReadInfoExtractorInterface $readInfoExtractor, protected readonly PropertyWriteInfoExtractorInterface $writeInfoExtractor, + protected readonly PropertyTypeExtractorInterface $sourceTypeExtractor, + protected readonly PropertyTypeExtractorInterface $targetTypeExtractor, ) { } diff --git a/src/Extractor/MappingExtractorInterface.php b/src/Extractor/MappingExtractorInterface.php index d3b4f82e..000e3f16 100644 --- a/src/Extractor/MappingExtractorInterface.php +++ b/src/Extractor/MappingExtractorInterface.php @@ -7,7 +7,7 @@ use AutoMapper\Event\PropertyMetadataEvent; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; +use Symfony\Component\TypeInfo\Type; /** * Extracts mapping. @@ -27,7 +27,10 @@ interface MappingExtractorInterface */ public function getProperties(string $class): iterable; - public function getTypes(string $source, SourcePropertyMetadata $sourceProperty, string $target, TargetPropertyMetadata $targetProperty, bool $extractTypesFromGetter): TypesMatching; + /** + * @return array{Type, Type} + */ + public function getTypes(string $source, SourcePropertyMetadata $sourceProperty, string $target, TargetPropertyMetadata $targetProperty, bool $extractTypesFromGetter): array; public function getDateTimeFormat(PropertyMetadataEvent $propertyMetadataEvent): string; diff --git a/src/Extractor/ReadAccessor.php b/src/Extractor/ReadAccessor.php index 4dbe4e36..a997e7b8 100644 --- a/src/Extractor/ReadAccessor.php +++ b/src/Extractor/ReadAccessor.php @@ -13,7 +13,6 @@ use PhpParser\Node\Param; use PhpParser\Node\Scalar; use PhpParser\Node\Stmt; -use Symfony\Component\PropertyInfo\Type; /** * Read accessor tell how to read from a property. @@ -24,8 +23,6 @@ */ final class ReadAccessor { - use GetTypeTrait; - public const TYPE_METHOD = 1; public const TYPE_PROPERTY = 2; public const TYPE_ARRAY_DIMENSION = 3; @@ -430,76 +427,4 @@ public function getExtractIsUndefinedCallback(string $className): ?Expr new Arg(new Scalar\String_($className)), ]); } - - /** - * @return Type[]|null - */ - public function getTypes(string $class): ?array - { - if (self::TYPE_METHOD === $this->type && (class_exists($class) || interface_exists($class))) { - try { - $reflectionMethod = new \ReflectionMethod($class, $this->accessor); - - if ($types = $this->extractFromDocBlock( - $reflectionMethod->getDocComment(), - $class, - $reflectionMethod->getDeclaringClass()->getName(), - $this->accessor, - '@return' - )) { - return $types; - } - - $reflectionReturnType = $reflectionMethod->getReturnType(); - - if ($reflectionReturnType === null) { - return null; - } - - return $this->extractFromReflectionType($reflectionReturnType, $reflectionMethod->getDeclaringClass()); - } catch (\ReflectionException $e) { - return null; - } - } - - if (self::TYPE_PROPERTY === $this->type && (class_exists($class) || interface_exists($class))) { - try { - $reflectionProperty = new \ReflectionProperty($class, $this->accessor); - - if ($reflectionProperty->isPromoted()) { - if ($types = $this->extractFromDocBlock( - $reflectionProperty->getDeclaringClass()->getConstructor()?->getDocComment(), - $class, - $reflectionProperty->getDeclaringClass()->getName(), - $this->accessor, - '@param' - )) { - return $types; - } - } - - if ($types = $this->extractFromDocBlock( - $reflectionProperty->getDocComment(), - $class, - $reflectionProperty->getDeclaringClass()->getName(), - $this->accessor, - '@var' - )) { - return $types; - } - - $reflectionType = $reflectionProperty->getType(); - - if ($reflectionType === null) { - return null; - } - - return $this->extractFromReflectionType($reflectionType, $reflectionProperty->getDeclaringClass()); - } catch (\ReflectionException $e) { - return null; - } - } - - return null; - } } diff --git a/src/Extractor/ReadWriteTypeExtractor.php b/src/Extractor/ReadWriteTypeExtractor.php deleted file mode 100644 index ae7f1f76..00000000 --- a/src/Extractor/ReadWriteTypeExtractor.php +++ /dev/null @@ -1,38 +0,0 @@ - $context - */ - public function getTypes(string $class, string $property, array $context = []): ?array - { - if (($accessor = $context[self::READ_ACCESSOR] ?? false) && $accessor instanceof ReadAccessor) { - return $accessor->getTypes($class); - } - - if (!($context[self::EXTRACT_TYPE_FROM_GETTER] ?? false) && ($mutator = $context[self::WRITE_MUTATOR] ?? false) && $mutator instanceof WriteMutator) { - return $mutator->getTypes($class); - } - - return null; - } -} diff --git a/src/Extractor/SourceTargetMappingExtractor.php b/src/Extractor/SourceTargetMappingExtractor.php index db7915fe..19ab178a 100644 --- a/src/Extractor/SourceTargetMappingExtractor.php +++ b/src/Extractor/SourceTargetMappingExtractor.php @@ -6,7 +6,7 @@ use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; +use Symfony\Component\TypeInfo\Type; /** * Extracts mapping between two objects, only gives properties that have the same name. @@ -17,11 +17,11 @@ */ class SourceTargetMappingExtractor extends MappingExtractor { - public function getTypes(string $source, SourcePropertyMetadata $sourceProperty, string $target, TargetPropertyMetadata $targetProperty, bool $extractTypesFromGetter): TypesMatching + public function getTypes(string $source, SourcePropertyMetadata $sourceProperty, string $target, TargetPropertyMetadata $targetProperty, bool $extractTypesFromGetter): array { - $sourceTypes = $this->propertyInfoExtractor->getTypes($source, $sourceProperty->property, [ReadWriteTypeExtractor::READ_ACCESSOR => $sourceProperty->accessor]) ?? []; - $targetTypes = $this->propertyInfoExtractor->getTypes($target, $targetProperty->property, [ReadWriteTypeExtractor::WRITE_MUTATOR => $targetProperty->writeMutator, ReadWriteTypeExtractor::EXTRACT_TYPE_FROM_GETTER => $extractTypesFromGetter]) ?? []; + $sourceType = $this->sourceTypeExtractor->getType($source, $sourceProperty->property) ?? Type::mixed(); + $targetType = $this->targetTypeExtractor->getType($target, $targetProperty->property) ?? Type::mixed(); - return TypesMatching::fromSourceAndTargetTypes($sourceTypes, $targetTypes); + return [$sourceType, $targetType]; } } diff --git a/src/Extractor/WriteMutator.php b/src/Extractor/WriteMutator.php index e48220d7..028e5338 100644 --- a/src/Extractor/WriteMutator.php +++ b/src/Extractor/WriteMutator.php @@ -11,7 +11,6 @@ use PhpParser\Node\Param; use PhpParser\Node\Scalar; use PhpParser\Node\Stmt; -use Symfony\Component\PropertyInfo\Type; /** * Writes mutator tell how to write to a property. @@ -22,8 +21,6 @@ */ final class WriteMutator { - use GetTypeTrait; - public const TYPE_METHOD = 1; public const TYPE_PROPERTY = 2; public const TYPE_ARRAY_DIMENSION = 3; @@ -147,83 +144,4 @@ public function getHydrateCallback(string $className): ?Expr new Arg(new Scalar\String_($className)), ]); } - - /** - * @return Type[]|null - */ - public function getTypes(string $target): ?array - { - if (self::TYPE_METHOD === $this->type && (class_exists($target) || interface_exists($target))) { - try { - $reflectionMethod = new \ReflectionMethod($target, $this->property); - - if ($types = $this->extractFromDocBlock( - $reflectionMethod->getDocComment(), - $target, - $reflectionMethod->getDeclaringClass()->getName(), - $reflectionMethod->getParameters()[0]->name, - '@param' - )) { - return $types; - } - - $parameters = $reflectionMethod->getParameters(); - - if (empty($parameters)) { - return null; - } - - $firstParameter = $parameters[0]; - $reflectionType = $firstParameter->getType(); - - if (null === $reflectionType) { - return null; - } - - return $this->extractFromReflectionType($reflectionType, $reflectionMethod->getDeclaringClass()); - } catch (\ReflectionException $e) { - return null; - } - } - - if (self::TYPE_PROPERTY === $this->type && (class_exists($target) || interface_exists($target))) { - try { - $reflectionProperty = new \ReflectionProperty($target, $this->property); - - if ($reflectionProperty->isPromoted()) { - if ($types = $this->extractFromDocBlock( - $reflectionProperty->getDeclaringClass()->getConstructor()?->getDocComment(), - $target, - $reflectionProperty->getDeclaringClass()->getName(), - $this->property, - '@param' - )) { - return $types; - } - } - - if ($types = $this->extractFromDocBlock( - $reflectionProperty->getDocComment(), - $target, - $reflectionProperty->getDeclaringClass()->getName(), - $this->property, - '@var' - )) { - return $types; - } - - $reflectionType = $reflectionProperty->getType(); - - if ($reflectionType === null) { - return null; - } - - return $this->extractFromReflectionType($reflectionType, $reflectionProperty->getDeclaringClass()); - } catch (\ReflectionException $e) { - return null; - } - } - - return null; - } } diff --git a/src/Metadata/MetadataFactory.php b/src/Metadata/MetadataFactory.php index 8e616398..a6f38aed 100644 --- a/src/Metadata/MetadataFactory.php +++ b/src/Metadata/MetadataFactory.php @@ -23,7 +23,6 @@ use AutoMapper\EventListener\Symfony\SerializerMaxDepthListener; use AutoMapper\Extractor\FromSourceMappingExtractor; use AutoMapper\Extractor\FromTargetMappingExtractor; -use AutoMapper\Extractor\ReadWriteTypeExtractor; use AutoMapper\Extractor\SourceTargetMappingExtractor; use AutoMapper\Extractor\WriteMutator; use AutoMapper\Generator\Shared\ClassDiscriminatorResolver; @@ -37,7 +36,9 @@ use AutoMapper\Transformer\DoctrineCollectionTransformerFactory; use AutoMapper\Transformer\EnumTransformerFactory; use AutoMapper\Transformer\MapperDependency; +use AutoMapper\Transformer\MixedTransformerFactory; use AutoMapper\Transformer\MultipleTransformerFactory; +use AutoMapper\Transformer\NullableTargetTransformerFactory; use AutoMapper\Transformer\NullableTransformerFactory; use AutoMapper\Transformer\ObjectTransformerFactory; use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerFactory; @@ -282,12 +283,20 @@ private function createGeneratorMetadata(MapperMetadata $mapperMetadata): Genera $sourcePropertyMetadata = SourcePropertyMetadata::fromEvent($propertyMappedEvent->source); $targetPropertyMetadata = TargetPropertyMetadata::fromEvent($propertyMappedEvent->target); - if (null === $propertyMappedEvent->types) { - $propertyMappedEvent->types = $extractor->getTypes($mapperMetadata->source, $sourcePropertyMetadata, $mapperMetadata->target, $targetPropertyMetadata, $propertyMappedEvent->extractTypesFromGetter ?? $this->configuration->extractTypesFromGetter); + if (null === $sourcePropertyMetadata->type || null === $targetPropertyMetadata->type) { + [$guessedSourceType, $guessedTargetType] = $extractor->getTypes($mapperMetadata->source, $sourcePropertyMetadata, $mapperMetadata->target, $targetPropertyMetadata, $propertyMappedEvent->extractTypesFromGetter ?? $this->configuration->extractTypesFromGetter); + + if ($sourcePropertyMetadata->type === null) { + $sourcePropertyMetadata = $sourcePropertyMetadata->withType($guessedSourceType); + } + + if ($targetPropertyMetadata->type === null) { + $targetPropertyMetadata = $targetPropertyMetadata->withType($guessedTargetType); + } } if (null === $propertyMappedEvent->transformer) { - $transformer = $this->transformerFactory->getTransformer($propertyMappedEvent->types, $sourcePropertyMetadata, $targetPropertyMetadata, $mapperMetadata); + $transformer = $this->transformerFactory->getTransformer($sourcePropertyMetadata, $targetPropertyMetadata, $mapperMetadata); if (null === $transformer) { $propertyMappedEvent->ignored = true; @@ -315,7 +324,6 @@ private function createGeneratorMetadata(MapperMetadata $mapperMetadata): Genera $propertiesMapping[] = new PropertyMetadata( $sourcePropertyMetadata, $targetPropertyMetadata, - $propertyMappedEvent->types, $propertyMappedEvent->transformer ?? new VoidTransformer(), $propertyMappedEvent->ignored ?? false, $propertyMappedEvent->ignoreReason ?? '', @@ -356,8 +364,15 @@ public static function create( $flags |= ReflectionExtractor::ALLOW_PRIVATE | ReflectionExtractor::ALLOW_PROTECTED; } + $reflectionSourceExtractor = new ReflectionExtractor(mutatorPrefixes: [], arrayMutatorPrefixes: [], enableConstructorExtraction: false, accessFlags: $flags); + $phpStanSourceExtractor = new PhpStanExtractor(mutatorPrefixes: [], arrayMutatorPrefixes: [], allowPrivateAccess: $configuration->mapPrivateProperties); + $reflectionTargetExtractor = new ReflectionExtractor(accessorPrefixes: [], accessFlags: $flags); + $phpStanTargetExtractor = new PhpStanExtractor(accessorPrefixes: [], allowPrivateAccess: $configuration->mapPrivateProperties); + + $sourceTypeExtractor = new PropertyInfoExtractor(typeExtractors: [$phpStanSourceExtractor, $reflectionSourceExtractor]); + $targetTypeExtractor = new PropertyInfoExtractor(typeExtractors: [$phpStanTargetExtractor, $reflectionTargetExtractor]); + $reflectionExtractor = new ReflectionExtractor(accessFlags: $flags); - $phpStanExtractor = new PhpStanExtractor(); if (null !== $classMetadataFactory) { $eventDispatcher->addListener(PropertyMetadataEvent::class, new AdvancedNameConverterListener(new MetadataAwareNameConverter($classMetadataFactory, $nameConverter))); @@ -380,24 +395,20 @@ public static function create( $eventDispatcher->addListener(GenerateMapperEvent::class, new MapperListener()); $eventDispatcher->addListener(GenerateMapperEvent::class, new MapProviderListener()); - $propertyInfoExtractor = new PropertyInfoExtractor( - listExtractors: [$reflectionExtractor], - typeExtractors: [new ReadWriteTypeExtractor(), $phpStanExtractor, $reflectionExtractor], - accessExtractors: [$reflectionExtractor] - ); - // Create transformer factories $factories = [ + new DoctrineCollectionTransformerFactory(), new MultipleTransformerFactory(), new NullableTransformerFactory(), + new NullableTargetTransformerFactory(), new UniqueTypeTransformerFactory(), new DateTimeTransformerFactory(), new BuiltinTransformerFactory(), - new DoctrineCollectionTransformerFactory(), new ArrayTransformerFactory(), new ObjectTransformerFactory(), new EnumTransformerFactory(), new PropertyTransformerFactory($customTransformerRegistry), + new MixedTransformerFactory(), new CopyTransformerFactory(), ]; @@ -409,23 +420,29 @@ public static function create( $sourceTargetMappingExtractor = new SourceTargetMappingExtractor( $configuration, - $propertyInfoExtractor, $reflectionExtractor, $reflectionExtractor, + $reflectionExtractor, + $sourceTypeExtractor, + $targetTypeExtractor, ); $fromTargetMappingExtractor = new FromTargetMappingExtractor( $configuration, - $propertyInfoExtractor, $reflectionExtractor, $reflectionExtractor, + $reflectionExtractor, + $sourceTypeExtractor, + $targetTypeExtractor, ); $fromSourceMappingExtractor = new FromSourceMappingExtractor( $configuration, - $propertyInfoExtractor, $reflectionExtractor, $reflectionExtractor, + $reflectionExtractor, + $sourceTypeExtractor, + $targetTypeExtractor, ); return new self( diff --git a/src/Metadata/PropertyMetadata.php b/src/Metadata/PropertyMetadata.php index 1036a147..a96e8bbd 100644 --- a/src/Metadata/PropertyMetadata.php +++ b/src/Metadata/PropertyMetadata.php @@ -21,7 +21,6 @@ final class PropertyMetadata public function __construct( public readonly SourcePropertyMetadata $source, public readonly TargetPropertyMetadata $target, - public readonly TypesMatching $types, public TransformerInterface $transformer, public bool $ignored = false, public string $ignoreReason = '', diff --git a/src/Metadata/SourcePropertyMetadata.php b/src/Metadata/SourcePropertyMetadata.php index b2827a5b..eebc981a 100644 --- a/src/Metadata/SourcePropertyMetadata.php +++ b/src/Metadata/SourcePropertyMetadata.php @@ -6,6 +6,7 @@ use AutoMapper\Event\SourcePropertyMetadata as SourcePropertyMetadataEvent; use AutoMapper\Extractor\ReadAccessor; +use Symfony\Component\TypeInfo\Type; /** * Source Property metadata. @@ -25,6 +26,7 @@ public function __construct( public bool $checkExists = false, public ?array $groups = null, public string $dateTimeFormat = \DateTimeInterface::RFC3339, + public ?Type $type = null, ) { } @@ -38,4 +40,16 @@ public static function fromEvent(SourcePropertyMetadataEvent $metadata): self $metadata->dateTimeFormat ?? \DateTimeInterface::RFC3339, ); } + + public function withType(?Type $type): self + { + return new self( + $this->property, + $this->accessor, + $this->checkExists, + $this->groups, + $this->dateTimeFormat, + $type, + ); + } } diff --git a/src/Metadata/TargetPropertyMetadata.php b/src/Metadata/TargetPropertyMetadata.php index 8010fae0..efcc2573 100644 --- a/src/Metadata/TargetPropertyMetadata.php +++ b/src/Metadata/TargetPropertyMetadata.php @@ -7,6 +7,7 @@ use AutoMapper\Event\TargetPropertyMetadata as EventTargetPropertyMetadata; use AutoMapper\Extractor\ReadAccessor; use AutoMapper\Extractor\WriteMutator; +use Symfony\Component\TypeInfo\Type; /** * Target Property metadata. @@ -27,6 +28,7 @@ public function __construct( public ?string $parameterInConstructor = null, public ?array $groups = null, public string $dateTimeFormat = \DateTimeInterface::RFC3339, + public ?Type $type = null, ) { } @@ -39,6 +41,20 @@ public static function fromEvent(EventTargetPropertyMetadata $metadata): self $metadata->parameterInConstructor, $metadata->groups, $metadata->dateTimeFormat ?? \DateTimeInterface::RFC3339, + $metadata->type, + ); + } + + public function withType(?Type $type): self + { + return new self( + $this->property, + $this->readAccessor, + $this->writeMutator, + $this->parameterInConstructor, + $this->groups, + $this->dateTimeFormat, + $type, ); } } diff --git a/src/Metadata/TypesMatching.php b/src/Metadata/TypesMatching.php deleted file mode 100644 index ee169e57..00000000 --- a/src/Metadata/TypesMatching.php +++ /dev/null @@ -1,64 +0,0 @@ - - */ -final class TypesMatching extends \SplObjectStorage -{ - /** - * Return the source Type if there is only one in the SplObjectStorage, null otherwise. - */ - public function getSourceUniqueType(): ?Type - { - if (0 === \count($this) || \count($this) > 1) { - return null; - } - - // Get first type from the SplObjectStorage - $this->rewind(); - $sourceType = $this->current(); - - if (!$sourceType instanceof Type) { - return null; - } - - return $sourceType; - } - - /** - * Return a target Type given a source Type if there is only one in the SplObjectStorage, null otherwise. - */ - public function getTargetUniqueType(Type $sourceType): ?Type - { - $targetTypes = $this[$sourceType] ?? []; - - if (0 === \count($targetTypes) || \count($targetTypes) > 1 || !$targetTypes[0] instanceof Type) { - return null; - } - - return $targetTypes[0]; - } - - /** - * @param Type[] $sourceTypes - * @param Type[] $targetTypes - */ - public static function fromSourceAndTargetTypes(array $sourceTypes, array $targetTypes): self - { - $types = new self(); - - foreach ($sourceTypes as $sourceType) { - $types[$sourceType] = $targetTypes; - } - - return $types; - } -} diff --git a/src/Symfony/Bundle/Command/DebugMapperCommand.php b/src/Symfony/Bundle/Command/DebugMapperCommand.php index 9a0ee51b..48872744 100644 --- a/src/Symfony/Bundle/Command/DebugMapperCommand.php +++ b/src/Symfony/Bundle/Command/DebugMapperCommand.php @@ -75,9 +75,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int [\sprintf('%s -> %s', $source, $target), 'If', 'Transformer', 'Groups', 'MaxDepth'], array_map( fn (PropertyMetadata $property) => [ - $property->source->property . ' -> ' . $property->target->property, + $property->source->property . ' (' . $property->source->type . ') -> ' . $property->target->property . ' (' . $property->target->type . ') ', $property->if, - \get_class($property->transformer), + $property->transformer instanceof \Stringable ? (string) $property->transformer : \get_class($property->transformer), $property->disableGroupsCheck ? 'Disabled' : implode(', ', $property->groups ?? []), $property->maxDepth, ], @@ -91,7 +91,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int [\sprintf('%s -> %s', $source, $target), 'Not used reason'], array_map( fn (PropertyMetadata $property) => [ - $property->source->property . ' -> ' . $property->target->property, + $property->source->property . ' (' . $property->source->type . ') -> ' . $property->target->property . ' (' . $property->target->type . ') ', $property->ignoreReason, ], array_filter($metadata->propertiesMetadata, fn (PropertyMetadata $property) => $property->ignored) diff --git a/src/Symfony/Bundle/DependencyInjection/AutoMapperExtension.php b/src/Symfony/Bundle/DependencyInjection/AutoMapperExtension.php index 1d805161..713c3b88 100644 --- a/src/Symfony/Bundle/DependencyInjection/AutoMapperExtension.php +++ b/src/Symfony/Bundle/DependencyInjection/AutoMapperExtension.php @@ -14,7 +14,6 @@ use AutoMapper\Loader\EvalLoader; use AutoMapper\Loader\FileLoader; use AutoMapper\Loader\FileReloadStrategy; -use AutoMapper\Metadata\MetadataFactory; use AutoMapper\Normalizer\AutoMapperNormalizer; use AutoMapper\Provider\ProviderInterface; use AutoMapper\Symfony\Bundle\CacheWarmup\CacheWarmer; diff --git a/src/Symfony/Bundle/Resources/config/metadata.php b/src/Symfony/Bundle/Resources/config/metadata.php index 5ec79cc1..9c814bda 100644 --- a/src/Symfony/Bundle/Resources/config/metadata.php +++ b/src/Symfony/Bundle/Resources/config/metadata.php @@ -18,25 +18,31 @@ ->set(SourceTargetMappingExtractor::class) ->args([ service(Configuration::class), - service('automapper.property_info'), service('automapper.property_info.reflection_extractor'), service('automapper.property_info.reflection_extractor'), + service('automapper.property_info.reflection_extractor'), + service('automapper.property_info.source_type_extractor'), + service('automapper.property_info.target_type_extractor'), ]) ->set(FromSourceMappingExtractor::class) ->args([ service(Configuration::class), - service('automapper.property_info'), service('automapper.property_info.reflection_extractor'), service('automapper.property_info.reflection_extractor'), + service('automapper.property_info.reflection_extractor'), + service('automapper.property_info.source_type_extractor'), + service('automapper.property_info.target_type_extractor'), ]) ->set(FromTargetMappingExtractor::class) ->args([ service(Configuration::class), - service('automapper.property_info'), service('automapper.property_info.reflection_extractor'), service('automapper.property_info.reflection_extractor'), + service('automapper.property_info.reflection_extractor'), + service('automapper.property_info.source_type_extractor'), + service('automapper.property_info.target_type_extractor'), ]) ->set(MetadataFactory::class) diff --git a/src/Symfony/Bundle/Resources/config/property_info.php b/src/Symfony/Bundle/Resources/config/property_info.php index 733ecdca..b6181e53 100644 --- a/src/Symfony/Bundle/Resources/config/property_info.php +++ b/src/Symfony/Bundle/Resources/config/property_info.php @@ -4,7 +4,6 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; -use AutoMapper\Extractor\ReadWriteTypeExtractor; use Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\PropertyInfoExtractor; @@ -12,23 +11,40 @@ return static function (ContainerConfigurator $container) { $container ->services() - ->set('automapper.property_info.reflection_extractor', ReflectionExtractor::class) + ->set('automapper.property_info.reflection_source_extractor', ReflectionExtractor::class) + ->args([ + '$accessFlags' => ReflectionExtractor::ALLOW_PUBLIC, + '$mutatorPrefixes' => [], + '$arrayMutatorPrefixes' => [], + '$enableConstructorExtraction' => false, + ]) + ->set('automapper.property_info.reflection_target_extractor', ReflectionExtractor::class) ->args([ - '$mutatorPrefixes' => null, - '$accessorPrefixes' => null, - '$arrayMutatorPrefixes' => null, - '$enableConstructorExtraction' => true, '$accessFlags' => ReflectionExtractor::ALLOW_PUBLIC, + '$accessorPrefixes' => [], + ]) + ->set('automapper.property_info.phpstan_source_extractor', PhpStanExtractor::class) + ->args([ + '$allowPrivateAccess' => false, + '$mutatorPrefixes' => [], + '$arrayMutatorPrefixes' => [], + ]) + ->set('automapper.property_info.phpstan_target_extractor', PhpStanExtractor::class) + ->args([ + '$allowPrivateAccess' => false, + '$accessorPrefixes' => [], + ]) + ->set('automapper.property_info.source_type_extractor', PropertyInfoExtractor::class) + ->args([ + '$typeExtractors' => [service('automapper.property_info.phpstan_source_extractor'), service('automapper.property_info.reflection_source_extractor')], ]) - ->set('automapper.property_info.read_write_type_extractor', ReadWriteTypeExtractor::class) - ->set('automapper.property_info.phpstan_extractor', PhpStanExtractor::class) - ->set('automapper.property_info', PropertyInfoExtractor::class) + ->set('automapper.property_info.target_type_extractor', PropertyInfoExtractor::class) ->args([ - [service('automapper.property_info.reflection_extractor')], - [service('automapper.property_info.read_write_type_extractor'), service('automapper.property_info.phpstan_extractor'), service('automapper.property_info.reflection_extractor')], - [service('automapper.property_info.reflection_extractor')], - [service('automapper.property_info.reflection_extractor')], - [service('automapper.property_info.reflection_extractor')], + '$typeExtractors' => [service('automapper.property_info.phpstan_target_extractor'), service('automapper.property_info.reflection_target_extractor')], + ]) + ->set('automapper.property_info.reflection_extractor', ReflectionExtractor::class) + ->args([ + '$accessFlags' => ReflectionExtractor::ALLOW_PUBLIC, ]) ; }; diff --git a/src/Symfony/Bundle/Resources/config/transformers.php b/src/Symfony/Bundle/Resources/config/transformers.php index 794ec674..f0f217a1 100644 --- a/src/Symfony/Bundle/Resources/config/transformers.php +++ b/src/Symfony/Bundle/Resources/config/transformers.php @@ -9,8 +9,11 @@ use AutoMapper\Transformer\ChainTransformerFactory; use AutoMapper\Transformer\CopyTransformerFactory; use AutoMapper\Transformer\DateTimeTransformerFactory; +use AutoMapper\Transformer\DoctrineCollectionTransformerFactory; use AutoMapper\Transformer\EnumTransformerFactory; +use AutoMapper\Transformer\MixedTransformerFactory; use AutoMapper\Transformer\MultipleTransformerFactory; +use AutoMapper\Transformer\NullableTargetTransformerFactory; use AutoMapper\Transformer\NullableTransformerFactory; use AutoMapper\Transformer\ObjectTransformerFactory; use AutoMapper\Transformer\SymfonyUidTransformerFactory; @@ -26,9 +29,12 @@ ->alias(TransformerFactoryInterface::class, ChainTransformerFactory::class) ->set(MultipleTransformerFactory::class) - ->tag('automapper.transformer_factory', ['priority' => 1002]) + ->tag('automapper.transformer_factory', ['priority' => 1003]) ->set(NullableTransformerFactory::class) + ->tag('automapper.transformer_factory', ['priority' => 1002]) + + ->set(NullableTargetTransformerFactory::class) ->tag('automapper.transformer_factory', ['priority' => 1001]) ->set(UniqueTypeTransformerFactory::class) @@ -37,6 +43,9 @@ ->set(DateTimeTransformerFactory::class) ->tag('automapper.transformer_factory', ['priority' => -1000]) + ->set(DoctrineCollectionTransformerFactory::class) + ->tag('automapper.transformer_factory', ['priority' => -1000]) + ->set(BuiltinTransformerFactory::class) ->tag('automapper.transformer_factory', ['priority' => -1001]) @@ -49,9 +58,12 @@ ->set(EnumTransformerFactory::class) ->tag('automapper.transformer_factory', ['priority' => -1004]) - ->set(CopyTransformerFactory::class) + ->set(MixedTransformerFactory::class) ->tag('automapper.transformer_factory', ['priority' => -1005]) + ->set(CopyTransformerFactory::class) + ->tag('automapper.transformer_factory', ['priority' => -1006]) + ->set(SymfonyUidTransformerFactory::class) ; }; diff --git a/src/Transformer/AbstractArrayTransformer.php b/src/Transformer/AbstractArrayTransformer.php index ecb5bf24..e28b712b 100644 --- a/src/Transformer/AbstractArrayTransformer.php +++ b/src/Transformer/AbstractArrayTransformer.php @@ -15,10 +15,11 @@ /** * @author Baptiste Leduc + *M * * @internal */ -abstract readonly class AbstractArrayTransformer implements TransformerInterface, DependentTransformerInterface +abstract readonly class AbstractArrayTransformer implements \Stringable, TransformerInterface, DependentTransformerInterface { public function __construct( protected TransformerInterface $itemTransformer, @@ -157,4 +158,9 @@ public function getDependencies(): array return $this->itemTransformer->getDependencies(); } + + public function __toString(): string + { + return \sprintf('%s<%s>', static::class, $this->itemTransformer instanceof \Stringable ? (string) $this->itemTransformer : \get_class($this->itemTransformer)); + } } diff --git a/src/Transformer/AbstractUniqueTypeTransformerFactory.php b/src/Transformer/AbstractUniqueTypeTransformerFactory.php deleted file mode 100644 index 5c199aa1..00000000 --- a/src/Transformer/AbstractUniqueTypeTransformerFactory.php +++ /dev/null @@ -1,40 +0,0 @@ - - * - * @internal - */ -abstract class AbstractUniqueTypeTransformerFactory implements TransformerFactoryInterface -{ - public function getTransformer(TypesMatching $types, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface - { - $sourceType = $types->getSourceUniqueType(); - - if (null === $sourceType) { - return null; - } - - $targetType = $types->getTargetUniqueType($sourceType); - - if (null === $targetType) { - return null; - } - - return $this->createTransformer($sourceType, $targetType, $source, $target, $mapperMetadata); - } - - abstract protected function createTransformer(Type $sourceType, Type $targetType, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface; -} diff --git a/src/Transformer/ArrayToDoctrineCollectionTransformer.php b/src/Transformer/ArrayToDoctrineCollectionTransformer.php index cb527bda..a515ff0d 100644 --- a/src/Transformer/ArrayToDoctrineCollectionTransformer.php +++ b/src/Transformer/ArrayToDoctrineCollectionTransformer.php @@ -19,7 +19,7 @@ * * @author Baptiste Leduc */ -final class ArrayToDoctrineCollectionTransformer implements TransformerInterface, DependentTransformerInterface, PrioritizedTransformerFactoryInterface +final class ArrayToDoctrineCollectionTransformer implements \Stringable, TransformerInterface, DependentTransformerInterface { public function __construct( protected TransformerInterface $itemTransformer, @@ -89,8 +89,8 @@ public function getDependencies(): array return $this->itemTransformer->getDependencies(); } - public function getPriority(): int + public function __toString(): string { - return 0; + return \sprintf('%s<%s>', static::class, $this->itemTransformer instanceof \Stringable ? (string) $this->itemTransformer : \get_class($this->itemTransformer)); } } diff --git a/src/Transformer/ArrayTransformerFactory.php b/src/Transformer/ArrayTransformerFactory.php index de6cf1e8..2dfd7e83 100644 --- a/src/Transformer/ArrayTransformerFactory.php +++ b/src/Transformer/ArrayTransformerFactory.php @@ -7,8 +7,8 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeIdentifier; /** * Create a decorated transformer to handle array type. @@ -17,49 +17,39 @@ * * @internal */ -final class ArrayTransformerFactory extends AbstractUniqueTypeTransformerFactory implements PrioritizedTransformerFactoryInterface, ChainTransformerFactoryAwareInterface +final class ArrayTransformerFactory implements TransformerFactoryInterface, PrioritizedTransformerFactoryInterface, ChainTransformerFactoryAwareInterface { use ChainTransformerFactoryAwareTrait; - protected function createTransformer(Type $sourceType, Type $targetType, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface + public function getTransformer(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface { - if (!($sourceType->isCollection() || ($sourceType->getBuiltinType() === Type::BUILTIN_TYPE_OBJECT && $sourceType->getClassName() === \Generator::class))) { + $sourceType = $source->type; + $targetType = $target->type; + + if (null === $sourceType || null === $targetType) { return null; } - if (!$targetType->isCollection()) { + if (!$this->isCollectionType($sourceType) || !$this->isCollectionType($targetType)) { return null; } - $sourceCollections = $sourceType->getCollectionValueTypes(); - $targetCollections = $targetType->getCollectionValueTypes(); + $sourceCollectionType = $sourceType instanceof Type\CollectionType ? $sourceType->getCollectionValueType() : Type::mixed(); + $targetCollectionType = $targetType instanceof Type\CollectionType ? $targetType->getCollectionValueType() : Type::mixed(); - if ([] === $sourceCollections && [] !== $targetCollections) { - // consider array as a collection of array - $sourceCollections = [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, false)]; - } + $newSource = $source->withType($sourceCollectionType); + $newTarget = $target->withType($targetCollectionType); - if ([] !== $sourceCollections && [] === $targetCollections) { - // consider array as a collection of array - $targetCollections = [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, false)]; - } - - if ([] === $sourceCollections || [] === $targetCollections) { - return new DictionaryTransformer(new CopyTransformer()); - } - - $types = TypesMatching::fromSourceAndTargetTypes($sourceCollections, $targetCollections); - $subItemTransformer = $this->chainTransformerFactory->getTransformer($types, $source, $target, $mapperMetadata); + $subItemTransformer = $this->chainTransformerFactory->getTransformer($newSource, $newTarget, $mapperMetadata); if (null !== $subItemTransformer) { if ($subItemTransformer instanceof ObjectTransformer) { $subItemTransformer->deepTargetToPopulate = false; } - $sourceCollectionKeyTypes = $sourceType->getCollectionKeyTypes(); - $sourceCollectionKeyType = $sourceCollectionKeyTypes[0] ?? null; + $sourceCollectionKeyType = $sourceType instanceof Type\CollectionType ? $sourceType->getCollectionKeyType() : Type::mixed(); - if ($sourceCollectionKeyType instanceof Type && Type::BUILTIN_TYPE_INT !== $sourceCollectionKeyType->getBuiltinType()) { + if ($sourceCollectionKeyType instanceof Type\BuiltinType && $sourceCollectionKeyType->getTypeIdentifier() !== TypeIdentifier::INT) { return new DictionaryTransformer($subItemTransformer); } @@ -69,6 +59,19 @@ protected function createTransformer(Type $sourceType, Type $targetType, SourceP return null; } + private function isCollectionType(Type $type): bool + { + if ($type instanceof Type\CollectionType) { + return true; + } + + if ($type instanceof Type\ObjectType && is_a($type->getClassName(), \Traversable::class, true)) { + return true; + } + + return false; + } + public function getPriority(): int { return 4; diff --git a/src/Transformer/BuiltinTransformer.php b/src/Transformer/BuiltinTransformer.php index aa699a3c..dfddaab0 100644 --- a/src/Transformer/BuiltinTransformer.php +++ b/src/Transformer/BuiltinTransformer.php @@ -10,7 +10,8 @@ use PhpParser\Node\Expr; use PhpParser\Node\Expr\Cast; use PhpParser\Node\Name; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeIdentifier; use function AutoMapper\PhpParser\create_expr_array_item; @@ -24,73 +25,91 @@ final readonly class BuiltinTransformer implements TransformerInterface, CheckTypeInterface { private const CAST_MAPPING = [ - Type::BUILTIN_TYPE_BOOL => [ - Type::BUILTIN_TYPE_INT => Cast\Int_::class, - Type::BUILTIN_TYPE_STRING => Cast\String_::class, - Type::BUILTIN_TYPE_FLOAT => Cast\Double::class, - Type::BUILTIN_TYPE_ARRAY => 'toArray', - Type::BUILTIN_TYPE_ITERABLE => 'toArray', + TypeIdentifier::BOOL->value => [ + TypeIdentifier::INT->value => Cast\Int_::class, + TypeIdentifier::STRING->value => Cast\String_::class, + TypeIdentifier::FLOAT->value => Cast\Double::class, + TypeIdentifier::ARRAY->value => 'toArray', + TypeIdentifier::ITERABLE->value => 'toArray', ], - Type::BUILTIN_TYPE_FLOAT => [ - Type::BUILTIN_TYPE_STRING => Cast\String_::class, - Type::BUILTIN_TYPE_INT => Cast\Int_::class, - Type::BUILTIN_TYPE_BOOL => Cast\Bool_::class, - Type::BUILTIN_TYPE_ARRAY => 'toArray', - Type::BUILTIN_TYPE_ITERABLE => 'toArray', + TypeIdentifier::MIXED->value => [ + TypeIdentifier::INT->value => Cast\Int_::class, + TypeIdentifier::STRING->value => Cast\String_::class, + TypeIdentifier::FLOAT->value => Cast\Double::class, + TypeIdentifier::ARRAY->value => 'toArray', + TypeIdentifier::ITERABLE->value => 'toArray', ], - Type::BUILTIN_TYPE_INT => [ - Type::BUILTIN_TYPE_FLOAT => Cast\Double::class, - Type::BUILTIN_TYPE_STRING => Cast\String_::class, - Type::BUILTIN_TYPE_BOOL => Cast\Bool_::class, - Type::BUILTIN_TYPE_ARRAY => 'toArray', - Type::BUILTIN_TYPE_ITERABLE => 'toArray', + TypeIdentifier::FLOAT->value => [ + TypeIdentifier::STRING->value => Cast\String_::class, + TypeIdentifier::INT->value => Cast\Int_::class, + TypeIdentifier::BOOL->value => Cast\Bool_::class, + TypeIdentifier::ARRAY->value => 'toArray', + TypeIdentifier::ITERABLE->value => 'toArray', ], - Type::BUILTIN_TYPE_ITERABLE => [ - Type::BUILTIN_TYPE_ARRAY => 'fromIteratorToArray', + TypeIdentifier::INT->value => [ + TypeIdentifier::FLOAT->value => Cast\Double::class, + TypeIdentifier::STRING->value => Cast\String_::class, + TypeIdentifier::BOOL->value => Cast\Bool_::class, + TypeIdentifier::ARRAY->value => 'toArray', + TypeIdentifier::ITERABLE->value => 'toArray', ], - Type::BUILTIN_TYPE_ARRAY => [], - Type::BUILTIN_TYPE_STRING => [ - Type::BUILTIN_TYPE_ARRAY => 'toArray', - Type::BUILTIN_TYPE_ITERABLE => 'toArray', - Type::BUILTIN_TYPE_FLOAT => Cast\Double::class, - Type::BUILTIN_TYPE_INT => Cast\Int_::class, - Type::BUILTIN_TYPE_BOOL => Cast\Bool_::class, + TypeIdentifier::ITERABLE->value => [ + TypeIdentifier::ARRAY->value => 'fromIteratorToArray', ], - Type::BUILTIN_TYPE_CALLABLE => [], - Type::BUILTIN_TYPE_RESOURCE => [], + TypeIdentifier::ARRAY->value => [], + TypeIdentifier::STRING->value => [ + TypeIdentifier::ARRAY->value => 'toArray', + TypeIdentifier::ITERABLE->value => 'toArray', + TypeIdentifier::FLOAT->value => Cast\Double::class, + TypeIdentifier::INT->value => Cast\Int_::class, + TypeIdentifier::BOOL->value => Cast\Bool_::class, + ], + TypeIdentifier::CALLABLE->value => [], + TypeIdentifier::RESOURCE->value => [], ]; private const CONDITION_MAPPING = [ - Type::BUILTIN_TYPE_BOOL => 'is_bool', - Type::BUILTIN_TYPE_INT => 'is_int', - Type::BUILTIN_TYPE_FLOAT => 'is_float', - Type::BUILTIN_TYPE_STRING => 'is_string', - Type::BUILTIN_TYPE_ARRAY => 'is_array', - Type::BUILTIN_TYPE_OBJECT => 'is_object', - Type::BUILTIN_TYPE_RESOURCE => 'is_resource', - Type::BUILTIN_TYPE_CALLABLE => 'is_callable', - Type::BUILTIN_TYPE_ITERABLE => 'is_iterable', + TypeIdentifier::BOOL->value => 'is_bool', + TypeIdentifier::INT->value => 'is_int', + TypeIdentifier::FLOAT->value => 'is_float', + TypeIdentifier::STRING->value => 'is_string', + TypeIdentifier::ARRAY->value => 'is_array', + TypeIdentifier::OBJECT->value => 'is_object', + TypeIdentifier::RESOURCE->value => 'is_resource', + TypeIdentifier::CALLABLE->value => 'is_callable', + TypeIdentifier::ITERABLE->value => 'is_iterable', ]; + /** + * @param Type\BuiltinType $sourceType + */ public function __construct( - private Type $sourceType, - /** @var Type[] $targetTypes */ - private array $targetTypes, + private Type\BuiltinType $sourceType, + private Type $targetType, ) { } public function transform(Expr $input, Expr $target, PropertyMetadata $propertyMapping, UniqueVariableScope $uniqueVariableScope, Expr\Variable $source, ?Expr $existingValue = null): array { - $targetTypes = array_map(function (Type $type) { - return $type->getBuiltinType(); - }, $this->targetTypes); + $targetTypes = []; + + foreach ($this->targetType->traverse() as $type) { + if ($type instanceof Type\BuiltinType) { + $targetTypes[] = $type->getTypeIdentifier()->value; + + if ($type->getTypeIdentifier() === $this->sourceType->getTypeIdentifier()) { + /* Output type can be the same as input type so we simply return the same expression */ + return [$input, []]; + } + } + } - if (\in_array($this->sourceType->getBuiltinType(), $targetTypes, true)) { - /* Output type is the same as input type so we simply return the same expression */ + if (!$targetTypes) { + /* When there is no possibility to cast we assume that the mutator will be able to handle the value */ return [$input, []]; } - foreach (self::CAST_MAPPING[$this->sourceType->getBuiltinType()] as $castType => $castMethod) { + foreach (self::CAST_MAPPING[$this->sourceType->getTypeIdentifier()->value] as $castType => $castMethod) { if (\in_array($castType, $targetTypes, true)) { if (method_exists($this, $castMethod)) { /* @@ -116,25 +135,20 @@ public function transform(Expr $input, Expr $target, PropertyMetadata $propertyM public function getCheckExpression(Expr $input, Expr $target, PropertyMetadata $propertyMapping, UniqueVariableScope $uniqueVariableScope, Expr\Variable $source): ?Expr { - if ($this->sourceType->getBuiltinType() === Type::BUILTIN_TYPE_NULL) { + if ($this->sourceType->getTypeIdentifier() === TypeIdentifier::NULL) { return null; } - $condition = new Expr\FuncCall( - new Name(self::CONDITION_MAPPING[$this->sourceType->getBuiltinType()]), + if (!isset(self::CONDITION_MAPPING[$this->sourceType->getTypeIdentifier()->value])) { + return null; + } + + return new Expr\FuncCall( + new Name(self::CONDITION_MAPPING[$this->sourceType->getTypeIdentifier()->value]), [ new Arg($input), ] ); - - if ($this->sourceType->getBuiltinType() === Type::BUILTIN_TYPE_OBJECT && \is_string($this->sourceType->getClassName())) { - $condition = new Expr\BinaryOp\BooleanAnd( - $condition, - new Expr\Instanceof_($input, new Name\FullyQualified($this->sourceType->getClassName())) - ); - } - - return $condition; } private function toArray(Expr $input): Expr diff --git a/src/Transformer/BuiltinTransformerFactory.php b/src/Transformer/BuiltinTransformerFactory.php index 8c673dec..273ee60f 100644 --- a/src/Transformer/BuiltinTransformerFactory.php +++ b/src/Transformer/BuiltinTransformerFactory.php @@ -7,8 +7,8 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeIdentifier; /** * @author Joel Wurtz @@ -17,29 +17,15 @@ */ final class BuiltinTransformerFactory implements TransformerFactoryInterface, PrioritizedTransformerFactoryInterface { - private const BUILTIN = [ - Type::BUILTIN_TYPE_BOOL, - Type::BUILTIN_TYPE_CALLABLE, - Type::BUILTIN_TYPE_FLOAT, - Type::BUILTIN_TYPE_INT, - Type::BUILTIN_TYPE_ITERABLE, - Type::BUILTIN_TYPE_NULL, - Type::BUILTIN_TYPE_RESOURCE, - Type::BUILTIN_TYPE_STRING, - ]; - - public function getTransformer(TypesMatching $types, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface + public function getTransformer(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface { - $sourceType = $types->getSourceUniqueType(); - - if (null === $sourceType) { + if (null === $source->type) { return null; } - $targetTypes = $types[$sourceType] ?? []; - - if (\in_array($sourceType->getBuiltinType(), self::BUILTIN, true)) { - return new BuiltinTransformer($sourceType, $targetTypes); + // We don't want to handle mixed here as we can better guess the type with other transformers + if ($source->type instanceof Type\BuiltinType && $source->type->getTypeIdentifier() !== TypeIdentifier::MIXED && $source->type->getTypeIdentifier() !== TypeIdentifier::NULL) { + return new BuiltinTransformer($source->type, $target->type ?? Type::mixed()); } return null; @@ -47,6 +33,6 @@ public function getTransformer(TypesMatching $types, SourcePropertyMetadata $sou public function getPriority(): int { - return 8; + return 16; } } diff --git a/src/Transformer/ChainTransformerFactory.php b/src/Transformer/ChainTransformerFactory.php index ed5514d7..4973fa35 100644 --- a/src/Transformer/ChainTransformerFactory.php +++ b/src/Transformer/ChainTransformerFactory.php @@ -7,14 +7,13 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; /** * @author Joel Wurtz * * @internal */ -final class ChainTransformerFactory implements TransformerFactoryInterface +class ChainTransformerFactory implements TransformerFactoryInterface { /** * @param array $factories @@ -31,11 +30,11 @@ public function __construct( $this->sortFactories(); } - public function getTransformer(TypesMatching $types, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface + public function getTransformer(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface { foreach ($this->factories as $factory) { if ($factory instanceof TransformerFactoryInterface) { - $transformer = $factory->getTransformer($types, $source, $target, $mapperMetadata); + $transformer = $factory->getTransformer($source, $target, $mapperMetadata); if (null !== $transformer) { return $transformer; diff --git a/src/Transformer/CopyTransformerFactory.php b/src/Transformer/CopyTransformerFactory.php index b6649028..2153a4cb 100644 --- a/src/Transformer/CopyTransformerFactory.php +++ b/src/Transformer/CopyTransformerFactory.php @@ -7,16 +7,15 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; /** * @internal */ final readonly class CopyTransformerFactory implements TransformerFactoryInterface, PrioritizedTransformerFactoryInterface { - public function getTransformer(TypesMatching $types, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface + public function getTransformer(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface { - if (\count($types) >= 1) { + if ($source->type !== null) { return null; } diff --git a/src/Transformer/DateTimeTransformerFactory.php b/src/Transformer/DateTimeTransformerFactory.php index e183c23f..0132c5a0 100644 --- a/src/Transformer/DateTimeTransformerFactory.php +++ b/src/Transformer/DateTimeTransformerFactory.php @@ -7,36 +7,37 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeIdentifier; /** * @author Joel Wurtz * * @internal */ -final class DateTimeTransformerFactory extends AbstractUniqueTypeTransformerFactory implements PrioritizedTransformerFactoryInterface +final class DateTimeTransformerFactory implements TransformerFactoryInterface, PrioritizedTransformerFactoryInterface { - protected function createTransformer(Type $sourceType, Type $targetType, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface + public function getTransformer(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface { - $isSourceDate = $this->isDateTimeType($sourceType); - $isTargetDate = $this->isDateTimeType($targetType); + $isSourceDate = $this->isDateTimeType($source->type); + $isTargetDate = $this->isDateTimeType($target->type); if ($isSourceDate && $isTargetDate) { - return $this->createTransformerForSourceAndTarget($sourceType, $targetType); + return $this->createTransformerForSourceAndTarget($target->type); } if ($isSourceDate) { - return $this->createTransformerForSource($targetType, $source); + return $this->createTransformerForSource($target->type, $source); } if ($isTargetDate) { - return $this->createTransformerForTarget($sourceType, $targetType, $target); + return $this->createTransformerForTarget($source->type, $target->type, $target); } return null; } - protected function createTransformerForSourceAndTarget(Type $sourceType, Type $targetType): ?TransformerInterface + protected function createTransformerForSourceAndTarget(?Type $targetType): ?TransformerInterface { // if target is mutable if ($this->isDateTimeMutable($targetType)) { @@ -47,31 +48,27 @@ protected function createTransformerForSourceAndTarget(Type $sourceType, Type $t return new DateTimeInterfaceToImmutableTransformer(); } - protected function createTransformerForSource(Type $targetType, SourcePropertyMetadata $metadata): ?TransformerInterface + protected function createTransformerForSource(?Type $targetType, SourcePropertyMetadata $metadata): ?TransformerInterface { - if (Type::BUILTIN_TYPE_STRING === $targetType->getBuiltinType()) { + if ($targetType !== null && $targetType->isIdentifiedBy(TypeIdentifier::STRING)) { return new DateTimeToStringTransformer($metadata->dateTimeFormat); } return null; } - protected function createTransformerForTarget(Type $sourceType, Type $targetType, TargetPropertyMetadata $metadata): ?TransformerInterface + protected function createTransformerForTarget(?Type $sourceType, ?Type $targetType, TargetPropertyMetadata $metadata): ?TransformerInterface { - if (Type::BUILTIN_TYPE_STRING === $sourceType->getBuiltinType()) { + if ($sourceType !== null && $sourceType->isIdentifiedBy(TypeIdentifier::STRING)) { return new StringToDateTimeTransformer($this->getClassName($targetType), $metadata->dateTimeFormat); } return null; } - private function isDateTimeType(Type $type): bool + private function isDateTimeType(?Type $type): bool { - if (Type::BUILTIN_TYPE_OBJECT !== $type->getBuiltinType()) { - return false; - } - - if (null === $type->getClassName()) { + if (!$type instanceof Type\ObjectType) { return false; } @@ -82,18 +79,22 @@ private function isDateTimeType(Type $type): bool return true; } - private function getClassName(Type $type): string + private function getClassName(?Type $type): string { - if (\DateTimeInterface::class === $type->getClassName() || $type->getClassName() === null) { + if (!$type instanceof Type\ObjectType) { + return \DateTimeImmutable::class; + } + + if (\DateTimeInterface::class === $type->getClassName()) { return \DateTimeImmutable::class; } return $type->getClassName(); } - private function isDateTimeMutable(Type $type): bool + private function isDateTimeMutable(?Type $type): bool { - if (null === $type->getClassName()) { + if (!$type instanceof Type\ObjectType) { return false; } @@ -106,6 +107,6 @@ private function isDateTimeMutable(Type $type): bool public function getPriority(): int { - return 16; + return 32; } } diff --git a/src/Transformer/DictionaryTransformer.php b/src/Transformer/DictionaryTransformer.php index f08ce822..196a35d0 100644 --- a/src/Transformer/DictionaryTransformer.php +++ b/src/Transformer/DictionaryTransformer.php @@ -4,7 +4,13 @@ namespace AutoMapper\Transformer; +use AutoMapper\Generator\UniqueVariableScope; +use AutoMapper\Metadata\PropertyMetadata; +use PhpParser\Node\Arg; use PhpParser\Node\Expr; +use PhpParser\Node\Name; + +use function AutoMapper\PhpParser\create_scalar_int; /** * Transformer dictionary decorator. @@ -13,7 +19,7 @@ * * @internal */ -final readonly class DictionaryTransformer extends AbstractArrayTransformer +final readonly class DictionaryTransformer extends AbstractArrayTransformer implements CheckTypeInterface { /** * Assign the value by using the key as the array key. @@ -28,4 +34,27 @@ protected function getAssignExpr(Expr $valuesVar, Expr $outputVar, Expr $loopKey return new Expr\Assign(new Expr\ArrayDimFetch($valuesVar, $loopKeyVar), $outputVar); } + + public function getCheckExpression(Expr $input, Expr $target, PropertyMetadata $propertyMapping, UniqueVariableScope $uniqueVariableScope, Expr\Variable $source): ?Expr + { + $isArrayCheck = new Expr\FuncCall( + new Name('is_iterable'), + [ + new Arg($input), + ] + ); + + if ($this->itemTransformer instanceof CheckTypeInterface) { + $itemCheck = $this->itemTransformer->getCheckExpression(new Expr\FuncCall(new Name('current'), [new Arg($input)]), $target, $propertyMapping, $uniqueVariableScope, $source); + + if ($itemCheck) { + return new Expr\BinaryOp\BooleanAnd($isArrayCheck, new Expr\BinaryOp\BooleanOr( + new Expr\BinaryOp\Identical(create_scalar_int(0), new Expr\FuncCall(new Name('count'), [new Arg($input)])), + $itemCheck + )); + } + } + + return $isArrayCheck; + } } diff --git a/src/Transformer/DoctrineCollectionTransformerFactory.php b/src/Transformer/DoctrineCollectionTransformerFactory.php index 338e2da2..e9e0666a 100644 --- a/src/Transformer/DoctrineCollectionTransformerFactory.php +++ b/src/Transformer/DoctrineCollectionTransformerFactory.php @@ -8,31 +8,46 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; use Doctrine\Common\Collections\Collection; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; /** * @author Baptiste Leduc */ -final class DoctrineCollectionTransformerFactory extends AbstractUniqueTypeTransformerFactory implements ChainTransformerFactoryAwareInterface +final class DoctrineCollectionTransformerFactory implements TransformerFactoryInterface, ChainTransformerFactoryAwareInterface { use ChainTransformerFactoryAwareTrait; - protected function createTransformer(Type $sourceType, Type $targetType, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface + public function getTransformer(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface { if (!interface_exists(Collection::class)) { return null; } - if (Type::BUILTIN_TYPE_OBJECT !== $targetType->getBuiltinType() || !\is_string($targetType->getClassName())) { + if (!$source->type instanceof Type\CollectionType || null === $target->type) { return null; } - if (Collection::class === $targetType->getClassName() || (false !== ($classImplements = class_implements($targetType->getClassName())) && \in_array(Collection::class, $classImplements))) { - $types = TypesMatching::fromSourceAndTargetTypes($sourceType->getCollectionValueTypes(), $targetType->getCollectionValueTypes()); + $isDoctrineCollection = $target->type->isSatisfiedBy(function (Type $type) { + if (!$type instanceof Type\ObjectType) { + return false; + } + + $className = $type->getClassName(); + $implementedClasses = class_implements($className); + + return $className === Collection::class || ($implementedClasses && \in_array(Collection::class, $implementedClasses, true)); + }); + + if ($isDoctrineCollection) { + $sourceItemType = $source->type->getCollectionValueType(); + $targetItemType = $target->type instanceof Type\CollectionType ? $target->type->getCollectionValueType() : Type::mixed(); + + $newSource = $source->withType($sourceItemType); + $newTarget = $target->withType($targetItemType); + + $subItemTransformer = $this->chainTransformerFactory->getTransformer($newSource, $newTarget, $mapperMetadata); - $subItemTransformer = $this->chainTransformerFactory->getTransformer($types, $source, $target, $mapperMetadata); if (null === $subItemTransformer) { return null; } diff --git a/src/Transformer/EnumTransformerFactory.php b/src/Transformer/EnumTransformerFactory.php index 65766b94..3b465ebe 100644 --- a/src/Transformer/EnumTransformerFactory.php +++ b/src/Transformer/EnumTransformerFactory.php @@ -7,47 +7,39 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; /** * @author Baptiste Leduc * * @internal */ -final class EnumTransformerFactory extends AbstractUniqueTypeTransformerFactory implements PrioritizedTransformerFactoryInterface +final class EnumTransformerFactory implements TransformerFactoryInterface, PrioritizedTransformerFactoryInterface { - protected function createTransformer(Type $sourceType, Type $targetType, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface + public function getTransformer(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface { // source is enum, target isn't - if ($this->isEnumType($sourceType, true) && !$this->isEnumType($targetType)) { + if ($this->isEnumType($source->type, true) && !$this->isEnumType($target->type)) { return new SourceEnumTransformer(); } // target is enum, source isn't - if (!$this->isEnumType($sourceType) && $this->isEnumType($targetType, true)) { + if (!$this->isEnumType($source->type) && $this->isEnumType($target->type, true)) { // @phpstan-ignore-next-line - return new TargetEnumTransformer($targetType->getClassName()); + return new TargetEnumTransformer($target->type->getClassName()); } // both source & target are enums - if ($this->isEnumType($sourceType) && $this->isEnumType($targetType)) { + if ($this->isEnumType($source->type) && $this->isEnumType($target->type)) { return new CopyEnumTransformer(); } return null; } - private function isEnumType(Type $type, bool $backed = false): bool + private function isEnumType(?Type $type, bool $backed = false): bool { - if (Type::BUILTIN_TYPE_OBJECT !== $type->getBuiltinType()) { - return false; - } - - if ($type->getClassName() === null) { - return false; - } - - if (!is_subclass_of($type->getClassName(), \UnitEnum::class)) { + if (!$type instanceof Type\EnumType) { return false; } diff --git a/src/Transformer/MixedTransformerFactory.php b/src/Transformer/MixedTransformerFactory.php new file mode 100644 index 00000000..45c63dc0 --- /dev/null +++ b/src/Transformer/MixedTransformerFactory.php @@ -0,0 +1,37 @@ + + * + * @internal + */ +final class MixedTransformerFactory implements TransformerFactoryInterface, PrioritizedTransformerFactoryInterface +{ + public function getTransformer(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface + { + if (null === $source->type) { + return null; + } + + if ($source->type instanceof Type\BuiltinType && $source->type->getTypeIdentifier() === TypeIdentifier::MIXED) { + return new BuiltinTransformer($source->type, $target->type ?? Type::mixed()); + } + + return null; + } + + public function getPriority(): int + { + return -32; + } +} diff --git a/src/Transformer/MultipleTransformer.php b/src/Transformer/MultipleTransformer.php index 2f3ff926..123224f0 100644 --- a/src/Transformer/MultipleTransformer.php +++ b/src/Transformer/MultipleTransformer.php @@ -8,7 +8,7 @@ use AutoMapper\Metadata\PropertyMetadata; use PhpParser\Node\Expr; use PhpParser\Node\Stmt; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; /** * Multiple transformer decorator. @@ -20,13 +20,13 @@ * * @internal */ -final class MultipleTransformer implements TransformerInterface, DependentTransformerInterface +final readonly class MultipleTransformer implements \Stringable, TransformerInterface, DependentTransformerInterface { /** * @param array $transformers */ public function __construct( - private readonly array $transformers, + private array $transformers, ) { } @@ -94,4 +94,20 @@ public function getDependencies(): array return $dependencies; } + + public function __toString(): string + { + $transformerStrings = array_map( + function ($transformerData) { + if ($transformerData['transformer'] instanceof \Stringable) { + return (string) $transformerData['transformer']; + } + + return \get_class($transformerData['transformer']); + }, + $this->transformers + ); + + return self::class . "<\n " . implode(",\n ", $transformerStrings) . "\n>"; + } } diff --git a/src/Transformer/MultipleTransformerFactory.php b/src/Transformer/MultipleTransformerFactory.php index 94ec6d32..b67bc822 100644 --- a/src/Transformer/MultipleTransformerFactory.php +++ b/src/Transformer/MultipleTransformerFactory.php @@ -7,7 +7,7 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; +use Symfony\Component\TypeInfo\Type\UnionType; /** * @author Joel Wurtz @@ -18,17 +18,17 @@ final class MultipleTransformerFactory implements TransformerFactoryInterface, P { use ChainTransformerFactoryAwareTrait; - public function getTransformer(TypesMatching $types, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface + public function getTransformer(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface { - if (\count($types) <= 1) { + if (!$source->type instanceof UnionType) { return null; } $transformers = []; - foreach ($types as $sourceType) { - $targetTypes = $types[$sourceType] ?? []; - $transformer = $this->chainTransformerFactory->getTransformer(TypesMatching::fromSourceAndTargetTypes([$sourceType], $targetTypes), $source, $target, $mapperMetadata); + foreach ($source->type->getTypes() as $sourceType) { + $newSource = $source->withType($sourceType); + $transformer = $this->chainTransformerFactory->getTransformer($newSource, $target, $mapperMetadata); if (null !== $transformer) { $transformers[] = [ @@ -51,6 +51,6 @@ public function getTransformer(TypesMatching $types, SourcePropertyMetadata $sou public function getPriority(): int { - return 128; + return 64; } } diff --git a/src/Transformer/NullableTargetTransformerFactory.php b/src/Transformer/NullableTargetTransformerFactory.php new file mode 100644 index 00000000..631416a1 --- /dev/null +++ b/src/Transformer/NullableTargetTransformerFactory.php @@ -0,0 +1,40 @@ + + * + * @internal + */ +final class NullableTargetTransformerFactory implements TransformerFactoryInterface, PrioritizedTransformerFactoryInterface, ChainTransformerFactoryAwareInterface +{ + use ChainTransformerFactoryAwareTrait; + + public function getTransformer(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface + { + if (null === $target->type) { + return null; + } + + if (!$target->type instanceof Type\NullableType) { + return null; + } + + $newTarget = $target->withType($target->type->getWrappedType()); + + return $this->chainTransformerFactory->getTransformer($source, $newTarget, $mapperMetadata); + } + + public function getPriority(): int + { + return 128; + } +} diff --git a/src/Transformer/NullableTransformer.php b/src/Transformer/NullableTransformer.php index a681df62..6800c35b 100644 --- a/src/Transformer/NullableTransformer.php +++ b/src/Transformer/NullableTransformer.php @@ -17,7 +17,7 @@ * * @internal */ -final readonly class NullableTransformer implements TransformerInterface, DependentTransformerInterface +final readonly class NullableTransformer implements \Stringable, TransformerInterface, DependentTransformerInterface { public function __construct( private TransformerInterface $itemTransformer, @@ -78,4 +78,9 @@ public function getDependencies(): array return $this->itemTransformer->getDependencies(); } + + public function __toString(): string + { + return static::class . '<' . \get_class($this->itemTransformer) . '>'; + } } diff --git a/src/Transformer/NullableTransformerFactory.php b/src/Transformer/NullableTransformerFactory.php index 13450c1c..ecc05c53 100644 --- a/src/Transformer/NullableTransformerFactory.php +++ b/src/Transformer/NullableTransformerFactory.php @@ -7,8 +7,7 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; /** * @author Joel Wurtz @@ -19,50 +18,24 @@ final class NullableTransformerFactory implements TransformerFactoryInterface, P { use ChainTransformerFactoryAwareTrait; - public function getTransformer(TypesMatching $types, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface + public function getTransformer(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface { - $sourceType = $types->getSourceUniqueType(); - - if (null === $sourceType) { + if (!$source->type instanceof Type\NullableType) { return null; } - if (!$sourceType->isNullable()) { - return null; - } - - $isTargetNullable = false; - $targetTypes = $types[$sourceType] ?? []; - - foreach ($targetTypes as $targetType) { - if ($targetType->isNullable()) { - $isTargetNullable = true; - - break; - } - } - - $newTypes = TypesMatching::fromSourceAndTargetTypes([new Type( - $sourceType->getBuiltinType(), - false, - $sourceType->getClassName(), - $sourceType->isCollection(), - $sourceType->getCollectionKeyTypes(), - $sourceType->getCollectionValueTypes() - )], $targetTypes); - - $subTransformer = $this->chainTransformerFactory->getTransformer($newTypes, $source, $target, $mapperMetadata); + $newSource = $source->withType($source->type->getWrappedType()); + $subTransformer = $this->chainTransformerFactory->getTransformer($newSource, $target, $mapperMetadata); if (null === $subTransformer) { return null; } - // Remove nullable property here to avoid infinite loop - return new NullableTransformer($subTransformer, $isTargetNullable); + return new NullableTransformer($subTransformer, $target->type?->isNullable() ?? true); } public function getPriority(): int { - return 64; + return 128; } } diff --git a/src/Transformer/ObjectTransformer.php b/src/Transformer/ObjectTransformer.php index a105f512..7a20c705 100644 --- a/src/Transformer/ObjectTransformer.php +++ b/src/Transformer/ObjectTransformer.php @@ -11,7 +11,8 @@ use PhpParser\Node\Expr; use PhpParser\Node\Name; use PhpParser\Node\Scalar; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeIdentifier; /** * Transform to an object which can be mapped by AutoMapper (sub mapping). @@ -81,24 +82,27 @@ public function transform(Expr $input, Expr $target, PropertyMetadata $propertyM public function getCheckExpression(Expr $input, Expr $target, PropertyMetadata $propertyMapping, UniqueVariableScope $uniqueVariableScope, Expr\Variable $source): ?Expr { - if ($this->sourceType->getBuiltinType() === Type::BUILTIN_TYPE_ARRAY) { - $condition = new Expr\FuncCall( - new Name('is_array'), - [ - new Arg($input), - ] - ); - } else { - if ($this->sourceType->getClassName() !== null) { - $condition = new Expr\Instanceof_($input, new Name\FullyQualified($this->sourceType->getClassName())); - } else { + if ($this->sourceType instanceof Type\ObjectType) { + $condition = new Expr\Instanceof_($input, new Name\FullyQualified($this->sourceType->getClassName())); + } elseif ($this->sourceType instanceof Type\BuiltinType) { + if ($this->sourceType->getTypeIdentifier() === TypeIdentifier::OBJECT) { $condition = new Expr\FuncCall( new Name('is_object'), [ new Arg($input), ] ); + } else { + $condition = new Expr\FuncCall( + new Name('is_array'), + [ + new Arg($input), + ] + ); } + } else { + // Other types are not supported for object mapping + return null; } return new Expr\BinaryOp\BooleanOr( @@ -129,7 +133,7 @@ private function getSource(): string { $sourceTypeName = 'array'; - if (Type::BUILTIN_TYPE_OBJECT === $this->sourceType->getBuiltinType()) { + if ($this->sourceType instanceof Type\ObjectType) { /** * Cannot be null since we check the source type is an Object. * @@ -148,7 +152,7 @@ private function getTarget(): string { $targetTypeName = 'array'; - if (Type::BUILTIN_TYPE_OBJECT === $this->targetType->getBuiltinType()) { + if ($this->targetType instanceof Type\ObjectType) { /** * Cannot be null since we check the target type is an Object. * @@ -195,4 +199,9 @@ public function getIdentifierExpression(Expr $input): Expr new Arg($input), ]); } + + public function __toString(): string + { + return \sprintf('%s<%s, %s>', static::class, $this->sourceType, $this->targetType); + } } diff --git a/src/Transformer/ObjectTransformerFactory.php b/src/Transformer/ObjectTransformerFactory.php index c15fd865..600f5bce 100644 --- a/src/Transformer/ObjectTransformerFactory.php +++ b/src/Transformer/ObjectTransformerFactory.php @@ -7,69 +7,59 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeIdentifier; /** * @author Joel Wurtz * * @internal */ -final class ObjectTransformerFactory extends AbstractUniqueTypeTransformerFactory implements PrioritizedTransformerFactoryInterface +final class ObjectTransformerFactory implements TransformerFactoryInterface, PrioritizedTransformerFactoryInterface { - protected function createTransformer(Type $sourceType, Type $targetType, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface + public function getTransformer(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface { - // Only deal with source type being an object or an array that is not a collection - if (!$this->isObjectType($sourceType) || !$this->isObjectType($targetType)) { + if ($source->type === null || $target->type === null) { return null; } - $sourceTypeName = 'array'; - $targetTypeName = 'array'; - - if (Type::BUILTIN_TYPE_OBJECT === $sourceType->getBuiltinType()) { - $sourceTypeName = $sourceType->getClassName(); - } - - if (Type::BUILTIN_TYPE_OBJECT === $targetType->getBuiltinType()) { - $targetTypeName = $targetType->getClassName(); + // Only deal with source type being an object or an array that is not a collection + if (!$this->isObjectType($source->type) || !$this->isObjectType($target->type)) { + return null; } - if (null !== $sourceTypeName && null !== $targetTypeName) { - return new ObjectTransformer($sourceType, $targetType); + // Check that we have at least one object correctly defined + if (!$source->type->isIdentifiedBy(TypeIdentifier::OBJECT) && !$target->type->isIdentifiedBy(TypeIdentifier::OBJECT)) { + return null; } - return null; + return new ObjectTransformer($source->type, $target->type); } private function isObjectType(Type $type): bool { - if (!\in_array($type->getBuiltinType(), [Type::BUILTIN_TYPE_OBJECT, Type::BUILTIN_TYPE_ARRAY])) { + if (!$type->isIdentifiedBy(TypeIdentifier::OBJECT) && !$type->isIdentifiedBy(TypeIdentifier::ARRAY) && !$type->isIdentifiedBy(TypeIdentifier::MIXED)) { return false; } - if (Type::BUILTIN_TYPE_ARRAY === $type->getBuiltinType() && $type->isCollection() && $type->getCollectionValueTypes()) { - return false; - } + if ($type instanceof Type\ObjectType) { + $className = $type->getClassName(); - if ($type->getClassName() !== null && is_subclass_of($type->getClassName(), \UnitEnum::class)) { - return false; - } - - /** @var class-string|null $class */ - $class = $type->getClassName(); - - if ($class === null || $class === \stdClass::class) { - return true; - } + if (is_subclass_of($className, \UnitEnum::class)) { + return false; + } - if (!class_exists($class) && !interface_exists($class)) { - return false; - } + if (!class_exists($className) && !interface_exists($className)) { + return false; + } - $reflectionClass = new \ReflectionClass($class); + if ($className !== \stdClass::class) { + $reflectionClass = new \ReflectionClass($className); - if ($reflectionClass->isInternal()) { - return false; + if ($reflectionClass->isInternal()) { + return false; + } + } } return true; diff --git a/src/Transformer/PropertyTransformer/PropertyTransformerFactory.php b/src/Transformer/PropertyTransformer/PropertyTransformerFactory.php index 0c0e5742..2ac70480 100644 --- a/src/Transformer/PropertyTransformer/PropertyTransformerFactory.php +++ b/src/Transformer/PropertyTransformer/PropertyTransformerFactory.php @@ -7,7 +7,6 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; use AutoMapper\Transformer\PrioritizedTransformerFactoryInterface; use AutoMapper\Transformer\TransformerFactoryInterface; use AutoMapper\Transformer\TransformerInterface; @@ -27,9 +26,9 @@ public function getPriority(): int return 256; } - public function getTransformer(TypesMatching $types, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface + public function getTransformer(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface { - $id = $this->propertyTransformerRegistry->getPropertyTransformersForMapper($types, $source, $target, $mapperMetadata); + $id = $this->propertyTransformerRegistry->getPropertyTransformersForMapper($source, $target, $mapperMetadata); if (null === $id) { return null; diff --git a/src/Transformer/PropertyTransformer/PropertyTransformerRegistry.php b/src/Transformer/PropertyTransformer/PropertyTransformerRegistry.php index 8c2c1621..fab90d65 100644 --- a/src/Transformer/PropertyTransformer/PropertyTransformerRegistry.php +++ b/src/Transformer/PropertyTransformer/PropertyTransformerRegistry.php @@ -7,7 +7,6 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; /** * @internal @@ -51,10 +50,10 @@ public function getPropertyTransformer(string $id): ?PropertyTransformerInterfac return $this->propertyTransformers[$id] ?? null; } - public function getPropertyTransformersForMapper(TypesMatching $types, SourcePropertyMetadata $sourceProperty, TargetPropertyMetadata $targetProperty, MapperMetadata $mapperMetadata): ?string + public function getPropertyTransformersForMapper(SourcePropertyMetadata $sourceProperty, TargetPropertyMetadata $targetProperty, MapperMetadata $mapperMetadata): ?string { foreach ($this->prioritizedPropertyTransformers() as $id => $propertyTransformer) { - if ($propertyTransformer instanceof PropertyTransformerSupportInterface && $propertyTransformer->supports($types, $sourceProperty, $targetProperty, $mapperMetadata)) { + if ($propertyTransformer instanceof PropertyTransformerSupportInterface && $propertyTransformer->supports($sourceProperty, $targetProperty, $mapperMetadata)) { return $id; } } diff --git a/src/Transformer/PropertyTransformer/PropertyTransformerSupportInterface.php b/src/Transformer/PropertyTransformer/PropertyTransformerSupportInterface.php index df04cbc9..01ea7aa3 100644 --- a/src/Transformer/PropertyTransformer/PropertyTransformerSupportInterface.php +++ b/src/Transformer/PropertyTransformer/PropertyTransformerSupportInterface.php @@ -7,7 +7,6 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; /** * Allow to automatically register a transformer for a specific property. @@ -21,10 +20,9 @@ interface PropertyTransformerSupportInterface * * This method is not need if the transformer is set in an #[MapTo] or #[MapFrom] attribute * - * @param TypesMatching $types The types of the source and target properties * @param SourcePropertyMetadata $source The source property metadata * @param TargetPropertyMetadata $target The target property metadata * @param MapperMetadata $mapperMetadata The mapper metadata */ - public function supports(TypesMatching $types, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): bool; + public function supports(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): bool; } diff --git a/src/Transformer/SymfonyUidTransformerFactory.php b/src/Transformer/SymfonyUidTransformerFactory.php index 2357acca..ddf9e566 100644 --- a/src/Transformer/SymfonyUidTransformerFactory.php +++ b/src/Transformer/SymfonyUidTransformerFactory.php @@ -7,7 +7,7 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; use Symfony\Component\Uid\AbstractUid; use Symfony\Component\Uid\Ulid; @@ -16,15 +16,15 @@ * * @internal */ -final class SymfonyUidTransformerFactory extends AbstractUniqueTypeTransformerFactory implements PrioritizedTransformerFactoryInterface +final class SymfonyUidTransformerFactory implements TransformerFactoryInterface, PrioritizedTransformerFactoryInterface { /** @var array */ private array $reflectionCache = []; - protected function createTransformer(Type $sourceType, Type $targetType, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface + public function getTransformer(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface { - $sourceUid = $this->getUid($sourceType); - $targetUid = $this->getUid($targetType); + $sourceUid = $this->getUid($source->type); + $targetUid = $this->getUid($target->type); if ($sourceUid[0] && $targetUid[0]) { return new SymfonyUidCopyTransformer(); @@ -44,9 +44,9 @@ protected function createTransformer(Type $sourceType, Type $targetType, SourceP /** * @return array{false, false, null}|array{true, bool, class-string} */ - private function getUid(Type $type): array + private function getUid(?Type $type): array { - if (Type::BUILTIN_TYPE_OBJECT !== $type->getBuiltinType()) { + if (!$type instanceof Type\ObjectType) { return [false, false, null]; } diff --git a/src/Transformer/TransformerFactoryInterface.php b/src/Transformer/TransformerFactoryInterface.php index b16116b6..e36c2edd 100644 --- a/src/Transformer/TransformerFactoryInterface.php +++ b/src/Transformer/TransformerFactoryInterface.php @@ -7,8 +7,6 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; -use Symfony\Component\PropertyInfo\Type; /** * Create transformer. @@ -22,5 +20,5 @@ interface TransformerFactoryInterface /** * Get transformer to use when mapping from an array of type to another array of type. */ - public function getTransformer(TypesMatching $types, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface; + public function getTransformer(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface; } diff --git a/src/Transformer/UniqueTypeTransformerFactory.php b/src/Transformer/UniqueTypeTransformerFactory.php index 7e0b6e61..2c572630 100644 --- a/src/Transformer/UniqueTypeTransformerFactory.php +++ b/src/Transformer/UniqueTypeTransformerFactory.php @@ -7,7 +7,7 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; +use Symfony\Component\TypeInfo\Type; /** * Reduce array of type to only one type on source and target. @@ -20,22 +20,19 @@ final class UniqueTypeTransformerFactory implements TransformerFactoryInterface, { use ChainTransformerFactoryAwareTrait; - public function getTransformer(TypesMatching $types, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface + public function getTransformer(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface { - $sourceType = $types->getSourceUniqueType(); - - if (null === $sourceType) { + if (null === $source->type) { return null; } - $targetTypes = $types[$sourceType] ?? []; - - if (\count($targetTypes) <= 1) { + if (!$target->type instanceof Type\UnionType) { return null; } - foreach ($targetTypes as $targetType) { - $transformer = $this->chainTransformerFactory->getTransformer(TypesMatching::fromSourceAndTargetTypes([$sourceType], [$targetType]), $source, $target, $mapperMetadata); + foreach ($target->type->getTypes() as $targetType) { + $newTarget = $target->withType($targetType); + $transformer = $this->chainTransformerFactory->getTransformer($source, $newTarget, $mapperMetadata); if (null !== $transformer) { return $transformer; @@ -47,6 +44,6 @@ public function getTransformer(TypesMatching $types, SourcePropertyMetadata $sou public function getPriority(): int { - return 32; + return 8; } } diff --git a/tests/AutoMapperTest.php b/tests/AutoMapperTest.php index 64969510..341e498c 100644 --- a/tests/AutoMapperTest.php +++ b/tests/AutoMapperTest.php @@ -47,8 +47,6 @@ use PHPUnit\Framework\Attributes\DataProvider; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\Finder\Finder; -use Symfony\Component\HttpKernel\Kernel; -use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; use Symfony\Component\VarDumper\Dumper\CliDumper; use Symfony\Component\VarDumper\Test\VarDumperTestTrait; @@ -620,47 +618,25 @@ public function testIgnoredAttributes(): void public function testNameConverter(): void { - if (Kernel::MAJOR_VERSION >= 7 && Kernel::MINOR_VERSION >= 2) { - $nameConverter = new class implements NameConverterInterface { - public function normalize($propertyName, ?string $class = null, ?string $format = null, array $context = []): string - { - if ('id' === $propertyName) { - return '@id'; - } - - return $propertyName; + $nameConverter = new class implements NameConverterInterface { + public function normalize($propertyName, ?string $class = null, ?string $format = null, array $context = []): string + { + if ('id' === $propertyName) { + return '@id'; } - public function denormalize($propertyName, ?string $class = null, ?string $format = null, array $context = []): string - { - if ('@id' === $propertyName) { - return 'id'; - } + return $propertyName; + } - return $propertyName; + public function denormalize($propertyName, ?string $class = null, ?string $format = null, array $context = []): string + { + if ('@id' === $propertyName) { + return 'id'; } - }; - } else { - $nameConverter = new class implements AdvancedNameConverterInterface { - public function normalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string - { - if ('id' === $propertyName) { - return '@id'; - } - - return $propertyName; - } - - public function denormalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string - { - if ('@id' === $propertyName) { - return 'id'; - } - return $propertyName; - } - }; - } + return $propertyName; + } + }; $autoMapper = AutoMapper::create(new Configuration(classPrefix: 'Mapper2_'), nameConverter: $nameConverter); $user = new Fixtures\User(1, 'yolo', '13'); @@ -744,7 +720,7 @@ public function testWithMixedArray(): void $dto = $this->autoMapper->map($user, Fixtures\UserDTOProperties::class); self::assertInstanceOf(Fixtures\UserDTOProperties::class, $dto); - self::assertSame(['foo' => 'bar'], $dto->getProperties()); + self::assertSame([0 => 'bar'], $dto->getProperties()); } public function testAdderAndRemoverWithClass(): void diff --git a/tests/AutoMapperTest/DifferentSetterGetterType/map.php b/tests/AutoMapperTest/DifferentSetterGetterType/map.php index 2e0617ff..f6c3916c 100644 --- a/tests/AutoMapperTest/DifferentSetterGetterType/map.php +++ b/tests/AutoMapperTest/DifferentSetterGetterType/map.php @@ -38,6 +38,11 @@ public function setAddress(AddressType $address): void } } -$object = new DifferentSetterGetterType(AddressType::FLAT); +function run() +{ + $object = new DifferentSetterGetterType(AddressType::FLAT); + + yield AutoMapperBuilder::buildAutoMapper()->map($object, 'array'); +} -return AutoMapperBuilder::buildAutoMapper()->map($object, 'array'); +return run(); diff --git a/tests/Bundle/ApiPlatformTest.php b/tests/Bundle/ApiPlatformTest.php index e9364bf0..9f032109 100644 --- a/tests/Bundle/ApiPlatformTest.php +++ b/tests/Bundle/ApiPlatformTest.php @@ -7,11 +7,25 @@ use ApiPlatform\Symfony\Bundle\Test\ApiTestCase; use Symfony\Component\Filesystem\Filesystem; +if (!class_exists(ApiTestCase::class)) { + class ApiPlatformTest extends \PHPUnit\Framework\TestCase + { + protected static function createClient(): void + { + self::markTestSkipped('API Platform is not installed.'); + } + } + + return; +} + class ApiPlatformTest extends ApiTestCase { protected function setUp(): void { static::$class = null; + static::$alwaysBootKernel = false; + $_SERVER['KERNEL_DIR'] = __DIR__ . '/Resources/App'; $_SERVER['KERNEL_CLASS'] = 'AutoMapper\Tests\Bundle\Resources\App\AppKernel'; $_SERVER['APP_DEBUG'] = false; diff --git a/tests/Bundle/Resources/App/Service/IdNameConverter.php b/tests/Bundle/Resources/App/Service/IdNameConverter.php index 3ed0476d..6530a72a 100644 --- a/tests/Bundle/Resources/App/Service/IdNameConverter.php +++ b/tests/Bundle/Resources/App/Service/IdNameConverter.php @@ -5,49 +5,25 @@ namespace AutoMapper\Tests\Bundle\Resources\App\Service; use AutoMapper\Tests\Bundle\Resources\App\Entity\User; -use Symfony\Component\HttpKernel\Kernel; -use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; +use Symfony\Component\Serializer\NameConverter\NameConverterInterface; -if (Kernel::MAJOR_VERSION < 6) { - class IdNameConverter implements AdvancedNameConverterInterface +class IdNameConverter implements NameConverterInterface +{ + public function normalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string { - public function normalize($propertyName, ?string $class = null, ?string $format = null, array $context = []): string - { - if ($class === User::class && 'id' === $propertyName) { - return '@id'; - } - - return $propertyName; + if ($class === User::class && 'id' === $propertyName) { + return '@id'; } - public function denormalize($propertyName, ?string $class = null, ?string $format = null, array $context = []): string - { - if ($class === User::class && '@id' === $propertyName) { - return 'id'; - } - - return $propertyName; - } + return $propertyName; } -} else { - class IdNameConverter implements AdvancedNameConverterInterface - { - public function normalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string - { - if ($class === User::class && 'id' === $propertyName) { - return '@id'; - } - return $propertyName; + public function denormalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string + { + if ($class === User::class && '@id' === $propertyName) { + return 'id'; } - public function denormalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string - { - if ($class === User::class && '@id' === $propertyName) { - return 'id'; - } - - return $propertyName; - } + return $propertyName; } } diff --git a/tests/Bundle/Resources/App/Service/YearOfBirthTransformer.php b/tests/Bundle/Resources/App/Service/YearOfBirthTransformer.php index 39cc57d1..065b8aab 100644 --- a/tests/Bundle/Resources/App/Service/YearOfBirthTransformer.php +++ b/tests/Bundle/Resources/App/Service/YearOfBirthTransformer.php @@ -7,7 +7,6 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; use AutoMapper\Tests\Bundle\Resources\App\Entity\User; use AutoMapper\Tests\Bundle\Resources\App\Entity\UserDTO; use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerInterface; @@ -22,7 +21,7 @@ public function transform(mixed $value, object|array $user, array $context): mix return ((int) date('Y')) - ((int) $user->age); } - public function supports(TypesMatching $types, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): bool + public function supports(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): bool { return User::class === $mapperMetadata->source && UserDTO::class === $mapperMetadata->target && 'yearOfBirth' === $source->property; } diff --git a/tests/Bundle/Resources/config/bundles.php b/tests/Bundle/Resources/config/bundles.php index 6ca9bfb9..fb8c7a20 100644 --- a/tests/Bundle/Resources/config/bundles.php +++ b/tests/Bundle/Resources/config/bundles.php @@ -2,11 +2,16 @@ declare(strict_types=1); -return [ +$bundles = [ Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true], Symfony\Bundle\TwigBundle\TwigBundle::class => ['dev' => true], AutoMapper\Symfony\Bundle\AutoMapperBundle::class => ['all' => true], Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true], Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], - ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true], ]; + +if (class_exists(ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class)) { + $bundles[ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class] = ['all' => true]; +} + +return $bundles; diff --git a/tests/Bundle/Resources/config/packages/framework.yaml b/tests/Bundle/Resources/config/packages/framework.yaml index ba6d1ed2..f231a537 100644 --- a/tests/Bundle/Resources/config/packages/framework.yaml +++ b/tests/Bundle/Resources/config/packages/framework.yaml @@ -1,6 +1,7 @@ framework: secret: automapper - property_info: ~ + property_info: + with_constructor_extractor: true test: ~ serializer: enabled: true diff --git a/tests/Doctrine/DoctrineTest.php b/tests/Doctrine/DoctrineTest.php index c0ce3071..85780879 100644 --- a/tests/Doctrine/DoctrineTest.php +++ b/tests/Doctrine/DoctrineTest.php @@ -39,11 +39,13 @@ private function buildDatabase() unlink(__DIR__ . '/db.sqlite'); } - $config = ORMSetup::createAttributeMetadataConfiguration( + $config = ORMSetup::createAttributeMetadataConfig( paths: [__DIR__ . '/Entity'], isDevMode: true, ); + $config->enableNativeLazyObjects(true); + $connection = DriverManager::getConnection([ 'driver' => 'pdo_sqlite', 'path' => __DIR__ . '/db.sqlite', diff --git a/tests/Extractor/FromSourceMappingExtractorTest.php b/tests/Extractor/FromSourceMappingExtractorTest.php index 7afaf9de..201a159f 100644 --- a/tests/Extractor/FromSourceMappingExtractorTest.php +++ b/tests/Extractor/FromSourceMappingExtractorTest.php @@ -49,6 +49,8 @@ private function fromSourceMappingExtractorBootstrap(bool $private = true): void $propertyInfoExtractor, $reflectionExtractor, $reflectionExtractor, + $propertyInfoExtractor, + $propertyInfoExtractor, ); } diff --git a/tests/Extractor/FromTargetMappingExtractorTest.php b/tests/Extractor/FromTargetMappingExtractorTest.php index b78e7916..b4b8de86 100644 --- a/tests/Extractor/FromTargetMappingExtractorTest.php +++ b/tests/Extractor/FromTargetMappingExtractorTest.php @@ -49,6 +49,8 @@ private function fromTargetMappingExtractorBootstrap(bool $private = true): void $propertyInfoExtractor, $reflectionExtractor, $reflectionExtractor, + $propertyInfoExtractor, + $propertyInfoExtractor, ); } diff --git a/tests/Fixtures/Transformer/CustomTransformer/FromSourceCustomModelTransformer.php b/tests/Fixtures/Transformer/CustomTransformer/FromSourceCustomModelTransformer.php index 4ba1e6c1..3dbd5b0b 100644 --- a/tests/Fixtures/Transformer/CustomTransformer/FromSourceCustomModelTransformer.php +++ b/tests/Fixtures/Transformer/CustomTransformer/FromSourceCustomModelTransformer.php @@ -7,23 +7,24 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; use AutoMapper\Tests\Fixtures\AddressDTO; use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerInterface; use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerSupportInterface; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; final readonly class FromSourceCustomModelTransformer implements PropertyTransformerInterface, PropertyTransformerSupportInterface { - public function supports(TypesMatching $types, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): bool + public function supports(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): bool { - $sourceUniqueType = $types->getSourceUniqueType(); + if (!$source->type->isSatisfiedBy(fn (Type $type) => $type instanceof Type\ObjectType && $type->getClassName() === AddressDTO::class)) { + return false; + } - if (null === $sourceUniqueType) { + if (!$target->type instanceof Type\CollectionType) { return false; } - return $sourceUniqueType->getClassName() === AddressDTO::class && $this->targetIsArray($types[$sourceUniqueType] ?? []); + return true; } public function transform(mixed $value, object|array $source, array $context): mixed diff --git a/tests/Fixtures/Transformer/CustomTransformer/FromSourceCustomPropertyTransformer.php b/tests/Fixtures/Transformer/CustomTransformer/FromSourceCustomPropertyTransformer.php index 603fec5a..241f090b 100644 --- a/tests/Fixtures/Transformer/CustomTransformer/FromSourceCustomPropertyTransformer.php +++ b/tests/Fixtures/Transformer/CustomTransformer/FromSourceCustomPropertyTransformer.php @@ -7,14 +7,13 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; use AutoMapper\Tests\Fixtures\UserDTO; use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerInterface; use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerSupportInterface; final readonly class FromSourceCustomPropertyTransformer implements PropertyTransformerInterface, PropertyTransformerSupportInterface { - public function supports(TypesMatching $types, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): bool + public function supports(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): bool { return $mapperMetadata->source === UserDTO::class && $mapperMetadata->target === 'array' && $source->property === 'name'; } diff --git a/tests/Fixtures/Transformer/CustomTransformer/FromTargetCustomModelTransformer.php b/tests/Fixtures/Transformer/CustomTransformer/FromTargetCustomModelTransformer.php index af3f0d62..6868808b 100644 --- a/tests/Fixtures/Transformer/CustomTransformer/FromTargetCustomModelTransformer.php +++ b/tests/Fixtures/Transformer/CustomTransformer/FromTargetCustomModelTransformer.php @@ -7,23 +7,25 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; use AutoMapper\Tests\Fixtures\AddressDTO; use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerInterface; use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerSupportInterface; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeIdentifier; final readonly class FromTargetCustomModelTransformer implements PropertyTransformerInterface, PropertyTransformerSupportInterface { - public function supports(TypesMatching $types, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): bool + public function supports(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): bool { - $sourceUniqueType = $types->getSourceUniqueType(); + if (!$source->type->isIdentifiedBy(TypeIdentifier::ARRAY)) { + return false; + } - if (null === $sourceUniqueType) { + if (!$target->type->isSatisfiedBy(fn (Type $type) => $type instanceof Type\ObjectType && $type->getClassName() === AddressDTO::class)) { return false; } - return $sourceUniqueType->getBuiltinType() === 'array' && $this->targetIsAddressDTO($types[$sourceUniqueType] ?? []); + return true; } public function transform(mixed $value, object|array $source, array $context): mixed @@ -33,18 +35,4 @@ public function transform(mixed $value, object|array $source, array $context): m return $addressDTO; } - - /** - * @param Type[] $targetTypes - */ - private function targetIsAddressDTO(array $targetTypes): bool - { - foreach ($targetTypes as $targetType) { - if ($targetType->getClassName() === AddressDTO::class) { - return true; - } - } - - return false; - } } diff --git a/tests/Fixtures/Transformer/CustomTransformer/FromTargetCustomPropertyTransformer.php b/tests/Fixtures/Transformer/CustomTransformer/FromTargetCustomPropertyTransformer.php index b22f5bf6..87bc12cb 100644 --- a/tests/Fixtures/Transformer/CustomTransformer/FromTargetCustomPropertyTransformer.php +++ b/tests/Fixtures/Transformer/CustomTransformer/FromTargetCustomPropertyTransformer.php @@ -7,14 +7,13 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; use AutoMapper\Tests\Fixtures\UserDTO; use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerInterface; use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerSupportInterface; final readonly class FromTargetCustomPropertyTransformer implements PropertyTransformerInterface, PropertyTransformerSupportInterface { - public function supports(TypesMatching $types, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): bool + public function supports(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): bool { return $mapperMetadata->source === 'array' && $mapperMetadata->target === UserDTO::class && $source->property === 'name'; } diff --git a/tests/Fixtures/Transformer/CustomTransformer/PrioritizedFromSourcePropertyPriorityTransformer.php b/tests/Fixtures/Transformer/CustomTransformer/PrioritizedFromSourcePropertyPriorityTransformer.php index af228e31..201d59cd 100644 --- a/tests/Fixtures/Transformer/CustomTransformer/PrioritizedFromSourcePropertyPriorityTransformer.php +++ b/tests/Fixtures/Transformer/CustomTransformer/PrioritizedFromSourcePropertyPriorityTransformer.php @@ -7,7 +7,6 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; use AutoMapper\Tests\Fixtures\UserDTO; use AutoMapper\Transformer\PropertyTransformer\PrioritizedPropertyTransformerInterface; use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerInterface; @@ -15,7 +14,7 @@ final readonly class PrioritizedFromSourcePropertyPriorityTransformer implements PropertyTransformerInterface, PropertyTransformerSupportInterface, PrioritizedPropertyTransformerInterface { - public function supports(TypesMatching $types, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): bool + public function supports(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): bool { return $mapperMetadata->source === UserDTO::class && $mapperMetadata->target === 'array' && $source->property === 'address'; } diff --git a/tests/Fixtures/Transformer/CustomTransformer/SourceTargetCustomModelTransformer.php b/tests/Fixtures/Transformer/CustomTransformer/SourceTargetCustomModelTransformer.php index 72519796..48ebf6ec 100644 --- a/tests/Fixtures/Transformer/CustomTransformer/SourceTargetCustomModelTransformer.php +++ b/tests/Fixtures/Transformer/CustomTransformer/SourceTargetCustomModelTransformer.php @@ -7,24 +7,25 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; use AutoMapper\Tests\Fixtures\Address; use AutoMapper\Tests\Fixtures\AddressDTO; use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerInterface; use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerSupportInterface; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; final readonly class SourceTargetCustomModelTransformer implements PropertyTransformerInterface, PropertyTransformerSupportInterface { - public function supports(TypesMatching $types, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): bool + public function supports(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): bool { - $sourceUniqueType = $types->getSourceUniqueType(); + if (!$source->type->isSatisfiedBy(fn (Type $type) => $type instanceof Type\ObjectType && $type->getClassName() === AddressDTO::class)) { + return false; + } - if (null === $sourceUniqueType) { + if (!$target->type->isSatisfiedBy(fn (Type $type) => $type instanceof Type\ObjectType && $type->getClassName() === Address::class)) { return false; } - return $sourceUniqueType->getClassName() === AddressDTO::class && $this->targetIsAddress($types[$sourceUniqueType] ?? []); + return true; } public function transform(mixed $value, object|array $source, array $context): mixed @@ -33,18 +34,4 @@ public function transform(mixed $value, object|array $source, array $context): m return Address::fromDTO($value); } - - /** - * @param Type[] $targetTypes - */ - private function targetIsAddress(array $targetTypes): bool - { - foreach ($targetTypes as $targetType) { - if ($targetType->getClassName() === Address::class) { - return true; - } - } - - return false; - } } diff --git a/tests/Fixtures/Transformer/CustomTransformer/SourceTargetCustomPropertyTransformer.php b/tests/Fixtures/Transformer/CustomTransformer/SourceTargetCustomPropertyTransformer.php index e318baae..d213da88 100644 --- a/tests/Fixtures/Transformer/CustomTransformer/SourceTargetCustomPropertyTransformer.php +++ b/tests/Fixtures/Transformer/CustomTransformer/SourceTargetCustomPropertyTransformer.php @@ -7,7 +7,6 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; use AutoMapper\Tests\Fixtures\User; use AutoMapper\Tests\Fixtures\UserDTO; use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerInterface; @@ -15,7 +14,7 @@ final readonly class SourceTargetCustomPropertyTransformer implements PropertyTransformerInterface, PropertyTransformerSupportInterface { - public function supports(TypesMatching $types, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): bool + public function supports(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): bool { return $mapperMetadata->source === UserDTO::class && $mapperMetadata->target === User::class && $source->property === 'name'; } diff --git a/tests/Fixtures/Transformer/CustomTransformer/SourceTargetMultiFieldsCustomPropertyTransformer.php b/tests/Fixtures/Transformer/CustomTransformer/SourceTargetMultiFieldsCustomPropertyTransformer.php index e1dc4718..f0b493ce 100644 --- a/tests/Fixtures/Transformer/CustomTransformer/SourceTargetMultiFieldsCustomPropertyTransformer.php +++ b/tests/Fixtures/Transformer/CustomTransformer/SourceTargetMultiFieldsCustomPropertyTransformer.php @@ -7,7 +7,6 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; use AutoMapper\Tests\Fixtures\BirthDateDateTime; use AutoMapper\Tests\Fixtures\BirthDateExploded; use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerInterface; @@ -15,7 +14,7 @@ final readonly class SourceTargetMultiFieldsCustomPropertyTransformer implements PropertyTransformerInterface, PropertyTransformerSupportInterface { - public function supports(TypesMatching $types, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): bool + public function supports(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): bool { return $mapperMetadata->source === BirthDateExploded::class && $mapperMetadata->target === BirthDateDateTime::class && $target->property === 'date'; } diff --git a/tests/Fixtures/Transformer/CustomTransformer/TransformerWithDependency.php b/tests/Fixtures/Transformer/CustomTransformer/TransformerWithDependency.php index 822cd28e..19884445 100644 --- a/tests/Fixtures/Transformer/CustomTransformer/TransformerWithDependency.php +++ b/tests/Fixtures/Transformer/CustomTransformer/TransformerWithDependency.php @@ -7,7 +7,6 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; use AutoMapper\Tests\Fixtures\CityFoo; use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerInterface; use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerSupportInterface; @@ -19,7 +18,7 @@ public function __construct( ) { } - public function supports(TypesMatching $types, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): bool + public function supports(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): bool { return $mapperMetadata->source === CityFoo::class && $mapperMetadata->target === 'array' && $source->property === 'name'; } diff --git a/tests/Metadata/MetadataFactoryTest.php b/tests/Metadata/MetadataFactoryTest.php index 8d63ac15..489d36c9 100644 --- a/tests/Metadata/MetadataFactoryTest.php +++ b/tests/Metadata/MetadataFactoryTest.php @@ -66,6 +66,8 @@ protected function setUp(): void $propertyInfoExtractor, $reflectionExtractor, $reflectionExtractor, + $propertyInfoExtractor, + $propertyInfoExtractor, ); $fromTargetMappingExtractor = new FromTargetMappingExtractor( @@ -73,6 +75,8 @@ protected function setUp(): void $propertyInfoExtractor, $reflectionExtractor, $reflectionExtractor, + $propertyInfoExtractor, + $propertyInfoExtractor, ); $fromSourceMappingExtractor = new FromSourceMappingExtractor( @@ -80,6 +84,8 @@ protected function setUp(): void $propertyInfoExtractor, $reflectionExtractor, $reflectionExtractor, + $propertyInfoExtractor, + $propertyInfoExtractor, ); $this->factory = new MetadataFactory( diff --git a/tests/Normalizer/Features/IgnoredAttributesTestTrait.php b/tests/Normalizer/Features/IgnoredAttributesTestTrait.php index 781fce12..c4e24602 100644 --- a/tests/Normalizer/Features/IgnoredAttributesTestTrait.php +++ b/tests/Normalizer/Features/IgnoredAttributesTestTrait.php @@ -44,7 +44,7 @@ public function testIgnoredAttributesNormalize() 'foo' => 'foo', 'inner' => ['foo' => 'innerFoo'], 'date' => null, - 'inners' => null, + 'inners' => [], ], $normalizer->normalize($objectOuter, null, $context) ); @@ -54,7 +54,7 @@ public function testIgnoredAttributesNormalize() [ 'bar' => 'bar', 'date' => null, - 'inners' => null, + 'inners' => [], ], $normalizer->normalize($objectOuter, null, $context) ); diff --git a/tests/Transformer/ArrayTransformerFactoryTest.php b/tests/Transformer/ArrayTransformerFactoryTest.php index 5f39a437..164e2d2c 100644 --- a/tests/Transformer/ArrayTransformerFactoryTest.php +++ b/tests/Transformer/ArrayTransformerFactoryTest.php @@ -7,28 +7,29 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; +use AutoMapper\Transformer\ArrayTransformer; use AutoMapper\Transformer\ArrayTransformerFactory; use AutoMapper\Transformer\ChainTransformerFactory; -use AutoMapper\Transformer\DictionaryTransformer; +use AutoMapper\Transformer\CopyTransformer; use PHPUnit\Framework\TestCase; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; class ArrayTransformerFactoryTest extends TestCase { public function testGetTransformer(): void { - $chainFactory = new ChainTransformerFactory(); $factory = new ArrayTransformerFactory(); + $chainFactory = $this->getMockBuilder(ChainTransformerFactory::class)->disableOriginalConstructor()->getMock(); + $chainFactory->expects($this->any())->method('getTransformer')->willReturn(new CopyTransformer()); + $factory->setChainTransformerFactory($chainFactory); $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('array', false, null, true)], [new Type('array', false, null, true)], ); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::array()); + $targetMapperMetadata = new TargetPropertyMetadata('foo', type: Type::array()); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); - self::assertInstanceOf(DictionaryTransformer::class, $transformer); + self::assertInstanceOf(ArrayTransformer::class, $transformer); } public function testNoTransformerTargetNoCollection(): void @@ -38,10 +39,9 @@ public function testNoTransformerTargetNoCollection(): void $factory->setChainTransformerFactory($chainFactory); $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('array', false, null, true)], [new Type('string')], ); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::array()); + $targetMapperMetadata = new TargetPropertyMetadata('foo', type: Type::string()); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNull($transformer); } @@ -53,10 +53,9 @@ public function testNoTransformerSourceNoCollection(): void $factory->setChainTransformerFactory($chainFactory); $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('string')], [new Type('array', false, null, true)], ); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::string()); + $targetMapperMetadata = new TargetPropertyMetadata('foo', type: Type::array()); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNull($transformer); } @@ -68,12 +67,9 @@ public function testNoTransformerIfNoSubTypeTransformerNoCollection(): void $factory->setChainTransformerFactory($chainFactory); $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); - $stringType = new Type('string'); - - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('array', false, null, true, null, $stringType)], [new Type('array', false, null, true, null, $stringType)], ); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::array(key: Type::string())); + $targetMapperMetadata = new TargetPropertyMetadata('foo', type: Type::array(key: Type::string())); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNull($transformer); } diff --git a/tests/Transformer/ArrayTransformerTest.php b/tests/Transformer/ArrayTransformerTest.php index 9f7b8106..6467c59c 100644 --- a/tests/Transformer/ArrayTransformerTest.php +++ b/tests/Transformer/ArrayTransformerTest.php @@ -7,7 +7,7 @@ use AutoMapper\Transformer\ArrayTransformer; use AutoMapper\Transformer\BuiltinTransformer; use PHPUnit\Framework\TestCase; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; class ArrayTransformerTest extends TestCase { @@ -15,7 +15,7 @@ class ArrayTransformerTest extends TestCase public function testArrayToArray(): void { - $transformer = new ArrayTransformer(new BuiltinTransformer(new Type('string'), [new Type('string')])); + $transformer = new ArrayTransformer(new BuiltinTransformer(Type::string(), Type::string())); $output = $this->evalTransformer($transformer, ['test']); self::assertEquals(['test'], $output); diff --git a/tests/Transformer/BuiltinTransformerFactoryTest.php b/tests/Transformer/BuiltinTransformerFactoryTest.php index 14232dfa..c82c8033 100644 --- a/tests/Transformer/BuiltinTransformerFactoryTest.php +++ b/tests/Transformer/BuiltinTransformerFactoryTest.php @@ -7,11 +7,10 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; use AutoMapper\Transformer\BuiltinTransformer; use AutoMapper\Transformer\BuiltinTransformerFactory; use PHPUnit\Framework\TestCase; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; class BuiltinTransformerFactoryTest extends TestCase { @@ -20,17 +19,15 @@ public function testGetTransformer(): void $factory = new BuiltinTransformerFactory(); $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('string')], [new Type('string')], ); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::string()); + $targetMapperMetadata = new TargetPropertyMetadata('foo', type: Type::string()); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertInstanceOf(BuiltinTransformer::class, $transformer); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('bool')], [new Type('string')], ); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::bool()); + $targetMapperMetadata = new TargetPropertyMetadata('foo', type: Type::string()); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertInstanceOf(BuiltinTransformer::class, $transformer); } @@ -40,31 +37,15 @@ public function testNoTransformer(): void $factory = new BuiltinTransformerFactory(); $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([], [new Type('string')], ); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::array()); + $targetMapperMetadata = new TargetPropertyMetadata('foo', type: Type::string()); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNull($transformer); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('string'), new Type('string')], [new Type('string')], ); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); - - self::assertNull($transformer); - - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('array')], [new Type('string')], ); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); - - self::assertNull($transformer); - - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('object')], [new Type('string')], ); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::object(\stdClass::class)); + $targetMapperMetadata = new TargetPropertyMetadata('foo', type: Type::string()); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNull($transformer); } diff --git a/tests/Transformer/BuiltinTransformerTest.php b/tests/Transformer/BuiltinTransformerTest.php index bae3ca43..84d868b4 100644 --- a/tests/Transformer/BuiltinTransformerTest.php +++ b/tests/Transformer/BuiltinTransformerTest.php @@ -6,7 +6,7 @@ use AutoMapper\Transformer\BuiltinTransformer; use PHPUnit\Framework\TestCase; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; class BuiltinTransformerTest extends TestCase { @@ -14,7 +14,7 @@ class BuiltinTransformerTest extends TestCase public function testStringToString(): void { - $transformer = new BuiltinTransformer(new Type('string'), [new Type('string')]); + $transformer = new BuiltinTransformer(Type::string(), Type::string()); $output = $this->evalTransformer($transformer, 'foo'); self::assertSame('foo', $output); @@ -22,7 +22,7 @@ public function testStringToString(): void public function testStringToArray(): void { - $transformer = new BuiltinTransformer(new Type('string'), [new Type('array')]); + $transformer = new BuiltinTransformer(Type::string(), Type::builtin('array')); $output = $this->evalTransformer($transformer, 'foo'); self::assertSame(['foo'], $output); @@ -30,7 +30,7 @@ public function testStringToArray(): void public function testStringToIterable(): void { - $transformer = new BuiltinTransformer(new Type('string'), [new Type('iterable')]); + $transformer = new BuiltinTransformer(Type::string(), Type::iterable()); $output = $this->evalTransformer($transformer, 'foo'); self::assertSame(['foo'], $output); @@ -38,7 +38,7 @@ public function testStringToIterable(): void public function testStringToFloat(): void { - $transformer = new BuiltinTransformer(new Type('string'), [new Type('float')]); + $transformer = new BuiltinTransformer(Type::string(), Type::float()); $output = $this->evalTransformer($transformer, '12.2'); self::assertSame(12.2, $output); @@ -46,7 +46,7 @@ public function testStringToFloat(): void public function testStringToInt(): void { - $transformer = new BuiltinTransformer(new Type('string'), [new Type('int')]); + $transformer = new BuiltinTransformer(Type::string(), Type::int()); $output = $this->evalTransformer($transformer, '12'); self::assertSame(12, $output); @@ -54,7 +54,7 @@ public function testStringToInt(): void public function testStringToBool(): void { - $transformer = new BuiltinTransformer(new Type('string'), [new Type('bool')]); + $transformer = new BuiltinTransformer(Type::string(), Type::bool()); $output = $this->evalTransformer($transformer, 'foo'); self::assertTrue($output); @@ -66,7 +66,7 @@ public function testStringToBool(): void public function testBoolToInt(): void { - $transformer = new BuiltinTransformer(new Type('bool'), [new Type('int')]); + $transformer = new BuiltinTransformer(Type::bool(), Type::int()); $output = $this->evalTransformer($transformer, true); self::assertSame(1, $output); @@ -78,7 +78,7 @@ public function testBoolToInt(): void public function testBoolToString(): void { - $transformer = new BuiltinTransformer(new Type('bool'), [new Type('string')]); + $transformer = new BuiltinTransformer(Type::bool(), Type::string()); $output = $this->evalTransformer($transformer, true); @@ -91,7 +91,7 @@ public function testBoolToString(): void public function testBoolToFloat(): void { - $transformer = new BuiltinTransformer(new Type('bool'), [new Type('float')]); + $transformer = new BuiltinTransformer(Type::bool(), Type::float()); $output = $this->evalTransformer($transformer, true); @@ -104,7 +104,7 @@ public function testBoolToFloat(): void public function testBoolToArray(): void { - $transformer = new BuiltinTransformer(new Type('bool'), [new Type('array')]); + $transformer = new BuiltinTransformer(Type::bool(), Type::builtin('array')); $output = $this->evalTransformer($transformer, true); @@ -117,7 +117,7 @@ public function testBoolToArray(): void public function testBoolToIterable(): void { - $transformer = new BuiltinTransformer(new Type('bool'), [new Type('iterable')]); + $transformer = new BuiltinTransformer(Type::bool(), Type::iterable()); $output = $this->evalTransformer($transformer, true); @@ -130,7 +130,7 @@ public function testBoolToIterable(): void public function testBoolToBool(): void { - $transformer = new BuiltinTransformer(new Type('bool'), [new Type('bool')]); + $transformer = new BuiltinTransformer(Type::bool(), Type::bool()); $output = $this->evalTransformer($transformer, true); @@ -143,7 +143,7 @@ public function testBoolToBool(): void public function testFloatToString(): void { - $transformer = new BuiltinTransformer(new Type('float'), [new Type('string')]); + $transformer = new BuiltinTransformer(Type::float(), Type::string()); $output = $this->evalTransformer($transformer, 12.23); @@ -152,7 +152,7 @@ public function testFloatToString(): void public function testFloatToInt(): void { - $transformer = new BuiltinTransformer(new Type('float'), [new Type('int')]); + $transformer = new BuiltinTransformer(Type::float(), Type::int()); $output = $this->evalTransformer($transformer, 12.23); @@ -161,7 +161,7 @@ public function testFloatToInt(): void public function testFloatToBool(): void { - $transformer = new BuiltinTransformer(new Type('float'), [new Type('bool')]); + $transformer = new BuiltinTransformer(Type::float(), Type::bool()); $output = $this->evalTransformer($transformer, 12.23); @@ -174,7 +174,7 @@ public function testFloatToBool(): void public function testFloatToArray(): void { - $transformer = new BuiltinTransformer(new Type('float'), [new Type('array')]); + $transformer = new BuiltinTransformer(Type::float(), Type::builtin('array')); $output = $this->evalTransformer($transformer, 12.23); @@ -183,7 +183,7 @@ public function testFloatToArray(): void public function testFloatToIterable(): void { - $transformer = new BuiltinTransformer(new Type('float'), [new Type('iterable')]); + $transformer = new BuiltinTransformer(Type::float(), Type::iterable()); $output = $this->evalTransformer($transformer, 12.23); @@ -192,7 +192,7 @@ public function testFloatToIterable(): void public function testFloatToFloat(): void { - $transformer = new BuiltinTransformer(new Type('float'), [new Type('float')]); + $transformer = new BuiltinTransformer(Type::float(), Type::float()); $output = $this->evalTransformer($transformer, 12.23); @@ -201,7 +201,7 @@ public function testFloatToFloat(): void public function testIntToInt(): void { - $transformer = new BuiltinTransformer(new Type('int'), [new Type('int')]); + $transformer = new BuiltinTransformer(Type::int(), Type::int()); $output = $this->evalTransformer($transformer, 12); @@ -210,7 +210,7 @@ public function testIntToInt(): void public function testIntToFloat(): void { - $transformer = new BuiltinTransformer(new Type('int'), [new Type('float')]); + $transformer = new BuiltinTransformer(Type::int(), Type::float()); $output = $this->evalTransformer($transformer, 12); @@ -219,7 +219,7 @@ public function testIntToFloat(): void public function testIntToString(): void { - $transformer = new BuiltinTransformer(new Type('int'), [new Type('string')]); + $transformer = new BuiltinTransformer(Type::int(), Type::string()); $output = $this->evalTransformer($transformer, 12); @@ -228,7 +228,7 @@ public function testIntToString(): void public function testIntToBool(): void { - $transformer = new BuiltinTransformer(new Type('int'), [new Type('bool')]); + $transformer = new BuiltinTransformer(Type::int(), Type::bool()); $output = $this->evalTransformer($transformer, 12); @@ -241,7 +241,7 @@ public function testIntToBool(): void public function testIntToArray(): void { - $transformer = new BuiltinTransformer(new Type('int'), [new Type('array')]); + $transformer = new BuiltinTransformer(Type::int(), Type::builtin('array')); $output = $this->evalTransformer($transformer, 12); @@ -250,7 +250,7 @@ public function testIntToArray(): void public function testIntToIterable(): void { - $transformer = new BuiltinTransformer(new Type('int'), [new Type('iterable')]); + $transformer = new BuiltinTransformer(Type::int(), Type::iterable()); $output = $this->evalTransformer($transformer, 12); @@ -259,7 +259,7 @@ public function testIntToIterable(): void public function testIterableToArray(): void { - $transformer = new BuiltinTransformer(new Type('iterable'), [new Type('array')]); + $transformer = new BuiltinTransformer(Type::builtin('iterable'), Type::builtin('array')); $closure = function () { yield 1; @@ -273,7 +273,7 @@ public function testIterableToArray(): void public function testArrayToIterable(): void { - $transformer = new BuiltinTransformer(new Type('array'), [new Type('iterable')]); + $transformer = new BuiltinTransformer(Type::builtin('array'), Type::iterable()); $output = $this->evalTransformer($transformer, [1, 2]); self::assertSame([1, 2], $output); @@ -281,7 +281,7 @@ public function testArrayToIterable(): void public function testToUnknowCast(): void { - $transformer = new BuiltinTransformer(new Type('callable'), [new Type('string')]); + $transformer = new BuiltinTransformer(Type::callable(), Type::string()); $output = $this->evalTransformer($transformer, function ($test) { return $test; diff --git a/tests/Transformer/ChainTransformerFactoryTest.php b/tests/Transformer/ChainTransformerFactoryTest.php index 8cb6ee04..f6240468 100644 --- a/tests/Transformer/ChainTransformerFactoryTest.php +++ b/tests/Transformer/ChainTransformerFactoryTest.php @@ -7,7 +7,6 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; use AutoMapper\Transformer\ChainTransformerFactory; use AutoMapper\Transformer\CopyTransformer; use AutoMapper\Transformer\TransformerFactoryInterface; @@ -30,8 +29,7 @@ public function testGetTransformer(): void $sourceMapperMetadata = new SourcePropertyMetadata('foo'); $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([], []); - $transformerReturned = $chainTransformerFactory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $transformerReturned = $chainTransformerFactory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertSame($transformer, $transformerReturned); } @@ -49,8 +47,7 @@ public function testNoTransformer(): void $sourceMapperMetadata = new SourcePropertyMetadata('foo'); $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([], []); - $transformerReturned = $chainTransformerFactory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $transformerReturned = $chainTransformerFactory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNull($transformerReturned); } diff --git a/tests/Transformer/DateTimeTransformerFactoryTest.php b/tests/Transformer/DateTimeTransformerFactoryTest.php index a0a83194..35488d05 100644 --- a/tests/Transformer/DateTimeTransformerFactoryTest.php +++ b/tests/Transformer/DateTimeTransformerFactoryTest.php @@ -7,14 +7,13 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; use AutoMapper\Transformer\DateTimeInterfaceToImmutableTransformer; use AutoMapper\Transformer\DateTimeInterfaceToMutableTransformer; use AutoMapper\Transformer\DateTimeToStringTransformer; use AutoMapper\Transformer\DateTimeTransformerFactory; use AutoMapper\Transformer\StringToDateTimeTransformer; use PHPUnit\Framework\TestCase; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; class DateTimeTransformerFactoryTest extends TestCase { @@ -23,26 +22,23 @@ public function testGetTransformer(): void $factory = new DateTimeTransformerFactory(); $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('object', false, \DateTime::class)], [new Type('object', false, \DateTime::class)], ); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::object(\DateTime::class)); + $targetMapperMetadata = new TargetPropertyMetadata('foo', type: Type::object(\DateTime::class)); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNotNull($transformer); self::assertInstanceOf(DateTimeInterfaceToMutableTransformer::class, $transformer); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('object', false, \DateTime::class)], [new Type('string')], ); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::object(\DateTime::class)); + $targetMapperMetadata = new TargetPropertyMetadata('foo', type: Type::string()); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNotNull($transformer); self::assertInstanceOf(DateTimeToStringTransformer::class, $transformer); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('string')], [new Type('object', false, \DateTime::class)], ); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::string()); + $targetMapperMetadata = new TargetPropertyMetadata('foo', type: Type::object(\DateTime::class)); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNotNull($transformer); self::assertInstanceOf(StringToDateTimeTransformer::class, $transformer); @@ -53,10 +49,9 @@ public function testGetTransformerImmutable(): void $factory = new DateTimeTransformerFactory(); $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('object', false, \DateTimeImmutable::class)], [new Type('object', false, \DateTime::class)], ); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::object(\DateTimeImmutable::class)); + $targetMapperMetadata = new TargetPropertyMetadata('foo', type: Type::object(\DateTime::class)); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNotNull($transformer); self::assertInstanceOf(DateTimeInterfaceToMutableTransformer::class, $transformer); @@ -67,10 +62,9 @@ public function testGetTransformerMutable(): void $factory = new DateTimeTransformerFactory(); $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('object', false, \DateTime::class)], [new Type('object', false, \DateTimeImmutable::class)], ); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::object(\DateTime::class)); + $targetMapperMetadata = new TargetPropertyMetadata('foo', type: Type::object(\DateTimeImmutable::class)); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNotNull($transformer); self::assertInstanceOf(DateTimeInterfaceToImmutableTransformer::class, $transformer); @@ -81,24 +75,21 @@ public function testNoTransformer(): void $factory = new DateTimeTransformerFactory(); $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('string')], [new Type('string')], ); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::string()); + $targetMapperMetadata = new TargetPropertyMetadata('foo', type: Type::string()); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNull($transformer); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('object', false, \DateTime::class)], [new Type('bool')], ); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::object(\DateTime::class)); + $targetMapperMetadata = new TargetPropertyMetadata('foo', type: Type::bool()); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNull($transformer); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('bool')], [new Type('object', false, \DateTime::class)], ); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::bool()); + $targetMapperMetadata = new TargetPropertyMetadata('foo', type: Type::object(\DateTime::class)); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNull($transformer); } diff --git a/tests/Transformer/EvalTransformerTrait.php b/tests/Transformer/EvalTransformerTrait.php index c5dfd46d..8d446b3c 100644 --- a/tests/Transformer/EvalTransformerTrait.php +++ b/tests/Transformer/EvalTransformerTrait.php @@ -9,13 +9,11 @@ use AutoMapper\Metadata\PropertyMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; use AutoMapper\Transformer\TransformerInterface; use PhpParser\Node\Expr; use PhpParser\Node\Param; use PhpParser\Node\Stmt; use PhpParser\PrettyPrinter\Standard; -use Symfony\Component\PropertyInfo\Type; trait EvalTransformerTrait { @@ -30,7 +28,6 @@ private function createTransformerFunction(TransformerInterface $transformer, ?P new TargetPropertyMetadata( 'dummy', ), - TypesMatching::fromSourceAndTargetTypes([new Type('string')], [new Type('string')]), $transformer, ); } diff --git a/tests/Transformer/MultipleTransformerFactoryTest.php b/tests/Transformer/MultipleTransformerFactoryTest.php index 4a917f55..bbb2ee26 100644 --- a/tests/Transformer/MultipleTransformerFactoryTest.php +++ b/tests/Transformer/MultipleTransformerFactoryTest.php @@ -7,14 +7,13 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; use AutoMapper\Transformer\BuiltinTransformer; use AutoMapper\Transformer\BuiltinTransformerFactory; use AutoMapper\Transformer\ChainTransformerFactory; use AutoMapper\Transformer\MultipleTransformer; use AutoMapper\Transformer\MultipleTransformerFactory; use PHPUnit\Framework\TestCase; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; class MultipleTransformerFactoryTest extends TestCase { @@ -26,18 +25,16 @@ public function testGetTransformer(): void $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::union(Type::string(), Type::int())); $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('string'), new Type('int')], []); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNotNull($transformer); self::assertInstanceOf(MultipleTransformer::class, $transformer); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::union(Type::string(), Type::object(\DateInterval::class))); $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('string'), new Type('object', false, \DateInterval::class)], []); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNotNull($transformer); self::assertInstanceOf(BuiltinTransformer::class, $transformer); @@ -51,10 +48,9 @@ public function testNoTransformerIfNoSubTransformer(): void $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::union(Type::string(), Type::int())); $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('string'), new Type('int')], []); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNull($transformer); } @@ -69,15 +65,13 @@ public function testNoTransformer(): void $sourceMapperMetadata = new SourcePropertyMetadata('foo'); $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([], []); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNull($transformer); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::string()); $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('string')], []); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNull($transformer); } diff --git a/tests/Transformer/MultipleTransformerTest.php b/tests/Transformer/MultipleTransformerTest.php index 2ed2be84..e950a951 100644 --- a/tests/Transformer/MultipleTransformerTest.php +++ b/tests/Transformer/MultipleTransformerTest.php @@ -7,7 +7,7 @@ use AutoMapper\Transformer\BuiltinTransformer; use AutoMapper\Transformer\MultipleTransformer; use PHPUnit\Framework\TestCase; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; class MultipleTransformerTest extends TestCase { @@ -17,12 +17,12 @@ public function testMultipleTransformer(): void { $transformer = new MultipleTransformer([ [ - 'transformer' => new BuiltinTransformer(new Type('string'), [new Type('int')]), - 'type' => new Type('string'), + 'transformer' => new BuiltinTransformer(Type::string(), Type::int()), + 'type' => Type::string(), ], [ - 'transformer' => new BuiltinTransformer(new Type('int'), [new Type('string')]), - 'type' => new Type('int'), + 'transformer' => new BuiltinTransformer(Type::int(), Type::string()), + 'type' => Type::int(), ], ]); diff --git a/tests/Transformer/NullableTransformerFactoryTest.php b/tests/Transformer/NullableTransformerFactoryTest.php index 00eeb8c5..088b3b71 100644 --- a/tests/Transformer/NullableTransformerFactoryTest.php +++ b/tests/Transformer/NullableTransformerFactoryTest.php @@ -7,13 +7,12 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; use AutoMapper\Transformer\BuiltinTransformerFactory; use AutoMapper\Transformer\ChainTransformerFactory; use AutoMapper\Transformer\NullableTransformer; use AutoMapper\Transformer\NullableTransformerFactory; use PHPUnit\Framework\TestCase; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; class NullableTransformerFactoryTest extends TestCase { @@ -33,37 +32,33 @@ public function testGetTransformer(): void $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('string', true)], [new Type('string')], ); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::nullable(Type::string())); + $targetMapperMetadata = new TargetPropertyMetadata('foo', type: Type::string()); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNotNull($transformer); self::assertInstanceOf(NullableTransformer::class, $transformer); self::assertFalse($this->isTargetNullableProperty->getValue($transformer)); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('string', true)], [new Type('string', true)], ); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::nullable(Type::string())); + $targetMapperMetadata = new TargetPropertyMetadata('foo', type: Type::nullable(Type::string())); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNotNull($transformer); self::assertInstanceOf(NullableTransformer::class, $transformer); self::assertTrue($this->isTargetNullableProperty->getValue($transformer)); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('string', true)], [new Type('string'), new Type('int', true)], ); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::nullable(Type::string())); + $targetMapperMetadata = new TargetPropertyMetadata('foo', type: Type::union(Type::string(), Type::nullable(Type::int()))); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNotNull($transformer); self::assertInstanceOf(NullableTransformer::class, $transformer); self::assertTrue($this->isTargetNullableProperty->getValue($transformer)); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('string', true)], [new Type('string'), new Type('int')], ); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::nullable(Type::string())); + $targetMapperMetadata = new TargetPropertyMetadata('foo', type: Type::union(Type::string(), Type::int())); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNotNull($transformer); self::assertInstanceOf(NullableTransformer::class, $transformer); @@ -78,10 +73,9 @@ public function testNullTransformerIfSourceTypeNotNullable(): void $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('string')], [new Type('string')], ); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::string()); + $targetMapperMetadata = new TargetPropertyMetadata('foo', type: Type::string()); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNull($transformer); } @@ -94,10 +88,9 @@ public function testNullTransformerIfMultipleSource(): void $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('string', true), new Type('string')], [new Type('string')], ); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::union(Type::nullable(Type::string()), Type::string())); + $targetMapperMetadata = new TargetPropertyMetadata('foo', type: Type::string()); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNull($transformer); } diff --git a/tests/Transformer/NullableTransformerTest.php b/tests/Transformer/NullableTransformerTest.php index 2c8e466e..9461dd96 100644 --- a/tests/Transformer/NullableTransformerTest.php +++ b/tests/Transformer/NullableTransformerTest.php @@ -7,7 +7,7 @@ use AutoMapper\Transformer\BuiltinTransformer; use AutoMapper\Transformer\NullableTransformer; use PHPUnit\Framework\TestCase; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; class NullableTransformerTest extends TestCase { @@ -15,7 +15,7 @@ class NullableTransformerTest extends TestCase public function testNullTransformerTargetNullable(): void { - $transformer = new NullableTransformer(new BuiltinTransformer(new Type('string'), [new Type('string', true)]), true); + $transformer = new NullableTransformer(new BuiltinTransformer(Type::string(), Type::nullable(Type::string())), true); $output = $this->evalTransformer($transformer, 'foo'); @@ -28,7 +28,7 @@ public function testNullTransformerTargetNullable(): void public function testNullTransformerTargetNotNullable(): void { - $transformer = new NullableTransformer(new BuiltinTransformer(new Type('string'), [new Type('string')]), false); + $transformer = new NullableTransformer(new BuiltinTransformer(Type::string(), Type::string()), false); $output = $this->evalTransformer($transformer, 'foo'); diff --git a/tests/Transformer/ObjectTransformerFactoryTest.php b/tests/Transformer/ObjectTransformerFactoryTest.php index 9071fcba..f7532ac5 100644 --- a/tests/Transformer/ObjectTransformerFactoryTest.php +++ b/tests/Transformer/ObjectTransformerFactoryTest.php @@ -7,11 +7,10 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; use AutoMapper\Transformer\ObjectTransformer; use AutoMapper\Transformer\ObjectTransformerFactory; use PHPUnit\Framework\TestCase; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; class ObjectTransformerFactoryTest extends TestCase { @@ -20,26 +19,23 @@ public function testGetTransformer(): void $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); $factory = new ObjectTransformerFactory(); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('object', false, \stdClass::class)], [new Type('object', false, \stdClass::class)], ); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::object(\stdClass::class)); + $targetMapperMetadata = new TargetPropertyMetadata('foo', type: Type::object(\stdClass::class)); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNotNull($transformer); self::assertInstanceOf(ObjectTransformer::class, $transformer); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('array')], [new Type('object', false, \stdClass::class)], ); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::array()); + $targetMapperMetadata = new TargetPropertyMetadata('foo', type: Type::object(\stdClass::class)); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNotNull($transformer); self::assertInstanceOf(ObjectTransformer::class, $transformer); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('object', false, \stdClass::class)], [new Type('array')], ); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::object(\stdClass::class)); + $targetMapperMetadata = new TargetPropertyMetadata('foo', type: Type::array()); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNotNull($transformer); self::assertInstanceOf(ObjectTransformer::class, $transformer); @@ -52,36 +48,19 @@ public function testNoTransformer(): void $sourceMapperMetadata = new SourcePropertyMetadata('foo'); $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([], []); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); - - self::assertNull($transformer); - - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('object')], []); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNull($transformer); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::object()); $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([], [new Type('object')], ); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNull($transformer); $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('object'), new Type('object')], [new Type('object')], ); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); - - self::assertNull($transformer); - - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('object')], [new Type('object'), new Type('object')], ); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $targetMapperMetadata = new TargetPropertyMetadata('foo', type: Type::object()); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNull($transformer); } diff --git a/tests/Transformer/ObjectTransformerTest.php b/tests/Transformer/ObjectTransformerTest.php index 460fd339..be05e903 100644 --- a/tests/Transformer/ObjectTransformerTest.php +++ b/tests/Transformer/ObjectTransformerTest.php @@ -6,7 +6,7 @@ use AutoMapper\Transformer\ObjectTransformer; use PHPUnit\Framework\TestCase; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; class ObjectTransformerTest extends TestCase { @@ -14,7 +14,7 @@ class ObjectTransformerTest extends TestCase public function testObjectTransformer(): void { - $transformer = new ObjectTransformer(new Type('object', false, Foo::class), new Type('object', false, Foo::class)); + $transformer = new ObjectTransformer(Type::object(Foo::class), Type::object(Foo::class)); $function = $this->createTransformerFunction($transformer); $class = new class { diff --git a/tests/Transformer/SymfonyUidTransformerFactoryTest.php b/tests/Transformer/SymfonyUidTransformerFactoryTest.php index cdfc048b..5d3ae0d8 100644 --- a/tests/Transformer/SymfonyUidTransformerFactoryTest.php +++ b/tests/Transformer/SymfonyUidTransformerFactoryTest.php @@ -7,11 +7,10 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; use AutoMapper\Transformer\SymfonyUidCopyTransformer; use AutoMapper\Transformer\SymfonyUidTransformerFactory; use PHPUnit\Framework\TestCase; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; use Symfony\Component\Uid\Ulid; class SymfonyUidTransformerFactoryTest extends TestCase @@ -20,11 +19,10 @@ public function testNoTransformer(): void { $factory = new SymfonyUidTransformerFactory(); $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('object', false, null)], [new Type('object', false, null)], ); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::object()); + $targetMapperMetadata = new TargetPropertyMetadata('foo', type: Type::object()); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNull($transformer); } @@ -33,11 +31,10 @@ public function testGetUlidCopyTransformer(): void { $factory = new SymfonyUidTransformerFactory(); $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('object', false, Ulid::class)], [new Type('object', false, Ulid::class)], ); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::object(Ulid::class)); + $targetMapperMetadata = new TargetPropertyMetadata('foo', type: Type::object(Ulid::class)); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNotNull($transformer); self::assertInstanceOf(SymfonyUidCopyTransformer::class, $transformer); diff --git a/tests/Transformer/UniqueTypeTransformerFactoryTest.php b/tests/Transformer/UniqueTypeTransformerFactoryTest.php index 95d90ea2..9a3e2b1d 100644 --- a/tests/Transformer/UniqueTypeTransformerFactoryTest.php +++ b/tests/Transformer/UniqueTypeTransformerFactoryTest.php @@ -7,13 +7,12 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Metadata\SourcePropertyMetadata; use AutoMapper\Metadata\TargetPropertyMetadata; -use AutoMapper\Metadata\TypesMatching; use AutoMapper\Transformer\BuiltinTransformer; use AutoMapper\Transformer\BuiltinTransformerFactory; use AutoMapper\Transformer\ChainTransformerFactory; use AutoMapper\Transformer\UniqueTypeTransformerFactory; use PHPUnit\Framework\TestCase; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\TypeInfo\Type; class UniqueTypeTransformerFactoryTest extends TestCase { @@ -24,11 +23,10 @@ public function testGetTransformer(): void $factory->setChainTransformerFactory($chainFactory); $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); - $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('string')], [new Type('string'), new Type('string')]); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::string()); + $targetMapperMetadata = new TargetPropertyMetadata('foo', type: Type::union(Type::string(), Type::string())); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNotNull($transformer); self::assertInstanceOf(BuiltinTransformer::class, $transformer); @@ -43,30 +41,26 @@ public function testNullTransformer(): void $mapperMetadata = $this->getMockBuilder(MapperMetadata::class)->disableOriginalConstructor()->getMock(); $sourceMapperMetadata = new SourcePropertyMetadata('foo'); $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([], []); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNull($transformer); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::string()); $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('string')], []); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNull($transformer); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::union(Type::string(), Type::string())); $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('string'), new Type('string')], []); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNull($transformer); - $sourceMapperMetadata = new SourcePropertyMetadata('foo'); + $sourceMapperMetadata = new SourcePropertyMetadata('foo', type: Type::string()); $targetMapperMetadata = new TargetPropertyMetadata('foo'); - $types = TypesMatching::fromSourceAndTargetTypes([new Type('string')], []); - $transformer = $factory->getTransformer($types, $sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); + $transformer = $factory->getTransformer($sourceMapperMetadata, $targetMapperMetadata, $mapperMetadata); self::assertNull($transformer); } From 213cc26c3ef2f38d8f6f0138c2a27d719181bb24 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Fri, 28 Nov 2025 14:48:07 +0100 Subject: [PATCH 3/9] ci(chore): fix php version matrice --- .github/workflows/ci.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0d6ad67c..3fbdde3e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: - name: setup uses: shivammathur/setup-php@2.32.0 with: - php-version: 8.3 + php-version: 8.4 coverage: none - name: composer install run: composer update --prefer-stable @@ -41,9 +41,8 @@ jobs: fail-fast: false matrix: php-versions: - - 8.2 - - 8.3 - 8.4 + - 8.5 steps: - name: checkout uses: actions/checkout@master @@ -76,7 +75,7 @@ jobs: - name: setup uses: shivammathur/setup-php@2.32.0 with: - php-version: 8.2 + php-version: 8.4 coverage: none extensions: mbstring, fileinfo, json, intl, dom - name: composer install From 9fd95a976dba3c00b5d780b1e3694b339ad75d04 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Fri, 28 Nov 2025 15:30:33 +0100 Subject: [PATCH 4/9] feat(type): allow to override mapping type --- src/Attribute/MapFrom.php | 4 ++++ src/Attribute/MapTo.php | 4 ++++ src/Event/PropertyMetadataEvent.php | 3 --- src/EventListener/MapFromListener.php | 4 ++-- src/EventListener/MapToListener.php | 4 ++-- tests/AutoMapperMapToTest.php | 2 ++ tests/Fixtures/MapTo/FooMapTo.php | 3 +++ 7 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/Attribute/MapFrom.php b/src/Attribute/MapFrom.php index 29ecdb7a..e5ae6c9f 100644 --- a/src/Attribute/MapFrom.php +++ b/src/Attribute/MapFrom.php @@ -4,6 +4,8 @@ namespace AutoMapper\Attribute; +use Symfony\Component\TypeInfo\Type; + /** * Configures a property to map from. */ @@ -34,6 +36,8 @@ public function __construct( public ?string $dateTimeFormat = null, public ?bool $extractTypesFromGetter = null, public ?bool $identifier = null, + public ?Type $sourceType = null, + public ?Type $targetType = null, ) { } } diff --git a/src/Attribute/MapTo.php b/src/Attribute/MapTo.php index f87c134b..5c85befd 100644 --- a/src/Attribute/MapTo.php +++ b/src/Attribute/MapTo.php @@ -4,6 +4,8 @@ namespace AutoMapper\Attribute; +use Symfony\Component\TypeInfo\Type; + /** * Configures a property to map to. */ @@ -34,6 +36,8 @@ public function __construct( public ?string $dateTimeFormat = null, public ?bool $extractTypesFromGetter = null, public ?bool $identifier = null, + public ?Type $sourceType = null, + public ?Type $targetType = null, ) { } } diff --git a/src/Event/PropertyMetadataEvent.php b/src/Event/PropertyMetadataEvent.php index 308adc55..bd51e205 100644 --- a/src/Event/PropertyMetadataEvent.php +++ b/src/Event/PropertyMetadataEvent.php @@ -6,7 +6,6 @@ use AutoMapper\Metadata\MapperMetadata; use AutoMapper\Transformer\TransformerInterface; -use Symfony\Component\TypeInfo\Type; /** * @internal @@ -20,8 +19,6 @@ public function __construct( public readonly MapperMetadata $mapperMetadata, public readonly SourcePropertyMetadata $source, public readonly TargetPropertyMetadata $target, - public ?Type $sourceType = null, - public ?Type $targetType = null, public ?int $maxDepth = null, public ?TransformerInterface $transformer = null, public ?string $dateTimeFormat = null, diff --git a/src/EventListener/MapFromListener.php b/src/EventListener/MapFromListener.php index 073e9b0f..f4ae5cce 100644 --- a/src/EventListener/MapFromListener.php +++ b/src/EventListener/MapFromListener.php @@ -68,8 +68,8 @@ private function addPropertyFromTarget(GenerateMapperEvent $event, MapFrom $mapF return; } - $sourceProperty = new SourcePropertyMetadata($mapFrom->property ?? $property); - $targetProperty = new TargetPropertyMetadata($property); + $sourceProperty = new SourcePropertyMetadata($mapFrom->property ?? $property, type: $mapFrom->sourceType); + $targetProperty = new TargetPropertyMetadata($property, type: $mapFrom->targetType); $propertyMetadata = new PropertyMetadataEvent( mapperMetadata: $event->mapperMetadata, diff --git a/src/EventListener/MapToListener.php b/src/EventListener/MapToListener.php index affaeb2f..888ca8c9 100644 --- a/src/EventListener/MapToListener.php +++ b/src/EventListener/MapToListener.php @@ -69,8 +69,8 @@ private function addPropertyFromSource(GenerateMapperEvent $event, MapTo $mapTo, return; } - $sourceProperty = new SourcePropertyMetadata($property); - $targetProperty = new TargetPropertyMetadata($mapTo->property ?? $property); + $sourceProperty = new SourcePropertyMetadata($property, type: $mapTo->sourceType); + $targetProperty = new TargetPropertyMetadata($mapTo->property ?? $property, type: $mapTo->targetType); $propertyMetadata = new PropertyMetadataEvent( mapperMetadata: $event->mapperMetadata, diff --git a/tests/AutoMapperMapToTest.php b/tests/AutoMapperMapToTest.php index 845732ff..1c83c1a6 100644 --- a/tests/AutoMapperMapToTest.php +++ b/tests/AutoMapperMapToTest.php @@ -60,6 +60,7 @@ public function testMapToArray() $this->assertSame('if', $bar['ifCallableOther']); $this->assertSame('transformed', $bar['transformFromExpressionLanguage']); $this->assertSame('bar', $bar['transformWithExpressionFunction']); + $this->assertSame(0, $bar['fooInt']); $foo = new FooMapTo('bar'); $bar = $this->autoMapper->map($foo, 'array'); @@ -78,6 +79,7 @@ public function testMapToArray() $this->assertSame('transformFromStringStatic_bar', $bar['transformFromStringStatic']); $this->assertSame('bar', $bar['transformFromCustomTransformerService']); $this->assertSame('not transformed', $bar['transformFromExpressionLanguage']); + $this->assertSame(0, $bar['fooInt']); } public function testMapToArrayGroups() diff --git a/tests/Fixtures/MapTo/FooMapTo.php b/tests/Fixtures/MapTo/FooMapTo.php index 74dd4f31..eb5a6a0d 100644 --- a/tests/Fixtures/MapTo/FooMapTo.php +++ b/tests/Fixtures/MapTo/FooMapTo.php @@ -6,6 +6,8 @@ use AutoMapper\Attribute\MapTo; use AutoMapper\Tests\Fixtures\Transformer\CustomTransformer\TransformerWithDependency; +use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\TypeIdentifier; #[MapTo('array', property: 'externalProperty', transformer: 'transformExternalProperty', groups: ['group1'])] #[MapTo('array', property: 'transformWithExpressionFunction', transformer: "transformerWithDependency().transform('foo', source, context)")] @@ -20,6 +22,7 @@ public function __construct( #[MapTo('array', property: 'transformFromCustomTransformerService', transformer: TransformerWithDependency::class)] #[MapTo('array', property: 'transformFromExpressionLanguage', transformer: "source.foo === 'foo' ? 'transformed' : 'not transformed'")] #[MapTo('array', property: 'foo')] + #[MapTo('array', property: 'fooInt', targetType: new Type\BuiltinType(TypeIdentifier::INT))] public string $foo, ) { } From 858b26db0388d54db0badbb3082ce6d702379870 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Fri, 28 Nov 2025 16:42:32 +0100 Subject: [PATCH 5/9] fix(deprec): fix php 8.5 deprecations --- phpstan.neon | 3 --- src/GeneratedMapper.php | 8 ++++---- src/Generator/IdentifierHashGenerator.php | 8 ++++---- src/Transformer/AbstractArrayTransformer.php | 4 ++-- .../ArrayToDoctrineCollectionTransformer.php | 2 +- src/Transformer/BuiltinTransformer.php | 17 +++++++++++++---- tests/AutoMapperMapToTest.php | 4 ++-- tests/AutoMapperTest.php | 1 - tests/Fixtures/MapTo/DateTimeFormatMapTo.php | 2 +- .../MapTo/MapperDateTimeFormatMapTo.php | 2 +- .../NullableTransformerFactoryTest.php | 1 - 11 files changed, 28 insertions(+), 24 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index fb0de07b..df4de3c2 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -9,9 +9,6 @@ parameters: tmpDir: cache ignoreErrors: - - "#Instantiated class fromIteratorToArray not found#" - - "#Instantiated class toArray not found#" - - message: "#^Method Symfony\\\\Component\\\\Serializer\\\\NameConverter\\\\NameConverterInterface\\:\\:normalize\\(\\) invoked with 2 parameters, 1 required\\.$#" count: 2 diff --git a/src/GeneratedMapper.php b/src/GeneratedMapper.php index ace39ffa..7d010c0b 100644 --- a/src/GeneratedMapper.php +++ b/src/GeneratedMapper.php @@ -41,14 +41,14 @@ public function getTargetIdentifiers(mixed $value): mixed return null; } - public function getSourceHash(mixed $value): ?string + public function getSourceHash(mixed $value): string { - return null; + return ''; } - public function getTargetHash(mixed $value): ?string + public function getTargetHash(mixed $value): string { - return null; + return ''; } /** @var array|MapperInterface>|MapperInterface, object>> */ diff --git a/src/Generator/IdentifierHashGenerator.php b/src/Generator/IdentifierHashGenerator.php index 4c03665d..46e831b6 100644 --- a/src/Generator/IdentifierHashGenerator.php +++ b/src/Generator/IdentifierHashGenerator.php @@ -65,7 +65,7 @@ private function getStatements(GeneratorMetadata $metadata, bool $fromSource): a if ($property->source->checkExists) { $statements[] = new Stmt\If_($property->source->accessor->getIsUndefinedExpression($valueVariable), [ 'stmts' => [ - new Stmt\Return_(new Expr\ConstFetch(new Name('null'))), + new Stmt\Return_(new Expr\ConstFetch(new Name("''"))), ], ]); } @@ -78,7 +78,7 @@ private function getStatements(GeneratorMetadata $metadata, bool $fromSource): a } else { $statements[] = new Stmt\If_($property->target->readAccessor->getIsUndefinedExpression($valueVariable, true), [ 'stmts' => [ - new Stmt\Return_(new Expr\ConstFetch(new Name('null'))), + new Stmt\Return_(new Expr\ConstFetch(new Name("''"))), ], ]); @@ -121,7 +121,7 @@ public function getSourceHashMethod(GeneratorMetadata $metadata): ?Stmt\ClassMet return (new Builder\Method('getSourceHash')) ->makePublic() - ->setReturnType('?string') + ->setReturnType('string') ->addParam(new Param( var: new Expr\Variable('value'), type: new Name('mixed')) @@ -149,7 +149,7 @@ public function getTargetHashMethod(GeneratorMetadata $metadata): ?Stmt\ClassMet return (new Builder\Method('getTargetHash')) ->makePublic() - ->setReturnType('?string') + ->setReturnType('string') ->addParam(new Param( var: new Expr\Variable('value'), type: new Name('mixed')) diff --git a/src/Transformer/AbstractArrayTransformer.php b/src/Transformer/AbstractArrayTransformer.php index e28b712b..53797afc 100644 --- a/src/Transformer/AbstractArrayTransformer.php +++ b/src/Transformer/AbstractArrayTransformer.php @@ -75,7 +75,7 @@ public function transform(Expr $input, Expr $target, PropertyMetadata $propertyM $loopExistingStatements[] = new Stmt\If_($isDeepPopulateExpr, [ 'stmts' => [ new Stmt\Expression(new Expr\Assign($targetHashVar, $this->itemTransformer->getTargetHashExpression($loopRemoveValueVar))), - new Stmt\If_(new Expr\BinaryOp\NotIdentical(new Expr\ConstFetch(new Name('null')), $targetHashVar), [ + new Stmt\If_(new Expr\BinaryOp\NotIdentical(new Expr\ConstFetch(new Name("''")), $targetHashVar), [ 'stmts' => [new Stmt\Expression(new Expr\Assign(new Expr\ArrayDimFetch($exisingValuesIndexed, $targetHashVar), $loopRemoveValueVar))], ]), ], @@ -124,7 +124,7 @@ public function transform(Expr $input, Expr $target, PropertyMetadata $propertyM new Stmt\Foreach_($propertyMapping->target->readAccessor->getExpression($target), $loopExistingValueVar, [ 'stmts' => [ new Stmt\Expression(new Expr\Assign($hashValueVariable, $this->itemTransformer->getTargetHashExpression($loopExistingValueVar))), - new Stmt\If_(new Expr\BinaryOp\NotIdentical(new Expr\ConstFetch(new Name('null')), $hashValueVariable), [ + new Stmt\If_(new Expr\BinaryOp\NotIdentical(new Expr\ConstFetch(new Name("''")), $hashValueVariable), [ 'stmts' => [ new Stmt\Expression(new Expr\Assign(new Expr\ArrayDimFetch($exisingValuesIndexed, $hashValueVariable), $loopExistingValueVar)), ], diff --git a/src/Transformer/ArrayToDoctrineCollectionTransformer.php b/src/Transformer/ArrayToDoctrineCollectionTransformer.php index a515ff0d..250b0794 100644 --- a/src/Transformer/ArrayToDoctrineCollectionTransformer.php +++ b/src/Transformer/ArrayToDoctrineCollectionTransformer.php @@ -56,7 +56,7 @@ public function transform(Expr $input, Expr $target, PropertyMetadata $propertyM new Stmt\Foreach_($propertyMapping->target->readAccessor->getExpression($target), $loopExistingValueVar, [ 'stmts' => [ new Stmt\Expression(new Expr\Assign($hashValueVariable, $this->itemTransformer->getTargetHashExpression($loopExistingValueVar))), - new Stmt\If_(new Expr\BinaryOp\NotIdentical(new Expr\ConstFetch(new Name('null')), $hashValueVariable), [ + new Stmt\If_(new Expr\BinaryOp\NotIdentical(new Expr\ConstFetch(new Name("''")), $hashValueVariable), [ 'stmts' => [ new Stmt\Expression(new Expr\Assign(new Expr\ArrayDimFetch($exisingValuesIndexed, $hashValueVariable), $loopExistingValueVar)), ], diff --git a/src/Transformer/BuiltinTransformer.php b/src/Transformer/BuiltinTransformer.php index dfddaab0..b028ea66 100644 --- a/src/Transformer/BuiltinTransformer.php +++ b/src/Transformer/BuiltinTransformer.php @@ -28,14 +28,14 @@ TypeIdentifier::BOOL->value => [ TypeIdentifier::INT->value => Cast\Int_::class, TypeIdentifier::STRING->value => Cast\String_::class, - TypeIdentifier::FLOAT->value => Cast\Double::class, + TypeIdentifier::FLOAT->value => 'toFloat', TypeIdentifier::ARRAY->value => 'toArray', TypeIdentifier::ITERABLE->value => 'toArray', ], TypeIdentifier::MIXED->value => [ TypeIdentifier::INT->value => Cast\Int_::class, TypeIdentifier::STRING->value => Cast\String_::class, - TypeIdentifier::FLOAT->value => Cast\Double::class, + TypeIdentifier::FLOAT->value => 'toFloat', TypeIdentifier::ARRAY->value => 'toArray', TypeIdentifier::ITERABLE->value => 'toArray', ], @@ -47,7 +47,7 @@ TypeIdentifier::ITERABLE->value => 'toArray', ], TypeIdentifier::INT->value => [ - TypeIdentifier::FLOAT->value => Cast\Double::class, + TypeIdentifier::FLOAT->value => 'toFloat', TypeIdentifier::STRING->value => Cast\String_::class, TypeIdentifier::BOOL->value => Cast\Bool_::class, TypeIdentifier::ARRAY->value => 'toArray', @@ -60,7 +60,7 @@ TypeIdentifier::STRING->value => [ TypeIdentifier::ARRAY->value => 'toArray', TypeIdentifier::ITERABLE->value => 'toArray', - TypeIdentifier::FLOAT->value => Cast\Double::class, + TypeIdentifier::FLOAT->value => 'toFloat', TypeIdentifier::INT->value => Cast\Int_::class, TypeIdentifier::BOOL->value => Cast\Bool_::class, ], @@ -120,6 +120,10 @@ public function transform(Expr $input, Expr $target, PropertyMetadata $propertyM return [$this->$castMethod($input), []]; } + if (!class_exists($castMethod)) { + continue; + } + /* * Use the cast expression find in the cast matrix * @@ -156,6 +160,11 @@ private function toArray(Expr $input): Expr return new Expr\Array_([create_expr_array_item($input)]); } + private function toFloat(Expr $input): Expr + { + return new Cast\Double($input, ['kind' => Cast\Double::KIND_FLOAT]); + } + private function fromIteratorToArray(Expr $input): Expr { return new Expr\FuncCall(new Name('iterator_to_array'), [ diff --git a/tests/AutoMapperMapToTest.php b/tests/AutoMapperMapToTest.php index 1c83c1a6..71afd750 100644 --- a/tests/AutoMapperMapToTest.php +++ b/tests/AutoMapperMapToTest.php @@ -149,7 +149,7 @@ public function testDateTimeFormat(): void self::assertArrayHasKey('immutable', $result); self::assertSame($immutable->format(\DateTimeInterface::RFC822), $result['immutable']); self::assertArrayHasKey('interface', $result); - self::assertSame($normal->format(\DateTimeInterface::RFC7231), $result['interface']); + self::assertSame($normal->format(\DateTimeInterface::RFC3339_EXTENDED), $result['interface']); $bar = new MapperDateTimeFormatMapTo($normal, $immutable, $normal); $result = $this->autoMapper->map($bar, 'array'); @@ -159,7 +159,7 @@ public function testDateTimeFormat(): void self::assertArrayHasKey('immutable', $result); self::assertSame($immutable->format(\DateTimeInterface::ATOM), $result['immutable']); self::assertArrayHasKey('interface', $result); - self::assertSame($normal->format(\DateTimeInterface::RFC7231), $result['interface']); + self::assertSame($normal->format(\DateTimeInterface::RFC3339_EXTENDED), $result['interface']); $baz = new MapperDateTimeFormatMapTo($normal, $immutable, $normal); $result = $this->autoMapper->map($baz, 'array', [MapperContext::DATETIME_FORMAT => \DateTimeInterface::RFC822]); diff --git a/tests/AutoMapperTest.php b/tests/AutoMapperTest.php index 341e498c..02b7149f 100644 --- a/tests/AutoMapperTest.php +++ b/tests/AutoMapperTest.php @@ -216,7 +216,6 @@ public function testNotReadable(): void self::assertInstanceOf(Address::class, $addressMapped); $property = (new \ReflectionClass($addressMapped))->getProperty('city'); - $property->setAccessible(true); $city = $property->getValue($addressMapped); diff --git a/tests/Fixtures/MapTo/DateTimeFormatMapTo.php b/tests/Fixtures/MapTo/DateTimeFormatMapTo.php index 9f5a2dda..9eb8d526 100644 --- a/tests/Fixtures/MapTo/DateTimeFormatMapTo.php +++ b/tests/Fixtures/MapTo/DateTimeFormatMapTo.php @@ -13,7 +13,7 @@ public function __construct( public \DateTime $normal, #[MapTo('array', dateTimeFormat: \DateTimeInterface::RFC822)] public \DateTimeImmutable $immutable, - #[MapTo('array', dateTimeFormat: \DateTimeInterface::RFC7231)] + #[MapTo('array', dateTimeFormat: \DateTimeInterface::RFC3339_EXTENDED)] public \DateTimeInterface $interface, ) { } diff --git a/tests/Fixtures/MapTo/MapperDateTimeFormatMapTo.php b/tests/Fixtures/MapTo/MapperDateTimeFormatMapTo.php index 23493e2b..a4a0947d 100644 --- a/tests/Fixtures/MapTo/MapperDateTimeFormatMapTo.php +++ b/tests/Fixtures/MapTo/MapperDateTimeFormatMapTo.php @@ -13,7 +13,7 @@ class MapperDateTimeFormatMapTo public function __construct( public \DateTime $normal, public \DateTimeImmutable $immutable, - #[MapTo('array', dateTimeFormat: \DateTimeInterface::RFC7231)] + #[MapTo('array', dateTimeFormat: \DateTimeInterface::RFC3339_EXTENDED)] public \DateTimeInterface $interface, ) { } diff --git a/tests/Transformer/NullableTransformerFactoryTest.php b/tests/Transformer/NullableTransformerFactoryTest.php index 088b3b71..295c1ec6 100644 --- a/tests/Transformer/NullableTransformerFactoryTest.php +++ b/tests/Transformer/NullableTransformerFactoryTest.php @@ -21,7 +21,6 @@ class NullableTransformerFactoryTest extends TestCase protected function setUp(): void { $this->isTargetNullableProperty = (new \ReflectionClass(NullableTransformer::class))->getProperty('isTargetNullable'); - $this->isTargetNullableProperty->setAccessible(true); } public function testGetTransformer(): void From c4c83b1bad617cdf369ead24b27d52bb2b3aecad Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Sun, 30 Nov 2025 11:14:10 +0100 Subject: [PATCH 6/9] chore(profiler): better show types in profiler --- CHANGELOG.md | 8 ++++++++ castor.php | 8 ++++++++ .../Bundle/DataCollector/MetadataCollector.php | 2 +- .../Resources/views/DataCollector/metadata.html.twig | 4 ++-- .../Bundle/Resources/config/routes/dev/profiler.yaml | 12 ++++++------ 5 files changed, 25 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bb81bca..a5a88ae3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +- [GH#297](https://github.com/jolicode/automapper/pull/297) : Support PHP 8.5 and Symfony 8, this library now use the `TypeInfo` Component for types instead of PropertyInfo directly. +- [BC Break] `PropertyTransformerSupportInterface` does not use a `TypesMatching` anymore, you can get the type directly from `SourcePropertyMetadata` or `TargetPropertyMetadata`. +- Debug command now show the type of each property mapped, transformers will also display more information. +- Profiler now show the type of each property mapped, transformers will also display more information. +- Add a castor task to serve the symfony app in tests for debugging purpose. + ## [9.5.0] - 2025-09-18 ### Added - [GH#260](https://github.com/jolicode/automapper/pull/260) Add support for identifiers detection and comparison of objects, this allow mappers to detect if objects are equals based on some properties, which allow better deep merge / update of collections. diff --git a/castor.php b/castor.php index 37d9542c..707f504e 100644 --- a/castor.php +++ b/castor.php @@ -154,3 +154,11 @@ function doc_build_github_pages() run('git reset --hard gh-pages', context: $context); } + +#[AsTask('serve', namespace: 'test', description: 'Serve the symfony app in tests')] +function test_serve($prod = false): void +{ + $file = $prod ? 'index.php' : 'dev.php'; + + run('php -S localhost:8000 tests/Bundle/Resources/public/' . $file, context: context()->withAllowFailure()); +} diff --git a/src/Symfony/Bundle/DataCollector/MetadataCollector.php b/src/Symfony/Bundle/DataCollector/MetadataCollector.php index 96dee13e..ddbfaa6a 100644 --- a/src/Symfony/Bundle/DataCollector/MetadataCollector.php +++ b/src/Symfony/Bundle/DataCollector/MetadataCollector.php @@ -84,7 +84,7 @@ function (PropertyMetadata $property) { return [ 'source' => $property->source, 'target' => $property->target, - 'transformer' => \get_class($property->transformer), + 'transformer' => $property->transformer instanceof \Stringable ? (string) $property->transformer : \get_class($property->transformer), 'maxDepth' => $property->maxDepth, 'if' => $property->if, 'groups' => $property->groups, diff --git a/src/Symfony/Bundle/Resources/views/DataCollector/metadata.html.twig b/src/Symfony/Bundle/Resources/views/DataCollector/metadata.html.twig index 829c68f7..8d41dd62 100644 --- a/src/Symfony/Bundle/Resources/views/DataCollector/metadata.html.twig +++ b/src/Symfony/Bundle/Resources/views/DataCollector/metadata.html.twig @@ -123,8 +123,8 @@ {% if property.source.accessor %} - {{ property.source.property }} -> - {{ property.target.property }} + {{ property.source.property }} ({{ property.source.type }}) -> + {{ property.target.property }} ({{ property.target.type }}) {% else %} {{ property.target.property }} {% endif %} diff --git a/tests/Bundle/Resources/config/routes/dev/profiler.yaml b/tests/Bundle/Resources/config/routes/dev/profiler.yaml index cab9dc00..b603f951 100644 --- a/tests/Bundle/Resources/config/routes/dev/profiler.yaml +++ b/tests/Bundle/Resources/config/routes/dev/profiler.yaml @@ -1,7 +1,7 @@ -web_profiler_wdt: - resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml' - prefix: /_wdt - -web_profiler_profiler: - resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml' +web_profiler_wdt: + resource: '@WebProfilerBundle/Resources/config/routing/wdt.php' + prefix: /_wdt + +web_profiler_profiler: + resource: '@WebProfilerBundle/Resources/config/routing/profiler.php' prefix: /_profiler \ No newline at end of file From 575e4076435b49ef5ce5cd04ebbb12cbf290a808 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Sun, 30 Nov 2025 11:30:59 +0100 Subject: [PATCH 7/9] correctly exclude config for cs fixer --- .php-cs-fixer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index 371f105b..b0352c13 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -4,7 +4,7 @@ ->in(__DIR__ . '/src') ->in(__DIR__ . '/tests') ->append([__DIR__ . '/castor.php']) - ->exclude(['cache', 'Bundle/Resources/var', 'Bundle/Resources/config/reference.php']) + ->exclude(['cache', 'Bundle/Resources/var', 'Bundle/Resources/config']) ; return (new PhpCsFixer\Config()) From 0cc6fedc303ea044a46a766902964ad94f0cca75 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Sun, 30 Nov 2025 13:45:32 +0100 Subject: [PATCH 8/9] chore(upgrade): add upgrade documentation --- docs/_nav.md | 3 +- docs/mapping/transformer.md | 2 +- docs/upgrading/upgrading-10.0.md | 52 +++++++++++++++++++++++++ docs/{ => upgrading}/upgrading-9.0.md | 56 +++++++++++++-------------- 4 files changed, 83 insertions(+), 30 deletions(-) create mode 100644 docs/upgrading/upgrading-10.0.md rename docs/{ => upgrading}/upgrading-9.0.md (62%) diff --git a/docs/_nav.md b/docs/_nav.md index 6d4f655b..ef15c01e 100644 --- a/docs/_nav.md +++ b/docs/_nav.md @@ -25,5 +25,6 @@ - [Api Platform](bundle/api-platform.md) - [Migrate existing application](bundle/migrate.md) - [Debugging](bundle/debugging.md) -- [Upgrading to 9.0](upgrading-9.0.md) +- [Upgrading](upgrading/upgrading-10.0.md) + - [Upgrading to 9.0](upgrading/upgrading-9.0.md) - [Contributing](contributing.md) diff --git a/docs/mapping/transformer.md b/docs/mapping/transformer.md index 0d8a4c6d..df8fe09f 100644 --- a/docs/mapping/transformer.md +++ b/docs/mapping/transformer.md @@ -135,7 +135,7 @@ class UrlTransformer implements PropertyTransformerInterface, PropertyTransforme { } - public function supports(TypesMatching $types, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): bool + public function supports(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): bool { return $source->type->isIdentifiedBy(TypeIdentifier::INT && $source->property === 'id' && $target->property === 'url'; } diff --git a/docs/upgrading/upgrading-10.0.md b/docs/upgrading/upgrading-10.0.md new file mode 100644 index 00000000..70ba9a13 --- /dev/null +++ b/docs/upgrading/upgrading-10.0.md @@ -0,0 +1,52 @@ +# Upgrading to 10.0 + +10.0 is major release of AutoMapper. It aims at supporting the new [`TypeInfo` Component](https://symfony.com/doc/current/components/type_info.html) of Symfony + +There is little change between 9.X and 10.0 version, but they are still a BC break so it need a major version. + +## Custom Transformers + +If you were using the `PropertyTransformerSupportInterface` interface, its signature has changed to use the new `TypeInfo` component. + +Before: + +```php +class MyTransformer implements PropertyTransformerSupportInterface +{ + public function supports(TypesMatching $types, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): bool + { + // ... + } +} +``` + +After: + +```php +class MyTransformer implements PropertyTransformerSupportInterface +{ + public function supports(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): bool + { + // ... + } +} +``` + +There is no more `TypesMatching` argument, you can get the source and target types from the `SourcePropertyMetadata` and `TargetPropertyMetadata` arguments, +which are instance of `Symfony\Component\TypeInfo\Type` class. + +```php +class MyTransformer implements PropertyTransformerSupportInterface +{ + public function supports(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): bool + { + if ($source->type->isNullable()) { + return false; + } + + // ... + } +} +``` + +See the [transformers documentation](../mapping/transformer.md#creating-a-custom-transformer) for more information. diff --git a/docs/upgrading-9.0.md b/docs/upgrading/upgrading-9.0.md similarity index 62% rename from docs/upgrading-9.0.md rename to docs/upgrading/upgrading-9.0.md index 7c6fab56..28c66888 100644 --- a/docs/upgrading-9.0.md +++ b/docs/upgrading/upgrading-9.0.md @@ -1,28 +1,28 @@ -# Upgrading from 8.x to 9.0 - -9.0 is major release of AutoMapper. It brings a lot of new features and improvements. We recommend first [to check -the new documentation](./index.md) to see if the new features are useful for your project. - -If you upgrade from 8.x to 9.0, you will need to make some changes to your code, but most of existing behavior should -still work. - -## Bundle - -If you use the bundle, it is now integrated in the main package. You can remove the `jolicode/automapper-bundle` package from your -`composer.json` file. - -Then you have to use the new namespace for the bundle: - -```php -use AutoMapper\Symfony\Bundle\AutoMapperBundle; -``` - -You will also need to update the bundle configuration, see the [bundle documentation](./bundle/configuration.md) for more -information. - -## Custom Transformers - -The `CustomPropertyTransformerInterface` and `CustomModelTransformerInterface` have been removed in favor of the -`PropertyTransformerInterface` interface handling both case. - -See the [transformers documentation](./mapping/transformer.md#creating-a-custom-transformer) for more information. +# Upgrading to 10.0 + +9.0 is major release of AutoMapper. It brings a lot of new features and improvements. We recommend first [to check +the new documentation](../index.md) to see if the new features are useful for your project. + +If you upgrade from 8.x to 9.0, you will need to make some changes to your code, but most of existing behavior should +still work. + +## Bundle + +If you use the bundle, it is now integrated in the main package. You can remove the `jolicode/automapper-bundle` package from your +`composer.json` file. + +Then you have to use the new namespace for the bundle: + +```php +use AutoMapper\Symfony\Bundle\AutoMapperBundle; +``` + +You will also need to update the bundle configuration, see the [bundle documentation](../bundle/configuration.md) for more +information. + +## Custom Transformers + +The `CustomPropertyTransformerInterface` and `CustomModelTransformerInterface` have been removed in favor of the +`PropertyTransformerInterface` interface handling both case. + +See the [transformers documentation](../mapping/transformer.md#creating-a-custom-transformer) for more information. \ No newline at end of file From 757bddba8e15d74deeff3009493b5299530d6c27 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Sun, 30 Nov 2025 15:14:41 +0100 Subject: [PATCH 9/9] fix(ugrade): remove annotation, use only attribute --- phpstan.neon | 6 ------ src/AutoMapper.php | 8 +------- ...rterListener.php => NameConverterListener.php} | 15 ++++++++++----- src/Extractor/FromSourceMappingExtractor.php | 4 ++++ src/Extractor/FromTargetMappingExtractor.php | 4 ++++ src/Extractor/MappingExtractorInterface.php | 3 +++ src/Extractor/SourceTargetMappingExtractor.php | 4 ++++ src/Metadata/MetadataFactory.php | 9 ++++----- .../DependencyInjection/AutoMapperExtension.php | 4 ++-- src/Symfony/Bundle/Resources/config/event.php | 8 ++++---- .../Bundle/Resources/config/event_serializer.php | 4 ++-- tests/AutoMapperTest/GroupOverride/map.php | 4 ++-- tests/AutoMapperTest/Ignore/map.php | 2 +- tests/AutoMapperTest/MaxDepth/map.php | 2 +- tests/Bundle/Resources/App/Entity/Pet.php | 2 +- tests/Fixtures/Bar.php | 2 +- tests/Fixtures/Foo.php | 2 +- tests/Fixtures/Pet.php | 2 +- tests/Fixtures/UserWithYearOfBirth.php | 2 +- 19 files changed, 47 insertions(+), 40 deletions(-) rename src/EventListener/Symfony/{AdvancedNameConverterListener.php => NameConverterListener.php} (68%) diff --git a/phpstan.neon b/phpstan.neon index df4de3c2..ea74cd00 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -7,9 +7,3 @@ parameters: - src/ tmpDir: cache - - ignoreErrors: - - - message: "#^Method Symfony\\\\Component\\\\Serializer\\\\NameConverter\\\\NameConverterInterface\\:\\:normalize\\(\\) invoked with 2 parameters, 1 required\\.$#" - count: 2 - path: src/EventListener/Symfony/AdvancedNameConverterListener.php diff --git a/src/AutoMapper.php b/src/AutoMapper.php index 963fd3fb..6089483d 100644 --- a/src/AutoMapper.php +++ b/src/AutoMapper.php @@ -19,7 +19,6 @@ use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerInterface; use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerRegistry; use AutoMapper\Transformer\TransformerFactoryInterface; -use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\Persistence\ObjectManager; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -28,9 +27,7 @@ use Symfony\Component\Lock\Store\FlockStore; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; -use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; -use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; /** @@ -143,7 +140,7 @@ public function mapCollection(iterable $collection, string $target, array $conte public static function create( Configuration $configuration = new Configuration(), ?string $cacheDirectory = null, - AdvancedNameConverterInterface|NameConverterInterface|null $nameConverter = null, + ?NameConverterInterface $nameConverter = null, array $transformerFactories = [], iterable $propertyTransformers = [], ?ExpressionLanguageProvider $expressionLanguageProvider = null, @@ -153,9 +150,6 @@ public static function create( ): AutoMapperInterface { if (class_exists(AttributeLoader::class)) { $loaderClass = new AttributeLoader(); - } elseif (class_exists(AnnotationReader::class) && class_exists(AnnotationLoader::class)) { - /** @var AttributeLoader $loaderClass */ - $loaderClass = new AnnotationLoader(new AnnotationReader()); } else { $loaderClass = null; } diff --git a/src/EventListener/Symfony/AdvancedNameConverterListener.php b/src/EventListener/Symfony/NameConverterListener.php similarity index 68% rename from src/EventListener/Symfony/AdvancedNameConverterListener.php rename to src/EventListener/Symfony/NameConverterListener.php index c5d6dce2..c7741b5e 100644 --- a/src/EventListener/Symfony/AdvancedNameConverterListener.php +++ b/src/EventListener/Symfony/NameConverterListener.php @@ -5,24 +5,29 @@ namespace AutoMapper\EventListener\Symfony; use AutoMapper\Event\PropertyMetadataEvent; -use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; -final readonly class AdvancedNameConverterListener +final readonly class NameConverterListener { public function __construct( - private AdvancedNameConverterInterface|NameConverterInterface $nameConverter, + private NameConverterInterface $nameConverter, ) { } public function __invoke(PropertyMetadataEvent $event): void { if (($event->mapperMetadata->source === 'array' || $event->mapperMetadata->source === \stdClass::class) && $event->source->property === $event->target->property) { - $event->source->property = $this->nameConverter->normalize($event->target->property, $event->mapperMetadata->target); + /** @var class-string $target */ + $target = $event->mapperMetadata->target; + + $event->source->property = $this->nameConverter->normalize($event->target->property, $target); } if (($event->mapperMetadata->target === 'array' || $event->mapperMetadata->target === \stdClass::class) && $event->source->property === $event->target->property) { - $event->target->property = $this->nameConverter->normalize($event->source->property, $event->mapperMetadata->source); + /** @var class-string $source */ + $source = $event->mapperMetadata->source; + + $event->target->property = $this->nameConverter->normalize($event->source->property, $source); } } } diff --git a/src/Extractor/FromSourceMappingExtractor.php b/src/Extractor/FromSourceMappingExtractor.php index 98c0b32a..b1f5201a 100644 --- a/src/Extractor/FromSourceMappingExtractor.php +++ b/src/Extractor/FromSourceMappingExtractor.php @@ -19,6 +19,10 @@ */ final class FromSourceMappingExtractor extends MappingExtractor { + /** + * @param class-string $source + * @param 'array' $target + */ public function getTypes(string $source, SourcePropertyMetadata $sourceProperty, string $target, TargetPropertyMetadata $targetProperty, bool $extractTypesFromGetter): array { $sourceType = $this->sourceTypeExtractor->getType($source, $sourceProperty->property) ?? Type::mixed(); diff --git a/src/Extractor/FromTargetMappingExtractor.php b/src/Extractor/FromTargetMappingExtractor.php index d940b481..599e340d 100644 --- a/src/Extractor/FromTargetMappingExtractor.php +++ b/src/Extractor/FromTargetMappingExtractor.php @@ -19,6 +19,10 @@ */ final class FromTargetMappingExtractor extends MappingExtractor { + /** + * @param 'array' $source + * @param class-string $target + */ public function getTypes(string $source, SourcePropertyMetadata $sourceProperty, string $target, TargetPropertyMetadata $targetProperty, bool $extractTypesFromGetter): array { $targetType = $this->targetTypeExtractor->getType($target, $targetProperty->property) ?? Type::mixed(); diff --git a/src/Extractor/MappingExtractorInterface.php b/src/Extractor/MappingExtractorInterface.php index 000e3f16..7e7e1dbe 100644 --- a/src/Extractor/MappingExtractorInterface.php +++ b/src/Extractor/MappingExtractorInterface.php @@ -28,6 +28,9 @@ interface MappingExtractorInterface public function getProperties(string $class): iterable; /** + * @param class-string|'array' $source + * @param class-string|'array' $target + * * @return array{Type, Type} */ public function getTypes(string $source, SourcePropertyMetadata $sourceProperty, string $target, TargetPropertyMetadata $targetProperty, bool $extractTypesFromGetter): array; diff --git a/src/Extractor/SourceTargetMappingExtractor.php b/src/Extractor/SourceTargetMappingExtractor.php index 19ab178a..fb3d1a3c 100644 --- a/src/Extractor/SourceTargetMappingExtractor.php +++ b/src/Extractor/SourceTargetMappingExtractor.php @@ -17,6 +17,10 @@ */ class SourceTargetMappingExtractor extends MappingExtractor { + /** + * @param class-string $source + * @param class-string $target + */ public function getTypes(string $source, SourcePropertyMetadata $sourceProperty, string $target, TargetPropertyMetadata $targetProperty, bool $extractTypesFromGetter): array { $sourceType = $this->sourceTypeExtractor->getType($source, $sourceProperty->property) ?? Type::mixed(); diff --git a/src/Metadata/MetadataFactory.php b/src/Metadata/MetadataFactory.php index a6f38aed..512128e4 100644 --- a/src/Metadata/MetadataFactory.php +++ b/src/Metadata/MetadataFactory.php @@ -16,8 +16,8 @@ use AutoMapper\EventListener\MapProviderListener; use AutoMapper\EventListener\MapToContextListener; use AutoMapper\EventListener\MapToListener; -use AutoMapper\EventListener\Symfony\AdvancedNameConverterListener; use AutoMapper\EventListener\Symfony\ClassDiscriminatorListener; +use AutoMapper\EventListener\Symfony\NameConverterListener; use AutoMapper\EventListener\Symfony\SerializerGroupListener; use AutoMapper\EventListener\Symfony\SerializerIgnoreListener; use AutoMapper\EventListener\Symfony\SerializerMaxDepthListener; @@ -56,7 +56,6 @@ use Symfony\Component\PropertyInfo\PropertyInfoExtractor; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; -use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; use Symfony\Component\Uid\AbstractUid; @@ -352,7 +351,7 @@ public static function create( MetadataRegistry $metadataRegistry, ClassDiscriminatorResolver $classDiscriminatorResolver, ?ClassMetadataFactory $classMetadataFactory = null, - AdvancedNameConverterInterface|NameConverterInterface|null $nameConverter = null, + ?NameConverterInterface $nameConverter = null, ExpressionLanguage $expressionLanguage = new ExpressionLanguage(), EventDispatcherInterface $eventDispatcher = new EventDispatcher(), ?ObjectManager $objectManager = null, @@ -375,13 +374,13 @@ public static function create( $reflectionExtractor = new ReflectionExtractor(accessFlags: $flags); if (null !== $classMetadataFactory) { - $eventDispatcher->addListener(PropertyMetadataEvent::class, new AdvancedNameConverterListener(new MetadataAwareNameConverter($classMetadataFactory, $nameConverter))); + $eventDispatcher->addListener(PropertyMetadataEvent::class, new NameConverterListener(new MetadataAwareNameConverter($classMetadataFactory, $nameConverter))); $eventDispatcher->addListener(PropertyMetadataEvent::class, new SerializerMaxDepthListener($classMetadataFactory)); $eventDispatcher->addListener(PropertyMetadataEvent::class, new SerializerGroupListener($classMetadataFactory)); $eventDispatcher->addListener(PropertyMetadataEvent::class, new SerializerIgnoreListener($classMetadataFactory)); $eventDispatcher->addListener(GenerateMapperEvent::class, new ClassDiscriminatorListener(new ClassDiscriminatorFromClassMetadata($classMetadataFactory))); } elseif (null !== $nameConverter) { - $eventDispatcher->addListener(PropertyMetadataEvent::class, new AdvancedNameConverterListener($nameConverter)); + $eventDispatcher->addListener(PropertyMetadataEvent::class, new NameConverterListener($nameConverter)); } if (null !== $objectManager) { diff --git a/src/Symfony/Bundle/DependencyInjection/AutoMapperExtension.php b/src/Symfony/Bundle/DependencyInjection/AutoMapperExtension.php index 713c3b88..1ee1a270 100644 --- a/src/Symfony/Bundle/DependencyInjection/AutoMapperExtension.php +++ b/src/Symfony/Bundle/DependencyInjection/AutoMapperExtension.php @@ -8,7 +8,7 @@ use AutoMapper\Configuration as AutoMapperConfiguration; use AutoMapper\ConstructorStrategy; use AutoMapper\Event\PropertyMetadataEvent; -use AutoMapper\EventListener\Symfony\AdvancedNameConverterListener; +use AutoMapper\EventListener\Symfony\NameConverterListener; use AutoMapper\Exception\LogicException; use AutoMapper\Loader\ClassLoaderInterface; use AutoMapper\Loader\EvalLoader; @@ -145,7 +145,7 @@ public function load(array $configs, ContainerBuilder $container): void ->setArgument(1, new Reference($config['name_converter'])); } else { $container - ->getDefinition(AdvancedNameConverterListener::class) + ->getDefinition(NameConverterListener::class) ->replaceArgument(0, new Reference($config['name_converter'])) ->addTag('kernel.event_listener', ['event' => PropertyMetadataEvent::class, 'priority' => -64]); } diff --git a/src/Symfony/Bundle/Resources/config/event.php b/src/Symfony/Bundle/Resources/config/event.php index 6c8f9806..6072f1cf 100644 --- a/src/Symfony/Bundle/Resources/config/event.php +++ b/src/Symfony/Bundle/Resources/config/event.php @@ -11,9 +11,9 @@ use AutoMapper\EventListener\MapProviderListener; use AutoMapper\EventListener\MapToContextListener; use AutoMapper\EventListener\MapToListener; -use AutoMapper\EventListener\Symfony\AdvancedNameConverterListener; +use AutoMapper\EventListener\Symfony\NameConverterListener; use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerRegistry; -use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; +use Symfony\Component\Serializer\NameConverter\NameConverterInterface; return static function (ContainerConfigurator $container) { $container->services() @@ -31,7 +31,7 @@ ->set(MapperListener::class) ->tag('kernel.event_listener', ['event' => GenerateMapperEvent::class, 'priority' => 256]) - ->set(AdvancedNameConverterListener::class) - ->args([service(AdvancedNameConverterInterface::class)]) + ->set(NameConverterListener::class) + ->args([service(NameConverterInterface::class)]) ; }; diff --git a/src/Symfony/Bundle/Resources/config/event_serializer.php b/src/Symfony/Bundle/Resources/config/event_serializer.php index 407a6898..8f8cef57 100644 --- a/src/Symfony/Bundle/Resources/config/event_serializer.php +++ b/src/Symfony/Bundle/Resources/config/event_serializer.php @@ -5,7 +5,7 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; use AutoMapper\Event\PropertyMetadataEvent; -use AutoMapper\EventListener\Symfony\AdvancedNameConverterListener; +use AutoMapper\EventListener\Symfony\NameConverterListener; use AutoMapper\EventListener\Symfony\SerializerGroupListener; use AutoMapper\EventListener\Symfony\SerializerIgnoreListener; use AutoMapper\EventListener\Symfony\SerializerMaxDepthListener; @@ -36,7 +36,7 @@ ->set('automapper.mapping.metadata_aware_name_converter', MetadataAwareNameConverter::class) ->args([service('serializer.mapping.class_metadata_factory')]) - ->set(AdvancedNameConverterListener::class) + ->set(NameConverterListener::class) ->args([service('automapper.mapping.metadata_aware_name_converter')]) ->tag('kernel.event_listener', ['event' => PropertyMetadataEvent::class, 'priority' => -64]) ; diff --git a/tests/AutoMapperTest/GroupOverride/map.php b/tests/AutoMapperTest/GroupOverride/map.php index 609de856..f67154a4 100644 --- a/tests/AutoMapperTest/GroupOverride/map.php +++ b/tests/AutoMapperTest/GroupOverride/map.php @@ -6,8 +6,8 @@ use AutoMapper\Attribute\MapTo; use AutoMapper\Tests\AutoMapperBuilder; -use Symfony\Component\Serializer\Annotation\Groups; -use Symfony\Component\Serializer\Annotation\Ignore; +use Symfony\Component\Serializer\Attribute\Groups; +use Symfony\Component\Serializer\Attribute\Ignore; class GroupOverride { diff --git a/tests/AutoMapperTest/Ignore/map.php b/tests/AutoMapperTest/Ignore/map.php index 2a802dae..5782c09a 100644 --- a/tests/AutoMapperTest/Ignore/map.php +++ b/tests/AutoMapperTest/Ignore/map.php @@ -5,7 +5,7 @@ namespace AutoMapper\Tests\AutoMapperTest\Ignore; use AutoMapper\Tests\AutoMapperBuilder; -use Symfony\Component\Serializer\Annotation\Ignore; +use Symfony\Component\Serializer\Attribute\Ignore; class FooIgnore { diff --git a/tests/AutoMapperTest/MaxDepth/map.php b/tests/AutoMapperTest/MaxDepth/map.php index 38ea699c..428fd26e 100644 --- a/tests/AutoMapperTest/MaxDepth/map.php +++ b/tests/AutoMapperTest/MaxDepth/map.php @@ -5,7 +5,7 @@ namespace AutoMapper\Tests\AutoMapperTest\MaxDepth; use AutoMapper\Tests\AutoMapperBuilder; -use Symfony\Component\Serializer\Annotation\MaxDepth; +use Symfony\Component\Serializer\Attribute\MaxDepth; class FooMaxDepth { diff --git a/tests/Bundle/Resources/App/Entity/Pet.php b/tests/Bundle/Resources/App/Entity/Pet.php index d5f076b8..12df3f64 100644 --- a/tests/Bundle/Resources/App/Entity/Pet.php +++ b/tests/Bundle/Resources/App/Entity/Pet.php @@ -4,7 +4,7 @@ namespace AutoMapper\Tests\Bundle\Resources\App\Entity; -use Symfony\Component\Serializer\Annotation\DiscriminatorMap; +use Symfony\Component\Serializer\Attribute\DiscriminatorMap; #[ DiscriminatorMap(typeProperty: 'type', mapping: [ diff --git a/tests/Fixtures/Bar.php b/tests/Fixtures/Bar.php index 464b081d..b46cc682 100644 --- a/tests/Fixtures/Bar.php +++ b/tests/Fixtures/Bar.php @@ -4,7 +4,7 @@ namespace AutoMapper\Tests\Fixtures; -use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Attribute\Groups; class Bar { diff --git a/tests/Fixtures/Foo.php b/tests/Fixtures/Foo.php index 3ad72e39..ecb8ff26 100644 --- a/tests/Fixtures/Foo.php +++ b/tests/Fixtures/Foo.php @@ -4,7 +4,7 @@ namespace AutoMapper\Tests\Fixtures; -use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Attribute\Groups; class Foo { diff --git a/tests/Fixtures/Pet.php b/tests/Fixtures/Pet.php index 50347c08..1e8b91d1 100644 --- a/tests/Fixtures/Pet.php +++ b/tests/Fixtures/Pet.php @@ -4,7 +4,7 @@ namespace AutoMapper\Tests\Fixtures; -use Symfony\Component\Serializer\Annotation\DiscriminatorMap; +use Symfony\Component\Serializer\Attribute\DiscriminatorMap; #[DiscriminatorMap(typeProperty: 'type', mapping: [ 'cat' => Cat::class, diff --git a/tests/Fixtures/UserWithYearOfBirth.php b/tests/Fixtures/UserWithYearOfBirth.php index 46b90546..bea488da 100644 --- a/tests/Fixtures/UserWithYearOfBirth.php +++ b/tests/Fixtures/UserWithYearOfBirth.php @@ -4,7 +4,7 @@ namespace AutoMapper\Tests\Fixtures; -use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Attribute\Groups; class UserWithYearOfBirth {