Skip to content

Commit

Permalink
fix(graphql): remove count query if paginationInfo is not requested
Browse files Browse the repository at this point in the history
  • Loading branch information
Xavier Leune committed Jan 3, 2024
1 parent fe07a7f commit 03791fe
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 53 deletions.
5 changes: 5 additions & 0 deletions src/GraphQl/Resolver/Factory/CollectionResolverFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ public function __construct(private readonly ReadStageInterface $readStage, priv
public function __invoke(string $resourceClass = null, string $rootClass = null, Operation $operation = null): callable
{
return function (?array $source, array $args, $context, ResolveInfo $info) use ($resourceClass, $rootClass, $operation): ?array {
if (!isset($info->getFieldSelection()['paginationInfo']) && $operation->getPaginationClientPartial()) {
$operation = $operation->withPaginationPartial(true);

Check warning on line 45 in src/GraphQl/Resolver/Factory/CollectionResolverFactory.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Resolver/Factory/CollectionResolverFactory.php#L44-L45

Added lines #L44 - L45 were not covered by tests
}

// If authorization has failed for a relation field (e.g. via ApiProperty security), the field is not present in the source: null can be returned directly to ensure the collection isn't in the response.
if (null === $resourceClass || null === $rootClass || (null !== $source && !\array_key_exists($info->fieldName, $source))) {
return null;
Expand All @@ -49,6 +53,7 @@ public function __invoke(string $resourceClass = null, string $rootClass = null,
$resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => true, 'is_mutation' => false, 'is_subscription' => false];

$collection = ($this->readStage)($resourceClass, $rootClass, $operation, $resolverContext);

if (!is_iterable($collection)) {
throw new \LogicException('Collection from read stage should be iterable.');
}
Expand Down
20 changes: 13 additions & 7 deletions src/GraphQl/Resolver/Stage/SerializeStage.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ public function __invoke(object|array|null $itemOrCollection, string $resourceCl
$data = $this->normalizer->normalize($itemOrCollection, ItemNormalizer::FORMAT, $normalizationContext);
}
}

if ($isCollection && is_iterable($itemOrCollection)) {
if (!$this->pagination->isGraphQlEnabled($operation, $context)) {
$data = [];
Expand Down Expand Up @@ -175,14 +174,21 @@ private function serializeCursorBasedPaginatedCollection(iterable $collection, a
*/
private function serializePageBasedPaginatedCollection(iterable $collection, array $normalizationContext): array
{
if (!($collection instanceof PaginatorInterface)) {
throw new \LogicException(sprintf('Collection returned by the collection data provider must implement %s.', PaginatorInterface::class));
if (!($collection instanceof PartialPaginatorInterface)) {
throw new \LogicException(sprintf('Collection returned by the collection data provider must implement %s or %s.', PaginatorInterface::class, PartialPaginatorInterface::class));

Check warning on line 178 in src/GraphQl/Resolver/Stage/SerializeStage.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Resolver/Stage/SerializeStage.php#L177-L178

Added lines #L177 - L178 were not covered by tests
}

$data = $this->getDefaultPageBasedPaginatedData();
$data['paginationInfo']['totalCount'] = $collection->getTotalItems();
$data['paginationInfo']['lastPage'] = $collection->getLastPage();
$data['paginationInfo']['itemsPerPage'] = $collection->getItemsPerPage();
$data = [
'collection' => [],
'paginationInfo' => [
'itemsPerPage' => $collection->getItemsPerPage(),
]
];

Check warning on line 186 in src/GraphQl/Resolver/Stage/SerializeStage.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Resolver/Stage/SerializeStage.php#L181-L186

Added lines #L181 - L186 were not covered by tests

if ($collection instanceof PaginatorInterface) {
$data['paginationInfo']['totalCount'] = $collection->getTotalItems();
$data['paginationInfo']['lastPage'] = $collection->getLastPage();

Check warning on line 190 in src/GraphQl/Resolver/Stage/SerializeStage.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Resolver/Stage/SerializeStage.php#L188-L190

Added lines #L188 - L190 were not covered by tests
}

foreach ($collection as $object) {
$data['collection'][] = $this->normalizer->normalize($object, ItemNormalizer::FORMAT, $normalizationContext);
Expand Down
19 changes: 13 additions & 6 deletions src/GraphQl/State/Processor/NormalizeProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -187,14 +187,21 @@ private function serializeCursorBasedPaginatedCollection(iterable $collection, a
*/
private function serializePageBasedPaginatedCollection(iterable $collection, array $normalizationContext): array
{
if (!($collection instanceof PaginatorInterface)) {
throw new \LogicException(sprintf('Collection returned by the collection data provider must implement %s.', PaginatorInterface::class));
if (!($collection instanceof PartialPaginatorInterface)) {
throw new \LogicException(sprintf('Collection returned by the collection data provider must implement %s or %s.', PaginatorInterface::class, PartialPaginatorInterface::class));

Check warning on line 191 in src/GraphQl/State/Processor/NormalizeProcessor.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/State/Processor/NormalizeProcessor.php#L190-L191

Added lines #L190 - L191 were not covered by tests
}

$data = $this->getDefaultPageBasedPaginatedData();
$data['paginationInfo']['totalCount'] = $collection->getTotalItems();
$data['paginationInfo']['lastPage'] = $collection->getLastPage();
$data['paginationInfo']['itemsPerPage'] = $collection->getItemsPerPage();
$data = [
'collection' => [],
'paginationInfo' => [
'itemsPerPage' => $collection->getItemsPerPage(),
]
];

Check warning on line 199 in src/GraphQl/State/Processor/NormalizeProcessor.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/State/Processor/NormalizeProcessor.php#L194-L199

Added lines #L194 - L199 were not covered by tests

if ($collection instanceof PaginatorInterface) {
$data['paginationInfo']['totalCount'] = $collection->getTotalItems();
$data['paginationInfo']['lastPage'] = $collection->getLastPage();

Check warning on line 203 in src/GraphQl/State/Processor/NormalizeProcessor.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/State/Processor/NormalizeProcessor.php#L201-L203

Added lines #L201 - L203 were not covered by tests
}

foreach ($collection as $object) {
$data['collection'][] = $this->normalizer->normalize($object, ItemNormalizer::FORMAT, $normalizationContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use ApiPlatform\Metadata\GraphQl\QueryCollection;
use GraphQL\Type\Definition\ResolveInfo;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy;
use Psr\Container\ContainerInterface;
Expand Down Expand Up @@ -68,7 +69,9 @@ public function testResolve(): void
$operation = (new QueryCollection())->withName($operationName);
$source = ['testField' => 0];
$args = ['args'];
$info = $this->prophesize(ResolveInfo::class)->reveal();
$infoProphecy = $this->prophesize(ResolveInfo::class);
$infoProphecy->getFieldSelection()->willReturn(['testField' => true]);
$info = $infoProphecy->reveal();

Check warning on line 74 in src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php#L72-L74

Added lines #L72 - L74 were not covered by tests
$info->fieldName = 'testField';
$resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => true, 'is_mutation' => false, 'is_subscription' => false];

Expand Down Expand Up @@ -101,7 +104,9 @@ public function testResolveFieldNotInSource(): void
$operation = (new QueryCollection())->withName($operationName);
$source = ['source'];
$args = ['args'];
$info = $this->prophesize(ResolveInfo::class)->reveal();
$infoProphecy = $this->prophesize(ResolveInfo::class);
$infoProphecy->getFieldSelection()->willReturn(['testField' => true]);
$info = $infoProphecy->reveal();

Check warning on line 109 in src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php#L107-L109

Added lines #L107 - L109 were not covered by tests
$info->fieldName = 'testField';
$resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => true, 'is_mutation' => false, 'is_subscription' => false];

Expand Down Expand Up @@ -132,7 +137,9 @@ public function testResolveNullSource(): void
$operation = (new QueryCollection())->withName($operationName);
$source = null;
$args = ['args'];
$info = $this->prophesize(ResolveInfo::class)->reveal();
$infoProphecy = $this->prophesize(ResolveInfo::class);
$infoProphecy->getFieldSelection()->willReturn([]);
$info = $infoProphecy->reveal();

Check warning on line 142 in src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php#L140-L142

Added lines #L140 - L142 were not covered by tests
$resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => true, 'is_mutation' => false, 'is_subscription' => false];

$readStageCollection = [new \stdClass()];
Expand Down Expand Up @@ -164,7 +171,9 @@ public function testResolveNullResourceClass(): void
$operation = (new QueryCollection())->withName($operationName);
$source = ['source'];
$args = ['args'];
$info = $this->prophesize(ResolveInfo::class)->reveal();
$infoProphecy = $this->prophesize(ResolveInfo::class);
$infoProphecy->getFieldSelection()->willReturn([]);
$info = $infoProphecy->reveal();

Check warning on line 176 in src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php#L174-L176

Added lines #L174 - L176 were not covered by tests

$this->assertNull(($this->collectionResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info));
}
Expand All @@ -177,7 +186,9 @@ public function testResolveNullRootClass(): void
$operation = (new QueryCollection())->withName($operationName);
$source = ['source'];
$args = ['args'];
$info = $this->prophesize(ResolveInfo::class)->reveal();
$infoProphecy = $this->prophesize(ResolveInfo::class);
$infoProphecy->getFieldSelection()->willReturn([]);
$info = $infoProphecy->reveal();

Check warning on line 191 in src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php#L189-L191

Added lines #L189 - L191 were not covered by tests

$this->assertNull(($this->collectionResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info));
}
Expand All @@ -190,7 +201,9 @@ public function testResolveBadReadStageCollection(): void
$operation = (new QueryCollection())->withName($operationName);
$source = null;
$args = ['args'];
$info = $this->prophesize(ResolveInfo::class)->reveal();
$infoProphecy = $this->prophesize(ResolveInfo::class);
$infoProphecy->getFieldSelection()->willReturn([]);
$info = $infoProphecy->reveal();

Check warning on line 206 in src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php#L204-L206

Added lines #L204 - L206 were not covered by tests
$resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => true, 'is_mutation' => false, 'is_subscription' => false];

$readStageCollection = new \stdClass();
Expand All @@ -210,31 +223,73 @@ public function testResolveCustom(): void
$operation = (new QueryCollection())->withResolver('query_resolver_id')->withName($operationName);
$source = null;
$args = ['args'];
$info = $this->prophesize(ResolveInfo::class)->reveal();
$infoProphecy = $this->prophesize(ResolveInfo::class);
$infoProphecy->getFieldSelection()->willReturn([]);
$info = $infoProphecy->reveal();

Check warning on line 228 in src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php#L226-L228

Added lines #L226 - L228 were not covered by tests
$resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => true, 'is_mutation' => false, 'is_subscription' => false];

$readStageCollection = [new \stdClass()];
$this->readStageProphecy->__invoke($resourceClass, $rootClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($readStageCollection);

$customCollection = [new \stdClass()];
$customCollection[0]->field = 'foo';
$this->queryResolverLocatorProphecy->get('query_resolver_id')->shouldBeCalled()->willReturn(fn (): array => $customCollection);
$this->queryResolverLocatorProphecy->get('query_resolver_id')->shouldBeCalled()->willReturn(fn(): array => $customCollection);

Check warning on line 236 in src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php#L236

Added line #L236 was not covered by tests

$this->securityStageProphecy->__invoke($resourceClass, $operation, $resolverContext + [
'extra_variables' => [
'object' => $customCollection,
],
])->shouldBeCalled();
'extra_variables' => [
'object' => $customCollection,
],
])->shouldBeCalled();

Check warning on line 242 in src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php#L239-L242

Added lines #L239 - L242 were not covered by tests
$this->securityPostDenormalizeStageProphecy->__invoke($resourceClass, $operation, $resolverContext + [
'extra_variables' => [
'object' => $customCollection,
'previous_object' => $customCollection,
],
])->shouldBeCalled();
'extra_variables' => [
'object' => $customCollection,
'previous_object' => $customCollection,
],
])->shouldBeCalled();

Check warning on line 248 in src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php#L244-L248

Added lines #L244 - L248 were not covered by tests

$serializeStageData = ['serialized'];
$this->serializeStageProphecy->__invoke($customCollection, $resourceClass, $operation, $resolverContext)->shouldBeCalled()->willReturn($serializeStageData);

$this->assertSame($serializeStageData, ($this->collectionResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info));
}

/**
* @dataProvider resolveAndPaginationInfoProvider
*/
public function testResolveAndPaginationInfo(bool $paginationClientPartialEnabled, bool $paginationInfoRequested, ?bool $shouldPaginationBePartial)

Check warning on line 259 in src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php#L259

Added line #L259 was not covered by tests
{
$resourceClass = \stdClass::class;
$rootClass = 'rootClass';
$operationName = 'collection_query';
$operation = (new QueryCollection())->withName($operationName)->withPaginationClientPartial($paginationClientPartialEnabled);
$source = ['testField' => 0];
$args = ['args'];
$infoProphecy = $this->prophesize(ResolveInfo::class);
$resolveInfoFields = ['testField' => true];
if ($paginationInfoRequested) {
$resolveInfoFields['paginationInfo'] = true;

Check warning on line 270 in src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php#L261-L270

Added lines #L261 - L270 were not covered by tests
}
$infoProphecy->getFieldSelection()->willReturn($resolveInfoFields);
$info = $infoProphecy->reveal();
$info->fieldName = 'testField';
$resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => true, 'is_mutation' => false, 'is_subscription' => false];

Check warning on line 275 in src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php#L272-L275

Added lines #L272 - L275 were not covered by tests

$readStageCollection = [new \stdClass()];
$this->readStageProphecy->__invoke($resourceClass, $rootClass, Argument::type($operation::class), $resolverContext)->shouldBeCalled()->willReturn($readStageCollection);

Check warning on line 278 in src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php#L277-L278

Added lines #L277 - L278 were not covered by tests

$serializeStageData = ['serialized'];
$this->serializeStageProphecy->__invoke($readStageCollection, $resourceClass, Argument::that(function ($operation) use ($shouldPaginationBePartial) {
return $operation->getPaginationPartial() === $shouldPaginationBePartial;
}), $resolverContext)->shouldBeCalled()->willReturn($serializeStageData);

Check warning on line 283 in src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php#L280-L283

Added lines #L280 - L283 were not covered by tests

$this->assertSame($serializeStageData, ($this->collectionResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info));

Check warning on line 285 in src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php#L285

Added line #L285 was not covered by tests
}

public static function resolveAndPaginationInfoProvider(): iterable

Check warning on line 288 in src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php#L288

Added line #L288 was not covered by tests
{
yield 'paginationClientPartial disabled - paginationInfo requested' => [false, true, null];
yield 'paginationClientPartial enabled - paginationInfo requested' => [true, true, null];
yield 'paginationClientPartial disabled - paginationInfo not requested' => [false, false, null];
yield 'paginationClientPartial enabled - paginationInfo not requested' => [true, false, true];

Check warning on line 293 in src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php

View check run for this annotation

Codecov / codecov/patch

src/GraphQl/Tests/Resolver/Factory/CollectionResolverFactoryTest.php#L290-L293

Added lines #L290 - L293 were not covered by tests
}
}
Loading

0 comments on commit 03791fe

Please sign in to comment.