diff --git a/src/Type/Definition/QueryPlan.php b/src/Type/Definition/QueryPlan.php index be914511f..1605b565d 100644 --- a/src/Type/Definition/QueryPlan.php +++ b/src/Type/Definition/QueryPlan.php @@ -164,59 +164,61 @@ private function analyzeSelectionSet(SelectionSetNode $selectionSet, Type $paren { $fields = []; $implementors = []; - foreach ($selectionSet->selections as $selectionNode) { - if ($selectionNode instanceof FieldNode) { - $fieldName = $selectionNode->name->value; + foreach ($selectionSet->selections as $selection) { + if ($selection instanceof FieldNode) { + $fieldName = $selection->name->value; if ($fieldName === Introspection::TYPE_NAME_FIELD_NAME) { continue; } - assert($parentType instanceof HasFieldsType, 'ensured by query validation and the check above which excludes union types'); + assert($parentType instanceof HasFieldsType, 'ensured by query validation'); $type = $parentType->getField($fieldName); $selectionType = $type->getType(); - $subfields = []; $subImplementors = []; - if (isset($selectionNode->selectionSet)) { - $subfields = $this->analyzeSubFields($selectionType, $selectionNode->selectionSet, $subImplementors); - } + $nestedSelectionSet = $selection->selectionSet; + $subfields = $nestedSelectionSet === null + ? [] + : $this->analyzeSubFields($selectionType, $nestedSelectionSet, $subImplementors); $fields[$fieldName] = [ 'type' => $selectionType, 'fields' => $subfields, - 'args' => Values::getArgumentValues($type, $selectionNode, $this->variableValues), + 'args' => Values::getArgumentValues($type, $selection, $this->variableValues), ]; if ($this->groupImplementorFields && $subImplementors) { $fields[$fieldName]['implementors'] = $subImplementors; } - } elseif ($selectionNode instanceof FragmentSpreadNode) { - $spreadName = $selectionNode->name->value; - if (isset($this->fragments[$spreadName])) { - $fragment = $this->fragments[$spreadName]; - $type = $this->schema->getType($fragment->typeCondition->name->value); - assert($type instanceof Type, 'ensured by query validation'); - - $subfields = $this->analyzeSubFields($type, $fragment->selectionSet); - $fields = $this->mergeFields($parentType, $type, $fields, $subfields, $implementors); + } elseif ($selection instanceof FragmentSpreadNode) { + $spreadName = $selection->name->value; + $fragment = $this->fragments[$spreadName] ?? null; + if ($fragment === null) { + continue; } - } elseif ($selectionNode instanceof InlineFragmentNode) { - $typeCondition = $selectionNode->typeCondition; + + $type = $this->schema->getType($fragment->typeCondition->name->value); + assert($type instanceof Type, 'ensured by query validation'); + + $subfields = $this->analyzeSubFields($type, $fragment->selectionSet); + $fields = $this->mergeFields($parentType, $type, $fields, $subfields, $implementors); + } elseif ($selection instanceof InlineFragmentNode) { + $typeCondition = $selection->typeCondition; $type = $typeCondition === null ? $parentType : $this->schema->getType($typeCondition->name->value); assert($type instanceof Type, 'ensured by query validation'); - $subfields = $this->analyzeSubFields($type, $selectionNode->selectionSet); + $subfields = $this->analyzeSubFields($type, $selection->selectionSet); $fields = $this->mergeFields($parentType, $type, $fields, $subfields, $implementors); } } $parentTypeName = $parentType->name(); - // TODO evaluate if this line is really necessary - it causes abstract types to appear - // in getReferencedTypes() even if they do not have any fields directly referencing them. + // TODO evaluate if this line is really necessary. + // It causes abstract types to appear in getReferencedTypes() even if they do not have any fields directly referencing them. $this->typeToFields[$parentTypeName] ??= []; foreach ($fields as $fieldName => $_) { $this->typeToFields[$parentTypeName][$fieldName] = true; diff --git a/src/Type/Definition/ResolveInfo.php b/src/Type/Definition/ResolveInfo.php index dcae8eecf..fc8d1cf19 100644 --- a/src/Type/Definition/ResolveInfo.php +++ b/src/Type/Definition/ResolveInfo.php @@ -4,12 +4,14 @@ use GraphQL\Error\Error; use GraphQL\Error\InvariantViolation; +use GraphQL\Executor\Values; use GraphQL\Language\AST\FieldNode; use GraphQL\Language\AST\FragmentDefinitionNode; use GraphQL\Language\AST\FragmentSpreadNode; use GraphQL\Language\AST\InlineFragmentNode; use GraphQL\Language\AST\OperationDefinitionNode; use GraphQL\Language\AST\SelectionSetNode; +use GraphQL\Type\Introspection; use GraphQL\Type\Schema; /** @@ -126,14 +128,15 @@ class ResolveInfo /** * @param \ArrayObject $fieldNodes * @param list $path - * @param list $unaliasedPath * * @phpstan-param Path $path - * @phpstan-param Path $unaliasedPath * * @param array $fragments * @param mixed|null $rootValue * @param array $variableValues + * @param list $unaliasedPath + * + * @phpstan-param Path $unaliasedPath */ public function __construct( FieldDefinition $fieldDefinition, @@ -162,37 +165,35 @@ public function __construct( } /** - * Helper method that returns names of all fields selected in query for - * $this->fieldName up to $depth levels. + * Returns names of all fields selected in query for `$this->fieldName` up to `$depth` levels. * * Example: - * query MyQuery{ * { * root { - * id, + * id * nested { - * nested1 - * nested2 { - * nested3 - * } + * nested1 + * nested2 { + * nested3 + * } * } * } * } * - * Given this ResolveInfo instance is a part of "root" field resolution, and $depth === 1, - * method will return: + * Given this ResolveInfo instance is a part of root field resolution, and $depth === 1, + * this method will return: * [ * 'id' => true, * 'nested' => [ - * nested1 => true, - * nested2 => true - * ] + * 'nested1' => true, + * 'nested2' => true, + * ], * ] * - * Warning: this method it is a naive implementation which does not take into account - * conditional typed fragments. So use it with care for fields of interface and union types. + * This method does not consider conditional typed fragments. + * Use it with care for fields of interface and union types. * - * @param int $depth How many levels to include in output + * @param int $depth How many levels to include in the output beyond the first * * @return array * @@ -203,10 +204,125 @@ public function getFieldSelection(int $depth = 0): array $fields = []; foreach ($this->fieldNodes as $fieldNode) { - if (isset($fieldNode->selectionSet)) { + $selectionSet = $fieldNode->selectionSet; + if ($selectionSet !== null) { + $fields = \array_merge_recursive( + $fields, + $this->foldSelectionSet($selectionSet, $depth) + ); + } + } + + return $fields; + } + + /** + * Returns names and args of all fields selected in query for `$this->fieldName` up to `$depth` levels, including aliases. + * + * The result maps original field names to a map of selections for that field, including aliases. + * For each of those selections, you can find the following keys: + * - "args" contains the passed arguments for this field/alias + * - "selectionSet" contains potential nested fields of this field/alias. The structure is recursive from here. + * + * Example: + * { + * root { + * id + * nested { + * nested1(myArg: 1) + * nested1Bis: nested1 + * } + * alias1: nested { + * nested1(myArg: 2, mySecondAg: "test") + * } + * } + * } + * + * Given this ResolveInfo instance is a part of "root" field resolution, and $depth === 1, + * this method will return: + * [ + * 'id' => [ + * 'id' => [ + * 'args' => [], + * ], + * ], + * 'nested' => [ + * 'nested' => [ + * 'args' => [], + * 'selectionSet' => [ + * 'nested1' => [ + * 'nested1' => [ + * 'args' => [ + * 'myArg' => 1, + * ], + * ], + * 'nested1Bis' => [ + * 'args' => [], + * ], + * ], + * ], + * ], + * 'alias1' => [ + * 'args' => [], + * 'selectionSet' => [ + * 'nested1' => [ + * 'nested1' => [ + * 'args' => [ + * 'myArg' => 2, + * 'mySecondAg' => "test, + * ], + * ], + * ], + * ], + * ], + * ], + * ] + * + * This method does not consider conditional typed fragments. + * Use it with care for fields of interface and union types. + * You can still alias the union type fields with the same name in order to extract their corresponding args. + * + * Example: + * { + * root { + * id + * unionPerson { + * ...on Child { + * name + * birthdate(format: "d/m/Y") + * } + * ...on Adult { + * adultName: name + * adultBirthDate: birthdate(format: "Y-m-d") + * job + * } + * } + * } + * } + * + * @param int $depth How many levels to include in the output beyond the first + * + * @throws \Exception + * @throws Error + * @throws InvariantViolation + * + * @return array + * + * @api + */ + public function getFieldSelectionWithAliases(int $depth = 0): array + { + $fields = []; + + foreach ($this->fieldNodes as $fieldNode) { + $selectionSet = $fieldNode->selectionSet; + if ($selectionSet !== null) { + $fieldType = $this->parentType->getField($fieldNode->name->value) + ->getType(); + $fields = \array_merge_recursive( $fields, - $this->foldSelectionSet($fieldNode->selectionSet, $depth) + $this->foldSelectionWithAlias($selectionSet, $depth, $fieldType) ); } } @@ -239,26 +355,99 @@ private function foldSelectionSet(SelectionSetNode $selectionSet, int $descend): /** @var array $fields */ $fields = []; - foreach ($selectionSet->selections as $selectionNode) { - if ($selectionNode instanceof FieldNode) { - $fields[$selectionNode->name->value] = $descend > 0 && $selectionNode->selectionSet !== null + foreach ($selectionSet->selections as $selection) { + if ($selection instanceof FieldNode) { + $fields[$selection->name->value] = $descend > 0 && $selection->selectionSet !== null ? \array_merge_recursive( - $fields[$selectionNode->name->value] ?? [], - $this->foldSelectionSet($selectionNode->selectionSet, $descend - 1) + $fields[$selection->name->value] ?? [], + $this->foldSelectionSet($selection->selectionSet, $descend - 1) ) : true; - } elseif ($selectionNode instanceof FragmentSpreadNode) { - $spreadName = $selectionNode->name->value; - if (isset($this->fragments[$spreadName])) { - $fragment = $this->fragments[$spreadName]; - $fields = \array_merge_recursive( - $this->foldSelectionSet($fragment->selectionSet, $descend), - $fields - ); + } elseif ($selection instanceof FragmentSpreadNode) { + $spreadName = $selection->name->value; + $fragment = $this->fragments[$spreadName] ?? null; + if ($fragment === null) { + continue; } - } elseif ($selectionNode instanceof InlineFragmentNode) { + + $fields = \array_merge_recursive( + $this->foldSelectionSet($fragment->selectionSet, $descend), + $fields + ); + } elseif ($selection instanceof InlineFragmentNode) { + $fields = \array_merge_recursive( + $this->foldSelectionSet($selection->selectionSet, $descend), + $fields + ); + } + } + + return $fields; + } + + /** + * @throws \Exception + * @throws Error + * @throws InvariantViolation + * + * @return array + */ + private function foldSelectionWithAlias(SelectionSetNode $selectionSet, int $descend, Type $parentType): array + { + /** @var array $fields */ + $fields = []; + + foreach ($selectionSet->selections as $selection) { + if ($selection instanceof FieldNode) { + $fieldName = $selection->name->value; + $aliasName = $selection->alias->value ?? $fieldName; + + if ($fieldName === Introspection::TYPE_NAME_FIELD_NAME) { + continue; + } + + assert($parentType instanceof HasFieldsType, 'ensured by query validation'); + + $fieldDef = $parentType->getField($fieldName); + $fieldType = $fieldDef->getType(); + if ($fieldType instanceof WrappingType) { + $fieldType = $fieldType->getInnermostType(); + } + $fields[$fieldName][$aliasName]['args'] = Values::getArgumentValues($fieldDef, $selection, $this->variableValues); + + if ($descend <= 0) { + continue; + } + + $nestedSelectionSet = $selection->selectionSet; + if ($nestedSelectionSet === null) { + continue; + } + + $fields[$fieldName][$aliasName]['selectionSet'] = $this->foldSelectionWithAlias($nestedSelectionSet, $descend - 1, $fieldType); + } elseif ($selection instanceof FragmentSpreadNode) { + $spreadName = $selection->name->value; + $fragment = $this->fragments[$spreadName] ?? null; + if ($fragment === null) { + continue; + } + + $fieldType = $this->schema->getType($fragment->typeCondition->name->value); + assert($fieldType instanceof Type, 'ensured by query validation'); + + $fields = \array_merge_recursive( + $this->foldSelectionWithAlias($fragment->selectionSet, $descend, $fieldType), + $fields + ); + } elseif ($selection instanceof InlineFragmentNode) { + $typeCondition = $selection->typeCondition; + $fieldType = $typeCondition === null + ? $parentType + : $this->schema->getType($typeCondition->name->value); + assert($fieldType instanceof Type, 'ensured by query validation'); + $fields = \array_merge_recursive( - $this->foldSelectionSet($selectionNode->selectionSet, $descend), + $this->foldSelectionWithAlias($selection->selectionSet, $descend, $fieldType), $fields ); } diff --git a/src/Validator/Rules/OverlappingFieldsCanBeMerged.php b/src/Validator/Rules/OverlappingFieldsCanBeMerged.php index 5b4e042c6..41f408ab4 100644 --- a/src/Validator/Rules/OverlappingFieldsCanBeMerged.php +++ b/src/Validator/Rules/OverlappingFieldsCanBeMerged.php @@ -255,9 +255,7 @@ protected function internalCollectFieldsAndFragmentNames( $fieldDef = $parentType->getField($fieldName); } - $responseName = isset($selection->alias) - ? $selection->alias->value - : $fieldName; + $responseName = $selection->alias->value ?? $fieldName; $astAndDefs[$responseName] ??= []; $astAndDefs[$responseName][] = [$parentType, $selection, $fieldDef]; diff --git a/src/Validator/Rules/QuerySecurityRule.php b/src/Validator/Rules/QuerySecurityRule.php index ce742b51b..7715c0360 100644 --- a/src/Validator/Rules/QuerySecurityRule.php +++ b/src/Validator/Rules/QuerySecurityRule.php @@ -110,66 +110,63 @@ protected function collectFieldASTsAndDefs( $astAndDefs ??= new \ArrayObject(); foreach ($selectionSet->selections as $selection) { - switch (true) { - case $selection instanceof FieldNode: - $fieldName = $selection->name->value; - - $fieldDef = null; - if ($parentType instanceof HasFieldsType) { - $schemaMetaFieldDef = Introspection::schemaMetaFieldDef(); - $typeMetaFieldDef = Introspection::typeMetaFieldDef(); - $typeNameMetaFieldDef = Introspection::typeNameMetaFieldDef(); - - $queryType = $context->getSchema()->getQueryType(); - - if ($fieldName === $schemaMetaFieldDef->name && $queryType === $parentType) { - $fieldDef = $schemaMetaFieldDef; - } elseif ($fieldName === $typeMetaFieldDef->name && $queryType === $parentType) { - $fieldDef = $typeMetaFieldDef; - } elseif ($fieldName === $typeNameMetaFieldDef->name) { - $fieldDef = $typeNameMetaFieldDef; - } elseif ($parentType->hasField($fieldName)) { - $fieldDef = $parentType->getField($fieldName); - } + if ($selection instanceof FieldNode) { + $fieldName = $selection->name->value; + + $fieldDef = null; + if ($parentType instanceof HasFieldsType) { + $schemaMetaFieldDef = Introspection::schemaMetaFieldDef(); + $typeMetaFieldDef = Introspection::typeMetaFieldDef(); + $typeNameMetaFieldDef = Introspection::typeNameMetaFieldDef(); + + $queryType = $context->getSchema()->getQueryType(); + + if ($fieldName === $schemaMetaFieldDef->name && $queryType === $parentType) { + $fieldDef = $schemaMetaFieldDef; + } elseif ($fieldName === $typeMetaFieldDef->name && $queryType === $parentType) { + $fieldDef = $typeMetaFieldDef; + } elseif ($fieldName === $typeNameMetaFieldDef->name) { + $fieldDef = $typeNameMetaFieldDef; + } elseif ($parentType->hasField($fieldName)) { + $fieldDef = $parentType->getField($fieldName); } - - $responseName = $this->getFieldName($selection); - $responseContext = $astAndDefs[$responseName] ??= new \ArrayObject(); - $responseContext[] = [$selection, $fieldDef]; - break; - case $selection instanceof InlineFragmentNode: - $typeCondition = $selection->typeCondition; - $fragmentParentType = $typeCondition === null - ? $parentType - : AST::typeFromAST([$context->getSchema(), 'getType'], $typeCondition); - $astAndDefs = $this->collectFieldASTsAndDefs( - $context, - $fragmentParentType, - $selection->selectionSet, - $visitedFragmentNames, - $astAndDefs - ); - break; - case $selection instanceof FragmentSpreadNode: - $fragName = $selection->name->value; - - if (! isset($visitedFragmentNames[$fragName])) { - $visitedFragmentNames[$fragName] = true; - - $fragment = $context->getFragment($fragName); - - if ($fragment !== null) { - $astAndDefs = $this->collectFieldASTsAndDefs( - $context, - AST::typeFromAST([$context->getSchema(), 'getType'], $fragment->typeCondition), - $fragment->selectionSet, - $visitedFragmentNames, - $astAndDefs - ); - } - } - - break; + } + + $responseName = $this->getFieldName($selection); + $responseContext = $astAndDefs[$responseName] ??= new \ArrayObject(); + $responseContext[] = [$selection, $fieldDef]; + } elseif ($selection instanceof InlineFragmentNode) { + $typeCondition = $selection->typeCondition; + $fragmentParentType = $typeCondition === null + ? $parentType + : AST::typeFromAST([$context->getSchema(), 'getType'], $typeCondition); + $astAndDefs = $this->collectFieldASTsAndDefs( + $context, + $fragmentParentType, + $selection->selectionSet, + $visitedFragmentNames, + $astAndDefs + ); + } elseif ($selection instanceof FragmentSpreadNode) { + $fragName = $selection->name->value; + + if (isset($visitedFragmentNames[$fragName])) { + continue; + } + $visitedFragmentNames[$fragName] = true; + + $fragment = $context->getFragment($fragName); + if ($fragment === null) { + continue; + } + + $astAndDefs = $this->collectFieldASTsAndDefs( + $context, + AST::typeFromAST([$context->getSchema(), 'getType'], $fragment->typeCondition), + $fragment->selectionSet, + $visitedFragmentNames, + $astAndDefs + ); } } diff --git a/tests/Type/ResolveInfoTest.php b/tests/Type/ResolveInfoTest.php index 050888020..ca355bac4 100644 --- a/tests/Type/ResolveInfoTest.php +++ b/tests/Type/ResolveInfoTest.php @@ -12,7 +12,7 @@ final class ResolveInfoTest extends TestCase { - public function testFieldSelection(): void + public function testGetFieldSelection(): void { $image = new ObjectType([ 'name' => 'Image', @@ -175,7 +175,7 @@ public function testFieldSelection(): void self::assertEquals($expectedDeepSelection, $actualDeepSelection); } - public function testFieldSelectionOnScalarTypes(): void + public function testGetFieldSelectionOnScalarTypes(): void { $query = ' query Ping { @@ -203,7 +203,7 @@ public function testFieldSelectionOnScalarTypes(): void self::assertSame(['data' => ['ping' => 'pong']], $result); } - public function testMergedFragmentsFieldSelection(): void + public function testGetFieldSelectionMergedFragments(): void { $image = new ObjectType([ 'name' => 'Image', @@ -371,17 +371,17 @@ public function testMergedFragmentsFieldSelection(): void self::assertEquals($expectedDeepSelection, $actualDeepSelection); } - public function testDeepFieldSelectionOnDuplicatedFields(): void + public function testGetFieldSelectionDeepOnDuplicatedFields(): void { $level2 = new ObjectType([ - 'name' => 'level2', + 'name' => 'Level2', 'fields' => [ 'scalar1' => ['type' => Type::int()], 'scalar2' => ['type' => Type::int()], ], ]); $level1 = new ObjectType([ - 'name' => 'level1', + 'name' => 'Level1', 'fields' => [ 'scalar1' => ['type' => Type::int()], 'scalar2' => ['type' => Type::int()], @@ -447,6 +447,397 @@ public function testDeepFieldSelectionOnDuplicatedFields(): void self::assertEquals($expectedDeepSelection, $actualDeepSelection); } + public function testGetFieldSelectionWithAliases(): void + { + $aliasArgsNbTests = 0; + + $returnResolveInfo = function ($value, array $args, $context, ResolveInfo $info) use (&$aliasArgsNbTests) { + $aliasArgs = $info->getFieldSelectionWithAliases(1); + ++$aliasArgsNbTests; + switch ($args['testName']) { + case 'NoAlias': + self::assertSame([ + 'level2' => [ + 'level2' => [ + 'args' => [ + 'width' => 1, + 'height' => 1, + ], + ], + ], + ], $aliasArgs); + break; + case 'NoAliasFirst': + self::assertSame([ + 'level2' => [ + 'level2' => [ + 'args' => [ + 'width' => 1, + 'height' => 1, + ], + ], + 'level1000' => [ + 'args' => [ + 'width' => 2, + 'height' => 20, + ], + ], + ], + ], $aliasArgs); + break; + case 'NoAliasLast': + self::assertSame([ + 'level2' => [ + 'level2000' => [ + 'args' => [ + 'width' => 1, + 'height' => 1, + ], + ], + 'level2' => [ + 'args' => [ + 'width' => 2, + 'height' => 20, + ], + ], + ], + ], $aliasArgs); + break; + case 'AllAliases': + self::assertSame([ + 'level2' => [ + 'level1000' => [ + 'args' => [ + 'width' => 1, + 'height' => 1, + ], + ], + 'level2000' => [ + 'args' => [ + 'width' => 2, + 'height' => 20, + ], + ], + ], + ], $aliasArgs); + break; + case 'MultiLvlSameAliasName': + case 'WithFragments': + self::assertSame([ + 'level2' => [ + 'level3000' => [ + 'args' => [ + 'width' => 1, + 'height' => 1, + ], + ], + 'level2' => [ + 'args' => [ + 'width' => 3, + 'height' => 30, + ], + ], + ], + 'level2bis' => [ + 'level2bis' => [ + 'args' => [], + 'selectionSet' => [ + 'level3' => [ + 'level3000' => [ + 'args' => [ + 'length' => 2, + ], + ], + 'level3' => [ + 'args' => [ + 'length' => 10, + ], + ], + ], + ], + ], + ], + ], $aliasArgs); + break; + case 'DeepestTooLowDepth': + $depth = 1; + // no break + case 'Deepest': + $depth ??= 5; + $aliasArgs = $info->getFieldSelectionWithAliases($depth); + self::assertSame([ + 'level2bis' => [ + 'level2Alias' => [ + 'args' => [], + 'selectionSet' => [ + 'level3deeper' => [ + 'level3deeper' => [ + 'args' => [], + 'selectionSet' => [ + 'level4evenmore' => [ + 'level4evenmore' => [ + 'args' => [], + 'selectionSet' => [ + 'level5' => [ + 'level5' => [ + 'args' => [ + 'crazyness' => 0.124, + ], + ], + 'lastAlias' => [ + 'args' => [ + 'crazyness' => 0.758, + ], + ], + ], + ], + ], + ], + 'level4' => [ + 'level4' => [ + 'args' => [ + 'temperature' => -20, + ], + ], + ], + ], + ], + ], + ], + ], + ], + ], $aliasArgs); + break; + default: + $aliasArgsNbTests--; + } + }; + + $level4EvenMore = new ObjectType([ + 'name' => 'Level4EvenMore', + 'fields' => [ + 'level5' => [ + 'type' => Type::string(), + 'resolve' => fn (): bool => true, + 'args' => [ + 'crazyness' => [ + 'type' => Type::float(), + ], + ], + ], + ], + ]); + + $level3Deeper = new ObjectType([ + 'name' => 'Level3Deeper', + 'fields' => [ + 'level4' => [ + 'type' => Type::int(), + 'resolve' => fn (): bool => true, + 'args' => [ + 'temperature' => [ + 'type' => Type::int(), + ], + ], + ], + 'level4evenmore' => [ + 'type' => $level4EvenMore, + 'resolve' => fn (): bool => true, + ], + ], + ]); + + $level2Bis = new ObjectType([ + 'name' => 'Level2bis', + 'fields' => [ + 'level3' => [ + 'type' => Type::int(), + 'resolve' => fn (): bool => true, + 'args' => [ + 'length' => [ + 'type' => Type::int(), + ], + ], + ], + 'level3deeper' => [ + 'type' => $level3Deeper, + 'resolve' => fn (): bool => true, + ], + ], + ]); + + $level1 = new ObjectType([ + 'name' => 'Level1', + 'fields' => [ + 'level2' => [ + 'type' => Type::nonNull(Type::int()), + 'resolve' => fn (): bool => true, + 'args' => [ + 'width' => [ + 'type' => Type::nonNull(Type::int()), + ], + 'height' => [ + 'type' => Type::int(), + ], + ], + ], + 'level2bis' => [ + 'type' => $level2Bis, + 'resolve' => fn (): bool => true, + ], + ], + ]); + + $query = new ObjectType([ + 'name' => 'Query', + 'fields' => [ + 'level1' => [ + 'type' => $level1, + 'resolve' => $returnResolveInfo, + 'args' => [ + 'testName' => [ + 'type' => Type::string(), + ], + ], + ], + ], + ]); + + $result1 = GraphQL::executeQuery( + new Schema(['query' => $query]), + << $query]), + << $query]), + << $query]), + << $query]), + << $query]), + << $query]), + << $query]), + <<errors, 'Query NoAlias should have no errors'); + self::assertEmpty($result2->errors, 'Query NoAliasFirst should have no errors'); + self::assertEmpty($result3->errors, 'Query NoAliasLast should have no errors'); + self::assertEmpty($result4->errors, 'Query AllAliases should have no errors'); + self::assertEmpty($result5->errors, 'Query MultiLvlSameAliasName should have no errors'); + self::assertEmpty($result6->errors, 'Query WithFragments should have no errors'); + self::assertSame('Failed asserting that two arrays are identical.', $result7->errors[0]->getMessage(), 'Query DeepestTooLowDepth should have failed'); + self::assertEmpty($result8->errors, 'Query Deepest should have no errors'); + } + public function testPathAndUnaliasedPath(): void { $resolveInfo = new ObjectType([ @@ -459,7 +850,7 @@ public function testPathAndUnaliasedPath(): void $returnResolveInfo = static fn ($value, array $args, $context, ResolveInfo $info): ResolveInfo => $info; $level2 = new ObjectType([ - 'name' => 'level2', + 'name' => 'Level2', 'fields' => [ 'info1' => [ 'type' => $resolveInfo, @@ -473,7 +864,7 @@ public function testPathAndUnaliasedPath(): void ]); $level1 = new ObjectType([ - 'name' => 'level1', + 'name' => 'Level1', 'fields' => [ 'level2' => [ 'type' => $level2, @@ -560,7 +951,7 @@ public function testPathAndUnaliasedPathForList(): void ]); $level1 = new ObjectType([ - 'name' => 'level1', + 'name' => 'Level1', 'fields' => [ 'level2' => [ 'type' => ListOfType::listOf($level2),