From e7f6781088353aac1ab1c10b74ec66ef937fed4a Mon Sep 17 00:00:00 2001 From: mrossard Date: Mon, 13 Jan 2025 17:52:47 +0100 Subject: [PATCH 1/6] wip: ajout d'un hook entityTransformer --- features/doctrine/transform_entity.feature | 18 +++++++++ .../State/EntityTransformerLocatorTrait.php | 32 ++++++++++++++++ src/Doctrine/Common/State/Options.php | 15 ++++++++ src/Doctrine/Odm/State/CollectionProvider.php | 13 ++++++- src/Doctrine/Odm/State/ItemProvider.php | 13 ++++++- src/Doctrine/Orm/State/CollectionProvider.php | 13 ++++++- src/Doctrine/Orm/State/ItemProvider.php | 13 ++++++- src/Doctrine/Orm/State/Options.php | 4 +- tests/Behat/DoctrineContext.php | 10 +++++ .../ApiResource/TransformedDummyRessource.php | 36 ++++++++++++++++++ .../Entity/TransformedDummyEntity.php | 38 +++++++++++++++++++ 11 files changed, 196 insertions(+), 9 deletions(-) create mode 100644 features/doctrine/transform_entity.feature create mode 100644 src/Doctrine/Common/State/EntityTransformerLocatorTrait.php create mode 100644 tests/Fixtures/TestBundle/ApiResource/TransformedDummyRessource.php create mode 100644 tests/Fixtures/TestBundle/Entity/TransformedDummyEntity.php diff --git a/features/doctrine/transform_entity.feature b/features/doctrine/transform_entity.feature new file mode 100644 index 00000000000..56d01ede183 --- /dev/null +++ b/features/doctrine/transform_entity.feature @@ -0,0 +1,18 @@ +Feature: Use an entity transformer to return the correct ressource + + @createSchema + Scenario: Get collection + Given there is a TransformedDummyEntity object for date '2025-01-01' + When I send a "GET" request to "/transformed_dummy_ressources" + Then the response status code should be 200 + And the response should be in JSON + And the JSON node "hydra:totalItems" should be equal to 1 + + Scenario: Get item + Given there is a TransformedDummyEntity object for date '2025-01-01' + When I send a "GET" request to "/transformed_dummy_ressources/1" + Then the response status code should be 200 + And the response should be in JSON + And the JSON node "year" should exist + And the JSON node year should be equal to "2025" + diff --git a/src/Doctrine/Common/State/EntityTransformerLocatorTrait.php b/src/Doctrine/Common/State/EntityTransformerLocatorTrait.php new file mode 100644 index 00000000000..b876f3407e9 --- /dev/null +++ b/src/Doctrine/Common/State/EntityTransformerLocatorTrait.php @@ -0,0 +1,32 @@ +getStateOptions()) || !$options instanceof Options) { + return null; + } + + $transformEntity = $options->getTransformEntity(); + if (\is_callable($transformEntity)) { + return $transformEntity; + } + + if ($this->transformEntityLocator && \is_string($transformEntity) && $this->transformEntityLocator->has($transformEntity)) { + return [$this->transformEntityLocator->get($transformEntity), 'transformEntity']; + } + + return null; + } +} diff --git a/src/Doctrine/Common/State/Options.php b/src/Doctrine/Common/State/Options.php index df42fc6cb84..e36dc631f7b 100644 --- a/src/Doctrine/Common/State/Options.php +++ b/src/Doctrine/Common/State/Options.php @@ -19,9 +19,11 @@ class Options implements OptionsInterface { /** * @param mixed $handleLinks experimental callable, typed mixed as we may want a service name in the future + * @param mixed $transformEntity experimental callable, typed mixed as we may want a service name in the future */ public function __construct( protected mixed $handleLinks = null, + protected mixed $transformEntity = null, ) { } @@ -37,4 +39,17 @@ public function withHandleLinks(mixed $handleLinks): self return $self; } + + public function getTransformEntity(): mixed + { + return $this->transformEntity; + } + + public function withTransformEntity(mixed $transformEntity): self + { + $self = clone $this; + $self->transformEntity = $transformEntity; + + return $self; + } } diff --git a/src/Doctrine/Odm/State/CollectionProvider.php b/src/Doctrine/Odm/State/CollectionProvider.php index 3c5c923f893..54efb69606f 100644 --- a/src/Doctrine/Odm/State/CollectionProvider.php +++ b/src/Doctrine/Odm/State/CollectionProvider.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Doctrine\Odm\State; +use ApiPlatform\Doctrine\Common\State\EntityTransformerLocatorTrait; use ApiPlatform\Doctrine\Common\State\LinksHandlerLocatorTrait; use ApiPlatform\Doctrine\Odm\Extension\AggregationCollectionExtensionInterface; use ApiPlatform\Doctrine\Odm\Extension\AggregationResultCollectionExtensionInterface; @@ -32,6 +33,7 @@ final class CollectionProvider implements ProviderInterface { use LinksHandlerLocatorTrait; use LinksHandlerTrait; + use EntityTransformerLocatorTrait; /** * @param AggregationCollectionExtensionInterface[] $collectionExtensions @@ -40,6 +42,7 @@ public function __construct(ResourceMetadataCollectionFactoryInterface $resource { $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->handleLinksLocator = $handleLinksLocator; + $this->transformEntityLocator = $handleLinksLocator; $this->managerRegistry = $managerRegistry; } @@ -70,13 +73,19 @@ public function provide(Operation $operation, array $uriVariables = [], array $c $extension->applyToCollection($aggregationBuilder, $documentClass, $operation, $context); if ($extension instanceof AggregationResultCollectionExtensionInterface && $extension->supportsResult($documentClass, $operation, $context)) { - return $extension->getResult($aggregationBuilder, $documentClass, $operation, $context); + $result = $extension->getResult($aggregationBuilder, $documentClass, $operation, $context); + break; } } $attribute = $operation->getExtraProperties()['doctrine_mongodb'] ?? []; $executeOptions = $attribute['execute_options'] ?? []; - return $aggregationBuilder->hydrate($documentClass)->execute($executeOptions); + $result = $result ?? $aggregationBuilder->hydrate($documentClass)->execute($executeOptions); + + return match($transformer = $this->getEntityTransformer($operation)){ + null => $result, + default => array_map($transformer, iterator_to_array($result)) + }; } } diff --git a/src/Doctrine/Odm/State/ItemProvider.php b/src/Doctrine/Odm/State/ItemProvider.php index d367ea4fa02..bcd9b278b33 100644 --- a/src/Doctrine/Odm/State/ItemProvider.php +++ b/src/Doctrine/Odm/State/ItemProvider.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Doctrine\Odm\State; +use ApiPlatform\Doctrine\Common\State\EntityTransformerLocatorTrait; use ApiPlatform\Doctrine\Common\State\LinksHandlerLocatorTrait; use ApiPlatform\Doctrine\Odm\Extension\AggregationItemExtensionInterface; use ApiPlatform\Doctrine\Odm\Extension\AggregationResultItemExtensionInterface; @@ -35,6 +36,7 @@ final class ItemProvider implements ProviderInterface { use LinksHandlerLocatorTrait; use LinksHandlerTrait; + use EntityTransformerLocatorTrait; /** * @param AggregationItemExtensionInterface[] $itemExtensions @@ -43,6 +45,7 @@ public function __construct(ResourceMetadataCollectionFactoryInterface $resource { $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->handleLinksLocator = $handleLinksLocator; + $this->transformEntityLocator = $handleLinksLocator; $this->managerRegistry = $managerRegistry; } @@ -78,12 +81,18 @@ public function provide(Operation $operation, array $uriVariables = [], array $c $extension->applyToItem($aggregationBuilder, $documentClass, $uriVariables, $operation, $context); if ($extension instanceof AggregationResultItemExtensionInterface && $extension->supportsResult($documentClass, $operation, $context)) { - return $extension->getResult($aggregationBuilder, $documentClass, $operation, $context); + $result = $extension->getResult($aggregationBuilder, $documentClass, $operation, $context); + break; } } $executeOptions = $operation->getExtraProperties()['doctrine_mongodb']['execute_options'] ?? []; - return $aggregationBuilder->hydrate($documentClass)->execute($executeOptions)->current() ?: null; + $result = $result ?? ($aggregationBuilder->hydrate($documentClass)->execute($executeOptions)->current() ?: null); + + return match($transformer = $this->getEntityTransformer($operation)){ + null => $result, + default => ($result !== null)? $transformer($result) : null + }; } } diff --git a/src/Doctrine/Orm/State/CollectionProvider.php b/src/Doctrine/Orm/State/CollectionProvider.php index ab2cb5a0810..96647cde75e 100644 --- a/src/Doctrine/Orm/State/CollectionProvider.php +++ b/src/Doctrine/Orm/State/CollectionProvider.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Doctrine\Orm\State; +use ApiPlatform\Doctrine\Common\State\EntityTransformerLocatorTrait; use ApiPlatform\Doctrine\Common\State\LinksHandlerLocatorTrait; use ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface; use ApiPlatform\Doctrine\Orm\Extension\QueryResultCollectionExtensionInterface; @@ -35,6 +36,7 @@ final class CollectionProvider implements ProviderInterface { use LinksHandlerLocatorTrait; use LinksHandlerTrait; + use EntityTransformerLocatorTrait; /** * @param QueryCollectionExtensionInterface[] $collectionExtensions @@ -43,6 +45,7 @@ public function __construct(ResourceMetadataCollectionFactoryInterface $resource { $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->handleLinksLocator = $handleLinksLocator; + $this->transformEntityLocator = $handleLinksLocator; $this->managerRegistry = $managerRegistry; } @@ -74,10 +77,16 @@ public function provide(Operation $operation, array $uriVariables = [], array $c $extension->applyToCollection($queryBuilder, $queryNameGenerator, $entityClass, $operation, $context); if ($extension instanceof QueryResultCollectionExtensionInterface && $extension->supportsResult($entityClass, $operation, $context)) { - return $extension->getResult($queryBuilder, $entityClass, $operation, $context); + $result = $extension->getResult($queryBuilder, $entityClass, $operation, $context); + break; } } - return $queryBuilder->getQuery()->getResult(); + $result = $result ?? $queryBuilder->getQuery()->getResult(); + + return match($transformer = $this->getEntityTransformer($operation)){ + null => $result, + default => array_map($transformer, iterator_to_array($result)) + }; } } diff --git a/src/Doctrine/Orm/State/ItemProvider.php b/src/Doctrine/Orm/State/ItemProvider.php index 0a7f4862c89..b43581c40bf 100644 --- a/src/Doctrine/Orm/State/ItemProvider.php +++ b/src/Doctrine/Orm/State/ItemProvider.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Doctrine\Orm\State; +use ApiPlatform\Doctrine\Common\State\EntityTransformerLocatorTrait; use ApiPlatform\Doctrine\Common\State\LinksHandlerLocatorTrait; use ApiPlatform\Doctrine\Orm\Extension\QueryItemExtensionInterface; use ApiPlatform\Doctrine\Orm\Extension\QueryResultItemExtensionInterface; @@ -35,6 +36,7 @@ final class ItemProvider implements ProviderInterface { use LinksHandlerLocatorTrait; use LinksHandlerTrait; + use EntityTransformerLocatorTrait; /** * @param QueryItemExtensionInterface[] $itemExtensions @@ -43,6 +45,7 @@ public function __construct(ResourceMetadataCollectionFactoryInterface $resource { $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->handleLinksLocator = $handleLinksLocator; + $this->transformEntityLocator = $handleLinksLocator; $this->managerRegistry = $managerRegistry; } @@ -80,10 +83,16 @@ public function provide(Operation $operation, array $uriVariables = [], array $c $extension->applyToItem($queryBuilder, $queryNameGenerator, $entityClass, $uriVariables, $operation, $context); if ($extension instanceof QueryResultItemExtensionInterface && $extension->supportsResult($entityClass, $operation, $context)) { - return $extension->getResult($queryBuilder, $entityClass, $operation, $context); + $result = $extension->getResult($queryBuilder, $entityClass, $operation, $context); + break; } } - return $queryBuilder->getQuery()->getOneOrNullResult(); + $result = $result ?? $queryBuilder->getQuery()->getOneOrNullResult(); + + return match($transformer = $this->getEntityTransformer($operation)){ + null => $result, + default=> $transformer($result) + }; } } diff --git a/src/Doctrine/Orm/State/Options.php b/src/Doctrine/Orm/State/Options.php index 3a9a46c3825..d94bae66d6d 100644 --- a/src/Doctrine/Orm/State/Options.php +++ b/src/Doctrine/Orm/State/Options.php @@ -20,14 +20,16 @@ class Options extends CommonOptions implements OptionsInterface { /** * @param string|callable $handleLinks experimental callable, typed mixed as we may want a service name in the future + * @param string|callable $transformEntity experimental callable, typed mixed as we may want a service name in the future * * @see LinksHandlerInterface */ public function __construct( protected ?string $entityClass = null, mixed $handleLinks = null, + mixed $transformEntity = null ) { - parent::__construct(handleLinks: $handleLinks); + parent::__construct(handleLinks: $handleLinks, transformEntity: $transformEntity); } public function getEntityClass(): ?string diff --git a/tests/Behat/DoctrineContext.php b/tests/Behat/DoctrineContext.php index 4639644beb4..b3df977d082 100644 --- a/tests/Behat/DoctrineContext.php +++ b/tests/Behat/DoctrineContext.php @@ -199,6 +199,7 @@ use ApiPlatform\Tests\Fixtures\TestBundle\Entity\SymfonyUuidDummy; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Taxon; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\ThirdLevel; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\TransformedDummyEntity; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\TreeDummy; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\UrlEncodedId; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\User; @@ -2325,6 +2326,15 @@ public function thereAreIssue6039Users(): void $this->manager->flush(); } + /** + * @Given there is a TransformedDummyEntity object for date :date + */ + public function thereIsATransformedDummyEntity(string $date): void + { + $this->manager->persist(new TransformedDummyEntity(new \DateTimeImmutable($date))); + $this->manager->flush(); + } + private function isOrm(): bool { return null !== $this->schemaTool; diff --git a/tests/Fixtures/TestBundle/ApiResource/TransformedDummyRessource.php b/tests/Fixtures/TestBundle/ApiResource/TransformedDummyRessource.php new file mode 100644 index 00000000000..deb1863cab4 --- /dev/null +++ b/tests/Fixtures/TestBundle/ApiResource/TransformedDummyRessource.php @@ -0,0 +1,36 @@ +id = $entity->getId(); + $resource->year = (int) $entity->getDate()->format('Y'); + + return $resource; + } + +} diff --git a/tests/Fixtures/TestBundle/Entity/TransformedDummyEntity.php b/tests/Fixtures/TestBundle/Entity/TransformedDummyEntity.php new file mode 100644 index 00000000000..50a3199ae4a --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/TransformedDummyEntity.php @@ -0,0 +1,38 @@ +setDate($date ?? new \DateTimeImmutable()); + } + + public function getDate(): \DateTimeInterface + { + return $this->date; + } + + public function setDate(\DateTimeInterface $date): void + { + $this->date = $date; + } + + public function getId(): ?int + { + return $this->id; + } +} From d69f17aff009f5641410f376f2e00fe6a8129e18 Mon Sep 17 00:00:00 2001 From: Manuel Rossard Date: Tue, 14 Jan 2025 11:52:35 +0100 Subject: [PATCH 2/6] feat: add a hook for entity to resource transformation --- features/doctrine/transform_entity.feature | 18 +++++++++ .../State/EntityTransformerLocatorTrait.php | 32 ++++++++++++++++ src/Doctrine/Common/State/Options.php | 15 ++++++++ src/Doctrine/Odm/State/CollectionProvider.php | 13 ++++++- src/Doctrine/Odm/State/ItemProvider.php | 13 ++++++- src/Doctrine/Orm/State/CollectionProvider.php | 13 ++++++- src/Doctrine/Orm/State/ItemProvider.php | 13 ++++++- src/Doctrine/Orm/State/Options.php | 4 +- tests/Behat/DoctrineContext.php | 16 ++++++++ .../ApiResource/TransformedDummyRessource.php | 36 ++++++++++++++++++ .../Document/TransformedDummyDocument.php | 37 ++++++++++++++++++ .../Entity/TransformedDummyEntity.php | 38 +++++++++++++++++++ 12 files changed, 239 insertions(+), 9 deletions(-) create mode 100644 features/doctrine/transform_entity.feature create mode 100644 src/Doctrine/Common/State/EntityTransformerLocatorTrait.php create mode 100644 tests/Fixtures/TestBundle/ApiResource/TransformedDummyRessource.php create mode 100644 tests/Fixtures/TestBundle/Document/TransformedDummyDocument.php create mode 100644 tests/Fixtures/TestBundle/Entity/TransformedDummyEntity.php diff --git a/features/doctrine/transform_entity.feature b/features/doctrine/transform_entity.feature new file mode 100644 index 00000000000..56d01ede183 --- /dev/null +++ b/features/doctrine/transform_entity.feature @@ -0,0 +1,18 @@ +Feature: Use an entity transformer to return the correct ressource + + @createSchema + Scenario: Get collection + Given there is a TransformedDummyEntity object for date '2025-01-01' + When I send a "GET" request to "/transformed_dummy_ressources" + Then the response status code should be 200 + And the response should be in JSON + And the JSON node "hydra:totalItems" should be equal to 1 + + Scenario: Get item + Given there is a TransformedDummyEntity object for date '2025-01-01' + When I send a "GET" request to "/transformed_dummy_ressources/1" + Then the response status code should be 200 + And the response should be in JSON + And the JSON node "year" should exist + And the JSON node year should be equal to "2025" + diff --git a/src/Doctrine/Common/State/EntityTransformerLocatorTrait.php b/src/Doctrine/Common/State/EntityTransformerLocatorTrait.php new file mode 100644 index 00000000000..b876f3407e9 --- /dev/null +++ b/src/Doctrine/Common/State/EntityTransformerLocatorTrait.php @@ -0,0 +1,32 @@ +getStateOptions()) || !$options instanceof Options) { + return null; + } + + $transformEntity = $options->getTransformEntity(); + if (\is_callable($transformEntity)) { + return $transformEntity; + } + + if ($this->transformEntityLocator && \is_string($transformEntity) && $this->transformEntityLocator->has($transformEntity)) { + return [$this->transformEntityLocator->get($transformEntity), 'transformEntity']; + } + + return null; + } +} diff --git a/src/Doctrine/Common/State/Options.php b/src/Doctrine/Common/State/Options.php index df42fc6cb84..e36dc631f7b 100644 --- a/src/Doctrine/Common/State/Options.php +++ b/src/Doctrine/Common/State/Options.php @@ -19,9 +19,11 @@ class Options implements OptionsInterface { /** * @param mixed $handleLinks experimental callable, typed mixed as we may want a service name in the future + * @param mixed $transformEntity experimental callable, typed mixed as we may want a service name in the future */ public function __construct( protected mixed $handleLinks = null, + protected mixed $transformEntity = null, ) { } @@ -37,4 +39,17 @@ public function withHandleLinks(mixed $handleLinks): self return $self; } + + public function getTransformEntity(): mixed + { + return $this->transformEntity; + } + + public function withTransformEntity(mixed $transformEntity): self + { + $self = clone $this; + $self->transformEntity = $transformEntity; + + return $self; + } } diff --git a/src/Doctrine/Odm/State/CollectionProvider.php b/src/Doctrine/Odm/State/CollectionProvider.php index 3c5c923f893..54efb69606f 100644 --- a/src/Doctrine/Odm/State/CollectionProvider.php +++ b/src/Doctrine/Odm/State/CollectionProvider.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Doctrine\Odm\State; +use ApiPlatform\Doctrine\Common\State\EntityTransformerLocatorTrait; use ApiPlatform\Doctrine\Common\State\LinksHandlerLocatorTrait; use ApiPlatform\Doctrine\Odm\Extension\AggregationCollectionExtensionInterface; use ApiPlatform\Doctrine\Odm\Extension\AggregationResultCollectionExtensionInterface; @@ -32,6 +33,7 @@ final class CollectionProvider implements ProviderInterface { use LinksHandlerLocatorTrait; use LinksHandlerTrait; + use EntityTransformerLocatorTrait; /** * @param AggregationCollectionExtensionInterface[] $collectionExtensions @@ -40,6 +42,7 @@ public function __construct(ResourceMetadataCollectionFactoryInterface $resource { $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->handleLinksLocator = $handleLinksLocator; + $this->transformEntityLocator = $handleLinksLocator; $this->managerRegistry = $managerRegistry; } @@ -70,13 +73,19 @@ public function provide(Operation $operation, array $uriVariables = [], array $c $extension->applyToCollection($aggregationBuilder, $documentClass, $operation, $context); if ($extension instanceof AggregationResultCollectionExtensionInterface && $extension->supportsResult($documentClass, $operation, $context)) { - return $extension->getResult($aggregationBuilder, $documentClass, $operation, $context); + $result = $extension->getResult($aggregationBuilder, $documentClass, $operation, $context); + break; } } $attribute = $operation->getExtraProperties()['doctrine_mongodb'] ?? []; $executeOptions = $attribute['execute_options'] ?? []; - return $aggregationBuilder->hydrate($documentClass)->execute($executeOptions); + $result = $result ?? $aggregationBuilder->hydrate($documentClass)->execute($executeOptions); + + return match($transformer = $this->getEntityTransformer($operation)){ + null => $result, + default => array_map($transformer, iterator_to_array($result)) + }; } } diff --git a/src/Doctrine/Odm/State/ItemProvider.php b/src/Doctrine/Odm/State/ItemProvider.php index d367ea4fa02..bcd9b278b33 100644 --- a/src/Doctrine/Odm/State/ItemProvider.php +++ b/src/Doctrine/Odm/State/ItemProvider.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Doctrine\Odm\State; +use ApiPlatform\Doctrine\Common\State\EntityTransformerLocatorTrait; use ApiPlatform\Doctrine\Common\State\LinksHandlerLocatorTrait; use ApiPlatform\Doctrine\Odm\Extension\AggregationItemExtensionInterface; use ApiPlatform\Doctrine\Odm\Extension\AggregationResultItemExtensionInterface; @@ -35,6 +36,7 @@ final class ItemProvider implements ProviderInterface { use LinksHandlerLocatorTrait; use LinksHandlerTrait; + use EntityTransformerLocatorTrait; /** * @param AggregationItemExtensionInterface[] $itemExtensions @@ -43,6 +45,7 @@ public function __construct(ResourceMetadataCollectionFactoryInterface $resource { $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->handleLinksLocator = $handleLinksLocator; + $this->transformEntityLocator = $handleLinksLocator; $this->managerRegistry = $managerRegistry; } @@ -78,12 +81,18 @@ public function provide(Operation $operation, array $uriVariables = [], array $c $extension->applyToItem($aggregationBuilder, $documentClass, $uriVariables, $operation, $context); if ($extension instanceof AggregationResultItemExtensionInterface && $extension->supportsResult($documentClass, $operation, $context)) { - return $extension->getResult($aggregationBuilder, $documentClass, $operation, $context); + $result = $extension->getResult($aggregationBuilder, $documentClass, $operation, $context); + break; } } $executeOptions = $operation->getExtraProperties()['doctrine_mongodb']['execute_options'] ?? []; - return $aggregationBuilder->hydrate($documentClass)->execute($executeOptions)->current() ?: null; + $result = $result ?? ($aggregationBuilder->hydrate($documentClass)->execute($executeOptions)->current() ?: null); + + return match($transformer = $this->getEntityTransformer($operation)){ + null => $result, + default => ($result !== null)? $transformer($result) : null + }; } } diff --git a/src/Doctrine/Orm/State/CollectionProvider.php b/src/Doctrine/Orm/State/CollectionProvider.php index ab2cb5a0810..96647cde75e 100644 --- a/src/Doctrine/Orm/State/CollectionProvider.php +++ b/src/Doctrine/Orm/State/CollectionProvider.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Doctrine\Orm\State; +use ApiPlatform\Doctrine\Common\State\EntityTransformerLocatorTrait; use ApiPlatform\Doctrine\Common\State\LinksHandlerLocatorTrait; use ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface; use ApiPlatform\Doctrine\Orm\Extension\QueryResultCollectionExtensionInterface; @@ -35,6 +36,7 @@ final class CollectionProvider implements ProviderInterface { use LinksHandlerLocatorTrait; use LinksHandlerTrait; + use EntityTransformerLocatorTrait; /** * @param QueryCollectionExtensionInterface[] $collectionExtensions @@ -43,6 +45,7 @@ public function __construct(ResourceMetadataCollectionFactoryInterface $resource { $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->handleLinksLocator = $handleLinksLocator; + $this->transformEntityLocator = $handleLinksLocator; $this->managerRegistry = $managerRegistry; } @@ -74,10 +77,16 @@ public function provide(Operation $operation, array $uriVariables = [], array $c $extension->applyToCollection($queryBuilder, $queryNameGenerator, $entityClass, $operation, $context); if ($extension instanceof QueryResultCollectionExtensionInterface && $extension->supportsResult($entityClass, $operation, $context)) { - return $extension->getResult($queryBuilder, $entityClass, $operation, $context); + $result = $extension->getResult($queryBuilder, $entityClass, $operation, $context); + break; } } - return $queryBuilder->getQuery()->getResult(); + $result = $result ?? $queryBuilder->getQuery()->getResult(); + + return match($transformer = $this->getEntityTransformer($operation)){ + null => $result, + default => array_map($transformer, iterator_to_array($result)) + }; } } diff --git a/src/Doctrine/Orm/State/ItemProvider.php b/src/Doctrine/Orm/State/ItemProvider.php index 0a7f4862c89..b43581c40bf 100644 --- a/src/Doctrine/Orm/State/ItemProvider.php +++ b/src/Doctrine/Orm/State/ItemProvider.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Doctrine\Orm\State; +use ApiPlatform\Doctrine\Common\State\EntityTransformerLocatorTrait; use ApiPlatform\Doctrine\Common\State\LinksHandlerLocatorTrait; use ApiPlatform\Doctrine\Orm\Extension\QueryItemExtensionInterface; use ApiPlatform\Doctrine\Orm\Extension\QueryResultItemExtensionInterface; @@ -35,6 +36,7 @@ final class ItemProvider implements ProviderInterface { use LinksHandlerLocatorTrait; use LinksHandlerTrait; + use EntityTransformerLocatorTrait; /** * @param QueryItemExtensionInterface[] $itemExtensions @@ -43,6 +45,7 @@ public function __construct(ResourceMetadataCollectionFactoryInterface $resource { $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->handleLinksLocator = $handleLinksLocator; + $this->transformEntityLocator = $handleLinksLocator; $this->managerRegistry = $managerRegistry; } @@ -80,10 +83,16 @@ public function provide(Operation $operation, array $uriVariables = [], array $c $extension->applyToItem($queryBuilder, $queryNameGenerator, $entityClass, $uriVariables, $operation, $context); if ($extension instanceof QueryResultItemExtensionInterface && $extension->supportsResult($entityClass, $operation, $context)) { - return $extension->getResult($queryBuilder, $entityClass, $operation, $context); + $result = $extension->getResult($queryBuilder, $entityClass, $operation, $context); + break; } } - return $queryBuilder->getQuery()->getOneOrNullResult(); + $result = $result ?? $queryBuilder->getQuery()->getOneOrNullResult(); + + return match($transformer = $this->getEntityTransformer($operation)){ + null => $result, + default=> $transformer($result) + }; } } diff --git a/src/Doctrine/Orm/State/Options.php b/src/Doctrine/Orm/State/Options.php index 3a9a46c3825..d94bae66d6d 100644 --- a/src/Doctrine/Orm/State/Options.php +++ b/src/Doctrine/Orm/State/Options.php @@ -20,14 +20,16 @@ class Options extends CommonOptions implements OptionsInterface { /** * @param string|callable $handleLinks experimental callable, typed mixed as we may want a service name in the future + * @param string|callable $transformEntity experimental callable, typed mixed as we may want a service name in the future * * @see LinksHandlerInterface */ public function __construct( protected ?string $entityClass = null, mixed $handleLinks = null, + mixed $transformEntity = null ) { - parent::__construct(handleLinks: $handleLinks); + parent::__construct(handleLinks: $handleLinks, transformEntity: $transformEntity); } public function getEntityClass(): ?string diff --git a/tests/Behat/DoctrineContext.php b/tests/Behat/DoctrineContext.php index 4639644beb4..8d21737dc59 100644 --- a/tests/Behat/DoctrineContext.php +++ b/tests/Behat/DoctrineContext.php @@ -97,6 +97,7 @@ use ApiPlatform\Tests\Fixtures\TestBundle\Document\SoMany as SoManyDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Document\Taxon as TaxonDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Document\ThirdLevel as ThirdLevelDocument; +use ApiPlatform\Tests\Fixtures\TestBundle\Document\TransformedDummyDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Document\UrlEncodedId as UrlEncodedIdDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Document\User as UserDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Document\VideoGame as VideoGameDocument; @@ -199,6 +200,7 @@ use ApiPlatform\Tests\Fixtures\TestBundle\Entity\SymfonyUuidDummy; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Taxon; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\ThirdLevel; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\TransformedDummyEntity; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\TreeDummy; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\UrlEncodedId; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\User; @@ -2325,6 +2327,15 @@ public function thereAreIssue6039Users(): void $this->manager->flush(); } + /** + * @Given there is a TransformedDummyEntity object for date :date + */ + public function thereIsATransformedDummyEntity(string $date): void + { + $this->manager->persist($this->buildTransformedDummy(new \DateTimeImmutable($date))); + $this->manager->flush(); + } + private function isOrm(): bool { return null !== $this->schemaTool; @@ -2704,4 +2715,9 @@ private function buildLinkHandledDummy(string $slug): LinkHandledDummy|LinkHandl { return $this->isOrm() ? new LinkHandledDummy($slug) : new LinkHandledDummyDocument($slug); } + + private function buildTransformedDummy(\DateTimeImmutable $dateTime): TransformedDummyEntity|TransformedDummyDocument + { + return $this->isOrm() ? new TransformedDummyEntity($dateTime) : new TransformedDummyDocument($dateTime); + } } diff --git a/tests/Fixtures/TestBundle/ApiResource/TransformedDummyRessource.php b/tests/Fixtures/TestBundle/ApiResource/TransformedDummyRessource.php new file mode 100644 index 00000000000..deb1863cab4 --- /dev/null +++ b/tests/Fixtures/TestBundle/ApiResource/TransformedDummyRessource.php @@ -0,0 +1,36 @@ +id = $entity->getId(); + $resource->year = (int) $entity->getDate()->format('Y'); + + return $resource; + } + +} diff --git a/tests/Fixtures/TestBundle/Document/TransformedDummyDocument.php b/tests/Fixtures/TestBundle/Document/TransformedDummyDocument.php new file mode 100644 index 00000000000..e120bd71e5e --- /dev/null +++ b/tests/Fixtures/TestBundle/Document/TransformedDummyDocument.php @@ -0,0 +1,37 @@ +setDate($date ?? new \DateTimeImmutable()); + } + + public function getDate(): \DateTimeInterface + { + return $this->date; + } + + public function setDate(\DateTimeInterface $date): void + { + $this->date = $date; + } + + public function getId(): ?int + { + return $this->id; + } + +} diff --git a/tests/Fixtures/TestBundle/Entity/TransformedDummyEntity.php b/tests/Fixtures/TestBundle/Entity/TransformedDummyEntity.php new file mode 100644 index 00000000000..50a3199ae4a --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/TransformedDummyEntity.php @@ -0,0 +1,38 @@ +setDate($date ?? new \DateTimeImmutable()); + } + + public function getDate(): \DateTimeInterface + { + return $this->date; + } + + public function setDate(\DateTimeInterface $date): void + { + $this->date = $date; + } + + public function getId(): ?int + { + return $this->id; + } +} From fbc5e1b1b9db4e8cd63d33baec7fa9eb04fa5263 Mon Sep 17 00:00:00 2001 From: mrossard Date: Wed, 15 Jan 2025 12:27:23 +0100 Subject: [PATCH 3/6] feat: add a hook for doctrine entity or document to resource transformation --- features/doctrine/transform_entity.feature | 18 --------- features/doctrine/transform_model.feature | 37 +++++++++++++++++++ ...t.php => ModelTransformerLocatorTrait.php} | 12 +++--- src/Doctrine/Common/State/Options.php | 11 +++--- src/Doctrine/Odm/State/CollectionProvider.php | 4 +- src/Doctrine/Odm/State/ItemProvider.php | 4 +- src/Doctrine/Odm/State/Options.php | 14 ++++++- src/Doctrine/Orm/State/CollectionProvider.php | 4 +- src/Doctrine/Orm/State/ItemProvider.php | 4 +- src/Doctrine/Orm/State/Options.php | 12 +++++- tests/Behat/DoctrineContext.php | 2 +- .../TransformedDummyDocumentRessource.php | 37 +++++++++++++++++++ ...hp => TransformedDummyEntityRessource.php} | 15 ++++---- .../Document/TransformedDummyDocument.php | 3 +- 14 files changed, 128 insertions(+), 49 deletions(-) delete mode 100644 features/doctrine/transform_entity.feature create mode 100644 features/doctrine/transform_model.feature rename src/Doctrine/Common/State/{EntityTransformerLocatorTrait.php => ModelTransformerLocatorTrait.php} (71%) create mode 100644 tests/Fixtures/TestBundle/ApiResource/TransformedDummyDocumentRessource.php rename tests/Fixtures/TestBundle/ApiResource/{TransformedDummyRessource.php => TransformedDummyEntityRessource.php} (51%) diff --git a/features/doctrine/transform_entity.feature b/features/doctrine/transform_entity.feature deleted file mode 100644 index 56d01ede183..00000000000 --- a/features/doctrine/transform_entity.feature +++ /dev/null @@ -1,18 +0,0 @@ -Feature: Use an entity transformer to return the correct ressource - - @createSchema - Scenario: Get collection - Given there is a TransformedDummyEntity object for date '2025-01-01' - When I send a "GET" request to "/transformed_dummy_ressources" - Then the response status code should be 200 - And the response should be in JSON - And the JSON node "hydra:totalItems" should be equal to 1 - - Scenario: Get item - Given there is a TransformedDummyEntity object for date '2025-01-01' - When I send a "GET" request to "/transformed_dummy_ressources/1" - Then the response status code should be 200 - And the response should be in JSON - And the JSON node "year" should exist - And the JSON node year should be equal to "2025" - diff --git a/features/doctrine/transform_model.feature b/features/doctrine/transform_model.feature new file mode 100644 index 00000000000..5ace2cf83d4 --- /dev/null +++ b/features/doctrine/transform_model.feature @@ -0,0 +1,37 @@ +Feature: Use an entity or document transformer to return the correct ressource + + @createSchema + @!mongodb + Scenario: Get collection from entities + Given there is a TransformedDummy for date '2025-01-01' + When I send a "GET" request to "/transformed_dummy_entity_ressources" + Then the response status code should be 200 + And the response should be in JSON + And the JSON node "hydra:totalItems" should be equal to 1 + + @!mongodb + Scenario: Get item from entity + Given there is a TransformedDummy for date '2025-01-01' + When I send a "GET" request to "/transformed_dummy_entity_ressources/1" + Then the response status code should be 200 + And the response should be in JSON + And the JSON node "year" should exist + And the JSON node year should be equal to "2025" + + @createSchema + @mongodb + Scenario: Get collection from documents + Given there is a TransformedDummy for date '2025-01-01' + When I send a "GET" request to "/transformed_dummy_document_ressources" + Then the response status code should be 200 + And the response should be in JSON + And the JSON node "hydra:totalItems" should be equal to 1 + + @mongodb + Scenario: Get item from document + Given there is a TransformedDummy for date '2025-01-01' + When I send a "GET" request to "/transformed_dummy_document_ressources/1" + Then the response status code should be 200 + And the response should be in JSON + And the JSON node "year" should exist + And the JSON node year should be equal to "2025" diff --git a/src/Doctrine/Common/State/EntityTransformerLocatorTrait.php b/src/Doctrine/Common/State/ModelTransformerLocatorTrait.php similarity index 71% rename from src/Doctrine/Common/State/EntityTransformerLocatorTrait.php rename to src/Doctrine/Common/State/ModelTransformerLocatorTrait.php index b876f3407e9..b3e26357553 100644 --- a/src/Doctrine/Common/State/EntityTransformerLocatorTrait.php +++ b/src/Doctrine/Common/State/ModelTransformerLocatorTrait.php @@ -8,7 +8,7 @@ /** * Maybe merge this and LinksHandlerLocatorTrait into a OptionsHooksLocatorTrait or something similar? */ -trait EntityTransformerLocatorTrait +trait ModelTransformerLocatorTrait { private ?ContainerInterface $transformEntityLocator; @@ -18,13 +18,13 @@ protected function getEntityTransformer(Operation $operation): ?callable return null; } - $transformEntity = $options->getTransformEntity(); - if (\is_callable($transformEntity)) { - return $transformEntity; + $transformModel = $options->getTransformModel(); + if (\is_callable($transformModel)) { + return $transformModel; } - if ($this->transformEntityLocator && \is_string($transformEntity) && $this->transformEntityLocator->has($transformEntity)) { - return [$this->transformEntityLocator->get($transformEntity), 'transformEntity']; + if ($this->transformEntityLocator && \is_string($transformModel) && $this->transformEntityLocator->has($transformModel)) { + return [$this->transformEntityLocator->get($transformModel), 'transformModel']; } return null; diff --git a/src/Doctrine/Common/State/Options.php b/src/Doctrine/Common/State/Options.php index e36dc631f7b..8d014162494 100644 --- a/src/Doctrine/Common/State/Options.php +++ b/src/Doctrine/Common/State/Options.php @@ -19,11 +19,10 @@ class Options implements OptionsInterface { /** * @param mixed $handleLinks experimental callable, typed mixed as we may want a service name in the future - * @param mixed $transformEntity experimental callable, typed mixed as we may want a service name in the future */ public function __construct( protected mixed $handleLinks = null, - protected mixed $transformEntity = null, + protected mixed $transformModel = null ) { } @@ -40,15 +39,15 @@ public function withHandleLinks(mixed $handleLinks): self return $self; } - public function getTransformEntity(): mixed + public function getTransformModel(): mixed { - return $this->transformEntity; + return $this->transformModel; } - public function withTransformEntity(mixed $transformEntity): self + public function withTransformModel(mixed $transformModel): self { $self = clone $this; - $self->transformEntity = $transformEntity; + $self->transformModel = $transformModel; return $self; } diff --git a/src/Doctrine/Odm/State/CollectionProvider.php b/src/Doctrine/Odm/State/CollectionProvider.php index 54efb69606f..39261068e3a 100644 --- a/src/Doctrine/Odm/State/CollectionProvider.php +++ b/src/Doctrine/Odm/State/CollectionProvider.php @@ -13,7 +13,7 @@ namespace ApiPlatform\Doctrine\Odm\State; -use ApiPlatform\Doctrine\Common\State\EntityTransformerLocatorTrait; +use ApiPlatform\Doctrine\Common\State\ModelTransformerLocatorTrait; use ApiPlatform\Doctrine\Common\State\LinksHandlerLocatorTrait; use ApiPlatform\Doctrine\Odm\Extension\AggregationCollectionExtensionInterface; use ApiPlatform\Doctrine\Odm\Extension\AggregationResultCollectionExtensionInterface; @@ -33,7 +33,7 @@ final class CollectionProvider implements ProviderInterface { use LinksHandlerLocatorTrait; use LinksHandlerTrait; - use EntityTransformerLocatorTrait; + use ModelTransformerLocatorTrait; /** * @param AggregationCollectionExtensionInterface[] $collectionExtensions diff --git a/src/Doctrine/Odm/State/ItemProvider.php b/src/Doctrine/Odm/State/ItemProvider.php index bcd9b278b33..05ca3282dbb 100644 --- a/src/Doctrine/Odm/State/ItemProvider.php +++ b/src/Doctrine/Odm/State/ItemProvider.php @@ -13,7 +13,7 @@ namespace ApiPlatform\Doctrine\Odm\State; -use ApiPlatform\Doctrine\Common\State\EntityTransformerLocatorTrait; +use ApiPlatform\Doctrine\Common\State\ModelTransformerLocatorTrait; use ApiPlatform\Doctrine\Common\State\LinksHandlerLocatorTrait; use ApiPlatform\Doctrine\Odm\Extension\AggregationItemExtensionInterface; use ApiPlatform\Doctrine\Odm\Extension\AggregationResultItemExtensionInterface; @@ -36,7 +36,7 @@ final class ItemProvider implements ProviderInterface { use LinksHandlerLocatorTrait; use LinksHandlerTrait; - use EntityTransformerLocatorTrait; + use ModelTransformerLocatorTrait; /** * @param AggregationItemExtensionInterface[] $itemExtensions diff --git a/src/Doctrine/Odm/State/Options.php b/src/Doctrine/Odm/State/Options.php index 459d6bc49ec..bc1d674bdfe 100644 --- a/src/Doctrine/Odm/State/Options.php +++ b/src/Doctrine/Odm/State/Options.php @@ -20,14 +20,16 @@ class Options extends CommonOptions implements OptionsInterface { /** * @param mixed $handleLinks experimental callable, typed mixed as we may want a service name in the future + * @param mixed $transformDocument experimental callable, typed mixed as we may want a service name in the future * * @see LinksHandlerInterface */ public function __construct( protected ?string $documentClass = null, mixed $handleLinks = null, + mixed $transformDocument = null, ) { - parent::__construct(handleLinks: $handleLinks); + parent::__construct(handleLinks: $handleLinks, transformModel: $transformDocument); } public function getDocumentClass(): ?string @@ -42,4 +44,14 @@ public function withDocumentClass(?string $documentClass): self return $self; } + + public function getTransformDocument(): mixed + { + return $this->getTransformModel(); + } + + public function withTransformDocument(mixed $transformDocument): self + { + return $this->withTransformModel($transformDocument); + } } diff --git a/src/Doctrine/Orm/State/CollectionProvider.php b/src/Doctrine/Orm/State/CollectionProvider.php index 96647cde75e..648d662b2ca 100644 --- a/src/Doctrine/Orm/State/CollectionProvider.php +++ b/src/Doctrine/Orm/State/CollectionProvider.php @@ -13,7 +13,7 @@ namespace ApiPlatform\Doctrine\Orm\State; -use ApiPlatform\Doctrine\Common\State\EntityTransformerLocatorTrait; +use ApiPlatform\Doctrine\Common\State\ModelTransformerLocatorTrait; use ApiPlatform\Doctrine\Common\State\LinksHandlerLocatorTrait; use ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface; use ApiPlatform\Doctrine\Orm\Extension\QueryResultCollectionExtensionInterface; @@ -36,7 +36,7 @@ final class CollectionProvider implements ProviderInterface { use LinksHandlerLocatorTrait; use LinksHandlerTrait; - use EntityTransformerLocatorTrait; + use ModelTransformerLocatorTrait; /** * @param QueryCollectionExtensionInterface[] $collectionExtensions diff --git a/src/Doctrine/Orm/State/ItemProvider.php b/src/Doctrine/Orm/State/ItemProvider.php index b43581c40bf..ab6b80e6880 100644 --- a/src/Doctrine/Orm/State/ItemProvider.php +++ b/src/Doctrine/Orm/State/ItemProvider.php @@ -13,7 +13,7 @@ namespace ApiPlatform\Doctrine\Orm\State; -use ApiPlatform\Doctrine\Common\State\EntityTransformerLocatorTrait; +use ApiPlatform\Doctrine\Common\State\ModelTransformerLocatorTrait; use ApiPlatform\Doctrine\Common\State\LinksHandlerLocatorTrait; use ApiPlatform\Doctrine\Orm\Extension\QueryItemExtensionInterface; use ApiPlatform\Doctrine\Orm\Extension\QueryResultItemExtensionInterface; @@ -36,7 +36,7 @@ final class ItemProvider implements ProviderInterface { use LinksHandlerLocatorTrait; use LinksHandlerTrait; - use EntityTransformerLocatorTrait; + use ModelTransformerLocatorTrait; /** * @param QueryItemExtensionInterface[] $itemExtensions diff --git a/src/Doctrine/Orm/State/Options.php b/src/Doctrine/Orm/State/Options.php index d94bae66d6d..404ac9c07e7 100644 --- a/src/Doctrine/Orm/State/Options.php +++ b/src/Doctrine/Orm/State/Options.php @@ -29,7 +29,7 @@ public function __construct( mixed $handleLinks = null, mixed $transformEntity = null ) { - parent::__construct(handleLinks: $handleLinks, transformEntity: $transformEntity); + parent::__construct(handleLinks: $handleLinks, transformModel: $transformEntity); } public function getEntityClass(): ?string @@ -44,4 +44,14 @@ public function withEntityClass(?string $entityClass): self return $self; } + + public function getTransformDocument(): mixed + { + return $this->getTransformModel(); + } + + public function withTransformDocument(mixed $transformEntity): self + { + return $this->withTransformModel($transformEntity); + } } diff --git a/tests/Behat/DoctrineContext.php b/tests/Behat/DoctrineContext.php index 8d21737dc59..e24de9be09d 100644 --- a/tests/Behat/DoctrineContext.php +++ b/tests/Behat/DoctrineContext.php @@ -2328,7 +2328,7 @@ public function thereAreIssue6039Users(): void } /** - * @Given there is a TransformedDummyEntity object for date :date + * @Given there is a TransformedDummy for date :date */ public function thereIsATransformedDummyEntity(string $date): void { diff --git a/tests/Fixtures/TestBundle/ApiResource/TransformedDummyDocumentRessource.php b/tests/Fixtures/TestBundle/ApiResource/TransformedDummyDocumentRessource.php new file mode 100644 index 00000000000..43eb7fb0537 --- /dev/null +++ b/tests/Fixtures/TestBundle/ApiResource/TransformedDummyDocumentRessource.php @@ -0,0 +1,37 @@ +id = $model->getId(); + $resource->year = (int) $model->getDate()->format('Y'); + + return $resource; + } + +} diff --git a/tests/Fixtures/TestBundle/ApiResource/TransformedDummyRessource.php b/tests/Fixtures/TestBundle/ApiResource/TransformedDummyEntityRessource.php similarity index 51% rename from tests/Fixtures/TestBundle/ApiResource/TransformedDummyRessource.php rename to tests/Fixtures/TestBundle/ApiResource/TransformedDummyEntityRessource.php index deb1863cab4..1ad98cf226d 100644 --- a/tests/Fixtures/TestBundle/ApiResource/TransformedDummyRessource.php +++ b/tests/Fixtures/TestBundle/ApiResource/TransformedDummyEntityRessource.php @@ -6,29 +6,30 @@ use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Tests\Fixtures\TestBundle\Document\TransformedDummyDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\TransformedDummyEntity; #[ApiResource( operations :[ - new GetCollection(), - new Get(), + new GetCollection(uriTemplate: '/transformed_dummy_entity_ressources'), + new Get(uriTemplate: '/transformed_dummy_entity_ressources/{id}'), ], stateOptions: new Options( entityClass: TransformedDummyEntity::class, - transformEntity: [self::class, 'transformEntity'], + transformEntity: [self::class, 'transformModel'], ) )] -class TransformedDummyRessource +class TransformedDummyEntityRessource { public ?int $id = null; public ?int $year = null; - public static function transformEntity(TransformedDummyEntity $entity): self + public static function transformModel(TransformedDummyEntity|TransformedDummyDocument $model): self { $resource = new self(); - $resource->id = $entity->getId(); - $resource->year = (int) $entity->getDate()->format('Y'); + $resource->id = $model->getId(); + $resource->year = (int) $model->getDate()->format('Y'); return $resource; } diff --git a/tests/Fixtures/TestBundle/Document/TransformedDummyDocument.php b/tests/Fixtures/TestBundle/Document/TransformedDummyDocument.php index e120bd71e5e..c6332854583 100644 --- a/tests/Fixtures/TestBundle/Document/TransformedDummyDocument.php +++ b/tests/Fixtures/TestBundle/Document/TransformedDummyDocument.php @@ -5,13 +5,14 @@ use Doctrine\DBAL\Types\Types; use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; +#[ ODM\Document] class TransformedDummyDocument { #[ODM\Id(type: 'int', strategy: 'INCREMENT')] private ?int $id = null; - #[ODM\Field(type: Types::DATETIME_IMMUTABLE)] + #[ODM\Field(type: 'date_immutable')] private \DateTimeInterface $date; public function __construct(\DateTimeInterface $date = null) From c7f7694f091872e447a5f28618d8863ac1d388f0 Mon Sep 17 00:00:00 2001 From: mrossard Date: Wed, 15 Jan 2025 13:18:16 +0100 Subject: [PATCH 4/6] fix: silly mistake --- src/Doctrine/Odm/State/Options.php | 4 +++- src/Doctrine/Orm/State/Options.php | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Doctrine/Odm/State/Options.php b/src/Doctrine/Odm/State/Options.php index bc1d674bdfe..e82c93e311c 100644 --- a/src/Doctrine/Odm/State/Options.php +++ b/src/Doctrine/Odm/State/Options.php @@ -52,6 +52,8 @@ public function getTransformDocument(): mixed public function withTransformDocument(mixed $transformDocument): self { - return $this->withTransformModel($transformDocument); + $self = clone $this; + $self->transformModel = $transformDocument; + return $self; } } diff --git a/src/Doctrine/Orm/State/Options.php b/src/Doctrine/Orm/State/Options.php index 404ac9c07e7..ce9dc0b362d 100644 --- a/src/Doctrine/Orm/State/Options.php +++ b/src/Doctrine/Orm/State/Options.php @@ -52,6 +52,8 @@ public function getTransformDocument(): mixed public function withTransformDocument(mixed $transformEntity): self { - return $this->withTransformModel($transformEntity); + $self = clone $this; + $self->transformModel = $transformEntity; + return $self; } } From 3b26b70e5141fabf558db226acd847573cb75aba Mon Sep 17 00:00:00 2001 From: mrossard Date: Wed, 15 Jan 2025 13:28:22 +0100 Subject: [PATCH 5/6] fix: run cs fixer --- .../State/ModelTransformerLocatorTrait.php | 11 +++++++++++ src/Doctrine/Common/State/Options.php | 2 +- src/Doctrine/Odm/State/CollectionProvider.php | 8 ++++---- src/Doctrine/Odm/State/ItemProvider.php | 6 +++--- src/Doctrine/Odm/State/Options.php | 3 ++- src/Doctrine/Orm/State/CollectionProvider.php | 6 +++--- src/Doctrine/Orm/State/ItemProvider.php | 6 +++--- src/Doctrine/Orm/State/Options.php | 5 +++-- .../TransformedDummyDocumentRessource.php | 15 ++++++++++++--- .../TransformedDummyEntityRessource.php | 14 ++++++++++++-- .../Document/TransformedDummyDocument.php | 16 ++++++++++++---- .../TestBundle/Entity/TransformedDummyEntity.php | 13 ++++++++++++- 12 files changed, 78 insertions(+), 27 deletions(-) diff --git a/src/Doctrine/Common/State/ModelTransformerLocatorTrait.php b/src/Doctrine/Common/State/ModelTransformerLocatorTrait.php index b3e26357553..cf76226f557 100644 --- a/src/Doctrine/Common/State/ModelTransformerLocatorTrait.php +++ b/src/Doctrine/Common/State/ModelTransformerLocatorTrait.php @@ -1,5 +1,16 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + namespace ApiPlatform\Doctrine\Common\State; use ApiPlatform\Metadata\Operation; diff --git a/src/Doctrine/Common/State/Options.php b/src/Doctrine/Common/State/Options.php index 8d014162494..3d75c18f57f 100644 --- a/src/Doctrine/Common/State/Options.php +++ b/src/Doctrine/Common/State/Options.php @@ -22,7 +22,7 @@ class Options implements OptionsInterface */ public function __construct( protected mixed $handleLinks = null, - protected mixed $transformModel = null + protected mixed $transformModel = null, ) { } diff --git a/src/Doctrine/Odm/State/CollectionProvider.php b/src/Doctrine/Odm/State/CollectionProvider.php index 39261068e3a..8c220e2a73e 100644 --- a/src/Doctrine/Odm/State/CollectionProvider.php +++ b/src/Doctrine/Odm/State/CollectionProvider.php @@ -13,8 +13,8 @@ namespace ApiPlatform\Doctrine\Odm\State; -use ApiPlatform\Doctrine\Common\State\ModelTransformerLocatorTrait; use ApiPlatform\Doctrine\Common\State\LinksHandlerLocatorTrait; +use ApiPlatform\Doctrine\Common\State\ModelTransformerLocatorTrait; use ApiPlatform\Doctrine\Odm\Extension\AggregationCollectionExtensionInterface; use ApiPlatform\Doctrine\Odm\Extension\AggregationResultCollectionExtensionInterface; use ApiPlatform\Metadata\Exception\RuntimeException; @@ -73,7 +73,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c $extension->applyToCollection($aggregationBuilder, $documentClass, $operation, $context); if ($extension instanceof AggregationResultCollectionExtensionInterface && $extension->supportsResult($documentClass, $operation, $context)) { - $result = $extension->getResult($aggregationBuilder, $documentClass, $operation, $context); + $result = $extension->getResult($aggregationBuilder, $documentClass, $operation, $context); break; } } @@ -83,9 +83,9 @@ public function provide(Operation $operation, array $uriVariables = [], array $c $result = $result ?? $aggregationBuilder->hydrate($documentClass)->execute($executeOptions); - return match($transformer = $this->getEntityTransformer($operation)){ + return match ($transformer = $this->getEntityTransformer($operation)) { null => $result, - default => array_map($transformer, iterator_to_array($result)) + default => array_map($transformer, iterator_to_array($result)), }; } } diff --git a/src/Doctrine/Odm/State/ItemProvider.php b/src/Doctrine/Odm/State/ItemProvider.php index 05ca3282dbb..3ffe386491c 100644 --- a/src/Doctrine/Odm/State/ItemProvider.php +++ b/src/Doctrine/Odm/State/ItemProvider.php @@ -13,8 +13,8 @@ namespace ApiPlatform\Doctrine\Odm\State; -use ApiPlatform\Doctrine\Common\State\ModelTransformerLocatorTrait; use ApiPlatform\Doctrine\Common\State\LinksHandlerLocatorTrait; +use ApiPlatform\Doctrine\Common\State\ModelTransformerLocatorTrait; use ApiPlatform\Doctrine\Odm\Extension\AggregationItemExtensionInterface; use ApiPlatform\Doctrine\Odm\Extension\AggregationResultItemExtensionInterface; use ApiPlatform\Metadata\Exception\RuntimeException; @@ -90,9 +90,9 @@ public function provide(Operation $operation, array $uriVariables = [], array $c $result = $result ?? ($aggregationBuilder->hydrate($documentClass)->execute($executeOptions)->current() ?: null); - return match($transformer = $this->getEntityTransformer($operation)){ + return match ($transformer = $this->getEntityTransformer($operation)) { null => $result, - default => ($result !== null)? $transformer($result) : null + default => (null !== $result) ? $transformer($result) : null, }; } } diff --git a/src/Doctrine/Odm/State/Options.php b/src/Doctrine/Odm/State/Options.php index e82c93e311c..2cb006448ad 100644 --- a/src/Doctrine/Odm/State/Options.php +++ b/src/Doctrine/Odm/State/Options.php @@ -19,7 +19,7 @@ class Options extends CommonOptions implements OptionsInterface { /** - * @param mixed $handleLinks experimental callable, typed mixed as we may want a service name in the future + * @param mixed $handleLinks experimental callable, typed mixed as we may want a service name in the future * @param mixed $transformDocument experimental callable, typed mixed as we may want a service name in the future * * @see LinksHandlerInterface @@ -54,6 +54,7 @@ public function withTransformDocument(mixed $transformDocument): self { $self = clone $this; $self->transformModel = $transformDocument; + return $self; } } diff --git a/src/Doctrine/Orm/State/CollectionProvider.php b/src/Doctrine/Orm/State/CollectionProvider.php index 648d662b2ca..09408657eab 100644 --- a/src/Doctrine/Orm/State/CollectionProvider.php +++ b/src/Doctrine/Orm/State/CollectionProvider.php @@ -13,8 +13,8 @@ namespace ApiPlatform\Doctrine\Orm\State; -use ApiPlatform\Doctrine\Common\State\ModelTransformerLocatorTrait; use ApiPlatform\Doctrine\Common\State\LinksHandlerLocatorTrait; +use ApiPlatform\Doctrine\Common\State\ModelTransformerLocatorTrait; use ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface; use ApiPlatform\Doctrine\Orm\Extension\QueryResultCollectionExtensionInterface; use ApiPlatform\Doctrine\Orm\Util\QueryNameGenerator; @@ -84,9 +84,9 @@ public function provide(Operation $operation, array $uriVariables = [], array $c $result = $result ?? $queryBuilder->getQuery()->getResult(); - return match($transformer = $this->getEntityTransformer($operation)){ + return match ($transformer = $this->getEntityTransformer($operation)) { null => $result, - default => array_map($transformer, iterator_to_array($result)) + default => array_map($transformer, iterator_to_array($result)), }; } } diff --git a/src/Doctrine/Orm/State/ItemProvider.php b/src/Doctrine/Orm/State/ItemProvider.php index ab6b80e6880..38a57fed70a 100644 --- a/src/Doctrine/Orm/State/ItemProvider.php +++ b/src/Doctrine/Orm/State/ItemProvider.php @@ -13,8 +13,8 @@ namespace ApiPlatform\Doctrine\Orm\State; -use ApiPlatform\Doctrine\Common\State\ModelTransformerLocatorTrait; use ApiPlatform\Doctrine\Common\State\LinksHandlerLocatorTrait; +use ApiPlatform\Doctrine\Common\State\ModelTransformerLocatorTrait; use ApiPlatform\Doctrine\Orm\Extension\QueryItemExtensionInterface; use ApiPlatform\Doctrine\Orm\Extension\QueryResultItemExtensionInterface; use ApiPlatform\Doctrine\Orm\Util\QueryNameGenerator; @@ -90,9 +90,9 @@ public function provide(Operation $operation, array $uriVariables = [], array $c $result = $result ?? $queryBuilder->getQuery()->getOneOrNullResult(); - return match($transformer = $this->getEntityTransformer($operation)){ + return match ($transformer = $this->getEntityTransformer($operation)) { null => $result, - default=> $transformer($result) + default => $transformer($result), }; } } diff --git a/src/Doctrine/Orm/State/Options.php b/src/Doctrine/Orm/State/Options.php index ce9dc0b362d..d22942b8a1b 100644 --- a/src/Doctrine/Orm/State/Options.php +++ b/src/Doctrine/Orm/State/Options.php @@ -19,7 +19,7 @@ class Options extends CommonOptions implements OptionsInterface { /** - * @param string|callable $handleLinks experimental callable, typed mixed as we may want a service name in the future + * @param string|callable $handleLinks experimental callable, typed mixed as we may want a service name in the future * @param string|callable $transformEntity experimental callable, typed mixed as we may want a service name in the future * * @see LinksHandlerInterface @@ -27,7 +27,7 @@ class Options extends CommonOptions implements OptionsInterface public function __construct( protected ?string $entityClass = null, mixed $handleLinks = null, - mixed $transformEntity = null + mixed $transformEntity = null, ) { parent::__construct(handleLinks: $handleLinks, transformModel: $transformEntity); } @@ -54,6 +54,7 @@ public function withTransformDocument(mixed $transformEntity): self { $self = clone $this; $self->transformModel = $transformEntity; + return $self; } } diff --git a/tests/Fixtures/TestBundle/ApiResource/TransformedDummyDocumentRessource.php b/tests/Fixtures/TestBundle/ApiResource/TransformedDummyDocumentRessource.php index 43eb7fb0537..92222729543 100644 --- a/tests/Fixtures/TestBundle/ApiResource/TransformedDummyDocumentRessource.php +++ b/tests/Fixtures/TestBundle/ApiResource/TransformedDummyDocumentRessource.php @@ -1,8 +1,18 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResource; -use ApiPlatform\Doctrine\Orm\State\Options; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; @@ -10,7 +20,7 @@ use ApiPlatform\Tests\Fixtures\TestBundle\Entity\TransformedDummyEntity; #[ApiResource( - operations :[ + operations : [ new GetCollection(uriTemplate: '/transformed_dummy_document_ressources'), new Get(uriTemplate: '/transformed_dummy_document_ressources/{id}'), ], @@ -33,5 +43,4 @@ public static function transformModel(TransformedDummyEntity|TransformedDummyDoc return $resource; } - } diff --git a/tests/Fixtures/TestBundle/ApiResource/TransformedDummyEntityRessource.php b/tests/Fixtures/TestBundle/ApiResource/TransformedDummyEntityRessource.php index 1ad98cf226d..8c6b48f039f 100644 --- a/tests/Fixtures/TestBundle/ApiResource/TransformedDummyEntityRessource.php +++ b/tests/Fixtures/TestBundle/ApiResource/TransformedDummyEntityRessource.php @@ -1,5 +1,16 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResource; use ApiPlatform\Doctrine\Orm\State\Options; @@ -10,7 +21,7 @@ use ApiPlatform\Tests\Fixtures\TestBundle\Entity\TransformedDummyEntity; #[ApiResource( - operations :[ + operations : [ new GetCollection(uriTemplate: '/transformed_dummy_entity_ressources'), new Get(uriTemplate: '/transformed_dummy_entity_ressources/{id}'), ], @@ -33,5 +44,4 @@ public static function transformModel(TransformedDummyEntity|TransformedDummyDoc return $resource; } - } diff --git a/tests/Fixtures/TestBundle/Document/TransformedDummyDocument.php b/tests/Fixtures/TestBundle/Document/TransformedDummyDocument.php index c6332854583..d3511295cae 100644 --- a/tests/Fixtures/TestBundle/Document/TransformedDummyDocument.php +++ b/tests/Fixtures/TestBundle/Document/TransformedDummyDocument.php @@ -1,21 +1,30 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + namespace ApiPlatform\Tests\Fixtures\TestBundle\Document; -use Doctrine\DBAL\Types\Types; use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; #[ ODM\Document] class TransformedDummyDocument { - #[ODM\Id(type: 'int', strategy: 'INCREMENT')] private ?int $id = null; #[ODM\Field(type: 'date_immutable')] private \DateTimeInterface $date; - public function __construct(\DateTimeInterface $date = null) + public function __construct(?\DateTimeInterface $date = null) { $this->setDate($date ?? new \DateTimeImmutable()); } @@ -34,5 +43,4 @@ public function getId(): ?int { return $this->id; } - } diff --git a/tests/Fixtures/TestBundle/Entity/TransformedDummyEntity.php b/tests/Fixtures/TestBundle/Entity/TransformedDummyEntity.php index 50a3199ae4a..9ffe6d5aff0 100644 --- a/tests/Fixtures/TestBundle/Entity/TransformedDummyEntity.php +++ b/tests/Fixtures/TestBundle/Entity/TransformedDummyEntity.php @@ -1,5 +1,16 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity; use Doctrine\DBAL\Types\Types; @@ -16,7 +27,7 @@ class TransformedDummyEntity #[ORM\Column(type: Types::DATETIME_IMMUTABLE)] private \DateTimeInterface $date; - public function __construct(\DateTimeInterface $date = null) + public function __construct(?\DateTimeInterface $date = null) { $this->setDate($date ?? new \DateTimeImmutable()); } From 9f4cb1a114e1663f37b19506b36b28bef399214f Mon Sep 17 00:00:00 2001 From: mrossard Date: Mon, 20 Jan 2025 17:18:57 +0100 Subject: [PATCH 6/6] feat: add a second hook for resource to entity or document transformation --- .../State/ModelTransformerLocatorTrait.php | 43 ------------- src/Doctrine/Common/State/Options.php | 26 ++++++-- .../Common/State/PersistProcessor.php | 1 + .../State/ResourceTransformerLocatorTrait.php | 61 +++++++++++++++++++ src/Doctrine/Odm/State/CollectionProvider.php | 8 +-- src/Doctrine/Odm/State/ItemProvider.php | 8 +-- src/Doctrine/Odm/State/Options.php | 12 ++-- src/Doctrine/Orm/State/CollectionProvider.php | 8 +-- src/Doctrine/Orm/State/ItemProvider.php | 8 +-- src/Doctrine/Orm/State/Options.php | 31 +++++++--- .../TransformedDummyDocumentRessource.php | 5 +- .../TransformedDummyEntityRessource.php | 5 +- 12 files changed, 132 insertions(+), 84 deletions(-) delete mode 100644 src/Doctrine/Common/State/ModelTransformerLocatorTrait.php create mode 100644 src/Doctrine/Common/State/ResourceTransformerLocatorTrait.php diff --git a/src/Doctrine/Common/State/ModelTransformerLocatorTrait.php b/src/Doctrine/Common/State/ModelTransformerLocatorTrait.php deleted file mode 100644 index cf76226f557..00000000000 --- a/src/Doctrine/Common/State/ModelTransformerLocatorTrait.php +++ /dev/null @@ -1,43 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace ApiPlatform\Doctrine\Common\State; - -use ApiPlatform\Metadata\Operation; -use Psr\Container\ContainerInterface; - -/** - * Maybe merge this and LinksHandlerLocatorTrait into a OptionsHooksLocatorTrait or something similar? - */ -trait ModelTransformerLocatorTrait -{ - private ?ContainerInterface $transformEntityLocator; - - protected function getEntityTransformer(Operation $operation): ?callable - { - if (!($options = $operation->getStateOptions()) || !$options instanceof Options) { - return null; - } - - $transformModel = $options->getTransformModel(); - if (\is_callable($transformModel)) { - return $transformModel; - } - - if ($this->transformEntityLocator && \is_string($transformModel) && $this->transformEntityLocator->has($transformModel)) { - return [$this->transformEntityLocator->get($transformModel), 'transformModel']; - } - - return null; - } -} diff --git a/src/Doctrine/Common/State/Options.php b/src/Doctrine/Common/State/Options.php index 3d75c18f57f..8dc88eca6a7 100644 --- a/src/Doctrine/Common/State/Options.php +++ b/src/Doctrine/Common/State/Options.php @@ -19,10 +19,13 @@ class Options implements OptionsInterface { /** * @param mixed $handleLinks experimental callable, typed mixed as we may want a service name in the future + * @param mixed $toResourceTransformer experimental callable, typed mixed as we may want a service name in the future + * @param mixed $fromResourceTransformer experimental callable, typed mixed as we may want a service name in the future */ public function __construct( protected mixed $handleLinks = null, - protected mixed $transformModel = null, + protected mixed $toResourceTransformer = null, + protected mixed $fromResourceTransformer = null, ) { } @@ -39,15 +42,28 @@ public function withHandleLinks(mixed $handleLinks): self return $self; } - public function getTransformModel(): mixed + public function getToResourceTransformer(): mixed { - return $this->transformModel; + return $this->toResourceTransformer; } - public function withTransformModel(mixed $transformModel): self + public function withToResourceTransformer(mixed $toResourceTransformer): self { $self = clone $this; - $self->transformModel = $transformModel; + $self->toResourceTransformer = $toResourceTransformer; + + return $self; + } + + public function getFromResourceTransformer(): mixed + { + return $this->fromResourceTransformer; + } + + public function withFromResourceTransformer(mixed $fromResourceTransformer): self + { + $self = clone $this; + $self->fromResourceTransformer = $fromResourceTransformer; return $self; } diff --git a/src/Doctrine/Common/State/PersistProcessor.php b/src/Doctrine/Common/State/PersistProcessor.php index abc21bf9e15..c0d85a1b5d0 100644 --- a/src/Doctrine/Common/State/PersistProcessor.php +++ b/src/Doctrine/Common/State/PersistProcessor.php @@ -24,6 +24,7 @@ final class PersistProcessor implements ProcessorInterface { use ClassInfoTrait; use LinksHandlerTrait; + use ResourceTransformerLocatorTrait; public function __construct(private readonly ManagerRegistry $managerRegistry) { diff --git a/src/Doctrine/Common/State/ResourceTransformerLocatorTrait.php b/src/Doctrine/Common/State/ResourceTransformerLocatorTrait.php new file mode 100644 index 00000000000..7441021e351 --- /dev/null +++ b/src/Doctrine/Common/State/ResourceTransformerLocatorTrait.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Doctrine\Common\State; + +use ApiPlatform\Metadata\Operation; +use Psr\Container\ContainerInterface; + +/** + * Maybe merge this and LinksHandlerLocatorTrait into a OptionsHooksLocatorTrait or something similar? + */ +trait ResourceTransformerLocatorTrait +{ + private ?ContainerInterface $resourceTransformerLocator; + + protected function getToResourceTransformer(Operation $operation): ?callable + { + if (!($options = $operation->getStateOptions()) || !$options instanceof Options) { + return null; + } + + $transformer = $options->getToResourceTransformer(); + if (\is_callable($transformer)) { + return $transformer; + } + + if ($this->resourceTransformerLocator && \is_string($transformer) && $this->resourceTransformerLocator->has($transformer)) { + return [$this->resourceTransformerLocator->get($transformer), 'toResource']; + } + + return null; + } + + protected function getFromResourceTransformer(Operation $operation): ?callable + { + if (!($options = $operation->getStateOptions()) || !$options instanceof Options) { + return null; + } + + $transformer = $options->getFromResourceTransformer(); + if (\is_callable($transformer)) { + return $transformer; + } + + if ($this->resourceTransformerLocator && \is_string($transformer) && $this->resourceTransformerLocator->has($transformer)) { + return [$this->resourceTransformerLocator->get($transformer), 'fromResource']; + } + + return null; + } +} diff --git a/src/Doctrine/Odm/State/CollectionProvider.php b/src/Doctrine/Odm/State/CollectionProvider.php index 8c220e2a73e..0b27e6795ab 100644 --- a/src/Doctrine/Odm/State/CollectionProvider.php +++ b/src/Doctrine/Odm/State/CollectionProvider.php @@ -14,7 +14,7 @@ namespace ApiPlatform\Doctrine\Odm\State; use ApiPlatform\Doctrine\Common\State\LinksHandlerLocatorTrait; -use ApiPlatform\Doctrine\Common\State\ModelTransformerLocatorTrait; +use ApiPlatform\Doctrine\Common\State\ResourceTransformerLocatorTrait; use ApiPlatform\Doctrine\Odm\Extension\AggregationCollectionExtensionInterface; use ApiPlatform\Doctrine\Odm\Extension\AggregationResultCollectionExtensionInterface; use ApiPlatform\Metadata\Exception\RuntimeException; @@ -33,7 +33,7 @@ final class CollectionProvider implements ProviderInterface { use LinksHandlerLocatorTrait; use LinksHandlerTrait; - use ModelTransformerLocatorTrait; + use ResourceTransformerLocatorTrait; /** * @param AggregationCollectionExtensionInterface[] $collectionExtensions @@ -42,7 +42,7 @@ public function __construct(ResourceMetadataCollectionFactoryInterface $resource { $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->handleLinksLocator = $handleLinksLocator; - $this->transformEntityLocator = $handleLinksLocator; + $this->resourceTransformerLocator = $handleLinksLocator; $this->managerRegistry = $managerRegistry; } @@ -83,7 +83,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c $result = $result ?? $aggregationBuilder->hydrate($documentClass)->execute($executeOptions); - return match ($transformer = $this->getEntityTransformer($operation)) { + return match ($transformer = $this->getToResourceTransformer($operation)) { null => $result, default => array_map($transformer, iterator_to_array($result)), }; diff --git a/src/Doctrine/Odm/State/ItemProvider.php b/src/Doctrine/Odm/State/ItemProvider.php index 3ffe386491c..5832e432362 100644 --- a/src/Doctrine/Odm/State/ItemProvider.php +++ b/src/Doctrine/Odm/State/ItemProvider.php @@ -14,7 +14,7 @@ namespace ApiPlatform\Doctrine\Odm\State; use ApiPlatform\Doctrine\Common\State\LinksHandlerLocatorTrait; -use ApiPlatform\Doctrine\Common\State\ModelTransformerLocatorTrait; +use ApiPlatform\Doctrine\Common\State\ResourceTransformerLocatorTrait; use ApiPlatform\Doctrine\Odm\Extension\AggregationItemExtensionInterface; use ApiPlatform\Doctrine\Odm\Extension\AggregationResultItemExtensionInterface; use ApiPlatform\Metadata\Exception\RuntimeException; @@ -36,7 +36,7 @@ final class ItemProvider implements ProviderInterface { use LinksHandlerLocatorTrait; use LinksHandlerTrait; - use ModelTransformerLocatorTrait; + use ResourceTransformerLocatorTrait; /** * @param AggregationItemExtensionInterface[] $itemExtensions @@ -45,7 +45,7 @@ public function __construct(ResourceMetadataCollectionFactoryInterface $resource { $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->handleLinksLocator = $handleLinksLocator; - $this->transformEntityLocator = $handleLinksLocator; + $this->resourceTransformerLocator = $handleLinksLocator; $this->managerRegistry = $managerRegistry; } @@ -90,7 +90,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c $result = $result ?? ($aggregationBuilder->hydrate($documentClass)->execute($executeOptions)->current() ?: null); - return match ($transformer = $this->getEntityTransformer($operation)) { + return match ($transformer = $this->getToResourceTransformer($operation)) { null => $result, default => (null !== $result) ? $transformer($result) : null, }; diff --git a/src/Doctrine/Odm/State/Options.php b/src/Doctrine/Odm/State/Options.php index 2cb006448ad..301b635b1ed 100644 --- a/src/Doctrine/Odm/State/Options.php +++ b/src/Doctrine/Odm/State/Options.php @@ -20,16 +20,16 @@ class Options extends CommonOptions implements OptionsInterface { /** * @param mixed $handleLinks experimental callable, typed mixed as we may want a service name in the future - * @param mixed $transformDocument experimental callable, typed mixed as we may want a service name in the future + * @param mixed $transformFromDocument experimental callable, typed mixed as we may want a service name in the future * * @see LinksHandlerInterface */ public function __construct( protected ?string $documentClass = null, - mixed $handleLinks = null, - mixed $transformDocument = null, + mixed $handleLinks = null, + mixed $transformFromDocument = null, ) { - parent::__construct(handleLinks: $handleLinks, transformModel: $transformDocument); + parent::__construct(handleLinks: $handleLinks, toResourceTransformer: $transformFromDocument); } public function getDocumentClass(): ?string @@ -47,13 +47,13 @@ public function withDocumentClass(?string $documentClass): self public function getTransformDocument(): mixed { - return $this->getTransformModel(); + return $this->getToResourceTransformer(); } public function withTransformDocument(mixed $transformDocument): self { $self = clone $this; - $self->transformModel = $transformDocument; + $self->toResourceTransformer = $transformDocument; return $self; } diff --git a/src/Doctrine/Orm/State/CollectionProvider.php b/src/Doctrine/Orm/State/CollectionProvider.php index 09408657eab..f8daee3f084 100644 --- a/src/Doctrine/Orm/State/CollectionProvider.php +++ b/src/Doctrine/Orm/State/CollectionProvider.php @@ -14,7 +14,7 @@ namespace ApiPlatform\Doctrine\Orm\State; use ApiPlatform\Doctrine\Common\State\LinksHandlerLocatorTrait; -use ApiPlatform\Doctrine\Common\State\ModelTransformerLocatorTrait; +use ApiPlatform\Doctrine\Common\State\ResourceTransformerLocatorTrait; use ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface; use ApiPlatform\Doctrine\Orm\Extension\QueryResultCollectionExtensionInterface; use ApiPlatform\Doctrine\Orm\Util\QueryNameGenerator; @@ -36,7 +36,7 @@ final class CollectionProvider implements ProviderInterface { use LinksHandlerLocatorTrait; use LinksHandlerTrait; - use ModelTransformerLocatorTrait; + use ResourceTransformerLocatorTrait; /** * @param QueryCollectionExtensionInterface[] $collectionExtensions @@ -45,7 +45,7 @@ public function __construct(ResourceMetadataCollectionFactoryInterface $resource { $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->handleLinksLocator = $handleLinksLocator; - $this->transformEntityLocator = $handleLinksLocator; + $this->resourceTransformerLocator = $handleLinksLocator; $this->managerRegistry = $managerRegistry; } @@ -84,7 +84,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c $result = $result ?? $queryBuilder->getQuery()->getResult(); - return match ($transformer = $this->getEntityTransformer($operation)) { + return match ($transformer = $this->getToResourceTransformer($operation)) { null => $result, default => array_map($transformer, iterator_to_array($result)), }; diff --git a/src/Doctrine/Orm/State/ItemProvider.php b/src/Doctrine/Orm/State/ItemProvider.php index 38a57fed70a..16e6a4888e4 100644 --- a/src/Doctrine/Orm/State/ItemProvider.php +++ b/src/Doctrine/Orm/State/ItemProvider.php @@ -14,7 +14,7 @@ namespace ApiPlatform\Doctrine\Orm\State; use ApiPlatform\Doctrine\Common\State\LinksHandlerLocatorTrait; -use ApiPlatform\Doctrine\Common\State\ModelTransformerLocatorTrait; +use ApiPlatform\Doctrine\Common\State\ResourceTransformerLocatorTrait; use ApiPlatform\Doctrine\Orm\Extension\QueryItemExtensionInterface; use ApiPlatform\Doctrine\Orm\Extension\QueryResultItemExtensionInterface; use ApiPlatform\Doctrine\Orm\Util\QueryNameGenerator; @@ -36,7 +36,7 @@ final class ItemProvider implements ProviderInterface { use LinksHandlerLocatorTrait; use LinksHandlerTrait; - use ModelTransformerLocatorTrait; + use ResourceTransformerLocatorTrait; /** * @param QueryItemExtensionInterface[] $itemExtensions @@ -45,7 +45,7 @@ public function __construct(ResourceMetadataCollectionFactoryInterface $resource { $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->handleLinksLocator = $handleLinksLocator; - $this->transformEntityLocator = $handleLinksLocator; + $this->resourceTransformerLocator = $handleLinksLocator; $this->managerRegistry = $managerRegistry; } @@ -90,7 +90,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c $result = $result ?? $queryBuilder->getQuery()->getOneOrNullResult(); - return match ($transformer = $this->getEntityTransformer($operation)) { + return match ($transformer = $this->getToResourceTransformer($operation)) { null => $result, default => $transformer($result), }; diff --git a/src/Doctrine/Orm/State/Options.php b/src/Doctrine/Orm/State/Options.php index d22942b8a1b..777ef189f60 100644 --- a/src/Doctrine/Orm/State/Options.php +++ b/src/Doctrine/Orm/State/Options.php @@ -20,16 +20,17 @@ class Options extends CommonOptions implements OptionsInterface { /** * @param string|callable $handleLinks experimental callable, typed mixed as we may want a service name in the future - * @param string|callable $transformEntity experimental callable, typed mixed as we may want a service name in the future + * @param string|callable $transformFromEntity experimental callable, typed mixed as we may want a service name in the future * * @see LinksHandlerInterface */ public function __construct( protected ?string $entityClass = null, - mixed $handleLinks = null, - mixed $transformEntity = null, + mixed $handleLinks = null, + mixed $transformFromEntity = null, + mixed $transformToEntity = null, ) { - parent::__construct(handleLinks: $handleLinks, transformModel: $transformEntity); + parent::__construct(handleLinks: $handleLinks, toResourceTransformer: $transformFromEntity, fromResourceTransformer: $transformToEntity); } public function getEntityClass(): ?string @@ -45,16 +46,30 @@ public function withEntityClass(?string $entityClass): self return $self; } - public function getTransformDocument(): mixed + public function getTransformFromEntity(): mixed { - return $this->getTransformModel(); + return $this->getToResourceTransformer(); } - public function withTransformDocument(mixed $transformEntity): self + public function withTransformFromEntity(mixed $transformFromEntity): self { $self = clone $this; - $self->transformModel = $transformEntity; + $self->toResourceTransformer = $transformFromEntity; return $self; } + + public function getTransformToEntity(): mixed + { + return $this->getFromResourceTransformer(); + } + + public function withTransformToEntity(mixed $transformToEntity): self + { + $self = clone $this; + $self->fromResourceTransformer = $transformToEntity; + + return $self; + } + } diff --git a/tests/Fixtures/TestBundle/ApiResource/TransformedDummyDocumentRessource.php b/tests/Fixtures/TestBundle/ApiResource/TransformedDummyDocumentRessource.php index 92222729543..bc55d335b55 100644 --- a/tests/Fixtures/TestBundle/ApiResource/TransformedDummyDocumentRessource.php +++ b/tests/Fixtures/TestBundle/ApiResource/TransformedDummyDocumentRessource.php @@ -17,7 +17,6 @@ use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Tests\Fixtures\TestBundle\Document\TransformedDummyDocument; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\TransformedDummyEntity; #[ApiResource( operations : [ @@ -26,7 +25,7 @@ ], stateOptions: new \ApiPlatform\Doctrine\Odm\State\Options( documentClass: TransformedDummyDocument::class, - transformDocument: [self::class, 'transformModel'], + transformFromDocument: [self::class, 'transformToResource'], ) )] class TransformedDummyDocumentRessource @@ -35,7 +34,7 @@ class TransformedDummyDocumentRessource public ?int $year = null; - public static function transformModel(TransformedDummyEntity|TransformedDummyDocument $model): self + public static function transformToResource(TransformedDummyDocument $model): self { $resource = new self(); $resource->id = $model->getId(); diff --git a/tests/Fixtures/TestBundle/ApiResource/TransformedDummyEntityRessource.php b/tests/Fixtures/TestBundle/ApiResource/TransformedDummyEntityRessource.php index 8c6b48f039f..c234439144a 100644 --- a/tests/Fixtures/TestBundle/ApiResource/TransformedDummyEntityRessource.php +++ b/tests/Fixtures/TestBundle/ApiResource/TransformedDummyEntityRessource.php @@ -17,7 +17,6 @@ use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; -use ApiPlatform\Tests\Fixtures\TestBundle\Document\TransformedDummyDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\TransformedDummyEntity; #[ApiResource( @@ -27,7 +26,7 @@ ], stateOptions: new Options( entityClass: TransformedDummyEntity::class, - transformEntity: [self::class, 'transformModel'], + transformFromEntity: [self::class, 'transformToResource'], ) )] class TransformedDummyEntityRessource @@ -36,7 +35,7 @@ class TransformedDummyEntityRessource public ?int $year = null; - public static function transformModel(TransformedDummyEntity|TransformedDummyDocument $model): self + public static function transformToResource(TransformedDummyEntity $model): self { $resource = new self(); $resource->id = $model->getId();