diff --git a/src/Doctrine/Common/State/LinksHandlerLocatorTrait.php b/src/Doctrine/Common/State/LinksHandlerLocatorTrait.php new file mode 100644 index 00000000000..84f8960d993 --- /dev/null +++ b/src/Doctrine/Common/State/LinksHandlerLocatorTrait.php @@ -0,0 +1,45 @@ + + * + * 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\Doctrine\Orm\State\Options; +use ApiPlatform\Metadata\Exception\RuntimeException; +use ApiPlatform\Metadata\Operation; +use Psr\Container\ContainerInterface; + +/** + * @internal + */ +trait LinksHandlerLocatorTrait +{ + private ?ContainerInterface $handleLinksLocator; + + private function getLinksHandler(Operation $operation): ?callable + { + if (!($options = $operation->getStateOptions()) || !$options instanceof Options) { + return null; + } + + $handleLinks = $options->getHandleLinks(); + if (\is_callable($handleLinks)) { + return $handleLinks; + } + + if ($this->handleLinksLocator && \is_string($handleLinks) && $this->handleLinksLocator->has($handleLinks)) { + return [$this->handleLinksLocator->get($handleLinks), 'handleLinks']; + } + + throw new RuntimeException(sprintf('Could not find handleLinks service "%s"', $handleLinks)); + } +} diff --git a/src/Doctrine/Common/State/LinksHandlerTrait.php b/src/Doctrine/Common/State/LinksHandlerTrait.php index acb6ea8b7de..9e4cf67184d 100644 --- a/src/Doctrine/Common/State/LinksHandlerTrait.php +++ b/src/Doctrine/Common/State/LinksHandlerTrait.php @@ -18,18 +18,17 @@ use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation; use ApiPlatform\Metadata\GraphQl\Query; use ApiPlatform\Metadata\HttpOperation; -use ApiPlatform\Metadata\Link; use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; -use Psr\Container\ContainerInterface; trait LinksHandlerTrait { private ?ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory; - private ?ContainerInterface $handleLinksLocator; /** - * @return Link[] + * @param array{linkClass?: string, linkProperty?: string}&array $context + * + * @return \ApiPlatform\Metadata\Link[] */ private function getLinks(string $resourceClass, Operation $operation, array $context): array { @@ -90,9 +89,12 @@ private function getLinks(string $resourceClass, Operation $operation, array $co return [$newLink]; } + /** + * @param array $identifiers + */ private function getIdentifierValue(array &$identifiers, string $name = null): mixed { - if (isset($identifiers[$name])) { + if (null !== $name && isset($identifiers[$name])) { $value = $identifiers[$name]; unset($identifiers[$name]); @@ -102,6 +104,9 @@ private function getIdentifierValue(array &$identifiers, string $name = null): m return array_shift($identifiers); } + /** + * @return \ApiPlatform\Metadata\Link[]|array + */ private function getOperationLinks(Operation $operation = null): array { if ($operation instanceof GraphQlOperation) { @@ -114,22 +119,4 @@ private function getOperationLinks(Operation $operation = null): array return []; } - - private function getLinksHandler(Operation $operation): ?callable - { - if (!($options = $operation->getStateOptions()) || !method_exists($options, 'getHandleLinks') || null === $options->getHandleLinks()) { - return null; - } - - $handleLinks = $options->getHandleLinks(); // @phpstan-ignore-line method_exists called above - if (\is_callable($handleLinks)) { - return $handleLinks; - } - - if ($this->handleLinksLocator && \is_string($handleLinks) && $this->handleLinksLocator->has($handleLinks)) { - return [$this->handleLinksLocator->get($handleLinks), 'handleLinks']; - } - - throw new RuntimeException(sprintf('Could not find handleLinks service "%s"', $handleLinks)); - } } diff --git a/src/Doctrine/Odm/Metadata/Resource/DoctrineMongoDbOdmResourceCollectionMetadataFactory.php b/src/Doctrine/Odm/Metadata/Resource/DoctrineMongoDbOdmResourceCollectionMetadataFactory.php index 989fdbbdcd1..096c75e31a0 100644 --- a/src/Doctrine/Odm/Metadata/Resource/DoctrineMongoDbOdmResourceCollectionMetadataFactory.php +++ b/src/Doctrine/Odm/Metadata/Resource/DoctrineMongoDbOdmResourceCollectionMetadataFactory.php @@ -82,6 +82,12 @@ public function create(string $resourceClass): ResourceMetadataCollection private function addDefaults(Operation $operation): Operation { + $options = $operation->getStateOptions() ?: new Options(); + if ($options instanceof Options && null === $options->getHandleLinks()) { + $options = $options->withHandleLinks('api_platform.doctrine.odm.links_handler'); + $operation = $operation->withStateOptions($options); + } + if (null === $operation->getProvider()) { $operation = $operation->withProvider($this->getProvider($operation)); } diff --git a/src/Doctrine/Odm/State/CollectionProvider.php b/src/Doctrine/Odm/State/CollectionProvider.php index cf257b0ec80..84d9cc7fd27 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\LinksHandlerLocatorTrait; use ApiPlatform\Doctrine\Odm\Extension\AggregationCollectionExtensionInterface; use ApiPlatform\Doctrine\Odm\Extension\AggregationResultCollectionExtensionInterface; use ApiPlatform\Exception\RuntimeException; @@ -29,15 +30,17 @@ */ final class CollectionProvider implements ProviderInterface { + use LinksHandlerLocatorTrait; use LinksHandlerTrait; /** * @param AggregationCollectionExtensionInterface[] $collectionExtensions */ - public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, private readonly ManagerRegistry $managerRegistry, private readonly iterable $collectionExtensions = [], ContainerInterface $handleLinksLocator = null) + public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, ManagerRegistry $managerRegistry, private readonly iterable $collectionExtensions = [], ContainerInterface $handleLinksLocator = null) { $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->handleLinksLocator = $handleLinksLocator; + $this->managerRegistry = $managerRegistry; } public function provide(Operation $operation, array $uriVariables = [], array $context = []): iterable diff --git a/src/Doctrine/Odm/State/ItemProvider.php b/src/Doctrine/Odm/State/ItemProvider.php index 47fd0e588c6..04f728e0b0c 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\LinksHandlerLocatorTrait; use ApiPlatform\Doctrine\Odm\Extension\AggregationItemExtensionInterface; use ApiPlatform\Doctrine\Odm\Extension\AggregationResultItemExtensionInterface; use ApiPlatform\Exception\RuntimeException; @@ -32,15 +33,17 @@ */ final class ItemProvider implements ProviderInterface { + use LinksHandlerLocatorTrait; use LinksHandlerTrait; /** * @param AggregationItemExtensionInterface[] $itemExtensions */ - public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, private readonly ManagerRegistry $managerRegistry, private readonly iterable $itemExtensions = [], ContainerInterface $handleLinksLocator = null) + public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, ManagerRegistry $managerRegistry, private readonly iterable $itemExtensions = [], ContainerInterface $handleLinksLocator = null) { $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->handleLinksLocator = $handleLinksLocator; + $this->managerRegistry = $managerRegistry; } public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?object diff --git a/src/Doctrine/Odm/State/LinksHandlerTrait.php b/src/Doctrine/Odm/State/LinksHandlerTrait.php index 1843d0f684e..ed9c52936dc 100644 --- a/src/Doctrine/Odm/State/LinksHandlerTrait.php +++ b/src/Doctrine/Odm/State/LinksHandlerTrait.php @@ -20,6 +20,7 @@ use Doctrine\ODM\MongoDB\Aggregation\Builder; use Doctrine\ODM\MongoDB\DocumentManager; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; +use Doctrine\Persistence\ManagerRegistry; /** * @internal @@ -28,6 +29,8 @@ trait LinksHandlerTrait { use CommonLinksHandlerTrait; + private ManagerRegistry $managerRegistry; + private function handleLinks(Builder $aggregationBuilder, array $identifiers, array $context, string $resourceClass, Operation $operation = null): void { if (!$identifiers) { diff --git a/src/Doctrine/Orm/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactory.php b/src/Doctrine/Orm/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactory.php index 3da905f449d..c1021823235 100644 --- a/src/Doctrine/Orm/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactory.php +++ b/src/Doctrine/Orm/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactory.php @@ -91,6 +91,12 @@ private function addDefaults(Operation $operation): Operation $operation = $operation->withProvider($this->getProvider($operation)); } + $options = $operation->getStateOptions() ?: new Options(); + if ($options instanceof Options && null === $options->getHandleLinks()) { + $options = $options->withHandleLinks('api_platform.doctrine.orm.links_handler'); + $operation = $operation->withStateOptions($options); + } + if (null === $operation->getProcessor()) { $operation = $operation->withProcessor($this->getProcessor($operation)); } diff --git a/src/Doctrine/Orm/State/CollectionProvider.php b/src/Doctrine/Orm/State/CollectionProvider.php index 7534f7e572d..94141e5a108 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\LinksHandlerLocatorTrait; use ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface; use ApiPlatform\Doctrine\Orm\Extension\QueryResultCollectionExtensionInterface; use ApiPlatform\Doctrine\Orm\Util\QueryNameGenerator; @@ -32,15 +33,17 @@ */ final class CollectionProvider implements ProviderInterface { + use LinksHandlerLocatorTrait; use LinksHandlerTrait; /** * @param QueryCollectionExtensionInterface[] $collectionExtensions */ - public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, private readonly ManagerRegistry $managerRegistry, private readonly iterable $collectionExtensions = [], ContainerInterface $handleLinksLocator = null) + public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, ManagerRegistry $managerRegistry, private readonly iterable $collectionExtensions = [], ContainerInterface $handleLinksLocator = null) { $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->handleLinksLocator = $handleLinksLocator; + $this->managerRegistry = $managerRegistry; } public function provide(Operation $operation, array $uriVariables = [], array $context = []): iterable diff --git a/src/Doctrine/Orm/State/ItemProvider.php b/src/Doctrine/Orm/State/ItemProvider.php index eccc400564a..c7235b58e3b 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\LinksHandlerLocatorTrait; use ApiPlatform\Doctrine\Orm\Extension\QueryItemExtensionInterface; use ApiPlatform\Doctrine\Orm\Extension\QueryResultItemExtensionInterface; use ApiPlatform\Doctrine\Orm\Util\QueryNameGenerator; @@ -32,15 +33,17 @@ */ final class ItemProvider implements ProviderInterface { + use LinksHandlerLocatorTrait; use LinksHandlerTrait; /** * @param QueryItemExtensionInterface[] $itemExtensions */ - public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, private readonly ManagerRegistry $managerRegistry, private readonly iterable $itemExtensions = [], ContainerInterface $handleLinksLocator = null) + public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, ManagerRegistry $managerRegistry, private readonly iterable $itemExtensions = [], ContainerInterface $handleLinksLocator = null) { $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->handleLinksLocator = $handleLinksLocator; + $this->managerRegistry = $managerRegistry; } public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?object diff --git a/src/Doctrine/Orm/State/LinksHandler.php b/src/Doctrine/Orm/State/LinksHandler.php new file mode 100644 index 00000000000..afc878a397e --- /dev/null +++ b/src/Doctrine/Orm/State/LinksHandler.php @@ -0,0 +1,37 @@ + + * + * 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\Orm\State; + +use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; +use Doctrine\ORM\QueryBuilder; +use Doctrine\Persistence\ManagerRegistry; + +final class LinksHandler implements LinksHandlerInterface +{ + use LinksHandlerTrait { + handleLinks as private handle; + } + + public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, ManagerRegistry $managerRegistry) + { + $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; + $this->managerRegistry = $managerRegistry; + } + + public function handleLinks(QueryBuilder $queryBuilder, array $uriVariables, QueryNameGeneratorInterface $queryNameGenerator, array $context): void + { + $this->handle($queryBuilder, $uriVariables, $queryNameGenerator, $context, $context['entityClass'], $context['operation']); + } +} diff --git a/src/Doctrine/Orm/State/LinksHandlerTrait.php b/src/Doctrine/Orm/State/LinksHandlerTrait.php index 585195da4f7..bde0c3a1afc 100644 --- a/src/Doctrine/Orm/State/LinksHandlerTrait.php +++ b/src/Doctrine/Orm/State/LinksHandlerTrait.php @@ -14,11 +14,12 @@ namespace ApiPlatform\Doctrine\Orm\State; use ApiPlatform\Doctrine\Common\State\LinksHandlerTrait as CommonLinksHandlerTrait; -use ApiPlatform\Doctrine\Orm\Util\QueryNameGenerator; +use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; use ApiPlatform\Metadata\Link; use ApiPlatform\Metadata\Operation; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\QueryBuilder; +use Doctrine\Persistence\ManagerRegistry; /** * @internal @@ -27,7 +28,9 @@ trait LinksHandlerTrait { use CommonLinksHandlerTrait; - private function handleLinks(QueryBuilder $queryBuilder, array $identifiers, QueryNameGenerator $queryNameGenerator, array $context, string $entityClass, Operation $operation): void + private ManagerRegistry $managerRegistry; + + private function handleLinks(QueryBuilder $queryBuilder, array $identifiers, QueryNameGeneratorInterface $queryNameGenerator, array $context, string $entityClass, Operation $operation): void { if (!$identifiers) { return; diff --git a/src/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml b/src/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml index 7e49e0125a9..f9e7ce7156f 100644 --- a/src/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml +++ b/src/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml @@ -151,6 +151,13 @@ + + + + + + + diff --git a/src/Symfony/Bundle/Resources/config/doctrine_orm.xml b/src/Symfony/Bundle/Resources/config/doctrine_orm.xml index 47f5f7a5ebc..ad0d77a5121 100644 --- a/src/Symfony/Bundle/Resources/config/doctrine_orm.xml +++ b/src/Symfony/Bundle/Resources/config/doctrine_orm.xml @@ -171,6 +171,12 @@ + + + + + +