Skip to content

Commit

Permalink
Improve performance of GraphQlReader.php stitching
Browse files Browse the repository at this point in the history
  • Loading branch information
convenient committed Jan 28, 2021
1 parent 51aab9d commit fb10610
Showing 1 changed file with 83 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -86,25 +86,103 @@ public function read($scope = null) : array
}

/**
* Gather as many schema together to be parsed in one go for performance
* Collect any duplicate types in an array to retry after the initial large parse
*
* Compatible with @see GraphQlReader::parseTypes
*/
$typesToRedo = [];
$knownTypes = [];
foreach ($schemaFiles as $filePath => $partialSchemaContent) {
foreach ($schemaFiles as $partialSchemaContent) {
$partialSchemaTypes = $this->parseTypes($partialSchemaContent);

// Keep declarations from current partial schema, add missing declarations from all previously read schemas
$knownTypes = $partialSchemaTypes + $knownTypes;
$schemaContent = implode("\n", $knownTypes);
// Filter out duplicated ones and save them into a list to be retried
$tmpTypes = $knownTypes;
foreach ($partialSchemaTypes as $intendedKey => $partialSchemaType) {
if (isset($tmpTypes[$intendedKey])) {
if (!isset($typesToRedo[$intendedKey])) {
$typesToRedo[$intendedKey] = [];
}
$typesToRedo[$intendedKey][] = $partialSchemaType;
continue;
}
$tmpTypes[$intendedKey] = $partialSchemaType;
}
$knownTypes = $tmpTypes;
}

/**
* Read this large batch of data, this builds most of the $results array
*/
$schemaContent = implode("\n", $knownTypes);
$results = $this->readPartialTypes($schemaContent);

/**
* Go over the list of types to be retried and batch them up into as few batches as possible
*/
$typesToRedoBatches = [];
foreach ($typesToRedo as $type => $batches) {
foreach ($batches as $id => $data) {
if (!isset($typesToRedoBatches[$id])) {
$typesToRedoBatches[$id] = [];
}
$typesToRedoBatches[$id][$type] = $data;
}
}

/**
* Process each remaining batch with the minimal amount of additional schema data for performance
*/
foreach ($typesToRedoBatches as $typesToRedoBatch) {
$typesToUse = $this->getTypesToUse($typesToRedoBatch, $knownTypes);
$knownTypes = $typesToUse + $knownTypes;
$schemaContent = implode("\n", $typesToUse);

$partialResults = $this->readPartialTypes($schemaContent);
$results = array_replace_recursive($results, $partialResults);
$results = $this->addModuleNameToTypes($results, $filePath);
}

$results = $this->copyInterfaceFieldsToConcreteTypes($results);
return $results;
}


/**
* Get the minimum amount of additional types so that performance is improved
*
* The use of a strpos check here is a bit odd in the context of feeding data into an AST but for the performance
* gains and to prevent downtime it is necessary
*
* @link https://github.com/webonyx/graphql-php/issues/244
* @link https://github.com/webonyx/graphql-php/issues/244#issuecomment-383912418
*
* @param array $typesToRedoBatch
* @param array $types
* @return array
*/
private function getTypesToUse($typesToRedoBatch, $types)
{
$counter = 0;
$totalKnownSymbolsCount = count($typesToRedoBatch) + count($types);

$typesToUse = $typesToRedoBatch;
for ($i=0; $i < $totalKnownSymbolsCount; $i++) {
$changesMade = false;
$schemaContent = implode("\n", $typesToUse);
foreach ($types as $type => $schema) {
if ((!isset($typesToUse[$type]) && strpos($schemaContent, $type) !== false)) {
$typesToUse[$type] = $schema;
$changesMade = true;
}
}
if (!$changesMade) {
break;
}
$counter++;
}
return $typesToUse;
}

/**
* Extract types as string from schema as string
*
Expand Down Expand Up @@ -298,49 +376,4 @@ private function removePlaceholderFromResults(array $partialResults) : array
}
return $partialResults;
}

/**
* Get a module name by file path
*
* @param string $file
* @return string
*/
private static function getModuleNameForRelevantFile(string $file): string
{
if (!isset(self::$componentRegistrar)) {
self::$componentRegistrar = new ComponentRegistrar();
}
$foundModuleName = '';
foreach (self::$componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $moduleName => $moduleDir) {
if (strpos($file, $moduleDir . '/') !== false) {
$foundModuleName = str_replace('_', '\\', $moduleName);
break;
}
}

return $foundModuleName;
}

/**
* Add a module name to types
*
* @param array $source
* @param string $filePath
* @return array
*/
private function addModuleNameToTypes(array $source, string $filePath): array
{
foreach ($source as $typeName => $typeDefinition) {
if (!isset($typeDefinition['module'])) {
$hasTypeResolver = (bool)($typeDefinition['typeResolver'] ?? false);
$hasImplements = (bool)($typeDefinition['implements'] ?? false);
$typeDefinition = (bool)($typeDefinition['type'] ?? false);
if ((($typeDefinition === InterfaceType::GRAPHQL_INTERFACE && $hasTypeResolver) || $hasImplements)) {
$source[$typeName]['module'] = self::getModuleNameForRelevantFile($filePath);
}
}
}

return $source;
}
}

0 comments on commit fb10610

Please sign in to comment.