From 95ed512a75ffdbab37173d9ba2c748a3daebf675 Mon Sep 17 00:00:00 2001 From: Jillur Rahman <22579419+developerjillur@users.noreply.github.com> Date: Wed, 28 Oct 2026 12:09:55 +0600 Subject: [PATCH] Fix alias-aware field selection merging --- src/Type/Definition/ResolveInfo.php | 11 ++- tests/Type/ResolveInfoTest.php | 111 ++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 4 deletions(-) diff --git a/src/Type/Definition/ResolveInfo.php b/src/Type/Definition/ResolveInfo.php index 5204cd547..64d131963 100644 --- a/src/Type/Definition/ResolveInfo.php +++ b/src/Type/Definition/ResolveInfo.php @@ -350,7 +350,7 @@ public function getFieldSelectionWithAliases(int $depth = 0): array $field = $this->parentType->getField($fieldNode->name->value); $fieldType = $field->getType(); - $fields = array_merge_recursive( + $fields = array_replace_recursive( $fields, $this->foldSelectionWithAlias($selectionSet, $depth, $fieldType) ); @@ -470,7 +470,10 @@ private function foldSelectionWithAlias(SelectionSetNode $selectionSet, int $des continue; } - $aliasInfo['selectionSet'] = $this->foldSelectionWithAlias($nestedSelectionSet, $descend - 1, $fieldType); + $aliasInfo['selectionSet'] = array_replace_recursive( + $aliasInfo['selectionSet'] ?? [], + $this->foldSelectionWithAlias($nestedSelectionSet, $descend - 1, $fieldType) + ); } elseif ($selection instanceof FragmentSpreadNode) { $spreadName = $selection->name->value; $fragment = $this->fragments[$spreadName] ?? null; @@ -481,7 +484,7 @@ private function foldSelectionWithAlias(SelectionSetNode $selectionSet, int $des $fieldType = $this->schema->getType($fragment->typeCondition->name->value); assert($fieldType instanceof Type, 'ensured by query validation'); - $fields = array_merge_recursive( + $fields = array_replace_recursive( $this->foldSelectionWithAlias($fragment->selectionSet, $descend, $fieldType), $fields ); @@ -500,7 +503,7 @@ private function foldSelectionWithAlias(SelectionSetNode $selectionSet, int $des continue; } - $fields = array_merge_recursive( + $fields = array_replace_recursive( $this->foldSelectionWithAlias($selection->selectionSet, $descend, $fieldType), $fields ); diff --git a/tests/Type/ResolveInfoTest.php b/tests/Type/ResolveInfoTest.php index 4940a2570..e6a6fda4c 100644 --- a/tests/Type/ResolveInfoTest.php +++ b/tests/Type/ResolveInfoTest.php @@ -1048,6 +1048,117 @@ public function testGetFieldSelectionWithAliases(): void self::assertEmpty($result10->errors, 'Query With Union type should have no errors'); } + /** @dataProvider providerGetFieldSelectionWithAliasesMergedSelections */ + public function testGetFieldSelectionWithAliasesMergedSelections(string $document, bool $recursiveType): void + { + $post = new ObjectType([ + 'name' => 'Post', + 'fields' => [ + 'id' => Type::id(), + 'text' => Type::string(), + ], + ]); + + if ($recursiveType) { + $post->config['fields'] = [ + 'id' => Type::id(), + 'text' => Type::string(), + 'related' => $post, + ]; + } + + $user = new ObjectType([ + 'name' => 'User', + 'fields' => [ + 'name' => Type::string(), + 'posts' => [ + 'type' => Type::nonNull(Type::listOf(Type::nonNull($post))), + 'args' => [ + 'limit' => Type::int(), + 'ids' => Type::listOf(Type::int()), + ], + ], + ], + ]); + + $selection = null; + $query = new ObjectType([ + 'name' => 'Query', + 'fields' => [ + 'user' => [ + 'type' => $user, + 'resolve' => static function ($value, array $args, $context, ResolveInfo $info) use (&$selection) { + $selection = $info->getFieldSelectionWithAliases(1); + + return null; + }, + ], + ], + ]); + + $result = GraphQL::executeQuery(new Schema(['query' => $query]), $document); + + self::assertSame([], $result->errors); + self::assertIsArray($selection); + self::assertSame($post, $selection['posts']['recent']['type']); + self::assertSame(Type::string(), $selection['name']['name']['type']); + self::assertSame(['limit' => 2, 'ids' => [1, 2]], $selection['posts']['recent']['args']); + self::assertEquals([ + 'name' => [ + 'name' => ['args' => [], 'type' => Type::string()], + ], + 'posts' => [ + 'recent' => [ + 'args' => ['limit' => 2, 'ids' => [1, 2]], + 'type' => $post, + 'selectionSet' => [ + 'id' => [ + 'id' => ['args' => [], 'type' => Type::id()], + ], + 'text' => [ + 'text' => ['args' => [], 'type' => Type::string()], + ], + ], + ], + ], + ], $selection); + } + + /** @return \Generator */ + public static function providerGetFieldSelectionWithAliasesMergedSelections(): \Generator + { + $first = 'name recent: posts(limit: 2, ids: [1, 2]) { id }'; + $second = 'name recent: posts(limit: 2, ids: [1, 2]) { text }'; + + $documents = [ + 'named fragment after field' => << << << << << << $document) { + yield $name => [$document, false]; + yield "{$name} with recursive type" => [$document, true]; + } + } + public function testPathAndUnaliasedPath(): void { $resolveInfo = new ObjectType([