Skip to content

Commit f478f3d

Browse files
Fix exponential time complexity in AliasArguments with circular type references
**Problem:** `getAliasesInFields()` traverses ALL possible fields in the type schema, not just fields present in the request data. With circular type references (e.g., PageInput ↔ SectionInput) and a depth limit calculated from request data, this causes exponential traversal. **Evidence:** - Mutations taking 11+ seconds instead of milliseconds - Logging showed 6 million recursive calls before timeout - Depth reaching 12-13 levels with circular paths **Solution:** Only traverse fields that exist in the actual request data. This prevents exponential explosion while preserving full alias functionality. **Performance:** - Before: 11+ seconds per mutation - After: ~150ms per mutation - Improvement: 98.7% reduction **Testing:** - Tested with deeply nested input types (11 levels) - Tested with circular type references - Verified alias functionality preserved (deprecated field name mapping) - Verified legitimate nested types work (e.g., Page->translations array)
1 parent 3666481 commit f478f3d

File tree

1 file changed

+44
-4
lines changed

1 file changed

+44
-4
lines changed

src/Support/AliasArguments/AliasArguments.php

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public function __construct(array $queryArguments, array $requestArguments)
3232

3333
public function get(): array
3434
{
35-
$pathsWithAlias = $this->getAliasesInFields($this->queryArguments);
35+
$pathsWithAlias = $this->getAliasesInFields($this->queryArguments, $this->requestArguments);
3636

3737
return (new ArrayKeyChange())->modify($this->requestArguments, $pathsWithAlias);
3838
}
@@ -59,7 +59,19 @@ protected function getArrayDepth(array $array): int
5959
return $maxDepth;
6060
}
6161

62-
protected function getAliasesInFields(array $fields, $prefix = ''): array
62+
/**
63+
* Get aliases from fields, only traversing fields present in request data.
64+
*
65+
* This prevents exponential time complexity with circular type references by only
66+
* exploring the actual data structure sent by the client, not all possible fields
67+
* in the type schema.
68+
*
69+
* @param array $fields Type field definitions
70+
* @param array|null $requestData Actual request data at this level (null for initial call)
71+
* @param string $prefix Path prefix for nested fields
72+
* @return array<string,string> Map of field paths to their aliases
73+
*/
74+
protected function getAliasesInFields(array $fields, ?array $requestData = null, string $prefix = ''): array
6375
{
6476
// checks for traversal beyond the max depth
6577
// this scenario occurs in types with recursive relations
@@ -69,6 +81,12 @@ protected function getAliasesInFields(array $fields, $prefix = ''): array
6981
$pathAndAlias = [];
7082

7183
foreach ($fields as $name => $arg) {
84+
// KEY FIX: Skip fields not present in actual request data
85+
// This prevents exponential explosion with circular type references
86+
if ($requestData !== null && !array_key_exists($name, $requestData)) {
87+
continue;
88+
}
89+
7290
$type = null;
7391

7492
// $arg is either an array DSL notation or an InputObjectField
@@ -91,7 +109,8 @@ protected function getAliasesInFields(array $fields, $prefix = ''): array
91109
$pathAndAlias[$newPrefix] = $alias;
92110
}
93111

94-
if ($this->isWrappedInList($type)) {
112+
$isWrappedInList = $this->isWrappedInList($type);
113+
if ($isWrappedInList) {
95114
$newPrefix .= '.*';
96115
}
97116

@@ -101,7 +120,28 @@ protected function getAliasesInFields(array $fields, $prefix = ''): array
101120
continue;
102121
}
103122

104-
$pathAndAlias = $pathAndAlias + $this->getAliasesInFields($type->getFields(), $newPrefix);
123+
// Get the actual data at this field (if requestData provided)
124+
$fieldData = $requestData !== null ? ($requestData[$name] ?? null) : null;
125+
126+
// If it's a list, process each item
127+
if ($isWrappedInList && is_array($fieldData)) {
128+
foreach ($fieldData as $item) {
129+
if (is_array($item)) {
130+
$pathAndAlias = $pathAndAlias + $this->getAliasesInFields(
131+
$type->getFields(),
132+
$item,
133+
$newPrefix
134+
);
135+
}
136+
}
137+
} elseif ($fieldData !== null && is_array($fieldData)) {
138+
// Single object
139+
$pathAndAlias = $pathAndAlias + $this->getAliasesInFields(
140+
$type->getFields(),
141+
$fieldData,
142+
$newPrefix
143+
);
144+
}
105145
}
106146

107147
return $pathAndAlias;

0 commit comments

Comments
 (0)