Skip to content

Commit d933888

Browse files
authored
Enable pagination per resource (#3035)
1 parent 8bd9120 commit d933888

File tree

22 files changed

+215
-28
lines changed

22 files changed

+215
-28
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* GraphQL: Add support for custom queries and mutations
77
* GraphQL: Add support for custom types
88
* GraphQL: Better pagination support (backwards pagination)
9+
* GraphQL: Support the pagination per resource
910
* GraphQL: Add the concept of *stages* in the workflow of the resolvers and add the possibility to disable them with operation attributes
1011
* GraphQL: Add GraphQL Playground besides GraphiQL and add the possibility to change the default IDE (or to disable it) for the GraphQL endpoint
1112
* GraphQL: Add a command to print the schema in SDL

features/graphql/collection.feature

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,26 @@ Feature: GraphQL collection support
482482
And the header "Content-Type" should be equal to "application/json"
483483
And the JSON node "data.dummies.edges" should have 0 element
484484

485+
@createSchema
486+
Scenario: Retrieve a collection with pagination disabled
487+
Given there are 4 foo objects with fake names
488+
When I send the following GraphQL request:
489+
"""
490+
{
491+
foos {
492+
id
493+
name
494+
bar
495+
}
496+
}
497+
"""
498+
Then the response status code should be 200
499+
And the response should be in JSON
500+
And the header "Content-Type" should be equal to "application/json"
501+
And the JSON node "data.foos[3].id" should be equal to "/foos/4"
502+
And the JSON node "data.foos[3].name" should be equal to "Separativeness"
503+
And the JSON node "data.foos[3].bar" should be equal to "Sit"
504+
485505
Scenario: Custom collection query
486506
Given there are 2 dummyCustomQuery objects
487507
When I send the following GraphQL request:

src/Bridge/Doctrine/MongoDbOdm/Extension/PaginationExtension.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ public function applyToCollection(Builder $aggregationBuilder, string $resourceC
5252
return;
5353
}
5454

55+
if (($context['graphql_operation_name'] ?? false) && !$this->pagination->isGraphQlEnabled($resourceClass, $operationName, $context)) {
56+
return;
57+
}
58+
5559
$context = $this->addCountToContext(clone $aggregationBuilder, $context);
5660

5761
[, $offset, $limit] = $this->pagination->getPagination($resourceClass, $operationName, $context);
@@ -90,6 +94,10 @@ public function applyToCollection(Builder $aggregationBuilder, string $resourceC
9094
*/
9195
public function supportsResult(string $resourceClass, string $operationName = null, array $context = []): bool
9296
{
97+
if ($context['graphql_operation_name'] ?? false) {
98+
return $this->pagination->isGraphQlEnabled($resourceClass, $operationName, $context);
99+
}
100+
93101
return $this->pagination->isEnabled($resourceClass, $operationName, $context);
94102
}
95103

src/Bridge/Doctrine/Orm/Extension/PaginationExtension.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,10 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator
134134
*/
135135
public function supportsResult(string $resourceClass, string $operationName = null, array $context = []): bool
136136
{
137+
if ($context['graphql_operation_name'] ?? false) {
138+
return $this->pagination->isGraphQlEnabled($resourceClass, $operationName, $context);
139+
}
140+
137141
if (null === $this->requestStack) {
138142
return $this->pagination->isEnabled($resourceClass, $operationName, $context);
139143
}
@@ -193,6 +197,10 @@ private function getPagination(QueryBuilder $queryBuilder, string $resourceClass
193197
return null;
194198
}
195199

200+
if (($context['graphql_operation_name'] ?? false) && !$this->pagination->isGraphQlEnabled($resourceClass, $operationName, $context)) {
201+
return null;
202+
}
203+
196204
$context = $this->addCountToContext($queryBuilder, $context);
197205

198206
return \array_slice($this->pagination->getPagination($resourceClass, $operationName, $context), 1);

src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,7 @@ private function registerGraphQlConfiguration(ContainerBuilder $container, array
378378
}
379379

380380
$container->setParameter('api_platform.graphql.default_ide', $config['graphql']['default_ide']);
381+
$container->setParameter('api_platform.graphql.collection.pagination', $config['graphql']['collection']['pagination']);
381382

382383
$loader->load('graphql.xml');
383384

src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,14 @@ private function addGraphQlSection(ArrayNodeDefinition $rootNode): void
239239
->arrayNode('graphql_playground')
240240
->{class_exists(GraphQL::class) && class_exists(TwigBundle::class) ? 'canBeDisabled' : 'canBeEnabled'}()
241241
->end()
242+
->arrayNode('collection')
243+
->addDefaultsIfNotSet()
244+
->children()
245+
->arrayNode('pagination')
246+
->canBeDisabled()
247+
->end()
248+
->end()
249+
->end()
242250
->end()
243251
->end()
244252
->end();

src/Bridge/Symfony/Bundle/Resources/config/data_provider.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
<service id="api_platform.pagination" class="ApiPlatform\Core\DataProvider\Pagination">
3131
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
3232
<argument>%api_platform.collection.pagination%</argument>
33+
<argument>%api_platform.graphql.collection.pagination%</argument>
3334
</service>
3435
</services>
3536

src/Bridge/Symfony/Bundle/Resources/config/graphql.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
6565
<argument type="service" id="serializer" />
6666
<argument type="service" id="api_platform.graphql.serializer.context_builder" />
67-
<argument>%api_platform.collection.pagination.enabled%</argument>
67+
<argument type="service" id="api_platform.pagination" />
6868
</service>
6969

7070
<service id="api_platform.graphql.resolver.stage.deserialize" class="ApiPlatform\Core\GraphQl\Resolver\Stage\DeserializeStage" public="false">
@@ -135,7 +135,7 @@
135135
<argument type="service" id="api_platform.graphql.resolver.factory.collection" />
136136
<argument type="service" id="api_platform.graphql.resolver.factory.item_mutation" />
137137
<argument type="service" id="api_platform.filter_locator" />
138-
<argument>%api_platform.collection.pagination.enabled%</argument>
138+
<argument type="service" id="api_platform.pagination" />
139139
</service>
140140

141141
<service id="api_platform.graphql.fields_builder_locator" class="Symfony\Component\DependencyInjection\ServiceLocator" public="false">

src/DataProvider/Pagination.php

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace ApiPlatform\Core\DataProvider;
1515

1616
use ApiPlatform\Core\Exception\InvalidArgumentException;
17+
use ApiPlatform\Core\Exception\ResourceClassNotFoundException;
1718
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
1819

1920
/**
@@ -24,9 +25,10 @@
2425
final class Pagination
2526
{
2627
private $options;
28+
private $graphQlOptions;
2729
private $resourceMetadataFactory;
2830

29-
public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, array $options = [])
31+
public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, array $options = [], array $graphQlOptions = [])
3032
{
3133
$this->resourceMetadataFactory = $resourceMetadataFactory;
3234
$this->options = array_merge([
@@ -43,6 +45,9 @@ public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFa
4345
'client_partial' => false,
4446
'partial_parameter_name' => 'partial',
4547
], $options);
48+
$this->graphQlOptions = array_merge([
49+
'enabled' => true,
50+
], $graphQlOptions);
4651
}
4752

4853
/**
@@ -171,6 +176,14 @@ public function isEnabled(string $resourceClass = null, string $operationName =
171176
return $this->getEnabled($context, $resourceClass, $operationName);
172177
}
173178

179+
/**
180+
* Is the pagination enabled for GraphQL?
181+
*/
182+
public function isGraphQlEnabled(?string $resourceClass = null, ?string $operationName = null, array $context = []): bool
183+
{
184+
return $this->getGraphQlEnabled($resourceClass, $operationName);
185+
}
186+
174187
/**
175188
* Is the partial pagination enabled?
176189
*/
@@ -198,6 +211,23 @@ private function getEnabled(array $context, string $resourceClass = null, string
198211
return filter_var($this->getParameterFromContext($context, $this->options[$partial ? 'partial_parameter_name' : 'enabled_parameter_name'], $enabled), FILTER_VALIDATE_BOOLEAN);
199212
}
200213

214+
return (bool) $enabled;
215+
}
216+
217+
private function getGraphQlEnabled(?string $resourceClass, ?string $operationName): bool
218+
{
219+
$enabled = $this->graphQlOptions['enabled'];
220+
221+
if (null !== $resourceClass) {
222+
try {
223+
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
224+
} catch (ResourceClassNotFoundException $e) {
225+
return $enabled;
226+
}
227+
228+
return (bool) $resourceMetadata->getGraphqlAttribute($operationName, 'pagination_enabled', $enabled, true);
229+
}
230+
201231
return $enabled;
202232
}
203233

src/GraphQl/Resolver/Factory/ItemResolverFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public function __invoke(?string $resourceClass = null, ?string $rootClass = nul
7979
if (null !== $queryResolverId) {
8080
/** @var QueryItemResolverInterface $queryResolver */
8181
$queryResolver = $this->queryResolverLocator->get($queryResolverId);
82-
$item = $queryResolver($item, ['source' => $source, 'args' => $args, 'info' => $info]);
82+
$item = $queryResolver($item, $resolverContext);
8383
$resourceClass = $this->getResourceClass($item, $resourceClass, $info, sprintf('Custom query resolver "%s"', $queryResolverId).' has to return an item of class %s but returned an item of class %s.');
8484
}
8585

0 commit comments

Comments
 (0)