1
1
## Overview
2
2
3
- GraphQL is data-storage agnostic. You can use any underlying data storage engine, including SQL or NoSQL database,
4
- plain files or in-memory data structures.
3
+ GraphQL is data-storage agnostic. You can use any underlying data storage engine, including but not limited to
4
+ SQL or NoSQL databases, plain files or in-memory data structures.
5
5
6
- In order to convert the GraphQL query to PHP array, ** graphql-php** traverses query fields (using depth-first algorithm) and
7
- runs special ** resolve** function on each field. This ** resolve** function is provided by you as a part of
6
+ In order to convert the GraphQL query to a PHP array, ** graphql-php** traverses query fields (using depth-first algorithm)
7
+ and runs the special ** resolve** function on each field. This ** resolve** function is provided by you as a part of the
8
8
[ field definition] ( type-definitions/object-types.md#field-configuration-options ) or [ query execution call] ( executing-queries.md ) .
9
9
10
- Result returned by ** resolve** function is directly included in the response (for scalars and enums)
10
+ The result returned by the ** resolve** function is directly included in the response (for scalars and enums)
11
11
or passed down to nested fields (for objects).
12
12
13
- Let's walk through an example. Consider following GraphQL query:
13
+ Let's walk through an example. Consider the following GraphQL query:
14
14
15
15
``` graphql
16
16
{
@@ -23,150 +23,166 @@ Let's walk through an example. Consider following GraphQL query:
23
23
}
24
24
```
25
25
26
- We need a Schema that can fulfill it. On the very top level the Schema contains Query type:
26
+ We need a ` Schema ` that can fulfill it. On the very top level, the ` Schema ` contains the ` Query ` type:
27
27
28
28
``` php
29
- <?php
30
29
use GraphQL\Type\Definition\ObjectType;
31
30
32
31
$queryType = new ObjectType([
33
- 'name' => 'Query',
34
- 'fields' => [
35
-
36
- 'lastStory' => [
37
- 'type' => $blogStoryType,
38
- 'resolve' => function() {
39
- return [
40
- 'id' => 1,
41
- 'title' => 'Example blog post',
42
- 'authorId' => 1
43
- ];
44
- }
32
+ 'name' => 'Query',
33
+ 'fields' => [
34
+ 'lastStory' => [
35
+ 'type' => $blogStoryType,
36
+ 'resolve' => fn (): array => [
37
+ 'id' => 1,
38
+ 'title' => 'Example blog post',
39
+ 'authorId' => 1
40
+ ],
41
+ ]
45
42
]
46
-
47
- ]
48
43
]);
49
44
```
50
45
51
- As we see field ** lastStory** has ** resolve** function that is responsible for fetching data.
46
+ As we see, the field ** lastStory** has a ** resolve** function that is responsible for fetching data.
52
47
53
- In our example, we simply return array value, but in the real-world application you would query
54
- your database/cache/search index and return the result.
48
+ In our example, we simply return a static value, but in the real-world application you would query
49
+ your data source and return the result from there .
55
50
56
- Since ** lastStory** is of composite type ** BlogStory** this result is passed down to fields of this type:
51
+ Since ** lastStory** is of composite type ** BlogStory** , this result is passed down to fields of this type:
57
52
58
53
``` php
59
- <?php
60
54
use GraphQL\Type\Definition\Type;
61
55
use GraphQL\Type\Definition\ObjectType;
62
56
63
- $blogStoryType = new ObjectType([
64
- 'name' => 'BlogStory',
65
- 'fields' => [
66
-
67
- 'author' => [
68
- 'type' => $userType,
69
- 'resolve' => function($blogStory) {
70
- $users = [
71
- 1 => [
72
- 'id' => 1,
73
- 'name' => 'Smith'
74
- ],
75
- 2 => [
76
- 'id' => 2,
77
- 'name' => 'Anderson'
78
- ]
79
- ];
80
- return $users[$blogStory['authorId']];
81
- }
57
+ const USERS = [
58
+ 1 => [
59
+ 'id' => 1,
60
+ 'name' => 'Smith'
82
61
],
83
-
84
- 'title ' => [
85
- 'type ' => Type::string()
62
+ 2 => [
63
+ 'id ' => 2,
64
+ 'name ' => 'Anderson'
86
65
]
66
+ ];
87
67
88
- ]
68
+ $blogStoryType = new ObjectType([
69
+ 'name' => 'BlogStory',
70
+ 'fields' => [
71
+ 'author' => [
72
+ 'type' => $userType,
73
+ 'resolve' => fn (array $blogStory): array => USERS[$blogStory['authorId']],
74
+ ],
75
+ 'title' => [
76
+ 'type' => Type::string()
77
+ ]
78
+ ]
89
79
]);
90
80
```
91
81
92
- Here ** $blogStory** is the array returned by ** lastStory** field above.
82
+ Here ** $blogStory** is the array returned by the ** lastStory** field above.
93
83
94
- Again: in the real-world applications you would fetch user data from data store by ** authorId** and return it.
84
+ Again: in real-world applications you would fetch user data from your data source by ** authorId** and return it.
95
85
Also, note that you don't have to return arrays. You can return any value, ** graphql-php** will pass it untouched
96
86
to nested resolvers.
97
87
98
- But then the question appears - field ** title** has no ** resolve** option. How is it resolved?
99
-
100
- There is a default resolver for all fields. When you define your own ** resolve** function
101
- for a field you simply override this default resolver.
88
+ But then the question appears - the field ** title** has no ** resolve** option, how is it resolved?
89
+ When you define no custom resolver, [ the default field resolver] ( #default-field-resolver ) applies.
102
90
103
91
## Default Field Resolver
104
92
105
- ** graphql-php** provides following default field resolver:
93
+ ** graphql-php** provides the following default field resolver:
106
94
107
95
``` php
108
- <?php
109
- function defaultFieldResolver($objectValue, $args, $context, \GraphQL\Type\Definition\ResolveInfo $info)
110
- {
111
- $fieldName = $info->fieldName;
112
- $property = null;
113
-
114
- if (is_array($objectValue) || $objectValue instanceof \ArrayAccess) {
115
- if (isset($objectValue[$fieldName])) {
116
- $property = $objectValue[$fieldName];
117
- }
118
- } elseif (is_object($objectValue)) {
119
- if (isset($objectValue->{$fieldName})) {
120
- $property = $objectValue->{$fieldName};
121
- }
122
- }
96
+ use GraphQL\Type\Definition\ResolveInfo;
123
97
124
- return $property instanceof Closure
125
- ? $property($objectValue, $args, $context, $info)
126
- : $property;
98
+ function defaultFieldResolver($objectValue, array $args, $context, ResolveInfo $info)
99
+ {
100
+ $fieldName = $info->fieldName;
101
+ $property = null;
102
+
103
+ if (is_array($objectValue) || $objectValue instanceof ArrayAccess) {
104
+ if (isset($objectValue[$fieldName])) {
105
+ $property = $objectValue[$fieldName];
106
+ }
107
+ } elseif (is_object($objectValue)) {
108
+ if (isset($objectValue->{$fieldName})) {
109
+ $property = $objectValue->{$fieldName};
110
+ }
127
111
}
112
+
113
+ return $property instanceof Closure
114
+ ? $property($objectValue, $args, $contextValue, $info)
115
+ : $property;
116
+ }
128
117
```
129
118
130
- As you see it returns value by key (for arrays) or property (for objects).
131
- If the value is not set - it returns ** null** .
119
+ It returns value by key (for arrays) or property (for objects).
120
+ If the value is not set, it returns ** null** .
132
121
133
- To override the default resolver, pass it as an argument of [ executeQuery] ( executing-queries.md ) call .
122
+ To override the default resolver, pass it as an argument to [ executeQuery] ( executing-queries.md ) .
134
123
135
124
## Default Field Resolver per Type
136
125
137
126
Sometimes it might be convenient to set default field resolver per type. You can do so by providing
138
127
[ resolveField option in type config] ( type-definitions/object-types.md#configuration-options ) . For example:
139
128
140
129
``` php
141
- <?php
142
130
use GraphQL\Type\Definition\Type;
143
131
use GraphQL\Type\Definition\ObjectType;
144
132
use GraphQL\Type\Definition\ResolveInfo;
145
133
146
134
$userType = new ObjectType([
147
- 'name' => 'User',
148
- 'fields' => [
149
-
150
- 'name' => Type::string(),
151
- 'email' => Type::string()
152
-
153
- ],
154
- 'resolveField' => function(User $user, $args, $context, ResolveInfo $info) {
155
- switch ($info->fieldName) {
156
- case 'name':
157
- return $user->getName();
158
- case 'email':
159
- return $user->getEmail();
160
- default:
161
- return null;
162
- }
163
- }
135
+ 'name' => 'User',
136
+ 'fields' => [
137
+ 'name' => Type::string(),
138
+ 'email' => Type::string()
139
+ ],
140
+ 'resolveField' => function (User $user, array $args, $context, ResolveInfo $info) {
141
+ switch ($info->fieldName) {
142
+ case 'name': return $user->getName();
143
+ case 'email': return $user->getEmail();
144
+ default: return null;
145
+ }
146
+ },
164
147
]);
165
148
```
166
149
167
150
Keep in mind that ** field resolver** has precedence over ** default field resolver per type** which in turn
168
151
has precedence over ** default field resolver** .
169
152
153
+ ## Optimize Resolvers
154
+
155
+ The 4th argument of ** resolver** functions is an instance of [ ResolveInfo] ( ./class-reference.md#graphqltypedefinitionresolveinfo ) .
156
+ It contains information that is useful for the field resolution process.
157
+
158
+ Depending on which data source is used, knowing which fields the client queried can be used to optimize
159
+ the performance of a resolver. For example, an SQL query may only need to select the queried fields.
160
+
161
+ The following example limits which columns are selected from the database:
162
+
163
+ ``` php
164
+ use GraphQL\Type\Definition\ObjectType;
165
+ use GraphQL\Type\Definition\ResolveInfo;
166
+
167
+ $queryType = new ObjectType([
168
+ 'name' => 'Query',
169
+ 'fields' => [
170
+ 'lastStory' => [
171
+ 'type' => $storyType,
172
+ 'resolve' => function ($root, array $args, $context, ResolveInfo $resolveInfo): Story {
173
+ // Fictitious API, use whatever database access your application/framework provides
174
+ $builder = Story::builder();
175
+ foreach ($resolveInfo->getFieldSelection() as $field => $_) {
176
+ $builder->addSelect($field);
177
+ }
178
+
179
+ return $builder->last();
180
+ }
181
+ ]
182
+ ]
183
+ ]);
184
+ ```
185
+
170
186
## Solving N+1 Problem
171
187
172
188
Since: 0.9.0
@@ -175,7 +191,7 @@ One of the most annoying problems with data fetching is a so-called
175
191
[ N+1 problem] ( https://secure.phabricator.com/book/phabcontrib/article/n_plus_one/ ) . <br >
176
192
Consider following GraphQL query:
177
193
178
- ```
194
+ ``` graphql
179
195
{
180
196
topStories (limit : 10 ) {
181
197
title
@@ -195,11 +211,12 @@ when one batched query could be executed instead of 10 distinct queries.
195
211
Here is an example of ** BlogStory** resolver for field ** author** that uses deferring:
196
212
197
213
``` php
198
- <?php
199
- 'resolve' => function($blogStory) {
214
+ use GraphQL\Deferred;
215
+
216
+ 'resolve' => function (array $blogStory): Deferred {
200
217
MyUserBuffer::add($blogStory['authorId']);
201
218
202
- return new GraphQL\ Deferred(function () use ($blogStory) {
219
+ return new Deferred(function () use ($blogStory): User {
203
220
MyUserBuffer::loadBuffered();
204
221
205
222
return MyUserBuffer::get($blogStory['authorId']);
@@ -211,7 +228,7 @@ In this example, we fill up the buffer with 10 author ids first. Then **graphql-
211
228
resolving other non-deferred fields until there are none of them left.
212
229
213
230
After that, it calls closures wrapped by ` GraphQL\Deferred ` which in turn load all buffered
214
- ids once (using SQL IN(?), Redis MGET or other similar tools) and returns final field value.
231
+ ids once (using SQL ` IN(?) ` , Redis ` MGET ` or similar tools) and returns the final field value.
215
232
216
233
Originally this approach was advocated by Facebook in their [ Dataloader] ( https://github.com/graphql/dataloader )
217
234
project. This solution enables very interesting optimizations at no cost. Consider the following query:
@@ -252,7 +269,6 @@ To start using this feature, switch facade method for query execution from
252
269
** executeQuery** to ** promiseToExecute** :
253
270
254
271
``` php
255
- <?php
256
272
use GraphQL\GraphQL;
257
273
use GraphQL\Executor\ExecutionResult;
258
274
@@ -267,9 +283,7 @@ $promise = GraphQL::promiseToExecute(
267
283
$fieldResolver = null,
268
284
$validationRules = null
269
285
);
270
- $promise->then(function(ExecutionResult $result) {
271
- return $result->toArray();
272
- });
286
+ $promise->then(fn (ExecutionResult $result): array => $result->toArray());
273
287
```
274
288
275
289
Where ** $promiseAdapter** is an instance of:
0 commit comments