Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 0244f6f

Browse files
committedMar 17, 2025·
ResolveInfo::getFieldSelectionWithAliases() => now add instance types and the folded union types to the returned schema.
1 parent e0db1f6 commit 0244f6f

File tree

3 files changed

+278
-47
lines changed

3 files changed

+278
-47
lines changed
 

‎src/Type/Definition/ResolveInfo.php

+89-46
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,10 @@ public function getFieldSelection(int $depth = 0): array
221221
*
222222
* The result maps original field names to a map of selections for that field, including aliases.
223223
* For each of those selections, you can find the following keys:
224-
* - "args" contains the passed arguments for this field/alias
225-
* - "selectionSet" contains potential nested fields of this field/alias. The structure is recursive from here.
224+
* - "args" contains the passed arguments for this field/alias (not on an union inline fragment)
225+
* - "type" contains the related Type instance found (will be the same for all aliases of a field)
226+
* - "selectionSet" contains potential nested fields of this field/alias (only on ObjectType). The structure is recursive from here.
227+
* - "unions" contains potential object types contained in an UnionType (only on UnionType). The structure is recursive from here and will go through the selectionSet of the object types.
226228
*
227229
* Example:
228230
* {
@@ -235,79 +237,107 @@ public function getFieldSelection(int $depth = 0): array
235237
* alias1: nested {
236238
* nested1(myArg: 2, mySecondAg: "test")
237239
* }
240+
* myUnion(myArg: 3) {
241+
* ...on Nested {
242+
* nested1(myArg: 4)
243+
* }
244+
* ...on MyCustomObject {
245+
* nested3
246+
* }
247+
* }
238248
* }
239249
* }
240250
*
241-
* Given this ResolveInfo instance is a part of "root" field resolution, and $depth === 1,
251+
* Given this ResolveInfo instance is a part of "root" field resolution, $depth === 1, and nested represent an ObjectType with a configured name "Nested",
242252
* this method will return:
243253
* [
244254
* 'id' => [
245255
* 'id' => [
246256
* 'args' => [],
257+
* 'type' => GraphQL\Type\Definition\IntType Object ( ... )),
247258
* ],
248259
* ],
249260
* 'nested' => [
250261
* 'nested' => [
251262
* 'args' => [],
263+
* 'type' => GraphQL\Type\Definition\ObjectType Object ( ... )),
252264
* 'selectionSet' => [
253265
* 'nested1' => [
254266
* 'nested1' => [
255267
* 'args' => [
256268
* 'myArg' => 1,
257269
* ],
270+
* 'type' => GraphQL\Type\Definition\StringType Object ( ... )),
258271
* ],
259272
* 'nested1Bis' => [
260273
* 'args' => [],
274+
* 'type' => GraphQL\Type\Definition\StringType Object ( ... )),
261275
* ],
262276
* ],
263277
* ],
264-
* ],
265-
* 'alias1' => [
278+
* ],
279+
* ],
280+
* 'alias1' => [
281+
* 'alias1' => [
266282
* 'args' => [],
283+
* 'type' => GraphQL\Type\Definition\ObjectType Object ( ... )),
267284
* 'selectionSet' => [
268-
* 'nested1' => [
269-
* 'nested1' => [
270-
* 'args' => [
271-
* 'myArg' => 2,
272-
* 'mySecondAg' => "test,
273-
* ],
285+
* 'nested1' => [
286+
* 'nested1' => [
287+
* 'args' => [
288+
* 'myArg' => 2,
289+
* 'mySecondAg' => "test,
290+
* ],
291+
* 'type' => GraphQL\Type\Definition\StringType Object ( ... )),
274292
* ],
275-
* ],
276-
* ],
293+
* ],
294+
* ],
277295
* ],
278296
* ],
297+
* 'myUnion' => [
298+
* 'myUnion' => [
299+
* 'args' => [
300+
* 'myArg' => 3
301+
* ],
302+
* 'type' => GraphQL\Type\Definition\UnionType Object ( ... )),
303+
* 'unions' => [
304+
* 'Nested' => [
305+
* 'type' => GraphQL\Type\Definition\ObjectType Object ( ... )),
306+
* 'selectionSet' => [
307+
* 'nested1' => [
308+
* 'nested1' => [
309+
* 'args' => [
310+
* 'myArg' => 4
311+
* ],
312+
* 'type' => GraphQL\Type\Definition\StringType Object ( ... )),
313+
* ],
314+
* ],
315+
* ],
316+
* ],
317+
* 'MyCustomObject' => [
318+
* 'type' => GraphQL\Tests\Type\TestClasses\MyCustomType Object ( ... )),
319+
* 'selectionSet' => [
320+
* 'nested3' => [
321+
* 'nested3' => [
322+
* 'args' => [],
323+
* 'type' => GraphQL\Type\Definition\StringType Object ( ... )),
324+
* ],
325+
* ],
326+
* ],
327+
* ],
328+
* ],
329+
* ],
330+
* ],
279331
* ]
280332
*
281-
* This method does not consider conditional typed fragments.
282-
* Use it with care for fields of interface and union types.
283-
* You can still alias the union type fields with the same name in order to extract their corresponding args.
284-
*
285-
* Example:
286-
* {
287-
* root {
288-
* id
289-
* unionPerson {
290-
* ...on Child {
291-
* name
292-
* birthdate(format: "d/m/Y")
293-
* }
294-
* ...on Adult {
295-
* adultName: name
296-
* adultBirthDate: birthdate(format: "Y-m-d")
297-
* job
298-
* }
299-
* }
300-
* }
301-
* }
302-
*
303333
* @param int $depth How many levels to include in the output beyond the first
304334
*
305-
* @throws \Exception
335+
* @return array<string, mixed>
336+
*
306337
* @throws Error
307338
* @throws InvariantViolation
308339
*
309-
* @return array<string, mixed>
310-
*
340+
* @throws \Exception
311341
* @api
312342
*/
313343
public function getFieldSelectionWithAliases(int $depth = 0): array
@@ -410,12 +440,17 @@ private function foldSelectionWithAlias(SelectionSetNode $selectionSet, int $des
410440
continue;
411441
}
412442

413-
assert($parentType instanceof HasFieldsType, 'ensured by query validation');
414-
415443
$fieldDef = $parentType->getField($fieldName);
416444
$fieldType = $fieldDef->getType();
417445
$fields[$fieldName][$aliasName]['args'] = Values::getArgumentValues($fieldDef, $selection, $this->variableValues);
418446

447+
$innerType = $fieldType;
448+
if ($innerType instanceof WrappingType) {
449+
$innerType = $innerType->getInnermostType();
450+
}
451+
452+
$fields[$fieldName][$aliasName]['type'] = $innerType;
453+
419454
if ($descend <= 0) {
420455
continue;
421456
}
@@ -425,6 +460,11 @@ private function foldSelectionWithAlias(SelectionSetNode $selectionSet, int $des
425460
continue;
426461
}
427462

463+
if (is_a($innerType, UnionType::class)) {
464+
$fields[$fieldName][$aliasName]['unions'] = $this->foldSelectionWithAlias($nestedSelectionSet, $descend, $fieldType);
465+
continue;
466+
}
467+
428468
$fields[$fieldName][$aliasName]['selectionSet'] = $this->foldSelectionWithAlias($nestedSelectionSet, $descend - 1, $fieldType);
429469
} elseif ($selection instanceof FragmentSpreadNode) {
430470
$spreadName = $selection->name->value;
@@ -434,7 +474,6 @@ private function foldSelectionWithAlias(SelectionSetNode $selectionSet, int $des
434474
}
435475

436476
$fieldType = $this->schema->getType($fragment->typeCondition->name->value);
437-
assert($fieldType instanceof Type, 'ensured by query validation');
438477

439478
$fields = \array_merge_recursive(
440479
$this->foldSelectionWithAlias($fragment->selectionSet, $descend, $fieldType),
@@ -445,12 +484,16 @@ private function foldSelectionWithAlias(SelectionSetNode $selectionSet, int $des
445484
$fieldType = $typeCondition === null
446485
? $parentType
447486
: $this->schema->getType($typeCondition->name->value);
448-
assert($fieldType instanceof Type, 'ensured by query validation');
449487

450-
$fields = \array_merge_recursive(
451-
$this->foldSelectionWithAlias($selection->selectionSet, $descend, $fieldType),
452-
$fields
453-
);
488+
if (is_a($parentType, UnionType::class)) {
489+
$fields[$fieldType->name()]['type'] = $fieldType;
490+
$fields[$fieldType->name()]['selectionSet'] = $this->foldSelectionWithAlias($selection->selectionSet, $descend, $fieldType);
491+
} else {
492+
$fields = \array_merge_recursive(
493+
$this->foldSelectionWithAlias($selection->selectionSet, $descend, $fieldType),
494+
$fields
495+
);
496+
}
454497
}
455498
}
456499

‎tests/Type/ResolveInfoTest.php

+170-1
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,18 @@
22

33
namespace GraphQL\Tests\Type;
44

5+
use GraphQL\Error\Error;
56
use GraphQL\GraphQL;
7+
use GraphQL\Tests\Type\TestClasses\CustomWithObject;
8+
use GraphQL\Tests\Type\TestClasses\MyCustomType;
9+
use GraphQL\Tests\Type\TestClasses\OtherCustom;
10+
use GraphQL\Type\Definition\IntType;
611
use GraphQL\Type\Definition\ListOfType;
712
use GraphQL\Type\Definition\ObjectType;
813
use GraphQL\Type\Definition\ResolveInfo;
14+
use GraphQL\Type\Definition\StringType;
915
use GraphQL\Type\Definition\Type;
16+
use GraphQL\Type\Definition\UnionType;
1017
use GraphQL\Type\Schema;
1118
use PHPUnit\Framework\TestCase;
1219

@@ -456,102 +463,128 @@
456463
++$aliasArgsNbTests;
457464
switch ($args['testName']) {
458465
case 'NoAlias':
466+
$level2Type = $aliasArgs['level2']['level2']['type'] ?? null;
467+
self::assertInstanceOf(IntType::class, $level2Type);
459468
self::assertSame([
460469
'level2' => [
461470
'level2' => [
462471
'args' => [
463472
'width' => 1,
464473
'height' => 1,
465474
],
475+
'type' => $level2Type,
466476
],
467477
],
468478
], $aliasArgs);
469479
break;
470480
case 'NoAliasFirst':
481+
$level2Type = $aliasArgs['level2']['level2']['type'] ?? null;
482+
self::assertInstanceOf(IntType::class, $level2Type);
471483
self::assertSame([
472484
'level2' => [
473485
'level2' => [
474486
'args' => [
475487
'width' => 1,
476488
'height' => 1,
477489
],
490+
'type' => $level2Type,
478491
],
479492
'level1000' => [
480493
'args' => [
481494
'width' => 2,
482495
'height' => 20,
483496
],
497+
'type' => $level2Type,
484498
],
485499
],
486500
], $aliasArgs);
487501
break;
488502
case 'NoAliasLast':
503+
$level2Type = $aliasArgs['level2']['level2000']['type'] ?? null;
504+
self::assertInstanceOf(IntType::class, $level2Type);
489505
self::assertSame([
490506
'level2' => [
491507
'level2000' => [
492508
'args' => [
493509
'width' => 1,
494510
'height' => 1,
495511
],
512+
'type' => $level2Type,
496513
],
497514
'level2' => [
498515
'args' => [
499516
'width' => 2,
500517
'height' => 20,
501518
],
519+
'type' => $level2Type,
502520
],
503521
],
504522
], $aliasArgs);
505523
break;
506524
case 'AllAliases':
525+
$level2Type = $aliasArgs['level2']['level1000']['type'] ?? null;
526+
self::assertInstanceOf(IntType::class, $level2Type);
507527
self::assertSame([
508528
'level2' => [
509529
'level1000' => [
510530
'args' => [
511531
'width' => 1,
512532
'height' => 1,
513533
],
534+
'type' => $level2Type,
514535
],
515536
'level2000' => [
516537
'args' => [
517538
'width' => 2,
518539
'height' => 20,
519540
],
541+
'type' => $level2Type,
520542
],
521543
],
522544
], $aliasArgs);
523545
break;
524546
case 'MultiLvlSameAliasName':
525547
case 'WithFragments':
548+
$level2Type = $aliasArgs['level2']['level3000']['type'] ?? null;
549+
$level2BisType = $aliasArgs['level2bis']['level2bis']['type'] ?? null;
550+
$level3Type = $aliasArgs['level2bis']['level2bis']['selectionSet']['level3']['level3000']['type'] ?? null;
551+
self::assertInstanceOf(IntType::class, $level2Type);
552+
self::assertInstanceOf(ObjectType::class, $level2BisType);
553+
self::assertInstanceOf(IntType::class, $level3Type);
526554
self::assertSame([
527555
'level2' => [
528556
'level3000' => [
529557
'args' => [
530558
'width' => 1,
531559
'height' => 1,
532560
],
561+
'type' => $level2Type,
533562
],
534563
'level2' => [
535564
'args' => [
536565
'width' => 3,
537566
'height' => 30,
538567
],
568+
'type' => $level2Type,
539569
],
540570
],
541571
'level2bis' => [
542572
'level2bis' => [
543573
'args' => [],
574+
'type' => $level2BisType,
544575
'selectionSet' => [
545576
'level3' => [
546577
'level3000' => [
547578
'args' => [
548579
'length' => 2,
549580
],
581+
'type' => $level3Type,
550582
],
551583
'level3' => [
552584
'args' => [
553585
'length' => 10,
554586
],
587+
'type' => $level3Type,
555588
],
556589
],
557590
],
@@ -565,29 +598,50 @@
565598
case 'Deepest':
566599
$depth ??= 5;
567600
$aliasArgs = $info->getFieldSelectionWithAliases($depth);
601+
602+
$level2BisType = $aliasArgs['level2bis']['level2Alias']['type'] ?? null;
603+
$level3DeeperType = $aliasArgs['level2bis']['level2Alias']['selectionSet']['level3deeper']['level3deeper']['type'] ?? null;
604+
$level4evenmoreType = $aliasArgs['level2bis']['level2Alias']['selectionSet']['level3deeper']['level3deeper']['selectionSet']['level4evenmore']['level4evenmore']['type'] ?? null;
605+
$level5Type = $aliasArgs['level2bis']['level2Alias']['selectionSet']['level3deeper']['level3deeper']['selectionSet']['level4evenmore']['level4evenmore']['selectionSet']['level5']['level5']['type'] ?? null;
606+
$level4Type = $aliasArgs['level2bis']['level2Alias']['selectionSet']['level3deeper']['level3deeper']['selectionSet']['level4']['level4']['type'] ?? null;
607+
608+
self::assertInstanceOf(ObjectType::class, $level2BisType);
609+
// Don't test the deepest types because we don't retrieve them with a low $depth
610+
if ($depth > 1) {
611+
self::assertInstanceOf(ObjectType::class, $level3DeeperType);
612+
self::assertInstanceOf(ObjectType::class, $level4evenmoreType);
613+
self::assertInstanceOf(StringType::class, $level5Type);
614+
self::assertInstanceOf(IntType::class, $level4Type);
615+
}
616+
568617
self::assertSame([
569618
'level2bis' => [
570619
'level2Alias' => [
571620
'args' => [],
621+
'type' => $level2BisType,
572622
'selectionSet' => [
573623
'level3deeper' => [
574624
'level3deeper' => [
575625
'args' => [],
626+
'type' => $level3DeeperType,
576627
'selectionSet' => [
577628
'level4evenmore' => [
578629
'level4evenmore' => [
579630
'args' => [],
631+
'type' => $level4evenmoreType,
580632
'selectionSet' => [
581633
'level5' => [
582634
'level5' => [
583635
'args' => [
584636
'crazyness' => 0.124,
585637
],
638+
'type' => $level5Type,
586639
],
587640
'lastAlias' => [
588641
'args' => [
589642
'crazyness' => 0.758,
590643
],
644+
'type' => $level5Type,
591645
],
592646
],
593647
],
@@ -598,6 +652,75 @@
598652
'args' => [
599653
'temperature' => -20,
600654
],
655+
'type' => $level4Type,
656+
],
657+
],
658+
],
659+
],
660+
],
661+
],
662+
],
663+
],
664+
], $aliasArgs);
665+
break;
666+
case 'WithUnion':
667+
$levelUnionType = $aliasArgs['levelUnion']['levelUnion']['type'] ?? null;
668+
$levelMyCustomType = $aliasArgs['levelUnion']['levelUnion']['unions']['MyCustom']['type'] ?? null;
669+
$levelAType = $aliasArgs['levelUnion']['levelUnion']['unions']['MyCustom']['selectionSet']['a']['a']['type'] ?? null;
670+
$levelCustomWithObjectType = $aliasArgs['levelUnion']['levelUnion']['unions']['CustomWithObject']['type'] ?? null;
671+
$levelOtherCustomType = $aliasArgs['levelUnion']['levelUnion']['unions']['CustomWithObject']['selectionSet']['customB']['customB']['type'] ?? null;
672+
$levelBType = $aliasArgs['levelUnion']['levelUnion']['unions']['CustomWithObject']['selectionSet']['customB']['customB']['selectionSet']['b']['b']['type'] ?? null;
673+
self::assertInstanceOf(UnionType::class, $levelUnionType);
674+
self::assertInstanceOf(MyCustomType::class, $levelMyCustomType);
675+
self::assertInstanceOf(StringType::class, $levelAType);
676+
self::assertInstanceOf(CustomWithObject::class, $levelCustomWithObjectType);
677+
self::assertInstanceOf(OtherCustom::class, $levelOtherCustomType);
678+
self::assertInstanceOf(StringType::class, $levelBType);
679+
self::assertSame([
680+
'levelUnion' => [
681+
'levelUnion' => [
682+
'args' => [],
683+
'type' => $levelUnionType,
684+
'unions' => [
685+
'MyCustom' => [
686+
'type' => $levelMyCustomType,
687+
'selectionSet' => [
688+
'a' => [
689+
'a' => [
690+
'args' => [],
691+
'type' => $levelAType,
692+
],
693+
],
694+
],
695+
],
696+
'CustomWithObject' => [
697+
'type' => $levelCustomWithObjectType,
698+
'selectionSet' => [
699+
'customA' => [
700+
'customA' => [
701+
'args' => [],
702+
'type' => $levelMyCustomType,
703+
'selectionSet' => [
704+
'a' => [
705+
'a' => [
706+
'args' => [],
707+
'type' => $levelAType,
708+
],
709+
],
710+
],
711+
],
712+
],
713+
'customB' => [
714+
'customB' => [
715+
'args' => [],
716+
'type' => $levelOtherCustomType,
717+
'selectionSet' => [
718+
'b' => [
719+
'b' => [
720+
'args' => [],
721+
'type' => $levelBType,
722+
],
723+
],
601724
],
602725
],
603726
],
@@ -613,6 +736,24 @@
613736
}
614737
};
615738

739+
$myCustomWithObjectType = new CustomWithObject();
740+
// retrieve the instance from the parent type in order to don't instantiate twice the same type in the same schema
741+
$myCustomType = $myCustomWithObjectType->config['fields']['customA'];
742+
$levelUnion = new UnionType([
743+
'name' => 'CustomOrOther',
744+
'types' => [
745+
$myCustomType,
746+
$myCustomWithObjectType,
747+
],
748+
'resolveType' => function ($value) use ($myCustomType, $myCustomWithObjectType): ObjectType {
749+
switch (get_class($value)) {
750+
case MyCustomType::class: return $myCustomType;
751+
case CustomWithObject::class: return $myCustomWithObjectType;
752+
default: throw new Error('Unexpected union type');
753+
}
754+
},
755+
]);
756+
616757
$level4EvenMore = new ObjectType([
617758
'name' => 'Level4EvenMore',
618759
'fields' => [
@@ -685,6 +826,10 @@
685826
'type' => $level2Bis,
686827
'resolve' => fn (): bool => true,
687828
],
829+
'levelUnion' => [
830+
'type' => $levelUnion,
831+
'resolve' => fn (): bool => true,
832+
],
688833
],
689834
]);
690835

@@ -797,7 +942,7 @@
797942
}
798943
}
799944
}
800-
945+
801946
fragment level3Frag on Level2bis {
802947
level3000: level3(length: 2)
803948
level3(length: 10)
@@ -855,6 +1000,29 @@
8551000
GRAPHQL
8561001
);
8571002

1003+
$result10 = GraphQL::executeQuery(
1004+
new Schema(['query' => $query]),
1005+
<<<GRAPHQL
1006+
query {
1007+
level1(testName: "WithUnion") {
1008+
levelUnion {
1009+
...on MyCustom {
1010+
a
1011+
}
1012+
...on CustomWithObject {
1013+
customA {
1014+
a
1015+
}
1016+
customB {
1017+
b
1018+
}
1019+
}
1020+
}
1021+
}
1022+
}
1023+
GRAPHQL
1024+
);
1025+
8581026
self::assertEmpty($result1->errors, 'Query NoAlias should have no errors');
8591027
self::assertEmpty($result2->errors, 'Query NoAliasFirst should have no errors');
8601028
self::assertEmpty($result3->errors, 'Query NoAliasLast should have no errors');
@@ -864,6 +1032,7 @@
8641032
self::assertSame('Failed asserting that two arrays are identical.', $result7->errors[0]->getMessage(), 'Query DeepestTooLowDepth should have failed');
8651033
self::assertEmpty($result8->errors, 'Query Deepest should have no errors');
8661034
self::assertEmpty($result9->errors, 'Query With ListOf type should have no errors');
1035+
self::assertEmpty($result10->errors, 'Query With Union type should have no errors');
8671036
}
8681037

8691038
public function testPathAndUnaliasedPath(): void
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace GraphQL\Tests\Type\TestClasses;
4+
5+
use GraphQL\Type\Definition\ObjectType;
6+
7+
final class CustomWithObject extends ObjectType
8+
{
9+
public function __construct()
10+
{
11+
$config = [
12+
'fields' => [
13+
'customA' => new MyCustomType(),
14+
'customB' => new OtherCustom(),
15+
],
16+
];
17+
parent::__construct($config);
18+
}
19+
}

0 commit comments

Comments
 (0)
Please sign in to comment.