13
13
use Drupal \graphql \Plugin \SchemaExtensionPluginInterface ;
14
14
use Drupal \graphql \Plugin \SchemaExtensionPluginManager ;
15
15
use Drupal \graphql \Plugin \SchemaPluginInterface ;
16
+ use GraphQL \Language \AST \DocumentNode ;
16
17
use GraphQL \Language \AST \InterfaceTypeDefinitionNode ;
17
18
use GraphQL \Language \AST \TypeDefinitionNode ;
18
19
use GraphQL \Language \AST \UnionTypeDefinitionNode ;
19
20
use GraphQL \Language \Parser ;
21
+ use GraphQL \Type \Schema ;
20
22
use GraphQL \Utils \BuildSchema ;
21
23
use GraphQL \Utils \SchemaExtender ;
24
+ use GraphQL \Utils \SchemaPrinter ;
22
25
use Symfony \Component \DependencyInjection \ContainerInterface ;
23
26
24
27
/**
@@ -117,15 +120,8 @@ public function __construct(
117
120
*/
118
121
public function getSchema (ResolverRegistryInterface $ registry ) {
119
122
$ extensions = $ this ->getExtensions ();
120
- $ resolver = [$ registry , 'resolveType ' ];
121
123
$ document = $ this ->getSchemaDocument ($ extensions );
122
- $ schema = BuildSchema::build ($ document , function ($ config , TypeDefinitionNode $ type ) use ($ resolver ) {
123
- if ($ type instanceof InterfaceTypeDefinitionNode || $ type instanceof UnionTypeDefinitionNode) {
124
- $ config ['resolveType ' ] = $ resolver ;
125
- }
126
-
127
- return $ config ;
128
- });
124
+ $ schema = $ this ->buildSchema ($ document , $ registry );
129
125
130
126
if (empty ($ extensions )) {
131
127
return $ schema ;
@@ -135,10 +131,31 @@ public function getSchema(ResolverRegistryInterface $registry) {
135
131
$ extension ->registerResolvers ($ registry );
136
132
}
137
133
138
- if ($ extendSchema = $ this ->getExtensionDocument ($ extensions )) {
139
- return SchemaExtender::extend ($ schema , $ extendSchema );
134
+ $ extendedDocument = $ this ->getFullSchemaDocument ($ schema , $ extensions );
135
+ if (empty ($ extendedDocument )) {
136
+ return $ schema ;
140
137
}
141
138
139
+ return $ this ->buildSchema ($ extendedDocument , $ registry );
140
+ }
141
+
142
+ /**
143
+ * Create a GraphQL schema object from the given AST document.
144
+ *
145
+ * This method is private for now as the build/cache approach might change.
146
+ */
147
+ private function buildSchema (DocumentNode $ astDocument , ResolverRegistryInterface $ registry ): Schema {
148
+ $ resolver = [$ registry , 'resolveType ' ];
149
+ // Performance: only validate the schema in development mode, skip it in
150
+ // production on every request.
151
+ $ options = empty ($ this ->inDevelopment ) ? ['assumeValid ' => TRUE ] : [];
152
+ $ schema = BuildSchema::build ($ astDocument , function ($ config , TypeDefinitionNode $ type ) use ($ resolver ) {
153
+ if ($ type instanceof InterfaceTypeDefinitionNode || $ type instanceof UnionTypeDefinitionNode) {
154
+ $ config ['resolveType ' ] = $ resolver ;
155
+ }
156
+
157
+ return $ config ;
158
+ }, $ options );
142
159
return $ schema ;
143
160
}
144
161
@@ -185,6 +202,33 @@ protected function getSchemaDocument(array $extensions = []) {
185
202
return $ ast ;
186
203
}
187
204
205
+ /**
206
+ * Returns the full AST combination of parsed schema with extensions, cached.
207
+ *
208
+ * This method is private for now as the build/cache approach might change.
209
+ */
210
+ private function getFullSchemaDocument (Schema $ schema , array $ extensions ): ?DocumentNode {
211
+ // Only use caching of the parsed document if we aren't in development mode.
212
+ $ cid = "full: {$ this ->getPluginId ()}" ;
213
+ if (empty ($ this ->inDevelopment ) && $ cache = $ this ->astCache ->get ($ cid )) {
214
+ return $ cache ->data ;
215
+ }
216
+
217
+ $ ast = NULL ;
218
+ if ($ extendAst = $ this ->getExtensionDocument ($ extensions )) {
219
+ $ fullSchema = SchemaExtender::extend ($ schema , $ extendAst );
220
+ // Performance: export the full schema as string and parse it again. That
221
+ // way we can cache the full AST.
222
+ $ fullSchemaString = SchemaPrinter::doPrint ($ fullSchema );
223
+ $ ast = Parser::parse ($ fullSchemaString , ['noLocation ' => TRUE ]);
224
+ }
225
+
226
+ if (empty ($ this ->inDevelopment )) {
227
+ $ this ->astCache ->set ($ cid , $ ast , CacheBackendInterface::CACHE_PERMANENT , ['graphql ' ]);
228
+ }
229
+ return $ ast ;
230
+ }
231
+
188
232
/**
189
233
* Retrieves the parsed AST of the schema extension definitions.
190
234
*
@@ -196,23 +240,14 @@ protected function getSchemaDocument(array $extensions = []) {
196
240
* @throws \GraphQL\Error\SyntaxError
197
241
*/
198
242
protected function getExtensionDocument (array $ extensions = []) {
199
- // Only use caching of the parsed document if we aren't in development mode.
200
- $ cid = "extension: {$ this ->getPluginId ()}" ;
201
- if (empty ($ this ->inDevelopment ) && $ cache = $ this ->astCache ->get ($ cid )) {
202
- return $ cache ->data ;
203
- }
204
-
205
243
$ extensions = array_filter (array_map (function (SchemaExtensionPluginInterface $ extension ) {
206
244
return $ extension ->getExtensionDefinition ();
207
245
}, $ extensions ), function ($ definition ) {
208
246
return !empty ($ definition );
209
247
});
210
248
211
249
$ ast = !empty ($ extensions ) ? Parser::parse (implode ("\n\n" , $ extensions ), ['noLocation ' => TRUE ]) : NULL ;
212
- if (empty ($ this ->inDevelopment )) {
213
- $ this ->astCache ->set ($ cid , $ ast , CacheBackendInterface::CACHE_PERMANENT , ['graphql ' ]);
214
- }
215
-
250
+ // No AST caching here as that will be done in getFullSchemaDocument().
216
251
return $ ast ;
217
252
}
218
253
0 commit comments