Skip to content

Commit cfa8f34

Browse files
authored
Introduce Resolver Stages (#2959)
1 parent 5882990 commit cfa8f34

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+2614
-1203
lines changed

features/graphql/input_output.feature

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,37 @@ Feature: GraphQL DTO input and output
5555
}
5656
"""
5757

58+
Scenario: Create an item with custom input and output
59+
When I send the following GraphQL request:
60+
"""
61+
mutation {
62+
createDummyDtoInputOutput(input: {foo: "A foo", bar: 4, clientMutationId: "myId"}) {
63+
dummyDtoInputOutput {
64+
baz,
65+
bat
66+
}
67+
clientMutationId
68+
}
69+
}
70+
"""
71+
Then the response status code should be 200
72+
And the response should be in JSON
73+
And the header "Content-Type" should be equal to "application/json"
74+
And the JSON should be equal to:
75+
"""
76+
{
77+
"data": {
78+
"createDummyDtoInputOutput": {
79+
"dummyDtoInputOutput": {
80+
"baz": 4,
81+
"bat": "A foo"
82+
},
83+
"clientMutationId": "myId"
84+
}
85+
}
86+
}
87+
"""
88+
5889
Scenario: Create an item using custom inputClass & disabled outputClass
5990
Given there are 2 dummyDtoNoOutput objects
6091
When I send the following GraphQL request:
@@ -135,33 +166,6 @@ Feature: GraphQL DTO input and output
135166
}
136167
"""
137168

138-
Scenario: Create an item with empty input fields using disabled inputClass (no persist done)
139-
When I send the following GraphQL request:
140-
"""
141-
mutation {
142-
createDummyDtoNoInput(input: {clientMutationId: "myId"}) {
143-
dummyDtoNoInput {
144-
id
145-
}
146-
clientMutationId
147-
}
148-
}
149-
"""
150-
Then the response status code should be 200
151-
And the response should be in JSON
152-
And the header "Content-Type" should be equal to "application/json"
153-
And the JSON should be equal to:
154-
"""
155-
{
156-
"data": {
157-
"createDummyDtoNoInput": {
158-
"dummyDtoNoInput": null,
159-
"clientMutationId": "myId"
160-
}
161-
}
162-
}
163-
"""
164-
165169
Scenario: Use messenger with GraphQL and an input where the handler gives a synchronous result
166170
When I send the following GraphQL request:
167171
"""

features/graphql/mutation.feature

Lines changed: 0 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -334,113 +334,6 @@ Feature: GraphQL mutation support
334334
And the header "Content-Type" should be equal to "application/json"
335335
And the JSON node "errors[0].message" should be equal to "name: This value should not be blank."
336336

337-
Scenario: Create an item using custom inputClass & disabled outputClass
338-
Given there are 2 dummyDtoNoOutput objects
339-
When I send the following GraphQL request:
340-
"""
341-
mutation {
342-
createDummyDtoNoOutput(input: {foo: "A new one", bar: 3, clientMutationId: "myId"}) {
343-
dummyDtoNoOutput {
344-
id
345-
}
346-
clientMutationId
347-
}
348-
}
349-
"""
350-
Then the response status code should be 200
351-
And the response should be in JSON
352-
And the header "Content-Type" should be equal to "application/json"
353-
And the JSON should be equal to:
354-
"""
355-
{
356-
"errors": [
357-
{
358-
"message": "Cannot query field \"id\" on type \"DummyDtoNoOutput\".",
359-
"extensions": {
360-
"category": "graphql"
361-
},
362-
"locations": [
363-
{
364-
"line": 4,
365-
"column": 7
366-
}
367-
]
368-
}
369-
]
370-
}
371-
"""
372-
373-
Scenario: Cannot create an item with input fields using disabled inputClass
374-
When I send the following GraphQL request:
375-
"""
376-
mutation {
377-
createDummyDtoNoInput(input: {lorem: "A new one", ipsum: 3, clientMutationId: "myId"}) {
378-
clientMutationId
379-
}
380-
}
381-
"""
382-
Then the response status code should be 200
383-
And the response should be in JSON
384-
And the header "Content-Type" should be equal to "application/json"
385-
And the JSON should be equal to:
386-
"""
387-
{
388-
"errors": [
389-
{
390-
"message": "Field \"lorem\" is not defined by type createDummyDtoNoInputInput.",
391-
"extensions": {
392-
"category": "graphql"
393-
},
394-
"locations": [
395-
{
396-
"line": 2,
397-
"column": 33
398-
}
399-
]
400-
},
401-
{
402-
"message": "Field \"ipsum\" is not defined by type createDummyDtoNoInputInput.",
403-
"extensions": {
404-
"category": "graphql"
405-
},
406-
"locations": [
407-
{
408-
"line": 2,
409-
"column": 53
410-
}
411-
]
412-
}
413-
]
414-
}
415-
"""
416-
417-
Scenario: Create an item with empty input fields using disabled inputClass (no persist done)
418-
When I send the following GraphQL request:
419-
"""
420-
mutation {
421-
createDummyDtoNoInput(input: {clientMutationId: "myId"}) {
422-
dummyDtoNoInput {
423-
id
424-
}
425-
clientMutationId
426-
}
427-
}
428-
"""
429-
Then the response status code should be 200
430-
And the response should be in JSON
431-
And the header "Content-Type" should be equal to "application/json"
432-
And the JSON should be equal to:
433-
"""
434-
{
435-
"data": {
436-
"createDummyDtoNoInput": {
437-
"dummyDtoNoInput": null,
438-
"clientMutationId": "myId"
439-
}
440-
}
441-
}
442-
"""
443-
444337
Scenario: Execute a custom mutation
445338
Given there are 1 dummyCustomMutation objects
446339
When I send the following GraphQL request:

phpstan.neon.dist

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ parameters:
3333
path: %currentWorkingDirectory%/src/Util/ClassInfoTrait.php
3434
-
3535
message: '#Cannot assign offset .+ to bool\.#'
36-
path: %currentWorkingDirectory%/src/GraphQl/Resolver/FieldsToAttributesTrait.php
36+
path: %currentWorkingDirectory%/src/GraphQl/Serializer/SerializerContextBuilder.php
3737
- '#Access to an undefined property Prophecy\\Prophecy\\ObjectProphecy<(\\?[a-zA-Z0-9_]+)+>::\$[a-zA-Z0-9_]+#'
3838
- '#Call to an undefined method Doctrine\\Common\\Persistence\\ObjectManager::getConnection\(\)#'
3939
# https://github.com/willdurand/Negotiation/issues/89#issuecomment-513283286
@@ -57,9 +57,6 @@ parameters:
5757
-
5858
message: '#Parameter \#1 \$resource of method ApiPlatform\\Core\\Metadata\\Extractor\\XmlExtractor::getAttributes\(\) expects SimpleXMLElement, object given\.#'
5959
path: %currentWorkingDirectory%/src/Metadata/Extractor/XmlExtractor.php
60-
-
61-
message: '#Parameter \#1 \$collection of method ApiPlatform\\Core\\Tests\\GraphQl\\Resolver\\Factory\\CollectionResolverFactoryTest::createCollectionResolverFactory\(\) expects array\|Iterator, object given\.#'
62-
path: %currentWorkingDirectory%/tests/GraphQl/Resolver/Factory/CollectionResolverFactoryTest.php
6360
-
6461
message: '#Parameter \#1 \$docblock of method phpDocumentor\\Reflection\\DocBlockFactoryInterface::create\(\) expects string, ReflectionClass given\.#'
6562
path: %currentWorkingDirectory%/src/Metadata/Resource/Factory/PhpDocResourceMetadataFactory.php

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public function getResult(Builder $aggregationBuilder, string $resourceClass, st
110110

111111
private function addCountToContext(Builder $aggregationBuilder, array $context): array
112112
{
113-
if (!($context['graphql'] ?? false)) {
113+
if (!($context['graphql_operation_name'] ?? false)) {
114114
return $context;
115115
}
116116

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ private function getPaginationParameter(Request $request, string $parameterName,
285285

286286
private function addCountToContext(QueryBuilder $queryBuilder, array $context): array
287287
{
288-
if (!($context['graphql'] ?? false)) {
288+
if (!($context['graphql_operation_name'] ?? false)) {
289289
return $context;
290290
}
291291

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

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,32 +10,70 @@
1010
<!-- Resolvers -->
1111

1212
<service id="api_platform.graphql.resolver.factory.item" class="ApiPlatform\Core\GraphQl\Resolver\Factory\ItemResolverFactory" public="false">
13-
<argument type="service" id="api_platform.iri_converter" />
13+
<argument type="service" id="api_platform.graphql.resolver.stage.read" />
14+
<argument type="service" id="api_platform.graphql.resolver.stage.deny_access" />
15+
<argument type="service" id="api_platform.graphql.resolver.stage.serialize" />
1416
<argument type="service" id="api_platform.graphql.query_resolver_locator" />
15-
<argument type="service" id="serializer" />
1617
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
17-
<argument type="service" id="api_platform.security.resource_access_checker" on-invalid="ignore" />
1818
</service>
1919

2020
<service id="api_platform.graphql.resolver.factory.collection" class="ApiPlatform\Core\GraphQl\Resolver\Factory\CollectionResolverFactory" public="false">
21-
<argument type="service" id="api_platform.collection_data_provider" />
22-
<argument type="service" id="api_platform.subresource_data_provider" />
21+
<argument type="service" id="api_platform.graphql.resolver.stage.read" />
22+
<argument type="service" id="api_platform.graphql.resolver.stage.deny_access" />
23+
<argument type="service" id="api_platform.graphql.resolver.stage.serialize" />
2324
<argument type="service" id="api_platform.graphql.query_resolver_locator" />
24-
<argument type="service" id="serializer" />
2525
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
26-
<argument type="service" id="api_platform.security.resource_access_checker" on-invalid="null" />
2726
<argument type="service" id="request_stack" />
28-
<argument>%api_platform.collection.pagination.enabled%</argument>
2927
</service>
3028

3129
<service id="api_platform.graphql.resolver.factory.item_mutation" class="ApiPlatform\Core\GraphQl\Resolver\Factory\ItemMutationResolverFactory" public="false">
32-
<argument type="service" id="api_platform.iri_converter" />
33-
<argument type="service" id="api_platform.data_persister" />
30+
<argument type="service" id="api_platform.graphql.resolver.stage.read" />
31+
<argument type="service" id="api_platform.graphql.resolver.stage.deny_access" />
32+
<argument type="service" id="api_platform.graphql.resolver.stage.serialize" />
33+
<argument type="service" id="api_platform.graphql.resolver.stage.deserialize" />
34+
<argument type="service" id="api_platform.graphql.resolver.stage.write" />
35+
<argument type="service" id="api_platform.graphql.resolver.stage.validate" />
3436
<argument type="service" id="api_platform.graphql.mutation_resolver_locator" />
37+
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
38+
</service>
39+
40+
<!-- Resolver Stages -->
41+
42+
<service id="api_platform.graphql.resolver.stage.read" class="ApiPlatform\Core\GraphQl\Resolver\Stage\ReadStage" public="false">
43+
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
44+
<argument type="service" id="api_platform.iri_converter" />
45+
<argument type="service" id="api_platform.collection_data_provider" />
46+
<argument type="service" id="api_platform.subresource_data_provider" />
47+
<argument type="service" id="api_platform.graphql.serializer.context_builder" />
48+
</service>
49+
50+
<service id="api_platform.graphql.resolver.stage.deny_access" class="ApiPlatform\Core\GraphQl\Resolver\Stage\DenyAccessStage" public="false">
51+
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
52+
<argument type="service" id="api_platform.security.resource_access_checker" />
53+
</service>
54+
55+
<service id="api_platform.graphql.resolver.stage.serialize" class="ApiPlatform\Core\GraphQl\Resolver\Stage\SerializeStage" public="false">
56+
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
57+
<argument type="service" id="serializer" />
58+
<argument type="service" id="api_platform.graphql.serializer.context_builder" />
59+
<argument>%api_platform.collection.pagination.enabled%</argument>
60+
</service>
61+
62+
<service id="api_platform.graphql.resolver.stage.deserialize" class="ApiPlatform\Core\GraphQl\Resolver\Stage\DeserializeStage" public="false">
63+
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
3564
<argument type="service" id="serializer" />
65+
<argument type="service" id="api_platform.graphql.serializer.context_builder" />
66+
</service>
67+
68+
<service id="api_platform.graphql.resolver.stage.write" class="ApiPlatform\Core\GraphQl\Resolver\Stage\WriteStage" public="false">
3669
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
37-
<argument type="service" id="api_platform.security.resource_access_checker" on-invalid="null" />
38-
<argument type="service" id="api_platform.validator" on-invalid="null" />
70+
<argument type="service" id="api_platform.data_persister" />
71+
<argument type="service" id="api_platform.graphql.serializer.context_builder" />
72+
</service>
73+
74+
<service id="api_platform.graphql.resolver.stage.validate" class="ApiPlatform\Core\GraphQl\Resolver\Stage\ValidateStage" public="false">
75+
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
76+
<argument type="service" id="api_platform.validator" />
3977
</service>
4078

4179
<service id="api_platform.graphql.resolver.resource_field" class="ApiPlatform\Core\GraphQl\Resolver\ResourceFieldResolver" public="false">
@@ -164,6 +202,11 @@
164202
<tag name="serializer.normalizer" priority="-995" />
165203
</service>
166204

205+
<service id="api_platform.graphql.serializer.context_builder" class="ApiPlatform\Core\GraphQl\Serializer\SerializerContextBuilder" public="false">
206+
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
207+
</service>
208+
<service id="ApiPlatform\Core\GraphQl\Serializer\SerializerContextBuilderInterface" alias="api_platform.graphql.serializer.context_builder" />
209+
167210
<!-- Command -->
168211

169212
<service id="api_platform.graphql.command.export_command" class="ApiPlatform\Core\Bridge\Symfony\Bundle\Command\GraphQlExportCommand">

src/DataProvider/Pagination.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public function getPage(array $context = []): int
7070
*/
7171
public function getOffset(string $resourceClass = null, string $operationName = null, array $context = []): int
7272
{
73-
$graphql = $context['graphql'] ?? false;
73+
$graphql = (bool) ($context['graphql_operation_name'] ?? false);
7474

7575
$limit = $this->getLimit($resourceClass, $operationName, $context);
7676

@@ -96,7 +96,7 @@ public function getOffset(string $resourceClass = null, string $operationName =
9696
*/
9797
public function getLimit(string $resourceClass = null, string $operationName = null, array $context = []): int
9898
{
99-
$graphql = $context['graphql'] ?? false;
99+
$graphql = (bool) ($context['graphql_operation_name'] ?? false);
100100

101101
$limit = $this->options['items_per_page'];
102102
$clientLimit = $this->options['client_items_per_page'];

0 commit comments

Comments
 (0)