Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 47 additions & 6 deletions src/Support/AliasArguments/AliasArguments.php
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<?php

declare(strict_types = 1);
Expand Down Expand Up @@ -32,7 +32,7 @@

public function get(): array
{
$pathsWithAlias = $this->getAliasesInFields($this->queryArguments);
$pathsWithAlias = $this->getAliasesInFields($this->queryArguments, $this->requestArguments);

return (new ArrayKeyChange())->modify($this->requestArguments, $pathsWithAlias);
}
Expand All @@ -59,7 +59,20 @@
return $maxDepth;
}

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

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

$type = null;

// $arg is either an array DSL notation or an InputObjectField
Expand All @@ -83,25 +102,47 @@
continue;
}

$newPrefix = $prefix ? $prefix . '.' . $name : $name;
$newPrefix = $prefix ? $prefix.'.'.$name : $name;

$alias = $arg->config['alias'] ?? $arg->alias ?? null;

if ($alias) {
$pathAndAlias[$newPrefix] = $alias;
}

if ($this->isWrappedInList($type)) {
$isWrappedInList = $this->isWrappedInList($type);
if ($isWrappedInList) {
$newPrefix .= '.*';
}

$type = $this->getWrappedType($type);

if (!($type instanceof InputObjectType)) {
if (! ($type instanceof InputObjectType)) {
continue;
}

$pathAndAlias = $pathAndAlias + $this->getAliasesInFields($type->getFields(), $newPrefix);
// Get the actual data at this field (if requestData provided)
$fieldData = $requestData !== null ? ($requestData[$name] ?? null) : null;

// If it's a list, process each item
if ($isWrappedInList && is_array($fieldData)) {
foreach ($fieldData as $item) {
if (is_array($item)) {
$pathAndAlias = $pathAndAlias + $this->getAliasesInFields(
$type->getFields(),
$item,
$newPrefix
);
}
}
} elseif ($fieldData !== null && is_array($fieldData)) {
// Single object
$pathAndAlias = $pathAndAlias + $this->getAliasesInFields(
$type->getFields(),
$fieldData,
$newPrefix
);
}
}

return $pathAndAlias;
Expand Down
Loading