1 | <?php |
||||
2 | |||||
3 | namespace Digia\GraphQL\Schema\Extension; |
||||
4 | |||||
5 | use Digia\GraphQL\Language\Node\DirectiveDefinitionNode; |
||||
6 | use Digia\GraphQL\Language\Node\DocumentNode; |
||||
7 | use Digia\GraphQL\Language\Node\EnumTypeExtensionNode; |
||||
8 | use Digia\GraphQL\Language\Node\InputObjectTypeExtensionNode; |
||||
9 | use Digia\GraphQL\Language\Node\InterfaceTypeExtensionNode; |
||||
10 | use Digia\GraphQL\Language\Node\NameAwareInterface; |
||||
11 | use Digia\GraphQL\Language\Node\NodeInterface; |
||||
12 | use Digia\GraphQL\Language\Node\ObjectTypeExtensionNode; |
||||
13 | use Digia\GraphQL\Language\Node\ScalarTypeExtensionNode; |
||||
14 | use Digia\GraphQL\Language\Node\SchemaDefinitionNode; |
||||
15 | use Digia\GraphQL\Language\Node\SchemaExtensionNode; |
||||
16 | use Digia\GraphQL\Language\Node\TypeSystemDefinitionNodeInterface; |
||||
17 | use Digia\GraphQL\Language\Node\UnionTypeExtensionNode; |
||||
18 | use Digia\GraphQL\Schema\DefinitionBuilder; |
||||
19 | use Digia\GraphQL\Schema\Resolver\ResolverRegistryInterface; |
||||
20 | use Digia\GraphQL\Schema\Schema; |
||||
21 | use Digia\GraphQL\Type\Definition\InterfaceType; |
||||
22 | use Digia\GraphQL\Type\Definition\ObjectType; |
||||
23 | use Digia\GraphQL\Type\Definition\TypeInterface; |
||||
24 | use function Digia\GraphQL\Type\newSchema; |
||||
25 | use function Digia\GraphQL\Util\toString; |
||||
26 | |||||
27 | class SchemaExtender implements SchemaExtenderInterface |
||||
28 | { |
||||
29 | /** |
||||
30 | * @inheritdoc |
||||
31 | */ |
||||
32 | public function extend( |
||||
33 | Schema $schema, |
||||
34 | DocumentNode $document, |
||||
35 | ?ResolverRegistryInterface $resolverRegistry = null, |
||||
36 | array $options = [] |
||||
37 | ): Schema { |
||||
38 | $context = $this->createContext($schema, $document, $resolverRegistry, $options); |
||||
39 | |||||
40 | // If this document contains no new types, extensions, or directives then |
||||
41 | // return the same unmodified GraphQLSchema instance. |
||||
42 | if (!$context->isSchemaExtended()) { |
||||
43 | return $schema; |
||||
44 | } |
||||
45 | |||||
46 | $operationTypes = $context->getExtendedOperationTypes(); |
||||
47 | |||||
48 | /** @noinspection PhpUnhandledExceptionInspection */ |
||||
49 | return newSchema([ |
||||
50 | 'query' => $operationTypes['query'] ?? null, |
||||
51 | 'mutation' => $operationTypes['mutation'] ?? null, |
||||
52 | 'subscription' => $operationTypes['subscription'] ?? null, |
||||
53 | 'types' => $context->getExtendedTypes(), |
||||
54 | 'directives' => $context->getExtendedDirectives(), |
||||
55 | 'astNode' => $schema->getAstNode(), |
||||
56 | ]); |
||||
57 | } |
||||
58 | |||||
59 | /** |
||||
60 | * @inheritdoc |
||||
61 | */ |
||||
62 | protected function createContext( |
||||
63 | Schema $schema, |
||||
64 | DocumentNode $document, |
||||
65 | ?ResolverRegistryInterface $resolverRegistry, |
||||
66 | array $options |
||||
67 | ): ExtensionContextInterface { |
||||
68 | /** @noinspection PhpUnhandledExceptionInspection */ |
||||
69 | $info = $this->createInfo($schema, $document); |
||||
70 | |||||
71 | // Context has to be created in order to create the definition builder, |
||||
72 | // because we are using its `resolveType` function to resolve types. |
||||
73 | $context = new ExtensionContext($info); |
||||
74 | |||||
75 | /** @noinspection PhpUnhandledExceptionInspection */ |
||||
76 | $definitionBuilder = new DefinitionBuilder( |
||||
77 | $info->getTypeDefinitionMap(), |
||||
78 | $resolverRegistry, |
||||
79 | $options['types'] ?? [], |
||||
80 | $options['directives'] ?? [], |
||||
81 | [$context, 'resolveType'] |
||||
82 | ); |
||||
83 | |||||
84 | return $context->setDefinitionBuilder($definitionBuilder); |
||||
85 | } |
||||
86 | |||||
87 | /** |
||||
88 | * @param Schema $schema |
||||
89 | * @param DocumentNode $document |
||||
90 | * @return ExtendInfo |
||||
91 | * @throws SchemaExtensionException |
||||
92 | */ |
||||
93 | protected function createInfo(Schema $schema, DocumentNode $document): ExtendInfo |
||||
94 | { |
||||
95 | $typeDefinitionMap = []; |
||||
96 | $typeExtensionsMap = []; |
||||
97 | $directiveDefinitions = []; |
||||
98 | $schemaExtensions = []; |
||||
99 | |||||
100 | foreach ($document->getDefinitions() as $definition) { |
||||
101 | if ($definition instanceof SchemaDefinitionNode) { |
||||
102 | // Sanity check that a schema extension is not defining a new schema |
||||
103 | throw new SchemaExtensionException('Cannot define a new schema within a schema extension.', [$definition]); |
||||
104 | } |
||||
105 | |||||
106 | if ($definition instanceof SchemaExtensionNode) { |
||||
107 | $schemaExtensions[] = $definition; |
||||
108 | |||||
109 | continue; |
||||
110 | } |
||||
111 | |||||
112 | if ($definition instanceof DirectiveDefinitionNode) { |
||||
113 | $directiveName = $definition->getNameValue(); |
||||
114 | $existingDirective = $schema->getDirective($directiveName); |
||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
115 | |||||
116 | if (null !== $existingDirective) { |
||||
117 | throw new SchemaExtensionException( |
||||
118 | \sprintf( |
||||
119 | 'Directive "%s" already exists in the schema. It cannot be redefined.', |
||||
120 | $directiveName |
||||
121 | ), |
||||
122 | [$definition] |
||||
123 | ); |
||||
124 | } |
||||
125 | |||||
126 | $directiveDefinitions[] = $definition; |
||||
127 | |||||
128 | continue; |
||||
129 | } |
||||
130 | |||||
131 | if ($definition instanceof TypeSystemDefinitionNodeInterface && $definition instanceof NameAwareInterface) { |
||||
132 | // Sanity check that none of the defined types conflict with the schema's existing types. |
||||
133 | $typeName = $definition->getNameValue(); |
||||
134 | $existingType = $schema->getType($typeName); |
||||
0 ignored issues
–
show
It seems like
$typeName can also be of type null ; however, parameter $name of Digia\GraphQL\Schema\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
![]() |
|||||
135 | |||||
136 | if (null !== $existingType) { |
||||
137 | throw new SchemaExtensionException( |
||||
138 | \sprintf( |
||||
139 | 'Type "%s" already exists in the schema. It cannot also ' . |
||||
140 | 'be defined in this type definition.', |
||||
141 | $typeName |
||||
142 | ), |
||||
143 | [$definition] |
||||
144 | ); |
||||
145 | } |
||||
146 | |||||
147 | $typeDefinitionMap[$typeName] = $definition; |
||||
148 | |||||
149 | continue; |
||||
150 | } |
||||
151 | |||||
152 | if ($definition instanceof ObjectTypeExtensionNode || $definition instanceof InterfaceTypeExtensionNode) { |
||||
153 | // Sanity check that this type extension exists within the schema's existing types. |
||||
154 | $extendedTypeName = $definition->getNameValue(); |
||||
155 | $existingType = $schema->getType($extendedTypeName); |
||||
156 | |||||
157 | if (null === $existingType) { |
||||
158 | throw new SchemaExtensionException( |
||||
159 | \sprintf( |
||||
160 | 'Cannot extend type "%s" because it does not exist in the existing schema.', |
||||
161 | $extendedTypeName |
||||
162 | ), |
||||
163 | [$definition] |
||||
164 | ); |
||||
165 | } |
||||
166 | |||||
167 | $this->checkExtensionNode($existingType, $definition); |
||||
168 | |||||
169 | $typeExtensionsMap[$extendedTypeName] = \array_merge( |
||||
170 | $typeExtensionsMap[$extendedTypeName] ?? [], |
||||
171 | [$definition] |
||||
172 | ); |
||||
173 | |||||
174 | continue; |
||||
175 | } |
||||
176 | |||||
177 | if ($definition instanceof ScalarTypeExtensionNode || |
||||
178 | $definition instanceof UnionTypeExtensionNode || |
||||
179 | $definition instanceof EnumTypeExtensionNode || |
||||
180 | $definition instanceof InputObjectTypeExtensionNode) { |
||||
181 | throw new SchemaExtensionException( |
||||
182 | \sprintf('The %s kind is not yet supported by extendSchema().', $definition->getKind()) |
||||
183 | ); |
||||
184 | } |
||||
185 | } |
||||
186 | |||||
187 | return new ExtendInfo( |
||||
188 | $schema, |
||||
189 | $document, |
||||
190 | $typeDefinitionMap, |
||||
191 | $typeExtensionsMap, |
||||
192 | $directiveDefinitions, |
||||
193 | $schemaExtensions |
||||
194 | ); |
||||
195 | } |
||||
196 | |||||
197 | /** |
||||
198 | * @param TypeInterface $type |
||||
199 | * @param NodeInterface $node |
||||
200 | * @throws SchemaExtensionException |
||||
201 | */ |
||||
202 | protected function checkExtensionNode(TypeInterface $type, NodeInterface $node): void |
||||
203 | { |
||||
204 | if ($node instanceof ObjectTypeExtensionNode && !($type instanceof ObjectType)) { |
||||
205 | throw new SchemaExtensionException( |
||||
206 | \sprintf('Cannot extend non-object type "%s".', toString($type)), |
||||
207 | [$node] |
||||
208 | ); |
||||
209 | } |
||||
210 | |||||
211 | if ($node instanceof InterfaceTypeExtensionNode && !($type instanceof InterfaceType)) { |
||||
212 | throw new SchemaExtensionException( |
||||
213 | \sprintf('Cannot extend non-interface type "%s".', toString($type)), |
||||
214 | [$node] |
||||
215 | ); |
||||
216 | } |
||||
217 | } |
||||
218 | } |
||||
219 |