1 | <?php |
||||
2 | |||||
3 | declare(strict_types=1); |
||||
4 | |||||
5 | namespace GraphQL\Utils; |
||||
6 | |||||
7 | use GraphQL\Error\Error; |
||||
8 | use GraphQL\Language\AST\DirectiveDefinitionNode; |
||||
9 | use GraphQL\Language\AST\DocumentNode; |
||||
10 | use GraphQL\Language\AST\EnumTypeExtensionNode; |
||||
11 | use GraphQL\Language\AST\InputObjectTypeExtensionNode; |
||||
12 | use GraphQL\Language\AST\InterfaceTypeExtensionNode; |
||||
13 | use GraphQL\Language\AST\Node; |
||||
14 | use GraphQL\Language\AST\ObjectTypeExtensionNode; |
||||
15 | use GraphQL\Language\AST\SchemaDefinitionNode; |
||||
16 | use GraphQL\Language\AST\SchemaTypeExtensionNode; |
||||
17 | use GraphQL\Language\AST\TypeDefinitionNode; |
||||
18 | use GraphQL\Language\AST\TypeExtensionNode; |
||||
19 | use GraphQL\Language\AST\UnionTypeExtensionNode; |
||||
20 | use GraphQL\Type\Definition\CustomScalarType; |
||||
21 | use GraphQL\Type\Definition\Directive; |
||||
22 | use GraphQL\Type\Definition\EnumType; |
||||
23 | use GraphQL\Type\Definition\EnumValueDefinition; |
||||
24 | use GraphQL\Type\Definition\FieldArgument; |
||||
25 | use GraphQL\Type\Definition\InputObjectType; |
||||
26 | use GraphQL\Type\Definition\InterfaceType; |
||||
27 | use GraphQL\Type\Definition\ListOfType; |
||||
28 | use GraphQL\Type\Definition\NamedType; |
||||
29 | use GraphQL\Type\Definition\NonNull; |
||||
30 | use GraphQL\Type\Definition\ObjectType; |
||||
31 | use GraphQL\Type\Definition\ScalarType; |
||||
32 | use GraphQL\Type\Definition\Type; |
||||
33 | use GraphQL\Type\Definition\UnionType; |
||||
34 | use GraphQL\Type\Introspection; |
||||
35 | use GraphQL\Type\Schema; |
||||
36 | use GraphQL\Validator\DocumentValidator; |
||||
37 | use function array_keys; |
||||
38 | use function array_map; |
||||
39 | use function array_merge; |
||||
40 | use function array_values; |
||||
41 | use function count; |
||||
42 | |||||
43 | class SchemaExtender |
||||
44 | { |
||||
45 | const SCHEMA_EXTENSION = 'SchemaExtension'; |
||||
46 | |||||
47 | /** @var Type[] */ |
||||
48 | protected static $extendTypeCache; |
||||
49 | |||||
50 | /** @var mixed[] */ |
||||
51 | protected static $typeExtensionsMap; |
||||
52 | |||||
53 | /** @var ASTDefinitionBuilder */ |
||||
54 | protected static $astBuilder; |
||||
55 | |||||
56 | /** |
||||
57 | * @return TypeExtensionNode[]|null |
||||
58 | */ |
||||
59 | 60 | protected static function getExtensionASTNodes(NamedType $type) : ?array |
|||
60 | { |
||||
61 | 60 | if (! $type instanceof Type) { |
|||
62 | return null; |
||||
63 | } |
||||
64 | |||||
65 | 60 | $name = $type->name; |
|||
66 | 60 | if ($type->extensionASTNodes !== null) { |
|||
67 | 59 | if (isset(static::$typeExtensionsMap[$name])) { |
|||
68 | 24 | return array_merge($type->extensionASTNodes, static::$typeExtensionsMap[$name]); |
|||
69 | } |
||||
70 | |||||
71 | 55 | return $type->extensionASTNodes; |
|||
72 | } |
||||
73 | |||||
74 | 53 | return static::$typeExtensionsMap[$name] ?? null; |
|||
75 | } |
||||
76 | |||||
77 | /** |
||||
78 | * @throws Error |
||||
79 | */ |
||||
80 | 39 | protected static function checkExtensionNode(Type $type, Node $node) : void |
|||
81 | { |
||||
82 | switch (true) { |
||||
83 | 39 | case $node instanceof ObjectTypeExtensionNode: |
|||
84 | 25 | if (! ($type instanceof ObjectType)) { |
|||
85 | 1 | throw new Error( |
|||
86 | 1 | 'Cannot extend non-object type "' . $type->name . '".', |
|||
87 | 1 | [$node] |
|||
88 | ); |
||||
89 | } |
||||
90 | 24 | break; |
|||
91 | 23 | case $node instanceof InterfaceTypeExtensionNode: |
|||
92 | 10 | if (! ($type instanceof InterfaceType)) { |
|||
93 | 1 | throw new Error( |
|||
94 | 1 | 'Cannot extend non-interface type "' . $type->name . '".', |
|||
95 | 1 | [$node] |
|||
96 | ); |
||||
97 | } |
||||
98 | 9 | break; |
|||
99 | 17 | case $node instanceof EnumTypeExtensionNode: |
|||
100 | 7 | if (! ($type instanceof EnumType)) { |
|||
101 | 1 | throw new Error( |
|||
102 | 1 | 'Cannot extend non-enum type "' . $type->name . '".', |
|||
103 | 1 | [$node] |
|||
104 | ); |
||||
105 | } |
||||
106 | 6 | break; |
|||
107 | 13 | case $node instanceof UnionTypeExtensionNode: |
|||
108 | 9 | if (! ($type instanceof UnionType)) { |
|||
109 | 1 | throw new Error( |
|||
110 | 1 | 'Cannot extend non-union type "' . $type->name . '".', |
|||
111 | 1 | [$node] |
|||
112 | ); |
||||
113 | } |
||||
114 | 8 | break; |
|||
115 | 8 | case $node instanceof InputObjectTypeExtensionNode: |
|||
116 | 7 | if (! ($type instanceof InputObjectType)) { |
|||
117 | 1 | throw new Error( |
|||
118 | 1 | 'Cannot extend non-input object type "' . $type->name . '".', |
|||
119 | 1 | [$node] |
|||
120 | ); |
||||
121 | } |
||||
122 | 6 | break; |
|||
123 | } |
||||
124 | 38 | } |
|||
125 | |||||
126 | 43 | protected static function extendScalarType(ScalarType $type) : CustomScalarType |
|||
127 | { |
||||
128 | 43 | return new CustomScalarType([ |
|||
129 | 43 | 'name' => $type->name, |
|||
130 | 43 | 'description' => $type->description, |
|||
131 | 43 | 'astNode' => $type->astNode, |
|||
132 | 43 | 'serialize' => $type->config['serialize'] ?? null, |
|||
133 | 43 | 'parseValue' => $type->config['parseValue'] ?? null, |
|||
134 | 43 | 'parseLiteral' => $type->config['parseLiteral'] ?? null, |
|||
135 | 43 | 'extensionASTNodes' => static::getExtensionASTNodes($type), |
|||
136 | ]); |
||||
137 | } |
||||
138 | |||||
139 | 46 | protected static function extendUnionType(UnionType $type) : UnionType |
|||
140 | { |
||||
141 | 46 | return new UnionType([ |
|||
142 | 46 | 'name' => $type->name, |
|||
143 | 46 | 'description' => $type->description, |
|||
144 | 'types' => static function () use ($type) { |
||||
145 | 45 | return static::extendPossibleTypes($type); |
|||
146 | 46 | }, |
|||
147 | 46 | 'astNode' => $type->astNode, |
|||
148 | 46 | 'resolveType' => $type->config['resolveType'] ?? null, |
|||
149 | 46 | 'extensionASTNodes' => static::getExtensionASTNodes($type), |
|||
150 | ]); |
||||
151 | } |
||||
152 | |||||
153 | 44 | protected static function extendEnumType(EnumType $type) : EnumType |
|||
154 | { |
||||
155 | 44 | return new EnumType([ |
|||
156 | 44 | 'name' => $type->name, |
|||
157 | 44 | 'description' => $type->description, |
|||
158 | 44 | 'values' => static::extendValueMap($type), |
|||
159 | 43 | 'astNode' => $type->astNode, |
|||
160 | 43 | 'extensionASTNodes' => static::getExtensionASTNodes($type), |
|||
161 | ]); |
||||
162 | } |
||||
163 | |||||
164 | 45 | protected static function extendInputObjectType(InputObjectType $type) : InputObjectType |
|||
165 | { |
||||
166 | 45 | return new InputObjectType([ |
|||
167 | 45 | 'name' => $type->name, |
|||
168 | 45 | 'description' => $type->description, |
|||
169 | 'fields' => static function () use ($type) { |
||||
170 | 45 | return static::extendInputFieldMap($type); |
|||
171 | 45 | }, |
|||
172 | 45 | 'astNode' => $type->astNode, |
|||
173 | 45 | 'extensionASTNodes' => static::getExtensionASTNodes($type), |
|||
174 | ]); |
||||
175 | } |
||||
176 | |||||
177 | /** |
||||
178 | * @return mixed[] |
||||
179 | */ |
||||
180 | 45 | protected static function extendInputFieldMap(InputObjectType $type) : array |
|||
181 | { |
||||
182 | 45 | $newFieldMap = []; |
|||
183 | 45 | $oldFieldMap = $type->getFields(); |
|||
184 | 45 | foreach ($oldFieldMap as $fieldName => $field) { |
|||
185 | 44 | $newFieldMap[$fieldName] = [ |
|||
186 | 44 | 'description' => $field->description, |
|||
187 | 44 | 'type' => static::extendType($field->type), |
|||
188 | 44 | 'astNode' => $field->astNode, |
|||
189 | ]; |
||||
190 | |||||
191 | 44 | if (! $field->defaultValueExists()) { |
|||
192 | 44 | continue; |
|||
193 | } |
||||
194 | |||||
195 | $newFieldMap[$fieldName]['defaultValue'] = $field->defaultValue; |
||||
196 | } |
||||
197 | |||||
198 | 45 | $extensions = static::$typeExtensionsMap[$type->name] ?? null; |
|||
199 | 45 | if ($extensions !== null) { |
|||
200 | 6 | foreach ($extensions as $extension) { |
|||
201 | 6 | foreach ($extension->fields as $field) { |
|||
202 | 5 | $fieldName = $field->name->value; |
|||
203 | 5 | if (isset($oldFieldMap[$fieldName])) { |
|||
204 | 1 | throw new Error('Field "' . $type->name . '.' . $fieldName . '" already exists in the schema. It cannot also be defined in this type extension.', [$field]); |
|||
205 | } |
||||
206 | |||||
207 | 5 | $newFieldMap[$fieldName] = static::$astBuilder->buildInputField($field); |
|||
208 | } |
||||
209 | } |
||||
210 | } |
||||
211 | |||||
212 | 45 | return $newFieldMap; |
|||
213 | } |
||||
214 | |||||
215 | /** |
||||
216 | * @return mixed[] |
||||
217 | */ |
||||
218 | 44 | protected static function extendValueMap(EnumType $type) : array |
|||
219 | { |
||||
220 | 44 | $newValueMap = []; |
|||
221 | /** @var EnumValueDefinition[] $oldValueMap */ |
||||
222 | 44 | $oldValueMap = []; |
|||
223 | 44 | foreach ($type->getValues() as $value) { |
|||
224 | 43 | $oldValueMap[$value->name] = $value; |
|||
225 | } |
||||
226 | |||||
227 | 44 | foreach ($oldValueMap as $key => $value) { |
|||
228 | 43 | $newValueMap[$key] = [ |
|||
229 | 43 | 'name' => $value->name, |
|||
230 | 43 | 'description' => $value->description, |
|||
231 | 43 | 'value' => $value->value, |
|||
232 | 43 | 'deprecationReason' => $value->deprecationReason, |
|||
233 | 43 | 'astNode' => $value->astNode, |
|||
234 | ]; |
||||
235 | } |
||||
236 | |||||
237 | 44 | $extensions = static::$typeExtensionsMap[$type->name] ?? null; |
|||
238 | 44 | if ($extensions !== null) { |
|||
239 | 6 | foreach ($extensions as $extension) { |
|||
240 | 6 | foreach ($extension->values as $value) { |
|||
241 | 5 | $valueName = $value->name->value; |
|||
242 | 5 | if (isset($oldValueMap[$valueName])) { |
|||
243 | 1 | throw new Error('Enum value "' . $type->name . '.' . $valueName . '" already exists in the schema. It cannot also be defined in this type extension.', [$value]); |
|||
244 | } |
||||
245 | 5 | $newValueMap[$valueName] = static::$astBuilder->buildEnumValue($value); |
|||
246 | } |
||||
247 | } |
||||
248 | } |
||||
249 | |||||
250 | 43 | return $newValueMap; |
|||
251 | } |
||||
252 | |||||
253 | /** |
||||
254 | * @return ObjectType[] |
||||
255 | */ |
||||
256 | 45 | protected static function extendPossibleTypes(UnionType $type) : array |
|||
257 | { |
||||
258 | $possibleTypes = array_map(static function ($type) { |
||||
259 | 44 | return static::extendNamedType($type); |
|||
260 | 45 | }, $type->getTypes()); |
|||
261 | |||||
262 | 45 | $extensions = static::$typeExtensionsMap[$type->name] ?? null; |
|||
263 | 45 | if ($extensions !== null) { |
|||
264 | 8 | foreach ($extensions as $extension) { |
|||
265 | 8 | foreach ($extension->types as $namedType) { |
|||
266 | 8 | $possibleTypes[] = static::$astBuilder->buildType($namedType); |
|||
267 | } |
||||
268 | } |
||||
269 | } |
||||
270 | |||||
271 | 45 | return $possibleTypes; |
|||
272 | } |
||||
273 | |||||
274 | /** |
||||
275 | * @return InterfaceType[] |
||||
276 | */ |
||||
277 | 55 | protected static function extendImplementedInterfaces(ObjectType $type) : array |
|||
278 | { |
||||
279 | $interfaces = array_map(static function (InterfaceType $interfaceType) { |
||||
280 | 46 | return static::extendNamedType($interfaceType); |
|||
281 | 55 | }, $type->getInterfaces()); |
|||
282 | |||||
283 | 55 | $extensions = static::$typeExtensionsMap[$type->name] ?? null; |
|||
284 | 55 | if ($extensions !== null) { |
|||
285 | /** @var ObjectTypeExtensionNode $extension */ |
||||
286 | 24 | foreach ($extensions as $extension) { |
|||
287 | 24 | foreach ($extension->interfaces as $namedType) { |
|||
288 | 24 | $interfaces[] = static::$astBuilder->buildType($namedType); |
|||
289 | } |
||||
290 | } |
||||
291 | } |
||||
292 | |||||
293 | 55 | return $interfaces; |
|||
294 | } |
||||
295 | |||||
296 | 56 | protected static function extendType($typeDef) |
|||
297 | { |
||||
298 | 56 | if ($typeDef instanceof ListOfType) { |
|||
299 | 42 | return Type::listOf(static::extendType($typeDef->ofType)); |
|||
300 | } |
||||
301 | |||||
302 | 56 | if ($typeDef instanceof NonNull) { |
|||
303 | 56 | return Type::nonNull(static::extendType($typeDef->getWrappedType())); |
|||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
304 | } |
||||
305 | |||||
306 | 56 | return static::extendNamedType($typeDef); |
|||
307 | } |
||||
308 | |||||
309 | /** |
||||
310 | * @param FieldArgument[] $args |
||||
311 | * |
||||
312 | * @return mixed[] |
||||
313 | */ |
||||
314 | 56 | protected static function extendArgs(array $args) : array |
|||
315 | { |
||||
316 | 56 | return Utils::keyValMap( |
|||
317 | 56 | $args, |
|||
318 | static function (FieldArgument $arg) { |
||||
319 | 56 | return $arg->name; |
|||
320 | 56 | }, |
|||
321 | static function (FieldArgument $arg) { |
||||
322 | $def = [ |
||||
323 | 56 | 'type' => static::extendType($arg->getType()), |
|||
324 | 56 | 'description' => $arg->description, |
|||
325 | 56 | 'astNode' => $arg->astNode, |
|||
326 | ]; |
||||
327 | |||||
328 | 56 | if ($arg->defaultValueExists()) { |
|||
329 | 55 | $def['defaultValue'] = $arg->defaultValue; |
|||
330 | } |
||||
331 | |||||
332 | 56 | return $def; |
|||
333 | 56 | } |
|||
334 | ); |
||||
335 | } |
||||
336 | |||||
337 | /** |
||||
338 | * @param InterfaceType|ObjectType $type |
||||
339 | * |
||||
340 | * @return mixed[] |
||||
341 | * |
||||
342 | * @throws Error |
||||
343 | */ |
||||
344 | 55 | protected static function extendFieldMap($type) : array |
|||
345 | { |
||||
346 | 55 | $newFieldMap = []; |
|||
347 | 55 | $oldFieldMap = $type->getFields(); |
|||
348 | |||||
349 | 55 | foreach (array_keys($oldFieldMap) as $fieldName) { |
|||
350 | 55 | $field = $oldFieldMap[$fieldName]; |
|||
351 | |||||
352 | 55 | $newFieldMap[$fieldName] = [ |
|||
353 | 55 | 'name' => $fieldName, |
|||
354 | 55 | 'description' => $field->description, |
|||
355 | 55 | 'deprecationReason' => $field->deprecationReason, |
|||
356 | 55 | 'type' => static::extendType($field->getType()), |
|||
357 | 55 | 'args' => static::extendArgs($field->args), |
|||
358 | 55 | 'astNode' => $field->astNode, |
|||
359 | 55 | 'resolve' => $field->resolveFn, |
|||
360 | ]; |
||||
361 | } |
||||
362 | |||||
363 | 55 | $extensions = static::$typeExtensionsMap[$type->name] ?? null; |
|||
364 | 55 | if ($extensions !== null) { |
|||
365 | 26 | foreach ($extensions as $extension) { |
|||
366 | 26 | foreach ($extension->fields as $field) { |
|||
367 | 24 | $fieldName = $field->name->value; |
|||
368 | 24 | if (isset($oldFieldMap[$fieldName])) { |
|||
369 | 1 | throw new Error('Field "' . $type->name . '.' . $fieldName . '" already exists in the schema. It cannot also be defined in this type extension.', [$field]); |
|||
370 | } |
||||
371 | |||||
372 | 25 | $newFieldMap[$fieldName] = static::$astBuilder->buildField($field); |
|||
373 | } |
||||
374 | } |
||||
375 | } |
||||
376 | |||||
377 | 55 | return $newFieldMap; |
|||
378 | } |
||||
379 | |||||
380 | 59 | protected static function extendObjectType(ObjectType $type) : ObjectType |
|||
381 | { |
||||
382 | 59 | return new ObjectType([ |
|||
383 | 59 | 'name' => $type->name, |
|||
384 | 59 | 'description' => $type->description, |
|||
385 | 'interfaces' => static function () use ($type) { |
||||
386 | 55 | return static::extendImplementedInterfaces($type); |
|||
387 | 59 | }, |
|||
388 | 'fields' => static function () use ($type) { |
||||
389 | 55 | return static::extendFieldMap($type); |
|||
390 | 59 | }, |
|||
391 | 59 | 'astNode' => $type->astNode, |
|||
392 | 59 | 'extensionASTNodes' => static::getExtensionASTNodes($type), |
|||
393 | 59 | 'isTypeOf' => $type->config['isTypeOf'] ?? null, |
|||
394 | ]); |
||||
395 | } |
||||
396 | |||||
397 | 47 | protected static function extendInterfaceType(InterfaceType $type) : InterfaceType |
|||
398 | { |
||||
399 | 47 | return new InterfaceType([ |
|||
400 | 47 | 'name' => $type->name, |
|||
401 | 47 | 'description' => $type->description, |
|||
402 | 'fields' => static function () use ($type) { |
||||
403 | 46 | return static::extendFieldMap($type); |
|||
404 | 47 | }, |
|||
405 | 47 | 'astNode' => $type->astNode, |
|||
406 | 47 | 'extensionASTNodes' => static::getExtensionASTNodes($type), |
|||
407 | 47 | 'resolveType' => $type->config['resolveType'] ?? null, |
|||
408 | ]); |
||||
409 | } |
||||
410 | |||||
411 | 60 | protected static function isSpecifiedScalarType(Type $type) : bool |
|||
412 | { |
||||
413 | 60 | return $type instanceof NamedType && |
|||
414 | ( |
||||
415 | 60 | $type->name === Type::STRING || |
|||
416 | 60 | $type->name === Type::INT || |
|||
417 | 60 | $type->name === Type::FLOAT || |
|||
418 | 60 | $type->name === Type::BOOLEAN || |
|||
419 | 60 | $type->name === Type::ID |
|||
420 | ); |
||||
421 | } |
||||
422 | |||||
423 | 60 | protected static function extendNamedType(Type $type) |
|||
424 | { |
||||
425 | 60 | if (Introspection::isIntrospectionType($type) || static::isSpecifiedScalarType($type)) { |
|||
426 | 57 | return $type; |
|||
427 | } |
||||
428 | |||||
429 | 60 | $name = $type->name; |
|||
430 | 60 | if (! isset(static::$extendTypeCache[$name])) { |
|||
431 | 60 | if ($type instanceof ScalarType) { |
|||
432 | 43 | static::$extendTypeCache[$name] = static::extendScalarType($type); |
|||
433 | 60 | } elseif ($type instanceof ObjectType) { |
|||
434 | 59 | static::$extendTypeCache[$name] = static::extendObjectType($type); |
|||
435 | 53 | } elseif ($type instanceof InterfaceType) { |
|||
436 | 47 | static::$extendTypeCache[$name] = static::extendInterfaceType($type); |
|||
437 | 50 | } elseif ($type instanceof UnionType) { |
|||
438 | 46 | static::$extendTypeCache[$name] = static::extendUnionType($type); |
|||
439 | 47 | } elseif ($type instanceof EnumType) { |
|||
440 | 44 | static::$extendTypeCache[$name] = static::extendEnumType($type); |
|||
441 | 45 | } elseif ($type instanceof InputObjectType) { |
|||
442 | 45 | static::$extendTypeCache[$name] = static::extendInputObjectType($type); |
|||
443 | } |
||||
444 | } |
||||
445 | |||||
446 | 60 | return static::$extendTypeCache[$name]; |
|||
447 | } |
||||
448 | |||||
449 | /** |
||||
450 | * @return mixed|null |
||||
451 | */ |
||||
452 | 60 | protected static function extendMaybeNamedType(?NamedType $type = null) |
|||
453 | { |
||||
454 | 60 | if ($type !== null) { |
|||
455 | 59 | return static::extendNamedType($type); |
|||
456 | } |
||||
457 | |||||
458 | 59 | return null; |
|||
459 | } |
||||
460 | |||||
461 | /** |
||||
462 | * @param DirectiveDefinitionNode[] $directiveDefinitions |
||||
463 | * |
||||
464 | * @return Directive[] |
||||
465 | */ |
||||
466 | 56 | protected static function getMergedDirectives(Schema $schema, array $directiveDefinitions) : array |
|||
467 | { |
||||
468 | $existingDirectives = array_map(static function (Directive $directive) { |
||||
469 | 56 | return static::extendDirective($directive); |
|||
470 | 56 | }, $schema->getDirectives()); |
|||
471 | |||||
472 | 56 | Utils::invariant(count($existingDirectives) > 0, 'schema must have default directives'); |
|||
473 | |||||
474 | 56 | return array_merge( |
|||
475 | 56 | $existingDirectives, |
|||
476 | array_map(static function (DirectiveDefinitionNode $directive) { |
||||
477 | 9 | return static::$astBuilder->buildDirective($directive); |
|||
478 | 56 | }, $directiveDefinitions) |
|||
479 | ); |
||||
480 | } |
||||
481 | |||||
482 | 56 | protected static function extendDirective(Directive $directive) : Directive |
|||
483 | { |
||||
484 | 56 | return new Directive([ |
|||
485 | 56 | 'name' => $directive->name, |
|||
486 | 56 | 'description' => $directive->description, |
|||
487 | 56 | 'locations' => $directive->locations, |
|||
488 | 56 | 'args' => static::extendArgs($directive->args), |
|||
489 | 56 | 'astNode' => $directive->astNode, |
|||
490 | ]); |
||||
491 | } |
||||
492 | |||||
493 | /** |
||||
494 | * @param mixed[]|null $options |
||||
495 | */ |
||||
496 | 66 | public static function extend(Schema $schema, DocumentNode $documentAST, ?array $options = null) : Schema |
|||
497 | { |
||||
498 | 66 | if ($options === null || ! (isset($options['assumeValid']) || isset($options['assumeValidSDL']))) { |
|||
499 | 64 | DocumentValidator::assertValidSDLExtension($documentAST, $schema); |
|||
500 | } |
||||
501 | |||||
502 | 65 | $typeDefinitionMap = []; |
|||
503 | 65 | static::$typeExtensionsMap = []; |
|||
504 | 65 | $directiveDefinitions = []; |
|||
505 | /** @var SchemaDefinitionNode|null $schemaDef */ |
||||
506 | 65 | $schemaDef = null; |
|||
507 | /** @var SchemaTypeExtensionNode[] $schemaExtensions */ |
||||
508 | 65 | $schemaExtensions = []; |
|||
509 | |||||
510 | 65 | $definitionsCount = count($documentAST->definitions); |
|||
511 | 65 | for ($i = 0; $i < $definitionsCount; $i++) { |
|||
512 | |||||
513 | /** @var Node $def */ |
||||
514 | 65 | $def = $documentAST->definitions[$i]; |
|||
515 | |||||
516 | 65 | if ($def instanceof SchemaDefinitionNode) { |
|||
517 | 1 | $schemaDef = $def; |
|||
518 | 64 | } elseif ($def instanceof SchemaTypeExtensionNode) { |
|||
519 | 9 | $schemaExtensions[] = $def; |
|||
520 | 62 | } elseif ($def instanceof TypeDefinitionNode) { |
|||
521 | 21 | $typeName = isset($def->name) ? $def->name->value : null; |
|||
522 | |||||
523 | try { |
||||
524 | 21 | $type = $schema->getType($typeName); |
|||
0 ignored issues
–
show
It seems like
$typeName can also be of type null ; however, parameter $name of GraphQL\Type\Schema::getType() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
525 | 2 | } catch (Error $error) { |
|||
526 | 2 | $type = null; |
|||
527 | } |
||||
528 | |||||
529 | 21 | if ($type) { |
|||
530 | 1 | throw new Error('Type "' . $typeName . '" already exists in the schema. It cannot also be defined in this type definition.', [$def]); |
|||
531 | } |
||||
532 | 20 | $typeDefinitionMap[$typeName] = $def; |
|||
533 | 47 | } elseif ($def instanceof TypeExtensionNode) { |
|||
534 | 40 | $extendedTypeName = isset($def->name) ? $def->name->value : null; |
|||
535 | 40 | $existingType = $schema->getType($extendedTypeName); |
|||
536 | 40 | if ($existingType === null) { |
|||
537 | 1 | throw new Error('Cannot extend type "' . $extendedTypeName . '" because it does not exist in the existing schema.', [$def]); |
|||
538 | } |
||||
539 | |||||
540 | 39 | static::checkExtensionNode($existingType, $def); |
|||
541 | |||||
542 | 38 | $existingTypeExtensions = static::$typeExtensionsMap[$extendedTypeName] ?? null; |
|||
543 | 38 | static::$typeExtensionsMap[$extendedTypeName] = $existingTypeExtensions !== null ? array_merge($existingTypeExtensions, [$def]) : [$def]; |
|||
544 | 11 | } elseif ($def instanceof DirectiveDefinitionNode) { |
|||
545 | 10 | $directiveName = $def->name->value; |
|||
546 | 10 | $existingDirective = $schema->getDirective($directiveName); |
|||
547 | 10 | if ($existingDirective !== null) { |
|||
548 | 2 | throw new Error('Directive "' . $directiveName . '" already exists in the schema. It cannot be redefined.', [$def]); |
|||
549 | } |
||||
550 | 9 | $directiveDefinitions[] = $def; |
|||
551 | } |
||||
552 | } |
||||
553 | |||||
554 | 61 | if (count(static::$typeExtensionsMap) === 0 && |
|||
555 | 61 | count($typeDefinitionMap) === 0 && |
|||
556 | 61 | count($directiveDefinitions) === 0 && |
|||
557 | 61 | count($schemaExtensions) === 0 && |
|||
558 | 61 | $schemaDef === null |
|||
559 | ) { |
||||
560 | 1 | return $schema; |
|||
561 | } |
||||
562 | |||||
563 | 60 | static::$astBuilder = new ASTDefinitionBuilder( |
|||
564 | 60 | $typeDefinitionMap, |
|||
565 | 60 | $options, |
|||
566 | static function (string $typeName) use ($schema) { |
||||
567 | /** @var ScalarType|ObjectType|InterfaceType|UnionType|EnumType|InputObjectType $existingType */ |
||||
568 | 12 | $existingType = $schema->getType($typeName); |
|||
569 | 12 | if ($existingType !== null) { |
|||
570 | 11 | return static::extendNamedType($existingType); |
|||
571 | } |
||||
572 | |||||
573 | 1 | throw new Error('Unknown type: "' . $typeName . '". Ensure that this type exists either in the original schema, or is added in a type definition.', [$typeName]); |
|||
574 | 60 | } |
|||
575 | ); |
||||
576 | |||||
577 | 60 | static::$extendTypeCache = []; |
|||
578 | |||||
579 | $operationTypes = [ |
||||
580 | 60 | 'query' => static::extendMaybeNamedType($schema->getQueryType()), |
|||
581 | 60 | 'mutation' => static::extendMaybeNamedType($schema->getMutationType()), |
|||
582 | 60 | 'subscription' => static::extendMaybeNamedType($schema->getSubscriptionType()), |
|||
583 | ]; |
||||
584 | |||||
585 | 60 | if ($schemaDef) { |
|||
586 | 1 | foreach ($schemaDef->operationTypes as $operationType) { |
|||
587 | 1 | $operation = $operationType->operation; |
|||
588 | 1 | $type = $operationType->type; |
|||
589 | |||||
590 | 1 | if (isset($operationTypes[$operation])) { |
|||
591 | throw new Error('Must provide only one ' . $operation . ' type in schema.'); |
||||
592 | } |
||||
593 | |||||
594 | 1 | $operationTypes[$operation] = static::$astBuilder->buildType($type); |
|||
595 | } |
||||
596 | } |
||||
597 | |||||
598 | 60 | foreach ($schemaExtensions as $schemaExtension) { |
|||
599 | 9 | if (! $schemaExtension->operationTypes) { |
|||
600 | 2 | continue; |
|||
601 | } |
||||
602 | |||||
603 | 8 | foreach ($schemaExtension->operationTypes as $operationType) { |
|||
604 | 8 | $operation = $operationType->operation; |
|||
605 | 8 | if (isset($operationTypes[$operation])) { |
|||
606 | 3 | throw new Error('Must provide only one ' . $operation . ' type in schema.'); |
|||
607 | } |
||||
608 | 7 | $operationTypes[$operation] = static::$astBuilder->buildType($operationType->type); |
|||
609 | } |
||||
610 | } |
||||
611 | |||||
612 | 57 | $schemaExtensionASTNodes = count($schemaExtensions) > 0 |
|||
613 | 6 | ? ($schema->extensionASTNodes |
|||
614 | 2 | ? array_merge($schema->extensionASTNodes, $schemaExtensions) |
|||
615 | 6 | : $schemaExtensions) |
|||
616 | 57 | : $schema->extensionASTNodes; |
|||
617 | |||||
618 | 57 | $types = array_merge( |
|||
619 | // Iterate through all types, getting the type definition for each, ensuring |
||||
620 | // that any type not directly referenced by a field will get created. |
||||
621 | array_map(static function ($type) { |
||||
622 | 57 | return static::extendNamedType($type); |
|||
623 | 57 | }, array_values($schema->getTypeMap())), |
|||
624 | // Do the same with new types. |
||||
625 | array_map(static function ($type) { |
||||
626 | 17 | return static::$astBuilder->buildType($type); |
|||
627 | 56 | }, array_values($typeDefinitionMap)) |
|||
628 | ); |
||||
629 | |||||
630 | 56 | return new Schema([ |
|||
631 | 56 | 'query' => $operationTypes['query'], |
|||
632 | 56 | 'mutation' => $operationTypes['mutation'], |
|||
633 | 56 | 'subscription' => $operationTypes['subscription'], |
|||
634 | 56 | 'types' => $types, |
|||
635 | 56 | 'directives' => static::getMergedDirectives($schema, $directiveDefinitions), |
|||
636 | 56 | 'astNode' => $schema->getAstNode(), |
|||
637 | 56 | 'extensionASTNodes' => $schemaExtensionASTNodes, |
|||
638 | ]); |
||||
639 | } |
||||
640 | } |
||||
641 |