Skip to content

Commit

Permalink
fix(laravel): graphql fix relationship loading
Browse files Browse the repository at this point in the history
fix loading relationships by only loading those related to the model

Closes: #6791
Signed-off-by: Tobias Oitzinger <[email protected]>
  • Loading branch information
toitzi committed Nov 14, 2024
1 parent 8109906 commit be467c9
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 16 deletions.
8 changes: 4 additions & 4 deletions src/Laravel/ApiPlatformProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -818,7 +818,7 @@ public function register(): void
$config->get('api-platform.formats'),
$app->make(Options::class),
$app->make(PaginationOptions::class), // ?PaginationOptions $paginationOptions = null,
// ?RouterInterface $router = null
// ?RouterInterface $router = null
);
});

Expand Down Expand Up @@ -954,7 +954,7 @@ public function register(): void
$app->make(ResourceMetadataCollectionFactoryInterface::class),
$app->make(ResourceAccessCheckerInterface::class),
null
// $app->make(TagCollectorInterface::class),
// $app->make(TagCollectorInterface::class),
);
});

Expand Down
73 changes: 62 additions & 11 deletions src/Laravel/Eloquent/State/LinksHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -25,6 +30,7 @@ final class LinksHandler implements LinksHandlerInterface
{
public function __construct(
private readonly Application $application,
private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory,
) {
}

Expand All @@ -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<Model> $builder
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*
* @return Builder<Model> $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);
}
}
3 changes: 2 additions & 1 deletion src/Metadata/Resource/ResourceMetadataCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\CollectionOperationInterface;
use ApiPlatform\Metadata\Exception\OperationNotFoundException;
use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation;
use ApiPlatform\Metadata\Operation;

/**
Expand All @@ -35,7 +36,7 @@ public function __construct(private readonly string $resourceClass, array $input
parent::__construct($input);
}

public function getOperation(?string $operationName = null, bool $forceCollection = false, bool $httpOperation = false, bool $forceGraphQl = false): Operation
public function getOperation(?string $operationName = null, bool $forceCollection = false, bool $httpOperation = false, bool $forceGraphQl = false): Operation|GraphQlOperation
{
$operationName ??= '';
$cachePrefix = ($forceCollection ? self::FORCE_COLLECTION : '').($httpOperation ? self::HTTP_OPERATION : '');
Expand Down

0 comments on commit be467c9

Please sign in to comment.