1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Date: 03.11.16 |
4
|
|
|
* |
5
|
|
|
* @author Portey Vasil <[email protected]> |
6
|
|
|
*/ |
7
|
|
|
|
8
|
|
|
namespace Youshido\GraphQL\Execution; |
9
|
|
|
|
10
|
|
|
|
11
|
|
|
use Youshido\GraphQL\Exception\ResolveException; |
12
|
|
|
use Youshido\GraphQL\Execution\Container\Container; |
13
|
|
|
use Youshido\GraphQL\Execution\Context\ExecutionContext; |
14
|
|
|
use Youshido\GraphQL\Execution\Visitor\MaxComplexityQueryVisitor; |
15
|
|
|
use Youshido\GraphQL\Field\Field; |
16
|
|
|
use Youshido\GraphQL\Field\FieldInterface; |
17
|
|
|
use Youshido\GraphQL\Field\InputField; |
18
|
|
|
use Youshido\GraphQL\Parser\Ast\ArgumentValue\InputList as AstInputList; |
19
|
|
|
use Youshido\GraphQL\Parser\Ast\ArgumentValue\InputObject as AstInputObject; |
20
|
|
|
use Youshido\GraphQL\Parser\Ast\ArgumentValue\Literal as AstLiteral; |
21
|
|
|
use Youshido\GraphQL\Parser\Ast\ArgumentValue\VariableReference; |
22
|
|
|
use Youshido\GraphQL\Parser\Ast\Field as AstField; |
23
|
|
|
use Youshido\GraphQL\Parser\Ast\FragmentReference; |
24
|
|
|
use Youshido\GraphQL\Parser\Ast\Interfaces\FieldInterface as AstFieldInterface; |
25
|
|
|
use Youshido\GraphQL\Parser\Ast\Mutation as AstMutation; |
26
|
|
|
use Youshido\GraphQL\Parser\Ast\Query as AstQuery; |
27
|
|
|
use Youshido\GraphQL\Parser\Ast\TypedFragmentReference; |
28
|
|
|
use Youshido\GraphQL\Parser\Parser; |
29
|
|
|
use Youshido\GraphQL\Schema\AbstractSchema; |
30
|
|
|
use Youshido\GraphQL\Type\AbstractType; |
31
|
|
|
use Youshido\GraphQL\Type\Enum\AbstractEnumType; |
32
|
|
|
use Youshido\GraphQL\Type\InputObject\AbstractInputObjectType; |
33
|
|
|
use Youshido\GraphQL\Type\InterfaceType\AbstractInterfaceType; |
34
|
|
|
use Youshido\GraphQL\Type\ListType\AbstractListType; |
35
|
|
|
use Youshido\GraphQL\Type\Object\AbstractObjectType; |
36
|
|
|
use Youshido\GraphQL\Type\Scalar\AbstractScalarType; |
37
|
|
|
use Youshido\GraphQL\Type\TypeMap; |
38
|
|
|
use Youshido\GraphQL\Type\Union\AbstractUnionType; |
39
|
|
|
use Youshido\GraphQL\Validator\RequestValidator\RequestValidator; |
40
|
|
|
use Youshido\GraphQL\Validator\ResolveValidator\ResolveValidator; |
41
|
|
|
use Youshido\GraphQL\Validator\ResolveValidator\ResolveValidatorInterface; |
42
|
|
|
|
43
|
|
|
class Processor |
44
|
|
|
{ |
45
|
|
|
|
46
|
|
|
const TYPE_NAME_QUERY = '__typename'; |
47
|
|
|
|
48
|
|
|
/** @var ExecutionContext */ |
49
|
|
|
protected $executionContext; |
50
|
|
|
|
51
|
|
|
/** @var ResolveValidatorInterface */ |
52
|
|
|
protected $resolveValidator; |
53
|
|
|
|
54
|
|
|
/** @var array */ |
55
|
|
|
protected $data; |
56
|
|
|
|
57
|
|
|
/** @var int */ |
58
|
|
|
protected $maxComplexity; |
59
|
|
|
|
60
|
|
|
/** @var array DeferredResult[] */ |
61
|
|
|
protected $deferredResultsLeaf = []; |
62
|
|
|
|
63
|
|
|
/** @var array DeferredResult[] */ |
64
|
|
|
protected $deferredResultsComplex = []; |
65
|
|
|
|
66
|
71 |
|
public function __construct(AbstractSchema $schema) |
67
|
|
|
{ |
68
|
71 |
|
if (empty($this->executionContext)) { |
69
|
71 |
|
$this->executionContext = new ExecutionContext($schema); |
70
|
71 |
|
$this->executionContext->setContainer(new Container()); |
71
|
|
|
} |
72
|
|
|
|
73
|
71 |
|
$this->resolveValidator = new ResolveValidator($this->executionContext); |
|
|
|
|
74
|
71 |
|
} |
75
|
|
|
|
76
|
69 |
|
public function processPayload($payload, $variables = [], $reducers = []) |
77
|
|
|
{ |
78
|
69 |
|
$this->data = []; |
79
|
|
|
|
80
|
|
|
try { |
81
|
69 |
|
$this->parseAndCreateRequest($payload, $variables); |
82
|
|
|
|
83
|
68 |
|
if ($this->maxComplexity) { |
84
|
1 |
|
$reducers[] = new MaxComplexityQueryVisitor($this->maxComplexity); |
85
|
|
|
} |
86
|
|
|
|
87
|
68 |
|
if ($reducers) { |
|
|
|
|
88
|
2 |
|
$reducer = new Reducer(); |
89
|
2 |
|
$reducer->reduceQuery($this->executionContext, $reducers); |
90
|
|
|
} |
91
|
|
|
|
92
|
68 |
|
foreach ($this->executionContext->getRequest()->getAllOperations() as $query) { |
93
|
68 |
|
if ($operationResult = $this->resolveQuery($query)) { |
94
|
68 |
|
$this->data = array_replace_recursive($this->data, $operationResult); |
95
|
|
|
}; |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
// If the processor found any deferred results, resolve them now. |
99
|
68 |
|
if (!empty($this->data) && (!empty($this->deferredResultsLeaf) || !empty($this->deferredResultsComplex))) { |
100
|
|
|
try { |
101
|
4 |
|
while ($deferredResolver = array_shift($this->deferredResultsComplex)) { |
102
|
4 |
|
$deferredResolver->resolve(); |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
// Deferred scalar and enum fields should be resolved last to |
106
|
|
|
// pick up as many as possible for a single batch. |
107
|
4 |
|
while ($deferredResolver = array_shift($this->deferredResultsLeaf)) { |
108
|
|
|
$deferredResolver->resolve(); |
109
|
|
|
} |
110
|
|
|
} catch (\Exception $e) { |
111
|
|
|
$this->executionContext->addError($e); |
112
|
4 |
|
} finally { |
113
|
68 |
|
$this->data = static::unpackDeferredResults($this->data); |
114
|
|
|
} |
115
|
|
|
} |
116
|
|
|
|
117
|
5 |
|
} catch (\Exception $e) { |
118
|
5 |
|
$this->executionContext->addError($e); |
119
|
|
|
} |
120
|
|
|
|
121
|
69 |
|
return $this; |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
/** |
125
|
|
|
* Unpack results stored inside deferred resolvers. |
126
|
|
|
* |
127
|
|
|
* @param mixed $result |
128
|
|
|
* The result ree. |
129
|
|
|
* |
130
|
|
|
* @return mixed |
131
|
|
|
* The unpacked result. |
132
|
|
|
*/ |
133
|
4 |
|
public static function unpackDeferredResults($result) |
134
|
|
|
{ |
135
|
4 |
|
while ($result instanceof DeferredResult) { |
136
|
4 |
|
$result = $result->result; |
137
|
|
|
} |
138
|
|
|
|
139
|
4 |
|
if (is_array($result)) { |
140
|
4 |
|
foreach ($result as $key => $value) { |
141
|
4 |
|
$result[$key] = static::unpackDeferredResults($value); |
142
|
|
|
} |
143
|
|
|
} |
144
|
|
|
|
145
|
4 |
|
return $result; |
146
|
|
|
} |
147
|
|
|
|
148
|
68 |
|
protected function resolveQuery(AstQuery $query) |
149
|
|
|
{ |
150
|
68 |
|
$schema = $this->executionContext->getSchema(); |
151
|
68 |
|
$type = $query instanceof AstMutation ? $schema->getMutationType() : $schema->getQueryType(); |
152
|
68 |
|
$field = new Field([ |
153
|
68 |
|
'name' => $query instanceof AstMutation ? 'mutation' : 'query', |
154
|
68 |
|
'type' => $type |
155
|
|
|
]); |
156
|
|
|
|
157
|
68 |
|
if (self::TYPE_NAME_QUERY == $query->getName()) { |
158
|
1 |
|
return [$this->getAlias($query) => $type->getName()]; |
159
|
|
|
} |
160
|
|
|
|
161
|
68 |
|
$this->resolveValidator->assetTypeHasField($type, $query); |
162
|
68 |
|
$value = $this->resolveField($field, $query); |
163
|
|
|
|
164
|
68 |
|
return [$this->getAlias($query) => $value]; |
165
|
|
|
} |
166
|
|
|
|
167
|
68 |
|
protected function resolveField(FieldInterface $field, AstFieldInterface $ast, $parentValue = null, $fromObject = false) |
168
|
|
|
{ |
169
|
|
|
try { |
170
|
|
|
/** @var AbstractObjectType $type */ |
171
|
68 |
|
$type = $field->getType(); |
172
|
68 |
|
$nonNullType = $type->getNullableType(); |
173
|
|
|
|
174
|
68 |
|
if (self::TYPE_NAME_QUERY == $ast->getName()) { |
175
|
2 |
|
return $nonNullType->getName(); |
176
|
|
|
} |
177
|
|
|
|
178
|
68 |
|
$this->resolveValidator->assetTypeHasField($nonNullType, $ast); |
179
|
|
|
|
180
|
68 |
|
$targetField = $nonNullType->getField($ast->getName()); |
181
|
|
|
|
182
|
68 |
|
$this->prepareAstArguments($targetField, $ast, $this->executionContext->getRequest()); |
183
|
67 |
|
$this->resolveValidator->assertValidArguments($targetField, $ast, $this->executionContext->getRequest()); |
184
|
|
|
|
185
|
62 |
|
switch ($kind = $targetField->getType()->getNullableType()->getKind()) { |
186
|
62 |
|
case TypeMap::KIND_ENUM: |
187
|
61 |
|
case TypeMap::KIND_SCALAR: |
188
|
54 |
|
if ($ast instanceof AstQuery && $ast->hasFields()) { |
189
|
2 |
|
throw new ResolveException(sprintf('You can\'t specify fields for scalar type "%s"', $targetField->getType()->getNullableType()->getName()), $ast->getLocation()); |
190
|
|
|
} |
191
|
|
|
|
192
|
54 |
|
return $this->resolveScalar($targetField, $ast, $parentValue); |
193
|
|
|
|
194
|
45 |
View Code Duplication |
case TypeMap::KIND_OBJECT: |
|
|
|
|
195
|
|
|
/** @var $type AbstractObjectType */ |
196
|
31 |
|
if (!$ast instanceof AstQuery) { |
197
|
1 |
|
throw new ResolveException(sprintf('You have to specify fields for "%s"', $ast->getName()), $ast->getLocation()); |
198
|
|
|
} |
199
|
|
|
|
200
|
31 |
|
return $this->resolveObject($targetField, $ast, $parentValue); |
201
|
|
|
|
202
|
31 |
|
case TypeMap::KIND_LIST: |
203
|
28 |
|
return $this->resolveList($targetField, $ast, $parentValue); |
204
|
|
|
|
205
|
6 |
|
case TypeMap::KIND_UNION: |
206
|
5 |
View Code Duplication |
case TypeMap::KIND_INTERFACE: |
|
|
|
|
207
|
6 |
|
if (!$ast instanceof AstQuery) { |
208
|
|
|
throw new ResolveException(sprintf('You have to specify fields for "%s"', $ast->getName()), $ast->getLocation()); |
209
|
|
|
} |
210
|
|
|
|
211
|
6 |
|
return $this->resolveComposite($targetField, $ast, $parentValue); |
212
|
|
|
|
213
|
|
|
default: |
214
|
|
|
throw new ResolveException(sprintf('Resolving type with kind "%s" not supported', $kind)); |
215
|
|
|
} |
216
|
17 |
|
} catch (\Exception $e) { |
217
|
17 |
|
$this->executionContext->addError($e); |
218
|
|
|
|
219
|
17 |
|
if ($fromObject) { |
220
|
4 |
|
throw $e; |
221
|
|
|
} |
222
|
|
|
|
223
|
15 |
|
return null; |
224
|
|
|
} |
225
|
|
|
} |
226
|
|
|
|
227
|
68 |
|
private function prepareAstArguments(FieldInterface $field, AstFieldInterface $query, Request $request) |
228
|
|
|
{ |
229
|
68 |
|
foreach ($query->getArguments() as $astArgument) { |
230
|
38 |
|
if ($field->hasArgument($astArgument->getName())) { |
231
|
38 |
|
$argumentType = $field->getArgument($astArgument->getName())->getType()->getNullableType(); |
232
|
|
|
|
233
|
38 |
|
$astArgument->setValue($this->prepareArgumentValue($astArgument->getValue(), $argumentType, $request)); |
234
|
|
|
} |
235
|
|
|
} |
236
|
67 |
|
} |
237
|
|
|
|
238
|
38 |
|
private function prepareArgumentValue($argumentValue, AbstractType $argumentType, Request $request) |
239
|
|
|
{ |
240
|
38 |
|
switch ($argumentType->getKind()) { |
241
|
38 |
|
case TypeMap::KIND_LIST: |
242
|
|
|
/** @var $argumentType AbstractListType */ |
243
|
12 |
|
$result = []; |
244
|
12 |
|
if ($argumentValue instanceof AstInputList || is_array($argumentValue)) { |
245
|
9 |
|
$list = is_array($argumentValue) ? $argumentValue : $argumentValue->getValue(); |
246
|
9 |
|
foreach ($list as $item) { |
247
|
9 |
|
$result[] = $this->prepareArgumentValue($item, $argumentType->getItemType()->getNullableType(), $request); |
248
|
|
|
} |
249
|
|
|
} else { |
250
|
3 |
|
if ($argumentValue instanceof VariableReference) { |
251
|
3 |
|
return $this->getVariableReferenceArgumentValue($argumentValue, $argumentType, $request); |
252
|
|
|
} |
253
|
|
|
} |
254
|
|
|
|
255
|
9 |
|
return $result; |
256
|
|
|
|
257
|
37 |
|
case TypeMap::KIND_INPUT_OBJECT: |
258
|
|
|
/** @var $argumentType AbstractInputObjectType */ |
259
|
6 |
|
$result = []; |
260
|
6 |
|
if ($argumentValue instanceof AstInputObject) { |
261
|
5 |
|
foreach ($argumentType->getFields() as $field) { |
262
|
|
|
/** @var $field Field */ |
263
|
5 |
|
if ($field->getConfig()->has('defaultValue')) { |
264
|
5 |
|
$result[$field->getName()] = $field->getType()->getNullableType()->parseInputValue($field->getConfig()->get('defaultValue')); |
265
|
|
|
} |
266
|
|
|
} |
267
|
5 |
|
foreach ($argumentValue->getValue() as $key => $item) { |
268
|
5 |
|
if ($argumentType->hasField($key)) { |
269
|
5 |
|
$result[$key] = $this->prepareArgumentValue($item, $argumentType->getField($key)->getType()->getNullableType(), $request); |
270
|
|
|
} else { |
271
|
5 |
|
$result[$key] = $item; |
272
|
|
|
} |
273
|
|
|
} |
274
|
|
|
} else { |
275
|
2 |
|
if ($argumentValue instanceof VariableReference) { |
276
|
|
|
return $this->getVariableReferenceArgumentValue($argumentValue, $argumentType, $request); |
277
|
|
|
} else { |
278
|
2 |
|
if (is_array($argumentValue)) { |
279
|
1 |
|
return $argumentValue; |
280
|
|
|
} |
281
|
|
|
} |
282
|
|
|
} |
283
|
|
|
|
284
|
6 |
|
return $result; |
285
|
|
|
|
286
|
36 |
|
case TypeMap::KIND_SCALAR: |
287
|
4 |
|
case TypeMap::KIND_ENUM: |
288
|
|
|
/** @var $argumentValue AstLiteral|VariableReference */ |
289
|
36 |
|
if ($argumentValue instanceof VariableReference) { |
290
|
7 |
|
return $this->getVariableReferenceArgumentValue($argumentValue, $argumentType, $request); |
291
|
|
|
} else { |
292
|
31 |
|
if ($argumentValue instanceof AstLiteral) { |
293
|
19 |
|
return $argumentValue->getValue(); |
294
|
|
|
} else { |
295
|
13 |
|
return $argumentValue; |
296
|
|
|
} |
297
|
|
|
} |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
throw new ResolveException('Argument type not supported'); |
301
|
|
|
} |
302
|
|
|
|
303
|
9 |
|
private function getVariableReferenceArgumentValue(VariableReference $variableReference, AbstractType $argumentType, Request $request) |
304
|
|
|
{ |
305
|
9 |
|
$variable = $variableReference->getVariable(); |
306
|
9 |
|
if ($argumentType->getKind() === TypeMap::KIND_LIST) { |
307
|
|
|
if ( |
308
|
3 |
|
(!$variable->isArray() && !is_array($variable->getValue())) || |
309
|
3 |
|
($variable->getTypeName() !== $argumentType->getNamedType()->getNullableType()->getName()) || |
310
|
3 |
|
($argumentType->getNamedType()->getKind() === TypeMap::KIND_NON_NULL && $variable->isArrayElementNullable()) |
311
|
|
|
) { |
312
|
3 |
|
throw new ResolveException(sprintf('Invalid variable "%s" type, allowed type is "%s"', $variable->getName(), $argumentType->getNamedType()->getNullableType()->getName()), $variable->getLocation()); |
313
|
|
|
} |
314
|
|
|
} else { |
315
|
7 |
|
if ($variable->getTypeName() !== $argumentType->getName()) { |
316
|
1 |
|
throw new ResolveException(sprintf('Invalid variable "%s" type, allowed type is "%s"', $variable->getName(), $argumentType->getName()), $variable->getLocation()); |
317
|
|
|
} |
318
|
|
|
} |
319
|
|
|
|
320
|
8 |
|
$requestValue = $request->getVariable($variable->getName()); |
321
|
8 |
|
if ((null === $requestValue && $variable->isNullable()) && !$request->hasVariable($variable->getName())) { |
322
|
|
|
throw new ResolveException(sprintf('Variable "%s" does not exist in request', $variable->getName()), $variable->getLocation()); |
323
|
|
|
} |
324
|
|
|
|
325
|
8 |
|
return $requestValue; |
326
|
|
|
} |
327
|
|
|
|
328
|
|
|
|
329
|
|
|
/** |
330
|
|
|
* @param FieldInterface $field |
331
|
|
|
* @param AbstractObjectType $type |
332
|
|
|
* @param AstFieldInterface $ast |
333
|
|
|
* @param $resolvedValue |
334
|
|
|
* @return array |
335
|
|
|
*/ |
336
|
40 |
|
private function collectResult(FieldInterface $field, AbstractObjectType $type, $ast, $resolvedValue) |
337
|
|
|
{ |
338
|
40 |
|
$result = []; |
339
|
|
|
|
340
|
40 |
|
foreach ($ast->getFields() as $astField) { |
341
|
|
|
switch (true) { |
342
|
40 |
|
case $astField instanceof TypedFragmentReference: |
343
|
2 |
|
$astName = $astField->getTypeName(); |
344
|
2 |
|
$typeName = $type->getName(); |
345
|
|
|
|
346
|
2 |
|
if ($typeName !== $astName) { |
347
|
2 |
|
foreach ($type->getInterfaces() as $interface) { |
348
|
1 |
|
if ($interface->getName() === $astName) { |
349
|
|
|
$result = array_replace_recursive($result, $this->collectResult($field, $type, $astField, $resolvedValue)); |
|
|
|
|
350
|
|
|
|
351
|
1 |
|
break; |
352
|
|
|
} |
353
|
|
|
} |
354
|
|
|
|
355
|
2 |
|
continue 2; |
356
|
|
|
} |
357
|
|
|
|
358
|
2 |
|
$result = array_replace_recursive($result, $this->collectResult($field, $type, $astField, $resolvedValue)); |
|
|
|
|
359
|
|
|
|
360
|
2 |
|
break; |
361
|
|
|
|
362
|
40 |
|
case $astField instanceof FragmentReference: |
363
|
8 |
|
$astFragment = $this->executionContext->getRequest()->getFragment($astField->getName()); |
364
|
8 |
|
$astFragmentModel = $astFragment->getModel(); |
365
|
8 |
|
$typeName = $type->getName(); |
366
|
|
|
|
367
|
8 |
|
if ($typeName !== $astFragmentModel) { |
368
|
2 |
|
foreach ($type->getInterfaces() as $interface) { |
369
|
1 |
|
if ($interface->getName() === $astFragmentModel) { |
370
|
1 |
|
$result = array_replace_recursive($result, $this->collectResult($field, $type, $astFragment, $resolvedValue)); |
|
|
|
|
371
|
|
|
|
372
|
1 |
|
break; |
373
|
|
|
} |
374
|
|
|
} |
375
|
|
|
|
376
|
2 |
|
continue 2; |
377
|
|
|
} |
378
|
|
|
|
379
|
7 |
|
$result = array_replace_recursive($result, $this->collectResult($field, $type, $astFragment, $resolvedValue)); |
|
|
|
|
380
|
|
|
|
381
|
7 |
|
break; |
382
|
|
|
|
383
|
|
|
default: |
384
|
39 |
|
$result = array_replace_recursive($result, [$this->getAlias($astField) => $this->resolveField($field, $astField, $resolvedValue, true)]); |
385
|
|
|
} |
386
|
|
|
} |
387
|
|
|
|
388
|
40 |
|
return $result; |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
/** |
392
|
|
|
* Apply post-process callbacks to all deferred resolvers. |
393
|
|
|
*/ |
394
|
62 |
|
protected function deferredResolve($resolvedValue, FieldInterface $field, callable $callback) { |
395
|
62 |
|
if ($resolvedValue instanceof DeferredResolverInterface) { |
396
|
4 |
|
$deferredResult = new DeferredResult($resolvedValue, $callback); |
397
|
|
|
|
398
|
|
|
// Whenever we stumble upon a deferred resolver, add it to the queue |
399
|
|
|
// to be resolved later. |
400
|
4 |
|
$type = $field->getType()->getNamedType(); |
401
|
4 |
|
if ($type instanceof AbstractScalarType || $type instanceof AbstractEnumType) { |
402
|
|
|
array_push($this->deferredResultsLeaf, $deferredResult); |
403
|
|
|
} else { |
404
|
4 |
|
array_push($this->deferredResultsComplex, $deferredResult); |
405
|
|
|
} |
406
|
|
|
|
407
|
4 |
|
return $deferredResult; |
408
|
|
|
} |
409
|
|
|
// For simple values, invoke the callback immediately. |
410
|
62 |
|
return $callback($resolvedValue); |
411
|
|
|
} |
412
|
|
|
|
413
|
55 |
|
protected function resolveScalar(FieldInterface $field, AstFieldInterface $ast, $parentValue) |
414
|
|
|
{ |
415
|
55 |
|
$resolvedValue = $this->doResolve($field, $ast, $parentValue); |
416
|
|
|
return $this->deferredResolve($resolvedValue, $field, function($resolvedValue) use ($field, $ast, $parentValue) { |
417
|
55 |
|
$this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue); |
418
|
|
|
|
419
|
|
|
/** @var AbstractScalarType $type */ |
420
|
54 |
|
$type = $field->getType()->getNullableType(); |
421
|
|
|
|
422
|
54 |
|
return $type->serialize($resolvedValue); |
423
|
55 |
|
}); |
424
|
|
|
} |
425
|
|
|
|
426
|
28 |
|
protected function resolveList(FieldInterface $field, AstFieldInterface $ast, $parentValue) |
427
|
|
|
{ |
428
|
|
|
/** @var AstQuery $ast */ |
429
|
28 |
|
$resolvedValue = $this->doResolve($field, $ast, $parentValue); |
430
|
|
|
|
431
|
|
|
return $this->deferredResolve($resolvedValue, $field, function ($resolvedValue) use ($field, $ast, $parentValue) { |
432
|
28 |
|
$this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue); |
433
|
|
|
|
434
|
26 |
|
if (null === $resolvedValue) { |
435
|
8 |
|
return null; |
436
|
|
|
} |
437
|
|
|
|
438
|
|
|
/** @var AbstractListType $type */ |
439
|
25 |
|
$type = $field->getType()->getNullableType(); |
440
|
25 |
|
$itemType = $type->getNamedType(); |
441
|
|
|
|
442
|
25 |
|
$fakeAst = clone $ast; |
443
|
25 |
|
if ($fakeAst instanceof AstQuery) { |
444
|
24 |
|
$fakeAst->setArguments([]); |
445
|
|
|
} |
446
|
|
|
|
447
|
25 |
|
$fakeField = new Field([ |
448
|
25 |
|
'name' => $field->getName(), |
449
|
25 |
|
'type' => $itemType, |
450
|
25 |
|
'args' => $field->getArguments(), |
451
|
|
|
]); |
452
|
|
|
|
453
|
25 |
|
$result = []; |
454
|
25 |
|
foreach ($resolvedValue as $resolvedValueItem) { |
455
|
|
|
try { |
456
|
|
|
$fakeField->getConfig()->set('resolve', function () use ($resolvedValueItem) { |
457
|
24 |
|
return $resolvedValueItem; |
458
|
24 |
|
}); |
459
|
|
|
|
460
|
24 |
|
switch ($itemType->getNullableType()->getKind()) { |
461
|
24 |
|
case TypeMap::KIND_ENUM: |
462
|
23 |
|
case TypeMap::KIND_SCALAR: |
463
|
6 |
|
$value = $this->resolveScalar($fakeField, $fakeAst, $resolvedValueItem); |
464
|
|
|
|
465
|
5 |
|
break; |
466
|
|
|
|
467
|
|
|
|
468
|
20 |
|
case TypeMap::KIND_OBJECT: |
469
|
17 |
|
$value = $this->resolveObject($fakeField, $fakeAst, $resolvedValueItem); |
470
|
|
|
|
471
|
17 |
|
break; |
472
|
|
|
|
473
|
4 |
|
case TypeMap::KIND_UNION: |
474
|
3 |
|
case TypeMap::KIND_INTERFACE: |
475
|
4 |
|
$value = $this->resolveComposite($fakeField, $fakeAst, $resolvedValueItem); |
476
|
|
|
|
477
|
4 |
|
break; |
478
|
|
|
|
479
|
|
|
default: |
480
|
23 |
|
$value = null; |
481
|
|
|
} |
482
|
1 |
|
} catch (\Exception $e) { |
483
|
1 |
|
$this->executionContext->addError($e); |
484
|
|
|
|
485
|
1 |
|
$value = null; |
486
|
|
|
} |
487
|
|
|
|
488
|
24 |
|
$result[] = $value; |
489
|
|
|
} |
490
|
|
|
|
491
|
25 |
|
return $result; |
492
|
28 |
|
}); |
493
|
|
|
} |
494
|
|
|
|
495
|
41 |
|
protected function resolveObject(FieldInterface $field, AstFieldInterface $ast, $parentValue, $fromUnion = false) |
496
|
|
|
{ |
497
|
41 |
|
$resolvedValue = $parentValue; |
498
|
41 |
|
if (!$fromUnion) { |
499
|
35 |
|
$resolvedValue = $this->doResolve($field, $ast, $parentValue); |
500
|
|
|
} |
501
|
|
|
|
502
|
|
|
return $this->deferredResolve($resolvedValue, $field, function ($resolvedValue) use ($field, $ast, $parentValue) { |
503
|
41 |
|
$this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue); |
504
|
|
|
|
505
|
41 |
|
if (null === $resolvedValue) { |
506
|
8 |
|
return null; |
507
|
|
|
} |
508
|
|
|
/** @var AbstractObjectType $type */ |
509
|
40 |
|
$type = $field->getType()->getNullableType(); |
510
|
|
|
|
511
|
|
|
try { |
512
|
40 |
|
return $this->collectResult($field, $type, $ast, $resolvedValue); |
513
|
4 |
|
} catch (\Exception $e) { |
514
|
4 |
|
return null; |
515
|
|
|
} |
516
|
41 |
|
}); |
517
|
|
|
} |
518
|
|
|
|
519
|
8 |
|
protected function resolveComposite(FieldInterface $field, AstFieldInterface $ast, $parentValue) |
520
|
|
|
{ |
521
|
|
|
/** @var AstQuery $ast */ |
522
|
8 |
|
$resolvedValue = $this->doResolve($field, $ast, $parentValue); |
523
|
8 |
|
return $this->deferredResolve($resolvedValue, $field, function ($resolvedValue) use ($field, $ast, $parentValue) { |
524
|
8 |
|
$this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue); |
525
|
|
|
|
526
|
8 |
|
if (null === $resolvedValue) { |
527
|
|
|
return null; |
528
|
|
|
} |
529
|
|
|
|
530
|
|
|
/** @var AbstractUnionType $type */ |
531
|
8 |
|
$type = $field->getType()->getNullableType(); |
532
|
8 |
|
$resolveInfo = new ResolveInfo( |
533
|
8 |
|
$field, |
534
|
8 |
|
$ast instanceof AstQuery ? $ast->getFields() : [], |
535
|
8 |
|
$this->executionContext |
536
|
|
|
); |
537
|
8 |
|
$resolvedType = $type->resolveType($resolvedValue, $resolveInfo); |
|
|
|
|
538
|
|
|
|
539
|
8 |
|
if (!$resolvedType) { |
540
|
|
|
throw new ResolveException('Resolving function must return type'); |
541
|
|
|
} |
542
|
|
|
|
543
|
8 |
|
if ($type instanceof AbstractInterfaceType) { |
544
|
6 |
|
$this->resolveValidator->assertTypeImplementsInterface($resolvedType, $type); |
545
|
|
|
} else { |
546
|
2 |
|
$this->resolveValidator->assertTypeInUnionTypes($resolvedType, $type); |
547
|
|
|
} |
548
|
|
|
|
549
|
8 |
|
$fakeField = new Field([ |
550
|
8 |
|
'name' => $field->getName(), |
551
|
8 |
|
'type' => $resolvedType, |
552
|
8 |
|
'args' => $field->getArguments(), |
553
|
|
|
]); |
554
|
|
|
|
555
|
8 |
|
return $this->resolveObject($fakeField, $ast, $resolvedValue, true); |
556
|
8 |
|
}); |
557
|
|
|
} |
558
|
|
|
|
559
|
69 |
|
protected function parseAndCreateRequest($payload, $variables = []) |
560
|
|
|
{ |
561
|
69 |
|
if (empty($payload)) { |
562
|
1 |
|
throw new \InvalidArgumentException('Must provide an operation.'); |
563
|
|
|
} |
564
|
|
|
|
565
|
69 |
|
$parser = new Parser(); |
566
|
69 |
|
$request = new Request($parser->parse($payload), $variables); |
567
|
|
|
|
568
|
69 |
|
(new RequestValidator())->validate($request); |
569
|
|
|
|
570
|
68 |
|
$this->executionContext->setRequest($request); |
571
|
68 |
|
} |
572
|
|
|
|
573
|
62 |
|
protected function doResolve(FieldInterface $field, AstFieldInterface $ast, $parentValue = null) |
574
|
|
|
{ |
575
|
|
|
/** @var AstQuery|AstField $ast */ |
576
|
62 |
|
$arguments = $this->parseArgumentsValues($field, $ast); |
577
|
62 |
|
$astFields = $ast instanceof AstQuery ? $ast->getFields() : []; |
578
|
|
|
|
579
|
62 |
|
return $field->resolve($parentValue, $arguments, $this->createResolveInfo($field, $astFields)); |
580
|
|
|
} |
581
|
|
|
|
582
|
62 |
|
protected function parseArgumentsValues(FieldInterface $field, AstFieldInterface $ast) |
583
|
|
|
{ |
584
|
62 |
|
$values = []; |
585
|
62 |
|
$defaults = []; |
586
|
|
|
|
587
|
62 |
|
foreach ($field->getArguments() as $argument) { |
588
|
|
|
/** @var $argument InputField */ |
589
|
45 |
|
if ($argument->getConfig()->has('defaultValue')) { |
590
|
45 |
|
$defaults[$argument->getName()] = $argument->getConfig()->getDefaultValue(); |
591
|
|
|
} |
592
|
|
|
} |
593
|
|
|
|
594
|
62 |
|
foreach ($ast->getArguments() as $astArgument) { |
595
|
34 |
|
$argument = $field->getArgument($astArgument->getName()); |
596
|
34 |
|
$argumentType = $argument->getType()->getNullableType(); |
597
|
|
|
|
598
|
34 |
|
$values[$argument->getName()] = $argumentType->parseValue($astArgument->getValue()); |
599
|
|
|
|
600
|
34 |
|
if (isset($defaults[$argument->getName()])) { |
601
|
34 |
|
unset($defaults[$argument->getName()]); |
602
|
|
|
} |
603
|
|
|
} |
604
|
|
|
|
605
|
62 |
|
return array_merge($values, $defaults); |
606
|
|
|
} |
607
|
|
|
|
608
|
68 |
|
private function getAlias(AstFieldInterface $ast) |
609
|
|
|
{ |
610
|
68 |
|
return $ast->getAlias() ?: $ast->getName(); |
611
|
|
|
} |
612
|
|
|
|
613
|
62 |
|
protected function createResolveInfo(FieldInterface $field, array $astFields) |
614
|
|
|
{ |
615
|
62 |
|
return new ResolveInfo($field, $astFields, $this->executionContext); |
616
|
|
|
} |
617
|
|
|
|
618
|
|
|
|
619
|
|
|
/** |
620
|
|
|
* You can access ExecutionContext to check errors and inject dependencies |
621
|
|
|
* |
622
|
|
|
* @return ExecutionContext |
623
|
|
|
*/ |
624
|
11 |
|
public function getExecutionContext() |
625
|
|
|
{ |
626
|
11 |
|
return $this->executionContext; |
627
|
|
|
} |
628
|
|
|
|
629
|
68 |
|
public function getResponseData() |
630
|
|
|
{ |
631
|
68 |
|
$result = []; |
632
|
|
|
|
633
|
68 |
|
if (!empty($this->data)) { |
634
|
67 |
|
$result['data'] = $this->data; |
635
|
|
|
} |
636
|
|
|
|
637
|
68 |
|
if ($this->executionContext->hasErrors()) { |
638
|
19 |
|
$result['errors'] = $this->executionContext->getErrorsArray(); |
639
|
|
|
} |
640
|
|
|
|
641
|
68 |
|
return $result; |
642
|
|
|
} |
643
|
|
|
|
644
|
|
|
/** |
645
|
|
|
* @return int |
646
|
|
|
*/ |
647
|
|
|
public function getMaxComplexity() |
648
|
|
|
{ |
649
|
|
|
return $this->maxComplexity; |
650
|
|
|
} |
651
|
|
|
|
652
|
|
|
/** |
653
|
|
|
* @param int $maxComplexity |
654
|
|
|
*/ |
655
|
1 |
|
public function setMaxComplexity($maxComplexity) |
656
|
|
|
{ |
657
|
1 |
|
$this->maxComplexity = $maxComplexity; |
658
|
1 |
|
} |
659
|
|
|
|
660
|
|
|
} |
661
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.