1313use Drupal \graphql \Plugin \SchemaExtensionPluginInterface ;
1414use Drupal \graphql \Plugin \SchemaExtensionPluginManager ;
1515use Drupal \graphql \Plugin \SchemaPluginInterface ;
16+ use GraphQL \Language \AST \DocumentNode ;
1617use GraphQL \Language \AST \InterfaceTypeDefinitionNode ;
1718use GraphQL \Language \AST \TypeDefinitionNode ;
1819use GraphQL \Language \AST \UnionTypeDefinitionNode ;
1920use GraphQL \Language \Parser ;
21+ use GraphQL \Type \Schema ;
2022use GraphQL \Utils \BuildSchema ;
2123use GraphQL \Utils \SchemaExtender ;
24+ use GraphQL \Utils \SchemaPrinter ;
2225use Symfony \Component \DependencyInjection \ContainerInterface ;
2326
2427/**
@@ -117,15 +120,8 @@ public function __construct(
117120 */
118121 public function getSchema (ResolverRegistryInterface $ registry ) {
119122 $ extensions = $ this ->getExtensions ();
120- $ resolver = [$ registry , 'resolveType ' ];
121123 $ 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 );
129125
130126 if (empty ($ extensions )) {
131127 return $ schema ;
@@ -135,10 +131,31 @@ public function getSchema(ResolverRegistryInterface $registry) {
135131 $ extension ->registerResolvers ($ registry );
136132 }
137133
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 ;
140137 }
141138
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 );
142159 return $ schema ;
143160 }
144161
@@ -185,6 +202,33 @@ protected function getSchemaDocument(array $extensions = []) {
185202 return $ ast ;
186203 }
187204
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+
188232 /**
189233 * Retrieves the parsed AST of the schema extension definitions.
190234 *
@@ -196,23 +240,14 @@ protected function getSchemaDocument(array $extensions = []) {
196240 * @throws \GraphQL\Error\SyntaxError
197241 */
198242 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-
205243 $ extensions = array_filter (array_map (function (SchemaExtensionPluginInterface $ extension ) {
206244 return $ extension ->getExtensionDefinition ();
207245 }, $ extensions ), function ($ definition ) {
208246 return !empty ($ definition );
209247 });
210248
211249 $ 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().
216251 return $ ast ;
217252 }
218253
0 commit comments