Skip to content

Commit f0f873a

Browse files
author
Xavier Leune
committed
fix(graphql): remove count query if paginationInfo is not requested
1 parent fe07a7f commit f0f873a

File tree

6 files changed

+163
-43
lines changed

6 files changed

+163
-43
lines changed

src/GraphQl/Resolver/Factory/CollectionResolverFactory.php

+5
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ public function __construct(private readonly ReadStageInterface $readStage, priv
4141
public function __invoke(string $resourceClass = null, string $rootClass = null, Operation $operation = null): callable
4242
{
4343
return function (?array $source, array $args, $context, ResolveInfo $info) use ($resourceClass, $rootClass, $operation): ?array {
44+
if (!isset($info->getFieldSelection()['paginationInfo']) && $operation->getPaginationClientPartial()) {
45+
$operation = $operation->withPaginationPartial(true);
46+
}
47+
4448
// 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.
4549
if (null === $resourceClass || null === $rootClass || (null !== $source && !\array_key_exists($info->fieldName, $source))) {
4650
return null;
@@ -49,6 +53,7 @@ public function __invoke(string $resourceClass = null, string $rootClass = null,
4953
$resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => true, 'is_mutation' => false, 'is_subscription' => false];
5054

5155
$collection = ($this->readStage)($resourceClass, $rootClass, $operation, $resolverContext);
56+
5257
if (!is_iterable($collection)) {
5358
throw new \LogicException('Collection from read stage should be iterable.');
5459
}

src/GraphQl/Resolver/Stage/SerializeStage.php

+13-7
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ public function __invoke(object|array|null $itemOrCollection, string $resourceCl
7878
$data = $this->normalizer->normalize($itemOrCollection, ItemNormalizer::FORMAT, $normalizationContext);
7979
}
8080
}
81-
8281
if ($isCollection && is_iterable($itemOrCollection)) {
8382
if (!$this->pagination->isGraphQlEnabled($operation, $context)) {
8483
$data = [];
@@ -175,14 +174,21 @@ private function serializeCursorBasedPaginatedCollection(iterable $collection, a
175174
*/
176175
private function serializePageBasedPaginatedCollection(iterable $collection, array $normalizationContext): array
177176
{
178-
if (!($collection instanceof PaginatorInterface)) {
179-
throw new \LogicException(sprintf('Collection returned by the collection data provider must implement %s.', PaginatorInterface::class));
177+
if (!($collection instanceof PartialPaginatorInterface)) {
178+
throw new \LogicException(sprintf('Collection returned by the collection data provider must implement %s or %s.', PaginatorInterface::class, PartialPaginatorInterface::class));
180179
}
181180

182-
$data = $this->getDefaultPageBasedPaginatedData();
183-
$data['paginationInfo']['totalCount'] = $collection->getTotalItems();
184-
$data['paginationInfo']['lastPage'] = $collection->getLastPage();
185-
$data['paginationInfo']['itemsPerPage'] = $collection->getItemsPerPage();
181+
$data = [
182+
'collection' => [],
183+
'paginationInfo' => [
184+
'itemsPerPage' => $collection->getItemsPerPage(),
185+
],
186+
];
187+
188+
if ($collection instanceof PaginatorInterface) {
189+
$data['paginationInfo']['totalCount'] = $collection->getTotalItems();
190+
$data['paginationInfo']['lastPage'] = $collection->getLastPage();
191+
}
186192

187193
foreach ($collection as $object) {
188194
$data['collection'][] = $this->normalizer->normalize($object, ItemNormalizer::FORMAT, $normalizationContext);

src/GraphQl/State/Processor/NormalizeProcessor.php

+13-6
Original file line numberDiff line numberDiff line change
@@ -187,14 +187,21 @@ private function serializeCursorBasedPaginatedCollection(iterable $collection, a
187187
*/
188188
private function serializePageBasedPaginatedCollection(iterable $collection, array $normalizationContext): array
189189
{
190-
if (!($collection instanceof PaginatorInterface)) {
191-
throw new \LogicException(sprintf('Collection returned by the collection data provider must implement %s.', PaginatorInterface::class));
190+
if (!($collection instanceof PartialPaginatorInterface)) {
191+
throw new \LogicException(sprintf('Collection returned by the collection data provider must implement %s or %s.', PaginatorInterface::class, PartialPaginatorInterface::class));
192192
}
193193

194-
$data = $this->getDefaultPageBasedPaginatedData();
195-
$data['paginationInfo']['totalCount'] = $collection->getTotalItems();
196-
$data['paginationInfo']['lastPage'] = $collection->getLastPage();
197-
$data['paginationInfo']['itemsPerPage'] = $collection->getItemsPerPage();
194+
$data = [
195+
'collection' => [],
196+
'paginationInfo' => [
197+
'itemsPerPage' => $collection->getItemsPerPage(),
198+
],
199+
];
200+
201+
if ($collection instanceof PaginatorInterface) {
202+
$data['paginationInfo']['totalCount'] = $collection->getTotalItems();
203+
$data['paginationInfo']['lastPage'] = $collection->getLastPage();
204+
}
198205

199206
foreach ($collection as $object) {
200207
$data['collection'][] = $this->normalizer->normalize($object, ItemNormalizer::FORMAT, $normalizationContext);

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

+62-7
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use ApiPlatform\Metadata\GraphQl\QueryCollection;
2222
use GraphQL\Type\Definition\ResolveInfo;
2323
use PHPUnit\Framework\TestCase;
24+
use Prophecy\Argument;
2425
use Prophecy\PhpUnit\ProphecyTrait;
2526
use Prophecy\Prophecy\ObjectProphecy;
2627
use Psr\Container\ContainerInterface;
@@ -68,7 +69,9 @@ public function testResolve(): void
6869
$operation = (new QueryCollection())->withName($operationName);
6970
$source = ['testField' => 0];
7071
$args = ['args'];
71-
$info = $this->prophesize(ResolveInfo::class)->reveal();
72+
$infoProphecy = $this->prophesize(ResolveInfo::class);
73+
$infoProphecy->getFieldSelection()->willReturn(['testField' => true]);
74+
$info = $infoProphecy->reveal();
7275
$info->fieldName = 'testField';
7376
$resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => true, 'is_mutation' => false, 'is_subscription' => false];
7477

@@ -101,7 +104,9 @@ public function testResolveFieldNotInSource(): void
101104
$operation = (new QueryCollection())->withName($operationName);
102105
$source = ['source'];
103106
$args = ['args'];
104-
$info = $this->prophesize(ResolveInfo::class)->reveal();
107+
$infoProphecy = $this->prophesize(ResolveInfo::class);
108+
$infoProphecy->getFieldSelection()->willReturn(['testField' => true]);
109+
$info = $infoProphecy->reveal();
105110
$info->fieldName = 'testField';
106111
$resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => true, 'is_mutation' => false, 'is_subscription' => false];
107112

@@ -132,7 +137,9 @@ public function testResolveNullSource(): void
132137
$operation = (new QueryCollection())->withName($operationName);
133138
$source = null;
134139
$args = ['args'];
135-
$info = $this->prophesize(ResolveInfo::class)->reveal();
140+
$infoProphecy = $this->prophesize(ResolveInfo::class);
141+
$infoProphecy->getFieldSelection()->willReturn([]);
142+
$info = $infoProphecy->reveal();
136143
$resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => true, 'is_mutation' => false, 'is_subscription' => false];
137144

138145
$readStageCollection = [new \stdClass()];
@@ -164,7 +171,9 @@ public function testResolveNullResourceClass(): void
164171
$operation = (new QueryCollection())->withName($operationName);
165172
$source = ['source'];
166173
$args = ['args'];
167-
$info = $this->prophesize(ResolveInfo::class)->reveal();
174+
$infoProphecy = $this->prophesize(ResolveInfo::class);
175+
$infoProphecy->getFieldSelection()->willReturn([]);
176+
$info = $infoProphecy->reveal();
168177

169178
$this->assertNull(($this->collectionResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info));
170179
}
@@ -177,7 +186,9 @@ public function testResolveNullRootClass(): void
177186
$operation = (new QueryCollection())->withName($operationName);
178187
$source = ['source'];
179188
$args = ['args'];
180-
$info = $this->prophesize(ResolveInfo::class)->reveal();
189+
$infoProphecy = $this->prophesize(ResolveInfo::class);
190+
$infoProphecy->getFieldSelection()->willReturn([]);
191+
$info = $infoProphecy->reveal();
181192

182193
$this->assertNull(($this->collectionResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info));
183194
}
@@ -190,7 +201,9 @@ public function testResolveBadReadStageCollection(): void
190201
$operation = (new QueryCollection())->withName($operationName);
191202
$source = null;
192203
$args = ['args'];
193-
$info = $this->prophesize(ResolveInfo::class)->reveal();
204+
$infoProphecy = $this->prophesize(ResolveInfo::class);
205+
$infoProphecy->getFieldSelection()->willReturn([]);
206+
$info = $infoProphecy->reveal();
194207
$resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => true, 'is_mutation' => false, 'is_subscription' => false];
195208

196209
$readStageCollection = new \stdClass();
@@ -210,7 +223,9 @@ public function testResolveCustom(): void
210223
$operation = (new QueryCollection())->withResolver('query_resolver_id')->withName($operationName);
211224
$source = null;
212225
$args = ['args'];
213-
$info = $this->prophesize(ResolveInfo::class)->reveal();
226+
$infoProphecy = $this->prophesize(ResolveInfo::class);
227+
$infoProphecy->getFieldSelection()->willReturn([]);
228+
$info = $infoProphecy->reveal();
214229
$resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => true, 'is_mutation' => false, 'is_subscription' => false];
215230

216231
$readStageCollection = [new \stdClass()];
@@ -237,4 +252,44 @@ public function testResolveCustom(): void
237252

238253
$this->assertSame($serializeStageData, ($this->collectionResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info));
239254
}
255+
256+
/**
257+
* @dataProvider resolveAndPaginationInfoProvider
258+
*/
259+
public function testResolveAndPaginationInfo(bool $paginationClientPartialEnabled, bool $paginationInfoRequested, ?bool $shouldPaginationBePartial): void
260+
{
261+
$resourceClass = \stdClass::class;
262+
$rootClass = 'rootClass';
263+
$operationName = 'collection_query';
264+
$operation = (new QueryCollection())->withName($operationName)->withPaginationClientPartial($paginationClientPartialEnabled);
265+
$source = ['testField' => 0];
266+
$args = ['args'];
267+
$infoProphecy = $this->prophesize(ResolveInfo::class);
268+
$resolveInfoFields = ['testField' => true];
269+
if ($paginationInfoRequested) {
270+
$resolveInfoFields['paginationInfo'] = true;
271+
}
272+
$infoProphecy->getFieldSelection()->willReturn($resolveInfoFields);
273+
$info = $infoProphecy->reveal();
274+
$info->fieldName = 'testField';
275+
$resolverContext = ['source' => $source, 'args' => $args, 'info' => $info, 'is_collection' => true, 'is_mutation' => false, 'is_subscription' => false];
276+
277+
$readStageCollection = [new \stdClass()];
278+
$this->readStageProphecy->__invoke($resourceClass, $rootClass, Argument::type($operation::class), $resolverContext)->shouldBeCalled()->willReturn($readStageCollection);
279+
280+
$serializeStageData = ['serialized'];
281+
$this->serializeStageProphecy->__invoke($readStageCollection, $resourceClass, Argument::that(function ($operation) use ($shouldPaginationBePartial) {
282+
return $operation->getPaginationPartial() === $shouldPaginationBePartial;
283+
}), $resolverContext)->shouldBeCalled()->willReturn($serializeStageData);
284+
285+
$this->assertSame($serializeStageData, ($this->collectionResolverFactory)($resourceClass, $rootClass, $operation)($source, $args, null, $info));
286+
}
287+
288+
public static function resolveAndPaginationInfoProvider(): iterable
289+
{
290+
yield 'paginationClientPartial disabled - paginationInfo requested' => [false, true, null];
291+
yield 'paginationClientPartial enabled - paginationInfo requested' => [true, true, null];
292+
yield 'paginationClientPartial disabled - paginationInfo not requested' => [false, false, null];
293+
yield 'paginationClientPartial enabled - paginationInfo not requested' => [true, false, true];
294+
}
240295
}

0 commit comments

Comments
 (0)