@@ -86,25 +86,101 @@ public function read($scope = null) : array
86
86
}
87
87
88
88
/**
89
+ * Gather as many schema together to be parsed in one go for performance
90
+ * Collect any duplicate types in an array to retry after the initial large parse
91
+ *
89
92
* Compatible with @see GraphQlReader::parseTypes
90
93
*/
94
+ $ typesToRedo = [];
91
95
$ knownTypes = [];
92
- foreach ($ schemaFiles as $ filePath => $ partialSchemaContent ) {
96
+ foreach ($ schemaFiles as $ partialSchemaContent ) {
93
97
$ partialSchemaTypes = $ this ->parseTypes ($ partialSchemaContent );
94
98
95
- // Keep declarations from current partial schema, add missing declarations from all previously read schemas
96
- $ knownTypes = $ partialSchemaTypes + $ knownTypes ;
97
- $ schemaContent = implode ("\n" , $ knownTypes );
99
+ // Filter out duplicated ones and save them into a list to be retried
100
+ $ tmpTypes = $ knownTypes ;
101
+ foreach ($ partialSchemaTypes as $ intendedKey => $ partialSchemaType ) {
102
+ if (isset ($ tmpTypes [$ intendedKey ])) {
103
+ if (!isset ($ typesToRedo [$ intendedKey ])) {
104
+ $ typesToRedo [$ intendedKey ] = [];
105
+ }
106
+ $ typesToRedo [$ intendedKey ][] = $ partialSchemaType ;
107
+ continue ;
108
+ }
109
+ $ tmpTypes [$ intendedKey ] = $ partialSchemaType ;
110
+ }
111
+ $ knownTypes = $ tmpTypes ;
112
+ }
113
+
114
+ /**
115
+ * Read this large batch of data, this builds most of the $results array
116
+ */
117
+ $ schemaContent = implode ("\n" , $ knownTypes );
118
+ $ results = $ this ->readPartialTypes ($ schemaContent );
119
+
120
+ /**
121
+ * Go over the list of types to be retried and batch them up into as few batches as possible
122
+ */
123
+ $ typesToRedoBatches = [];
124
+ foreach ($ typesToRedo as $ type => $ batches ) {
125
+ foreach ($ batches as $ id => $ data ) {
126
+ if (!isset ($ typesToRedoBatches [$ id ])) {
127
+ $ typesToRedoBatches [$ id ] = [];
128
+ }
129
+ $ typesToRedoBatches [$ id ][$ type ] = $ data ;
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Process each remaining batch with the minimal amount of additional schema data for performance
135
+ */
136
+ foreach ($ typesToRedoBatches as $ typesToRedoBatch ) {
137
+ $ typesToUse = $ this ->getTypesToUse ($ typesToRedoBatch , $ knownTypes );
138
+ $ knownTypes = $ typesToUse + $ knownTypes ;
139
+ $ schemaContent = implode ("\n" , $ typesToUse );
98
140
99
141
$ partialResults = $ this ->readPartialTypes ($ schemaContent );
100
142
$ results = array_replace_recursive ($ results , $ partialResults );
101
- $ results = $ this ->addModuleNameToTypes ($ results , $ filePath );
102
143
}
103
144
104
145
$ results = $ this ->copyInterfaceFieldsToConcreteTypes ($ results );
105
146
return $ results ;
106
147
}
107
148
149
+
150
+ /**
151
+ * Get the minimum amount of additional types so that performance is improved
152
+ *
153
+ * The use of a strpos check here is a bit odd in the context of feeding data into an AST but for the performance
154
+ * gains and to prevent downtime it is necessary
155
+ *
156
+ * @link https://github.com/webonyx/graphql-php/issues/244
157
+ * @link https://github.com/webonyx/graphql-php/issues/244#issuecomment-383912418
158
+ *
159
+ * @param array $typesToRedoBatch
160
+ * @param array $types
161
+ * @return array
162
+ */
163
+ private function getTypesToUse ($ typesToRedoBatch , $ types )
164
+ {
165
+ $ totalKnownSymbolsCount = count ($ typesToRedoBatch ) + count ($ types );
166
+
167
+ $ typesToUse = $ typesToRedoBatch ;
168
+ for ($ i =0 ; $ i < $ totalKnownSymbolsCount ; $ i ++) {
169
+ $ changesMade = false ;
170
+ $ schemaContent = implode ("\n" , $ typesToUse );
171
+ foreach ($ types as $ type => $ schema ) {
172
+ if ((!isset ($ typesToUse [$ type ]) && strpos ($ schemaContent , $ type ) !== false )) {
173
+ $ typesToUse [$ type ] = $ schema ;
174
+ $ changesMade = true ;
175
+ }
176
+ }
177
+ if (!$ changesMade ) {
178
+ break ;
179
+ }
180
+ }
181
+ return $ typesToUse ;
182
+ }
183
+
108
184
/**
109
185
* Extract types as string from schema as string
110
186
*
@@ -298,49 +374,4 @@ private function removePlaceholderFromResults(array $partialResults) : array
298
374
}
299
375
return $ partialResults ;
300
376
}
301
-
302
- /**
303
- * Get a module name by file path
304
- *
305
- * @param string $file
306
- * @return string
307
- */
308
- private static function getModuleNameForRelevantFile (string $ file ): string
309
- {
310
- if (!isset (self ::$ componentRegistrar )) {
311
- self ::$ componentRegistrar = new ComponentRegistrar ();
312
- }
313
- $ foundModuleName = '' ;
314
- foreach (self ::$ componentRegistrar ->getPaths (ComponentRegistrar::MODULE ) as $ moduleName => $ moduleDir ) {
315
- if (strpos ($ file , $ moduleDir . '/ ' ) !== false ) {
316
- $ foundModuleName = str_replace ('_ ' , '\\' , $ moduleName );
317
- break ;
318
- }
319
- }
320
-
321
- return $ foundModuleName ;
322
- }
323
-
324
- /**
325
- * Add a module name to types
326
- *
327
- * @param array $source
328
- * @param string $filePath
329
- * @return array
330
- */
331
- private function addModuleNameToTypes (array $ source , string $ filePath ): array
332
- {
333
- foreach ($ source as $ typeName => $ typeDefinition ) {
334
- if (!isset ($ typeDefinition ['module ' ])) {
335
- $ hasTypeResolver = (bool )($ typeDefinition ['typeResolver ' ] ?? false );
336
- $ hasImplements = (bool )($ typeDefinition ['implements ' ] ?? false );
337
- $ typeDefinition = (bool )($ typeDefinition ['type ' ] ?? false );
338
- if ((($ typeDefinition === InterfaceType::GRAPHQL_INTERFACE && $ hasTypeResolver ) || $ hasImplements )) {
339
- $ source [$ typeName ]['module ' ] = self ::getModuleNameForRelevantFile ($ filePath );
340
- }
341
- }
342
- }
343
-
344
- return $ source ;
345
- }
346
377
}
0 commit comments