Skip to content

Commit 0672892

Browse files
committed
Improve performance of GraphQlReader.php stitching
1 parent 51aab9d commit 0672892

File tree

1 file changed

+81
-50
lines changed

1 file changed

+81
-50
lines changed

lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php

Lines changed: 81 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -86,25 +86,101 @@ public function read($scope = null) : array
8686
}
8787

8888
/**
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+
*
8992
* Compatible with @see GraphQlReader::parseTypes
9093
*/
94+
$typesToRedo = [];
9195
$knownTypes = [];
92-
foreach ($schemaFiles as $filePath => $partialSchemaContent) {
96+
foreach ($schemaFiles as $partialSchemaContent) {
9397
$partialSchemaTypes = $this->parseTypes($partialSchemaContent);
9498

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);
98140

99141
$partialResults = $this->readPartialTypes($schemaContent);
100142
$results = array_replace_recursive($results, $partialResults);
101-
$results = $this->addModuleNameToTypes($results, $filePath);
102143
}
103144

104145
$results = $this->copyInterfaceFieldsToConcreteTypes($results);
105146
return $results;
106147
}
107148

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+
108184
/**
109185
* Extract types as string from schema as string
110186
*
@@ -298,49 +374,4 @@ private function removePlaceholderFromResults(array $partialResults) : array
298374
}
299375
return $partialResults;
300376
}
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-
}
346377
}

0 commit comments

Comments
 (0)