-
-
Notifications
You must be signed in to change notification settings - Fork 570
Description
Hello 👋
Recently I ran into some weird recursion errors when executing queries like these:
query ExampleQuery {
user {
posts {
id
}
...ExampleFragment
}
}
fragment ExampleFragment on User {
posts {
text
}
}
This results in a Recursion detected
error. It seems like the problem is the way the ResolveInfo
class is merging the fields of the query and the fragment. There, the PHP function array_merge_recursive
is used:
graphql-php/src/Type/Definition/ResolveInfo.php
Lines 484 to 487 in e165b2e
$fields = array_merge_recursive( | |
$this->foldSelectionWithAlias($fragment->selectionSet, $descend, $fieldType), | |
$fields | |
); |
After some digging I found out, that array_merge_recursive
is deeply converting objects to arrays. So in my case it seems like one of my Type
implementations is having a reference to itself or is forming a recursive cycle with its references. array_merge_recursive
tries to follow this cycle and runs into an infinite loop.
I feel like this deep object conversion is not intended here as it's not documented anywhere in PHP's manual and is not very practical anyways. There's even a bug report from 2005 pointing out this behavior as unintuitive.
While researching I also found this method in QueryPlan
:
graphql-php/src/Type/Definition/QueryPlan.php
Lines 280 to 306 in 08f1556
/** | |
* Merges nested arrays, but handles non array values differently from array_merge_recursive. | |
* While array_merge_recursive tries to merge non-array values, in this implementation they will be overwritten. | |
* | |
* @see https://stackoverflow.com/a/25712428 | |
* | |
* @param array<mixed> $array1 | |
* @param array<mixed> $array2 | |
* | |
* @return array<mixed> | |
*/ | |
private function arrayMergeDeep(array $array1, array $array2): array | |
{ | |
foreach ($array2 as $key => &$value) { | |
if (is_numeric($key)) { | |
if (! in_array($value, $array1, true)) { | |
$array1[] = $value; | |
} | |
} elseif (is_array($value) && isset($array1[$key]) && is_array($array1[$key])) { | |
$array1[$key] = $this->arrayMergeDeep($array1[$key], $value); | |
} else { | |
$array1[$key] = $value; | |
} | |
} | |
return $array1; | |
} |
Maybe something like this would also work for ResolveInfo
?
Let me know if you need further information or if I can help anywhere.