diff --git a/.gitignore b/.gitignore index 0397a122..916c038c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ tools/phpstan/cache/ cache/ site/ .build/ -.castor* \ No newline at end of file +.castor* +tests/Doctrine/db.sqlite \ No newline at end of file diff --git a/composer.json b/composer.json index d494cb54..d8c7ea3f 100644 --- a/composer.json +++ b/composer.json @@ -30,9 +30,11 @@ }, "require-dev": { "api-platform/core": "^3.0.4 || ^4", - "doctrine/annotations": "~1.0", + "doctrine/annotations": "^2.0", + "doctrine/doctrine-bundle": "^2.15", "doctrine/collections": "^2.2", "doctrine/inflector": "^2.0", + "doctrine/orm": "^3.3", "matthiasnoback/symfony-dependency-injection-test": "^5.1", "moneyphp/money": "^3.3.2", "phpunit/phpunit": "^9.0", diff --git a/src/AutoMapper.php b/src/AutoMapper.php index 730611f9..b803739f 100644 --- a/src/AutoMapper.php +++ b/src/AutoMapper.php @@ -12,6 +12,7 @@ use AutoMapper\Loader\FileLoader; use AutoMapper\Metadata\MetadataFactory; use AutoMapper\Metadata\MetadataRegistry; +use AutoMapper\Provider\Doctrine\DoctrineProvider; use AutoMapper\Provider\ProviderInterface; use AutoMapper\Provider\ProviderRegistry; use AutoMapper\Symfony\ExpressionLanguageProvider; @@ -19,6 +20,7 @@ use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerRegistry; use AutoMapper\Transformer\TransformerFactoryInterface; use Doctrine\Common\Annotations\AnnotationReader; +use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; @@ -148,6 +150,7 @@ public static function create( EventDispatcherInterface $eventDispatcher = new EventDispatcher(), iterable $providers = [], bool $removeDefaultProperties = false, + ?EntityManagerInterface $entityManager = 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__); @@ -176,6 +179,12 @@ public static function create( $classDiscriminatorFromClassMetadata = new ClassDiscriminatorFromClassMetadata($classMetadataFactory); } + $providers = iterator_to_array($providers); + + if (null !== $entityManager) { + $providers[] = new DoctrineProvider($entityManager); + } + $customTransformerRegistry = new PropertyTransformerRegistry($propertyTransformers); $metadataRegistry = new MetadataRegistry($configuration); $providerRegistry = new ProviderRegistry($providers); @@ -192,6 +201,7 @@ public static function create( $expressionLanguage, $eventDispatcher, $removeDefaultProperties, + $entityManager, ); $mapperGenerator = new MapperGenerator( diff --git a/src/EventListener/Doctrine/DoctrineIdentifierListener.php b/src/EventListener/Doctrine/DoctrineIdentifierListener.php new file mode 100644 index 00000000..f8073b83 --- /dev/null +++ b/src/EventListener/Doctrine/DoctrineIdentifierListener.php @@ -0,0 +1,29 @@ +mapperMetadata->target === 'array' || !$this->entityManager->getMetadataFactory()->hasMetadataFor($event->mapperMetadata->target)) { + return; + } + + $metadata = $this->entityManager->getClassMetadata($event->mapperMetadata->target); + + if ($metadata->isIdentifier($event->target->property)) { + $event->identifier = true; + } + } +} diff --git a/src/EventListener/Doctrine/DoctrineProviderListener.php b/src/EventListener/Doctrine/DoctrineProviderListener.php new file mode 100644 index 00000000..123ab68d --- /dev/null +++ b/src/EventListener/Doctrine/DoctrineProviderListener.php @@ -0,0 +1,26 @@ +mapperMetadata->target === 'array' || !$this->entityManager->getMetadataFactory()->hasMetadataFor($event->mapperMetadata->target)) { + return; + } + + $event->provider = DoctrineProvider::class; + } +} diff --git a/src/GeneratedMapper.php b/src/GeneratedMapper.php index a3b3eab2..ace39ffa 100644 --- a/src/GeneratedMapper.php +++ b/src/GeneratedMapper.php @@ -36,6 +36,11 @@ public function registerMappers(AutoMapperRegistryInterface $registry): void { } + public function getTargetIdentifiers(mixed $value): mixed + { + return null; + } + public function getSourceHash(mixed $value): ?string { return null; diff --git a/src/Generator/IdentifierHashGenerator.php b/src/Generator/IdentifierHashGenerator.php index 1bd70066..4c03665d 100644 --- a/src/Generator/IdentifierHashGenerator.php +++ b/src/Generator/IdentifierHashGenerator.php @@ -4,10 +4,14 @@ namespace AutoMapper\Generator; +use AutoMapper\Extractor\ReadAccessor; use AutoMapper\Metadata\GeneratorMetadata; +use AutoMapper\Transformer\IdentifierHashInterface; +use PhpParser\Builder; use PhpParser\Node\Arg; use PhpParser\Node\Expr; use PhpParser\Node\Name; +use PhpParser\Node\Param; use PhpParser\Node\Scalar; use PhpParser\Node\Stmt; @@ -16,7 +20,7 @@ /** * @return list */ - public function getStatements(GeneratorMetadata $metadata, bool $fromSource): array + private function getStatements(GeneratorMetadata $metadata, bool $fromSource): array { $identifiers = []; @@ -97,4 +101,146 @@ public function getStatements(GeneratorMetadata $metadata, bool $fromSource): ar return $statements; } + + /** + * Create the getSourceHash method for this mapper. + * + * ```php + * public function getSourceHash(mixed $source, mixed $target): ?string { + * ... // statements + * } + * ``` + */ + public function getSourceHashMethod(GeneratorMetadata $metadata): ?Stmt\ClassMethod + { + $stmts = $this->getStatements($metadata, true); + + if (empty($stmts)) { + return null; + } + + return (new Builder\Method('getSourceHash')) + ->makePublic() + ->setReturnType('?string') + ->addParam(new Param( + var: new Expr\Variable('value'), + type: new Name('mixed')) + ) + ->addStmts($stmts) + ->getNode(); + } + + /** + * Create the getTargetHash method for this mapper. + * + * ```php + * public function getTargetHash(mixed $source, mixed $target): ?string { + * ... // statements + * } + * ``` + */ + public function getTargetHashMethod(GeneratorMetadata $metadata): ?Stmt\ClassMethod + { + $stmts = $this->getStatements($metadata, false); + + if (empty($stmts)) { + return null; + } + + return (new Builder\Method('getTargetHash')) + ->makePublic() + ->setReturnType('?string') + ->addParam(new Param( + var: new Expr\Variable('value'), + type: new Name('mixed')) + ) + ->addStmts($stmts) + ->getNode(); + } + + /** + * Create the getTargetIdentifiers method for this mapper. + * + * ```php + * public function getTargetIdentifiers(mixed $source): mixed { + * ... // statements + * } + * ``` + */ + public function getTargetIdentifiersMethod(GeneratorMetadata $metadata): ?Stmt\ClassMethod + { + $identifiers = []; + + foreach ($metadata->propertiesMetadata as $propertyMetadata) { + if (!$propertyMetadata->identifier) { + continue; + } + + if (null === $propertyMetadata->source->accessor) { + continue; + } + + $identifiers[] = $propertyMetadata; + } + + if (empty($identifiers)) { + return null; + } + + $isUnique = \count($identifiers) === 1; + + $identifiersVariable = new Expr\Variable('identifiers'); + $valueVariable = new Expr\Variable('value'); + $statements = []; + + if (!$isUnique) { + $statements[] = new Stmt\Expression(new Expr\Assign($identifiersVariable, new Expr\Array_())); + } + + // foreach property we check + foreach ($identifiers as $property) { + /** @var ReadAccessor $accessor */ + $accessor = $property->source->accessor; + + // check if the source is defined + if ($property->source->checkExists) { + $statements[] = new Stmt\If_($accessor->getIsUndefinedExpression($valueVariable), [ + 'stmts' => [ + new Stmt\Return_(new Expr\ConstFetch(new Name('null'))), + ], + ]); + } + + $fieldValueExpr = $accessor->getExpression($valueVariable); + $transformer = $property->transformer; + + if ($transformer instanceof IdentifierHashInterface) { + $fieldValueExpr = $transformer->getIdentifierExpression($fieldValueExpr); + } + + if ($isUnique) { + $statements[] = new Stmt\Return_($fieldValueExpr); + } else { + $statements[] = new Stmt\Expression(new Expr\Assign( + new Expr\ArrayDimFetch($identifiersVariable, new Scalar\String_($property->target->property)), + $fieldValueExpr + )); + } + } + + // return hash as string + if (!$isUnique) { + $statements[] = new Stmt\Return_($identifiersVariable); + } + + return (new Builder\Method('getTargetIdentifiers')) + ->makePublic() + ->setReturnType('mixed') + ->addParam(new Param( + var: new Expr\Variable('value'), + type: new Name('mixed')) + ) + ->addStmts($statements) + ->getNode(); + } } diff --git a/src/Generator/MapMethodStatementsGenerator.php b/src/Generator/MapMethodStatementsGenerator.php index cd6297d6..b3d7d951 100644 --- a/src/Generator/MapMethodStatementsGenerator.php +++ b/src/Generator/MapMethodStatementsGenerator.php @@ -316,6 +316,9 @@ private function initializeTargetFromProvider(GeneratorMetadata $metadata): arra new Arg(new Scalar\String_($metadata->mapperMetadata->target)), new Arg($variableRegistry->getSourceInput()), new Arg($variableRegistry->getContext()), + new Arg(new Expr\MethodCall(new Expr\Variable('this'), 'getTargetIdentifiers', [ + new Arg(new Expr\Variable('value')), + ])), ]), ) ); diff --git a/src/Generator/MapperGenerator.php b/src/Generator/MapperGenerator.php index 33b3eee7..0d57128f 100644 --- a/src/Generator/MapperGenerator.php +++ b/src/Generator/MapperGenerator.php @@ -86,14 +86,18 @@ public function generate(GeneratorMetadata $metadata): array ->addStmt($this->mapMethod($metadata)) ->addStmt($this->registerMappersMethod($metadata)); - if ($sourceHashMethod = $this->getSourceHashMethod($metadata)) { + if ($sourceHashMethod = $this->identifierHashGenerator->getSourceHashMethod($metadata)) { $builder->addStmt($sourceHashMethod); } - if ($targetHashMethod = $this->getTargetHashMethod($metadata)) { + if ($targetHashMethod = $this->identifierHashGenerator->getTargetHashMethod($metadata)) { $builder->addStmt($targetHashMethod); } + if ($targetIdentifierMethod = $this->identifierHashGenerator->getTargetIdentifiersMethod($metadata)) { + $builder->addStmt($targetIdentifierMethod); + } + $statements[] = $builder->getNode(); return $statements; @@ -174,60 +178,4 @@ private function registerMappersMethod(GeneratorMetadata $metadata): Stmt\ClassM ->addStmts($this->injectMapperMethodStatementsGenerator->getStatements($param, $metadata)) ->getNode(); } - - /** - * Create the getSourceHash method for this mapper. - * - * ```php - * public function getSourceHash(mixed $source, mixed $target): ?string { - * ... // statements - * } - * ``` - */ - private function getSourceHashMethod(GeneratorMetadata $metadata): ?Stmt\ClassMethod - { - $stmts = $this->identifierHashGenerator->getStatements($metadata, true); - - if (empty($stmts)) { - return null; - } - - return (new Builder\Method('getSourceHash')) - ->makePublic() - ->setReturnType('?string') - ->addParam(new Param( - var: new Expr\Variable('value'), - type: new Name('mixed')) - ) - ->addStmts($stmts) - ->getNode(); - } - - /** - * Create the getTargetHash method for this mapper. - * - * ```php - * public function getSourceHash(mixed $source, mixed $target): ?string { - * ... // statements - * } - * ``` - */ - private function getTargetHashMethod(GeneratorMetadata $metadata): ?Stmt\ClassMethod - { - $stmts = $this->identifierHashGenerator->getStatements($metadata, false); - - if (empty($stmts)) { - return null; - } - - return (new Builder\Method('getTargetHash')) - ->makePublic() - ->setReturnType('?string') - ->addParam(new Param( - var: new Expr\Variable('value'), - type: new Name('mixed')) - ) - ->addStmts($stmts) - ->getNode(); - } } diff --git a/src/Metadata/MapperMetadata.php b/src/Metadata/MapperMetadata.php index 019b6b85..0287fa81 100644 --- a/src/Metadata/MapperMetadata.php +++ b/src/Metadata/MapperMetadata.php @@ -22,7 +22,9 @@ class MapperMetadata * @param class-string|'array' $target */ public function __construct( + /** @var class-string|'array' $source */ public string $source, + /** @var class-string|'array' $target */ public string $target, public bool $registered, private string $classPrefix = 'Mapper_', diff --git a/src/Metadata/MetadataFactory.php b/src/Metadata/MetadataFactory.php index e260a8f9..513dad1c 100644 --- a/src/Metadata/MetadataFactory.php +++ b/src/Metadata/MetadataFactory.php @@ -9,6 +9,8 @@ use AutoMapper\Event\PropertyMetadataEvent; use AutoMapper\Event\SourcePropertyMetadata as SourcePropertyMetadataEvent; use AutoMapper\Event\TargetPropertyMetadata as TargetPropertyMetadataEvent; +use AutoMapper\EventListener\Doctrine\DoctrineIdentifierListener; +use AutoMapper\EventListener\Doctrine\DoctrineProviderListener; use AutoMapper\EventListener\MapFromListener; use AutoMapper\EventListener\MapperListener; use AutoMapper\EventListener\MapProviderListener; @@ -44,6 +46,7 @@ use AutoMapper\Transformer\TransformerFactoryInterface; use AutoMapper\Transformer\UniqueTypeTransformerFactory; use AutoMapper\Transformer\VoidTransformer; +use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; @@ -355,6 +358,7 @@ public static function create( ExpressionLanguage $expressionLanguage = new ExpressionLanguage(), EventDispatcherInterface $eventDispatcher = new EventDispatcher(), bool $removeDefaultProperties = false, + ?EntityManagerInterface $entityManager = null, ): self { // Create property info extractors $flags = ReflectionExtractor::ALLOW_PUBLIC; @@ -376,6 +380,11 @@ public static function create( $eventDispatcher->addListener(PropertyMetadataEvent::class, new AdvancedNameConverterListener($nameConverter)); } + if (null !== $entityManager) { + $eventDispatcher->addListener(GenerateMapperEvent::class, new DoctrineProviderListener($entityManager)); + $eventDispatcher->addListener(PropertyMetadataEvent::class, new DoctrineIdentifierListener($entityManager)); + } + $eventDispatcher->addListener(PropertyMetadataEvent::class, new MapToContextListener($reflectionExtractor)); $eventDispatcher->addListener(GenerateMapperEvent::class, new MapToListener($customTransformerRegistry, $expressionLanguage)); $eventDispatcher->addListener(GenerateMapperEvent::class, new MapFromListener($customTransformerRegistry, $expressionLanguage)); diff --git a/src/Provider/Doctrine/DoctrineProvider.php b/src/Provider/Doctrine/DoctrineProvider.php new file mode 100644 index 00000000..3f8a8362 --- /dev/null +++ b/src/Provider/Doctrine/DoctrineProvider.php @@ -0,0 +1,31 @@ + $targetType The class name of the + */ + public function provide(string $targetType, mixed $source, array $context, /* mixed $id */): object|array|null + { + $repository = $this->entityManager->getRepository($targetType); + $id = 4 <= \func_num_args() ? func_get_arg(3) : null; + + if ($id !== null) { + return $repository->find($id); + } + + return null; + } +} diff --git a/src/Provider/ProviderInterface.php b/src/Provider/ProviderInterface.php index 6553ee87..a17451b0 100644 --- a/src/Provider/ProviderInterface.php +++ b/src/Provider/ProviderInterface.php @@ -20,5 +20,5 @@ interface ProviderInterface * * @return object|array|null the value to provide */ - public function provide(string $targetType, mixed $source, array $context): object|array|null; + public function provide(string $targetType, mixed $source, array $context, /* mixed $id */): object|array|null; } diff --git a/src/Symfony/Bundle/DependencyInjection/AutoMapperExtension.php b/src/Symfony/Bundle/DependencyInjection/AutoMapperExtension.php index a5420f87..c317d528 100644 --- a/src/Symfony/Bundle/DependencyInjection/AutoMapperExtension.php +++ b/src/Symfony/Bundle/DependencyInjection/AutoMapperExtension.php @@ -144,6 +144,10 @@ public function load(array $configs, ContainerBuilder $container): void $loader->load('api_platform.php'); } + if ($config['doctrine']) { + $loader->load('doctrine.php'); + } + if (null !== $config['name_converter']) { if ($container->has('automapper.mapping.metadata_aware_name_converter')) { $container->getDefinition('automapper.mapping.metadata_aware_name_converter') diff --git a/src/Symfony/Bundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/DependencyInjection/Configuration.php index 232cacac..6793e99e 100644 --- a/src/Symfony/Bundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/DependencyInjection/Configuration.php @@ -43,6 +43,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->end() ->booleanNode('serializer_attributes')->defaultValue(interface_exists(SerializerInterface::class))->end() ->booleanNode('api_platform')->defaultFalse()->end() + ->booleanNode('doctrine')->defaultFalse()->end() ->scalarNode('name_converter')->defaultNull()->end() ->arrayNode('loader') ->children() diff --git a/src/Symfony/Bundle/Resources/config/doctrine.php b/src/Symfony/Bundle/Resources/config/doctrine.php new file mode 100644 index 00000000..0094badf --- /dev/null +++ b/src/Symfony/Bundle/Resources/config/doctrine.php @@ -0,0 +1,28 @@ +services() + ->set(DoctrineIdentifierListener::class) + ->args([service(EntityManagerInterface::class)]) + ->tag('kernel.event_listener', ['event' => PropertyMetadataEvent::class, 'priority' => 0]) + + ->set(DoctrineProviderListener::class) + ->args([service(EntityManagerInterface::class)]) + ->tag('kernel.event_listener', ['event' => GenerateMapperEvent::class, 'priority' => 0]) + + ->set(DoctrineProvider::class) + ->args([service(EntityManagerInterface::class)]) + ->tag('automapper.provider', ['priority' => 0]) + ; +}; diff --git a/src/Transformer/BuiltinTransformer.php b/src/Transformer/BuiltinTransformer.php index 5dd8d6ad..aa699a3c 100644 --- a/src/Transformer/BuiltinTransformer.php +++ b/src/Transformer/BuiltinTransformer.php @@ -65,7 +65,6 @@ Type::BUILTIN_TYPE_INT => 'is_int', Type::BUILTIN_TYPE_FLOAT => 'is_float', Type::BUILTIN_TYPE_STRING => 'is_string', - Type::BUILTIN_TYPE_NULL => 'is_null', Type::BUILTIN_TYPE_ARRAY => 'is_array', Type::BUILTIN_TYPE_OBJECT => 'is_object', Type::BUILTIN_TYPE_RESOURCE => 'is_resource', @@ -117,6 +116,10 @@ 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) { + return null; + } + $condition = new Expr\FuncCall( new Name(self::CONDITION_MAPPING[$this->sourceType->getBuiltinType()]), [ diff --git a/src/Transformer/IdentifierHashInterface.php b/src/Transformer/IdentifierHashInterface.php index 970d14a6..1717d662 100644 --- a/src/Transformer/IdentifierHashInterface.php +++ b/src/Transformer/IdentifierHashInterface.php @@ -30,4 +30,14 @@ public function getSourceHashExpression(Expr $source): Expr; * ``` */ public function getTargetHashExpression(Expr $target): Expr; + + /** + * Return an expression to get identifier of the target. + * + * As an example: + * ```php + * return $input->id; + * ``` + */ + public function getIdentifierExpression(Expr $input): Expr; } diff --git a/src/Transformer/MultipleTransformer.php b/src/Transformer/MultipleTransformer.php index 5bd014d6..2f3ff926 100644 --- a/src/Transformer/MultipleTransformer.php +++ b/src/Transformer/MultipleTransformer.php @@ -55,7 +55,6 @@ public function transform(Expr $input, Expr $target, PropertyMetadata $propertyM */ foreach ($this->transformers as $transformerData) { $transformer = $transformerData['transformer']; - $type = $transformerData['type']; [$transformerOutput, $transformerStatements] = $transformer->transform($input, $target, $propertyMapping, $uniqueVariableScope, $source, $existingValue); diff --git a/src/Transformer/ObjectTransformer.php b/src/Transformer/ObjectTransformer.php index 7a88b3a6..a105f512 100644 --- a/src/Transformer/ObjectTransformer.php +++ b/src/Transformer/ObjectTransformer.php @@ -81,15 +81,29 @@ 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->getClassName() !== null) { - return new Expr\Instanceof_($input, new Name\FullyQualified($this->sourceType->getClassName())); + 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 { + $condition = new Expr\FuncCall( + new Name('is_object'), + [ + new Arg($input), + ] + ); + } } - return new Expr\FuncCall( - new Name('is_object'), - [ - new Arg($input), - ] + return new Expr\BinaryOp\BooleanOr( + new Expr\BinaryOp\Identical(new Expr\ConstFetch(new Name('null')), $input), + $condition ); } @@ -169,4 +183,16 @@ public function getTargetHashExpression(Expr $target): Expr new Arg($target), ]); } + + public function getIdentifierExpression(Expr $input): Expr + { + $mapperName = $this->getDependencyName(); + + return new Expr\MethodCall(new Expr\ArrayDimFetch( + new Expr\PropertyFetch(new Expr\Variable('this'), 'mappers'), + new Scalar\String_($mapperName) + ), 'getTargetIdentifiers', [ + new Arg($input), + ]); + } } diff --git a/tests/AutoMapperBuilder.php b/tests/AutoMapperBuilder.php index ad4b7688..cfb467b6 100644 --- a/tests/AutoMapperBuilder.php +++ b/tests/AutoMapperBuilder.php @@ -8,6 +8,7 @@ use AutoMapper\Configuration; use AutoMapper\ConstructorStrategy; use AutoMapper\Symfony\ExpressionLanguageProvider; +use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Filesystem\Filesystem; @@ -29,6 +30,7 @@ public static function buildAutoMapper( ?ExpressionLanguageProvider $expressionLanguageProvider = null, EventDispatcherInterface $eventDispatcher = new EventDispatcher(), bool $removeDefaultProperties = false, + ?EntityManagerInterface $entityManager = null, ): AutoMapper { $skipCacheRemove = $_SERVER['SKIP_CACHE_REMOVE'] ?? false; @@ -54,6 +56,7 @@ classPrefix: $classPrefix, eventDispatcher: $eventDispatcher, providers: $providers, removeDefaultProperties: $removeDefaultProperties, + entityManager: $entityManager, ); } } diff --git a/tests/Bundle/Resources/config/bundles.php b/tests/Bundle/Resources/config/bundles.php index 0b782604..6ca9bfb9 100644 --- a/tests/Bundle/Resources/config/bundles.php +++ b/tests/Bundle/Resources/config/bundles.php @@ -7,5 +7,6 @@ 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], ]; diff --git a/tests/Bundle/Resources/config/packages/automapper.yaml b/tests/Bundle/Resources/config/packages/automapper.yaml index 76c07904..39e1374d 100644 --- a/tests/Bundle/Resources/config/packages/automapper.yaml +++ b/tests/Bundle/Resources/config/packages/automapper.yaml @@ -6,6 +6,7 @@ automapper: eval: false reload_strategy: 'always' api_platform: true + doctrine: true name_converter: AutoMapper\Tests\Bundle\Resources\App\Service\IdNameConverter map_private_properties: false check_attributes: false diff --git a/tests/Bundle/Resources/config/packages/doctrine.yaml b/tests/Bundle/Resources/config/packages/doctrine.yaml new file mode 100644 index 00000000..4cb5a8e6 --- /dev/null +++ b/tests/Bundle/Resources/config/packages/doctrine.yaml @@ -0,0 +1,17 @@ +doctrine: + dbal: + connections: + default: + driver: 'pdo_sqlite' + 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: + App: + type: attribute + dir: '%kernel.project_dir%/App/Entity' + prefix: 'App\Entity' + alias: App \ No newline at end of file diff --git a/tests/Doctrine/DoctrineTest.php b/tests/Doctrine/DoctrineTest.php new file mode 100644 index 00000000..c1a57742 --- /dev/null +++ b/tests/Doctrine/DoctrineTest.php @@ -0,0 +1,118 @@ + + */ +class DoctrineTest extends AutoMapperTestCase +{ + private EntityManagerInterface $entityManager; + + protected function setUp(): void + { + parent::setUp(); + $this->buildDatabase(); + + $this->autoMapper = AutoMapperBuilder::buildAutoMapper(entityManager: $this->entityManager); + } + + private function buildDatabase() + { + // delete the database file + if (file_exists(__DIR__ . '/db.sqlite')) { + unlink(__DIR__ . '/db.sqlite'); + } + + $config = ORMSetup::createAttributeMetadataConfiguration( + paths: [__DIR__ . '/Entity'], + isDevMode: true, + ); + + $connection = DriverManager::getConnection([ + 'driver' => 'pdo_sqlite', + 'path' => __DIR__ . '/db.sqlite', + ], $config); + + $entityManager = new EntityManager($connection, $config); + + // Generate schema + $schemaTool = new SchemaTool($entityManager); + $schemaTool->createSchema($entityManager->getMetadataFactory()->getAllMetadata()); + + $this->entityManager = $entityManager; + } + + public function testAutoMapping(): void + { + $book = new Book(); + + $this->entityManager->persist($book); + $this->entityManager->flush(); + + $this->assertNotNull($book->id); + + $bookArray = $this->autoMapper->map($book, 'array'); + $bookArray['author'] = 'John Doe'; + + $this->autoMapper->map($bookArray, Book::class); + $this->entityManager->flush(); + $this->entityManager->clear(); + + $book = $this->entityManager->find(Book::class, $book->id); + + $this->assertEquals('John Doe', $book->author); + } + + public function testAutoMappingMultipleIdentifiers(): void + { + $book = new Book(); + $user = new User(); + $user->name = 'Foo'; + + $this->entityManager->persist($book); + $this->entityManager->persist($user); + $this->entityManager->flush(); + + $review = new Review(); + $review->book = $book; + $review->user = $user; + $review->rating = 10; + + $this->entityManager->persist($review); + $this->entityManager->flush(); + + $reviewArray = []; + $reviewArray['rating'] = 5; + $reviewArray['book'] = ['id' => $book->id]; + $reviewArray['user'] = ['id' => $user->id, 'name' => 'Bar']; + + $this->autoMapper->map($reviewArray, Review::class, [ + MapperContext::DEEP_TARGET_TO_POPULATE => true, + ]); + $this->entityManager->flush(); + $this->entityManager->clear(); + + $review = $this->entityManager->find(Review::class, [ + 'book' => $book->id, + 'user' => $user->id, + ]); + + $this->assertEquals(5, $review->rating); + $this->assertEquals('Bar', $review->user->name); + } +} diff --git a/tests/Doctrine/Entity/Book.php b/tests/Doctrine/Entity/Book.php new file mode 100644 index 00000000..2dbf5363 --- /dev/null +++ b/tests/Doctrine/Entity/Book.php @@ -0,0 +1,36 @@ + */ + public Collection $reviews; + + public function __construct() + { + $this->reviews = new ArrayCollection(); + } +} diff --git a/tests/Doctrine/Entity/Review.php b/tests/Doctrine/Entity/Review.php new file mode 100644 index 00000000..a8df3876 --- /dev/null +++ b/tests/Doctrine/Entity/Review.php @@ -0,0 +1,22 @@ + */ + public Collection $reviews; + + public function __construct() + { + $this->reviews = new ArrayCollection(); + } +}