2
2
3
3
namespace Drupal \graphql \Plugin \GraphQL \Schema ;
4
4
5
+ use Drupal \Component \PhpStorage \PhpStorageInterface ;
5
6
use Drupal \Component \Plugin \Exception \InvalidPluginDefinitionException ;
6
7
use Drupal \Component \Plugin \PluginBase ;
7
8
use Drupal \Core \Cache \CacheableDependencyInterface ;
13
14
use Drupal \graphql \Plugin \SchemaExtensionPluginInterface ;
14
15
use Drupal \graphql \Plugin \SchemaExtensionPluginManager ;
15
16
use Drupal \graphql \Plugin \SchemaPluginInterface ;
17
+ use GraphQL \Error \Error ;
18
+ use GraphQL \Language \AST \DocumentNode ;
16
19
use GraphQL \Language \AST \InterfaceTypeDefinitionNode ;
20
+ use GraphQL \Language \AST \Node ;
21
+ use GraphQL \Language \AST \NodeList ;
17
22
use GraphQL \Language \AST \TypeDefinitionNode ;
18
23
use GraphQL \Language \AST \UnionTypeDefinitionNode ;
19
24
use GraphQL \Language \Parser ;
25
+ use GraphQL \Language \Source ;
26
+ use GraphQL \Type \Definition \Type ;
27
+ use GraphQL \Type \Schema ;
28
+ use GraphQL \Utils \AST ;
20
29
use GraphQL \Utils \BuildSchema ;
21
30
use GraphQL \Utils \SchemaExtender ;
31
+ use GraphQL \Utils \SchemaPrinter ;
22
32
use Symfony \Component \DependencyInjection \ContainerInterface ;
23
33
24
34
/**
@@ -28,11 +38,11 @@ abstract class SdlSchemaPluginBase extends PluginBase implements SchemaPluginInt
28
38
use RefinableCacheableDependencyTrait;
29
39
30
40
/**
31
- * The cache bin for caching the parsed SDL.
41
+ * The file cache for caching the parsed SDL.
32
42
*
33
- * @var \Drupal\Core\Cache\CacheBackendInterface
43
+ * @var \Drupal\Component\PhpStorage\PhpStorageInterface
34
44
*/
35
- protected $ astCache ;
45
+ protected $ documentCache ;
36
46
37
47
/**
38
48
* Whether the system is currently in development mode.
@@ -65,7 +75,7 @@ public static function create(ContainerInterface $container, array $configuratio
65
75
$ configuration ,
66
76
$ plugin_id ,
67
77
$ plugin_definition ,
68
- $ container ->get ('cache.graphql.ast ' ),
78
+ $ container ->get ('cache.graphql.document ' ),
69
79
$ container ->get ('module_handler ' ),
70
80
$ container ->get ('plugin.manager.graphql.schema_extension ' ),
71
81
$ container ->getParameter ('graphql.config ' )
@@ -81,8 +91,8 @@ public static function create(ContainerInterface $container, array $configuratio
81
91
* The plugin id.
82
92
* @param array $pluginDefinition
83
93
* The plugin definition array.
84
- * @param \Drupal\Core\Cache\CacheBackendInterface $astCache
85
- * The cache bin for caching the parsed SDL.
94
+ * @param \Drupal\Component\PhpStorage\PhpStorageInterface $documentCache
95
+ * The file cache for caching the parsed SDL.
86
96
* @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
87
97
* The module handler service.
88
98
* @param \Drupal\graphql\Plugin\SchemaExtensionPluginManager $extensionManager
@@ -93,17 +103,17 @@ public static function create(ContainerInterface $container, array $configuratio
93
103
* @codeCoverageIgnore
94
104
*/
95
105
public function __construct (
96
- array $ configuration ,
97
- $ pluginId ,
98
- array $ pluginDefinition ,
99
- CacheBackendInterface $ astCache ,
100
- ModuleHandlerInterface $ moduleHandler ,
106
+ array $ configuration ,
107
+ $ pluginId ,
108
+ array $ pluginDefinition ,
109
+ PhpStorageInterface $ documentCache ,
110
+ ModuleHandlerInterface $ moduleHandler ,
101
111
SchemaExtensionPluginManager $ extensionManager ,
102
- array $ config
112
+ array $ config
103
113
) {
104
114
parent ::__construct ($ configuration , $ pluginId , $ pluginDefinition );
105
115
$ this ->inDevelopment = !empty ($ config ['development ' ]);
106
- $ this ->astCache = $ astCache ;
116
+ $ this ->documentCache = $ documentCache ;
107
117
$ this ->extensionManager = $ extensionManager ;
108
118
$ this ->moduleHandler = $ moduleHandler ;
109
119
}
@@ -117,29 +127,68 @@ public function __construct(
117
127
*/
118
128
public function getSchema (ResolverRegistryInterface $ registry ) {
119
129
$ extensions = $ this ->getExtensions ();
130
+
131
+ // TODO: The fact that the registry gets extended here means we can't cache
132
+ // all of it easily
133
+ foreach ($ extensions as $ extension ) {
134
+ $ extension ->registerResolvers ($ registry );
135
+ }
136
+
137
+ // TODO: We might need to think about the naming to work with multiple webheads and configs?
120
138
$ resolver = [$ registry , 'resolveType ' ];
121
- $ document = $ this ->getSchemaDocument ($ extensions );
122
- $ schema = BuildSchema::build ($ document , function ($ config , TypeDefinitionNode $ type ) use ($ resolver ) {
139
+ $ document = empty ($ this ->inDevelopment ) ? $ this ->loadCachedDocument () : NULL ;
140
+ if ($ document !== NULL ) {
141
+ return BuildSchema::build ($ document , function ($ config , TypeDefinitionNode $ type ) use ($ resolver ) {
142
+ if ($ type instanceof InterfaceTypeDefinitionNode || $ type instanceof UnionTypeDefinitionNode) {
143
+ $ config ['resolveType ' ] = $ resolver ;
144
+ }
145
+
146
+ return $ config ;
147
+ });
148
+ }
149
+
150
+ $ schema = BuildSchema::build ($ this ->getBaseSchemaAst (), function ($ config , TypeDefinitionNode $ type ) use ($ resolver ) {
123
151
if ($ type instanceof InterfaceTypeDefinitionNode || $ type instanceof UnionTypeDefinitionNode) {
124
152
$ config ['resolveType ' ] = $ resolver ;
125
153
}
126
154
127
155
return $ config ;
128
156
});
129
157
130
- if (empty ($ extensions )) {
131
- return $ schema ;
158
+ /** @var DocumentNode $extensionAst */
159
+ foreach ($ this ->getExtensionAsts ($ extensions ) as $ extensionAst ) {
160
+ $ schema = SchemaExtender::extend ($ schema , $ extensionAst );
132
161
}
133
162
134
- foreach ($ extensions as $ extension ) {
135
- $ extension ->registerResolvers ($ registry );
163
+ // This does a full schema load, which is slow. But it's the most correct
164
+ // way to ensure we can get a cacheable AST that's quick.
165
+ // TODO: Move this to a drush command that can precalculate this.
166
+ $ schemaDefinitions = [$ schema ->getAstNode (), ...$ schema ->extensionASTNodes ];
167
+ foreach ($ schema ->getTypeMap () as $ type ) {
168
+ if ($ type ->astNode !== NULL ) {
169
+ $ schemaDefinitions [] = $ type ->astNode ;
170
+ }
171
+ foreach ($ type ->extensionASTNodes ?? [] as $ extensionNode ) {
172
+ $ schemaDefinitions [] = $ extensionNode ;
173
+ }
136
174
}
175
+ $ ast = new DocumentNode (
176
+ ['definitions ' => new NodeList ($ schemaDefinitions )]
177
+ );
178
+ $ this ->storeCachedDocument ($ ast );
179
+
180
+ return $ schema ;
181
+ }
137
182
138
- if ($ extendSchema = $ this ->getExtensionDocument ($ extensions )) {
139
- return SchemaExtender::extend ($ schema , $ extendSchema );
183
+ private function loadCachedDocument () : ?Node {
184
+ if ($ this ->documentCache ->load ($ this ->getPluginId ())) {
185
+ return AST ::fromArray (__do_get_schema ());
140
186
}
187
+ return NULL ;
188
+ }
141
189
142
- return $ schema ;
190
+ private function storeCachedDocument (Node $ document ) : bool {
191
+ return $ this ->documentCache ->save ($ this ->getPluginId (), "<?php \nfunction __do_get_schema() { return " . var_export (AST ::toArray ($ document ), true ) . "; } " );
143
192
}
144
193
145
194
/**
@@ -149,6 +198,41 @@ protected function getExtensions() {
149
198
return $ this ->extensionManager ->getExtensions ($ this ->getPluginId ());
150
199
}
151
200
201
+ protected function getBaseSchemaAst () {
202
+ $ base_source = $ this ->getSchemaDefinition ();
203
+ if (is_string ($ base_source )) {
204
+ @trigger_error ("Returning a string from " . __CLASS__ . "::getSchemaDefinition is deprecated. Return an instance of Source or NULL instead. " );
205
+ $ base_source = new Source ($ base_source , __CLASS__ );
206
+ }
207
+ return Parser::parse ($ base_source ?? "" );
208
+ }
209
+
210
+ protected function getExtensionAsts (array $ extensions = []) {
211
+ $ extension_base_asts = [];
212
+ $ extension_extend_asts = [];
213
+ foreach ($ extensions as $ id => $ extension ) {
214
+ $ base_definition = $ extension ->getBaseDefinition ();
215
+ if (is_string ($ base_definition )) {
216
+ @trigger_error ("Returning a string from " . get_class ($ extension ) . "::getBaseDefinition is deprecated. Return an instance of Source or NULL instead. " );
217
+ $ base_definition = new Source ($ base_definition , $ id . "_base " );
218
+ }
219
+ if ($ base_definition !== NULL ) {
220
+ $ extension_base_asts [] = Parser::parse ($ base_definition );
221
+ }
222
+
223
+ $ extend_definition = $ extension ->getExtensionDefinition ();
224
+ if (is_string ($ extend_definition )) {
225
+ @trigger_error ("Returning a string from " . get_class ($ extension ) . "::getExtensionDefinition is deprecated. Return an instance of Source or NULL instead. " );
226
+ $ extend_definition = new Source ($ extend_definition , $ id . "_extend " );
227
+ }
228
+ if ($ extend_definition !== NULL ) {
229
+ $ extension_extend_asts [] = Parser::parse ($ extend_definition );
230
+ }
231
+ }
232
+
233
+ return [...$ extension_base_asts , ...$ extension_extend_asts ];
234
+ }
235
+
152
236
/**
153
237
* Retrieves the parsed AST of the schema definition.
154
238
*
@@ -161,23 +245,27 @@ protected function getExtensions() {
161
245
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
162
246
*/
163
247
protected function getSchemaDocument (array $ extensions = []) {
164
- // Only use caching of the parsed document if we aren't in development mode.
165
- $ cid = "schema: {$ this ->getPluginId ()}" ;
166
- if (empty ($ this ->inDevelopment ) && $ cache = $ this ->astCache ->get ($ cid )) {
167
- return $ cache ->data ;
168
- }
169
248
170
- $ extensions = array_filter (array_map (function (SchemaExtensionPluginInterface $ extension ) {
171
- return $ extension ->getBaseDefinition ();
172
- }, $ extensions ), function ($ definition ) {
173
- return !empty ($ definition );
174
- });
249
+ $ extensions = array_filter (
250
+ array_map (
251
+ function (SchemaExtensionPluginInterface $ extension ) {
252
+ $ definition = $ extension ->getBaseDefinition ();
253
+ if (is_string ($ definition )) {
254
+ @trigger_error ("Returning a string from " . get_class ($ extension ) . "::getBaseDefinition is deprecated. Return an instance of Source or NULL instead. " );
255
+ $ definition = new Source ($ definition );
256
+ }
257
+ return $ definition ;
258
+ },
259
+ $ extensions
260
+ ),
261
+ function ($ definition ) {
262
+ return !empty ($ definition );
263
+ }
264
+ );
265
+
175
266
176
267
$ schema = array_merge ([$ this ->getSchemaDefinition ()], $ extensions );
177
268
$ ast = Parser::parse (implode ("\n\n" , $ schema ));
178
- if (empty ($ this ->inDevelopment )) {
179
- $ this ->astCache ->set ($ cid , $ ast , CacheBackendInterface::CACHE_PERMANENT , ['graphql ' ]);
180
- }
181
269
182
270
return $ ast ;
183
271
}
@@ -193,30 +281,21 @@ protected function getSchemaDocument(array $extensions = []) {
193
281
* @throws \GraphQL\Error\SyntaxError
194
282
*/
195
283
protected function getExtensionDocument (array $ extensions = []) {
196
- // Only use caching of the parsed document if we aren't in development mode.
197
- $ cid = "extension: {$ this ->getPluginId ()}" ;
198
- if (empty ($ this ->inDevelopment ) && $ cache = $ this ->astCache ->get ($ cid )) {
199
- return $ cache ->data ;
200
- }
201
-
202
284
$ extensions = array_filter (array_map (function (SchemaExtensionPluginInterface $ extension ) {
203
285
return $ extension ->getExtensionDefinition ();
204
286
}, $ extensions ), function ($ definition ) {
205
287
return !empty ($ definition );
206
288
});
207
289
208
290
$ ast = !empty ($ extensions ) ? Parser::parse (implode ("\n\n" , $ extensions )) : NULL ;
209
- if (empty ($ this ->inDevelopment )) {
210
- $ this ->astCache ->set ($ cid , $ ast , CacheBackendInterface::CACHE_PERMANENT , ['graphql ' ]);
211
- }
212
291
213
292
return $ ast ;
214
293
}
215
294
216
295
/**
217
296
* Retrieves the raw schema definition string.
218
297
*
219
- * @return string
298
+ * @return \GraphQL\Language\Source| string
220
299
* The schema definition.
221
300
*
222
301
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
@@ -236,7 +315,8 @@ protected function getSchemaDefinition() {
236
315
$ module ->getName (), $ path , $ definition ['class ' ]));
237
316
}
238
317
239
- return file_get_contents ($ file ) ?: NULL ;
318
+ $ contents = file_get_contents ($ file );
319
+ return $ contents ? new Source ($ contents , $ file ) : NULL ;
240
320
}
241
321
242
322
}
0 commit comments