From dc8c09b1e1ac15a7bcd4961fc3e80b06bec82e77 Mon Sep 17 00:00:00 2001 From: Tobias Oitzinger <42447585+toitzi@users.noreply.github.com> Date: Fri, 15 Nov 2024 10:53:51 +0100 Subject: [PATCH] fix(laravel) graphQl Relationship loading (#6792) Co-authored-by: Antoine Bluchet Closes: #6791 fixes api-platform/api-platform#2795, #6791 --- src/Laravel/ApiPlatformProvider.php | 4 +- src/Laravel/Eloquent/State/LinksHandler.php | 73 +++++++++++++++++---- 2 files changed, 64 insertions(+), 13 deletions(-) diff --git a/src/Laravel/ApiPlatformProvider.php b/src/Laravel/ApiPlatformProvider.php index e44f2f3ebf9..04534fee036 100644 --- a/src/Laravel/ApiPlatformProvider.php +++ b/src/Laravel/ApiPlatformProvider.php @@ -432,12 +432,12 @@ public function register(): void $this->app->singleton(ItemProvider::class, function (Application $app) { $tagged = iterator_to_array($app->tagged(LinksHandlerInterface::class)); - return new ItemProvider(new LinksHandler($app), new ServiceLocator($tagged)); + return new ItemProvider(new LinksHandler($app, $app->make(ResourceMetadataCollectionFactoryInterface::class)), new ServiceLocator($tagged)); }); $this->app->singleton(CollectionProvider::class, function (Application $app) { $tagged = iterator_to_array($app->tagged(LinksHandlerInterface::class)); - return new CollectionProvider($app->make(Pagination::class), new LinksHandler($app), $app->tagged(QueryExtensionInterface::class), new ServiceLocator($tagged)); + return new CollectionProvider($app->make(Pagination::class), new LinksHandler($app, $app->make(ResourceMetadataCollectionFactoryInterface::class)), $app->tagged(QueryExtensionInterface::class), new ServiceLocator($tagged)); }); $this->app->tag([ItemProvider::class, CollectionProvider::class], ProviderInterface::class); diff --git a/src/Laravel/Eloquent/State/LinksHandler.php b/src/Laravel/Eloquent/State/LinksHandler.php index 32256ab717c..559a506a5e7 100644 --- a/src/Laravel/Eloquent/State/LinksHandler.php +++ b/src/Laravel/Eloquent/State/LinksHandler.php @@ -13,7 +13,12 @@ namespace ApiPlatform\Laravel\Eloquent\State; +use ApiPlatform\Metadata\Exception\OperationNotFoundException; +use ApiPlatform\Metadata\GraphQl\Operation; +use ApiPlatform\Metadata\GraphQl\Query; use ApiPlatform\Metadata\HttpOperation; +use ApiPlatform\Metadata\Link; +use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use Illuminate\Contracts\Foundation\Application; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; @@ -25,6 +30,7 @@ final class LinksHandler implements LinksHandlerInterface { public function __construct( private readonly Application $application, + private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, ) { } @@ -34,27 +40,72 @@ public function handleLinks(Builder $builder, array $uriVariables, array $contex if ($operation instanceof HttpOperation) { foreach (array_reverse($operation->getUriVariables() ?? []) as $uriVariable => $link) { - $identifier = $uriVariables[$uriVariable]; + $builder = $this->buildQuery($builder, $link, $uriVariables[$uriVariable]); + } - if ($to = $link->getToProperty()) { - $builder = $builder->where($builder->getModel()->{$to}()->getQualifiedForeignKeyName(), $identifier); + return $builder; + } - continue; - } + if (!($linkClass = $context['linkClass'] ?? false)) { + return $builder; + } - if ($from = $link->getFromProperty()) { - $relation = $this->application->make($link->getFromClass()); - $builder = $builder->getModel()->where($relation->{$from}()->getQualifiedForeignKeyName(), $identifier); + $newLink = null; + $linkedOperation = null; + $linkProperty = $context['linkProperty'] ?? null; - continue; + try { + $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($linkClass); + $linkedOperation = $resourceMetadataCollection->getOperation($operation->getName()); + } catch (OperationNotFoundException) { + // Instead, we'll look for the first Query available. + foreach ($resourceMetadataCollection as $resourceMetadata) { + foreach ($resourceMetadata->getGraphQlOperations() as $op) { + if ($op instanceof Query) { + $linkedOperation = $op; + } } + } + } + + if (!$linkedOperation instanceof Operation) { + return $builder; + } - $builder->where($builder->getModel()->qualifyColumn($link->getIdentifiers()[0]), $identifier); + $resourceClass = $builder->getModel()::class; + foreach ($linkedOperation->getLinks() ?? [] as $link) { + if ($resourceClass === $link->getToClass() && $linkProperty === $link->getFromProperty()) { + $newLink = $link; + break; } + } + if (!$newLink) { return $builder; } - return $builder; + return $this->buildQuery($builder, $newLink, $uriVariables[$newLink->getIdentifiers()[0]]); + } + + /** + * @param Builder $builder + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + * + * @return Builder $builder + */ + private function buildQuery(Builder $builder, Link $link, mixed $identifier): Builder + { + if ($to = $link->getToProperty()) { + return $builder->where($builder->getModel()->{$to}()->getQualifiedForeignKeyName(), $identifier); + } + + if ($from = $link->getFromProperty()) { + $relation = $this->application->make($link->getFromClass()); + + return $builder->getModel()->where($relation->{$from}()->getQualifiedForeignKeyName(), $identifier); + } + + return $builder->where($builder->getModel()->qualifyColumn($link->getIdentifiers()[0]), $identifier); } }