From 90b51e1a5dcf632c72fdb385b7fdf5cae26cf016 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Sat, 6 Oct 2018 00:30:05 +0200 Subject: [PATCH 001/256] Replace node kind checks by InstanceOf checks --- src/Executor/ReferenceExecutor.php | 14 ++-- src/Experimental/Executor/Collector.php | 10 +-- src/Language/AST/ListTypeNode.php | 2 +- src/Language/AST/NonNullTypeNode.php | 2 +- src/Language/Printer.php | 6 +- src/Utils/AST.php | 2 +- src/Utils/ASTDefinitionBuilder.php | 62 ++++++----------- src/Utils/BuildSchema.php | 43 ++++++------ src/Utils/SchemaExtender.php | 17 +++-- src/Utils/TypeInfo.php | 59 +++++++++------- src/Validator/Rules/KnownArgumentNames.php | 6 +- src/Validator/Rules/KnownDirectives.php | 67 ++++++++++++------- .../Rules/LoneAnonymousOperation.php | 2 +- src/Validator/Rules/QueryComplexity.php | 11 ++- src/Validator/Rules/QueryDepth.php | 14 ++-- src/Validator/Rules/QuerySecurityRule.php | 9 ++- src/Validator/ValidationContext.php | 4 +- tests/Experimental/Executor/CollectorTest.php | 2 +- tests/Language/VisitorTest.php | 2 +- 19 files changed, 177 insertions(+), 157 deletions(-) diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 837628a41..7924783ac 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -138,8 +138,8 @@ private static function buildExecutionContext( $operation = null; $hasMultipleAssumedOperations = false; foreach ($documentNode->definitions as $definition) { - switch ($definition->kind) { - case NodeKind::OPERATION_DEFINITION: + switch (true) { + case $definition instanceof OperationDefinitionNode: if (! $operationName && $operation) { $hasMultipleAssumedOperations = true; } @@ -148,7 +148,7 @@ private static function buildExecutionContext( $operation = $definition; } break; - case NodeKind::FRAGMENT_DEFINITION: + case $definition instanceof FragmentDefinitionNode: $fragments[$definition->name->value] = $definition; break; } @@ -334,8 +334,8 @@ private function collectFields( ) { $exeContext = $this->exeContext; foreach ($selectionSet->selections as $selection) { - switch ($selection->kind) { - case NodeKind::FIELD: + switch (true) { + case $selection instanceof FieldNode: if (! $this->shouldIncludeNode($selection)) { break; } @@ -345,7 +345,7 @@ private function collectFields( } $fields[$name][] = $selection; break; - case NodeKind::INLINE_FRAGMENT: + case $selection instanceof InlineFragmentNode: if (! $this->shouldIncludeNode($selection) || ! $this->doesFragmentConditionMatch($selection, $runtimeType) ) { @@ -358,7 +358,7 @@ private function collectFields( $visitedFragmentNames ); break; - case NodeKind::FRAGMENT_SPREAD: + case $selection instanceof FragmentSpreadNode: $fragName = $selection->name->value; if (! empty($visitedFragmentNames[$fragName]) || ! $this->shouldIncludeNode($selection)) { break; diff --git a/src/Experimental/Executor/Collector.php b/src/Experimental/Executor/Collector.php index 34f1e0e77..a342b89f7 100644 --- a/src/Experimental/Executor/Collector.php +++ b/src/Experimental/Executor/Collector.php @@ -64,7 +64,7 @@ public function initialize(DocumentNode $documentNode, ?string $operationName = foreach ($documentNode->definitions as $definitionNode) { /** @var DefinitionNode|Node $definitionNode */ - if ($definitionNode->kind === NodeKind::OPERATION_DEFINITION) { + if ($definitionNode instanceof OperationDefinitionNode) { /** @var OperationDefinitionNode $definitionNode */ if ($operationName === null && $this->operation !== null) { $hasMultipleAssumedOperations = true; @@ -74,7 +74,7 @@ public function initialize(DocumentNode $documentNode, ?string $operationName = ) { $this->operation = $definitionNode; } - } elseif ($definitionNode->kind === NodeKind::FRAGMENT_DEFINITION) { + } elseif ($definitionNode instanceof FragmentDefinitionNode) { /** @var FragmentDefinitionNode $definitionNode */ $this->fragments[$definitionNode->name->value] = $definitionNode; } @@ -194,7 +194,7 @@ private function doCollectFields(ObjectType $runtimeType, ?SelectionSetNode $sel } } - if ($selection->kind === NodeKind::FIELD) { + if ($selection instanceof FieldNode) { /** @var FieldNode $selection */ $resultName = $selection->alias ? $selection->alias->value : $selection->name->value; @@ -204,7 +204,7 @@ private function doCollectFields(ObjectType $runtimeType, ?SelectionSetNode $sel } $this->fields[$resultName][] = $selection; - } elseif ($selection->kind === NodeKind::FRAGMENT_SPREAD) { + } elseif ($selection instanceof FragmentSpreadNode) { /** @var FragmentSpreadNode $selection */ $fragmentName = $selection->name->value; @@ -245,7 +245,7 @@ private function doCollectFields(ObjectType $runtimeType, ?SelectionSetNode $sel } $this->doCollectFields($runtimeType, $fragmentDefinition->selectionSet); - } elseif ($selection->kind === NodeKind::INLINE_FRAGMENT) { + } elseif ($selection instanceof InlineFragmentNode) { /** @var InlineFragmentNode $selection */ if ($selection->typeCondition !== null) { diff --git a/src/Language/AST/ListTypeNode.php b/src/Language/AST/ListTypeNode.php index 104810a75..29e81f9de 100644 --- a/src/Language/AST/ListTypeNode.php +++ b/src/Language/AST/ListTypeNode.php @@ -9,6 +9,6 @@ class ListTypeNode extends Node implements TypeNode /** @var string */ public $kind = NodeKind::LIST_TYPE; - /** @var Node */ + /** @var TypeNode */ public $type; } diff --git a/src/Language/AST/NonNullTypeNode.php b/src/Language/AST/NonNullTypeNode.php index f8b97c04c..36b61e5ef 100644 --- a/src/Language/AST/NonNullTypeNode.php +++ b/src/Language/AST/NonNullTypeNode.php @@ -9,6 +9,6 @@ class NonNullTypeNode extends Node implements TypeNode /** @var string */ public $kind = NodeKind::NON_NULL_TYPE; - /** @var NameNode | ListTypeNode */ + /** @var NamedTypeNode | ListTypeNode */ public $type; } diff --git a/src/Language/Printer.php b/src/Language/Printer.php index ff11d5053..b948ec4dc 100644 --- a/src/Language/Printer.php +++ b/src/Language/Printer.php @@ -28,6 +28,7 @@ use GraphQL\Language\AST\ListTypeNode; use GraphQL\Language\AST\ListValueNode; use GraphQL\Language\AST\NamedTypeNode; +use GraphQL\Language\AST\NameNode; use GraphQL\Language\AST\Node; use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\NonNullTypeNode; @@ -47,6 +48,7 @@ use GraphQL\Language\AST\UnionTypeDefinitionNode; use GraphQL\Language\AST\UnionTypeExtensionNode; use GraphQL\Language\AST\VariableDefinitionNode; +use GraphQL\Language\AST\VariableNode; use GraphQL\Utils\Utils; use function count; use function implode; @@ -97,11 +99,11 @@ public function printAST($ast) $ast, [ 'leave' => [ - NodeKind::NAME => static function (Node $node) { + NodeKind::NAME => static function (NameNode $node) { return '' . $node->value; }, - NodeKind::VARIABLE => static function ($node) { + NodeKind::VARIABLE => static function (VariableNode $node) { return '$' . $node->name; }, diff --git a/src/Utils/AST.php b/src/Utils/AST.php index 5ef4233f1..2b7753006 100644 --- a/src/Utils/AST.php +++ b/src/Utils/AST.php @@ -322,7 +322,7 @@ public static function astFromValue($value, InputType $type) * * @api */ - public static function valueFromAST($valueNode, InputType $type, ?array $variables = null) + public static function valueFromAST($valueNode, Type $type, ?array $variables = null) { $undefined = Utils::undefined(); diff --git a/src/Utils/ASTDefinitionBuilder.php b/src/Utils/ASTDefinitionBuilder.php index 8e452e2ba..67960170e 100644 --- a/src/Utils/ASTDefinitionBuilder.php +++ b/src/Utils/ASTDefinitionBuilder.php @@ -17,7 +17,6 @@ use GraphQL\Language\AST\ListTypeNode; use GraphQL\Language\AST\NamedTypeNode; use GraphQL\Language\AST\Node; -use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\NonNullTypeNode; use GraphQL\Language\AST\ObjectTypeDefinitionNode; use GraphQL\Language\AST\ScalarTypeDefinitionNode; @@ -244,23 +243,20 @@ private function internalBuildType($typeName, $typeNode = null) * * @throws Error */ - private function makeSchemaDef($def) + private function makeSchemaDef(Node $def) { - if (! $def) { - throw new Error('def must be defined.'); - } - switch ($def->kind) { - case NodeKind::OBJECT_TYPE_DEFINITION: + switch (true) { + case $def instanceof ObjectTypeDefinitionNode: return $this->makeTypeDef($def); - case NodeKind::INTERFACE_TYPE_DEFINITION: + case $def instanceof InterfaceTypeDefinitionNode: return $this->makeInterfaceDef($def); - case NodeKind::ENUM_TYPE_DEFINITION: + case $def instanceof EnumTypeDefinitionNode: return $this->makeEnumDef($def); - case NodeKind::UNION_TYPE_DEFINITION: + case $def instanceof UnionTypeDefinitionNode: return $this->makeUnionDef($def); - case NodeKind::SCALAR_TYPE_DEFINITION: + case $def instanceof ScalarTypeDefinitionNode: return $this->makeScalarDef($def); - case NodeKind::INPUT_OBJECT_TYPE_DEFINITION: + case $def instanceof InputObjectTypeDefinitionNode: return $this->makeInputObjectDef($def); default: throw new Error(sprintf('Type kind of %s not supported.', $def->kind)); @@ -430,62 +426,48 @@ private function makeInputObjectDef(InputObjectTypeDefinitionNode $def) } /** - * @param ObjectTypeDefinitionNode|InterfaceTypeDefinitionNode|EnumTypeExtensionNode|ScalarTypeDefinitionNode|InputObjectTypeDefinitionNode $def - * @param mixed[] $config + * @param mixed[] $config * * @return CustomScalarType|EnumType|InputObjectType|InterfaceType|ObjectType|UnionType * * @throws Error */ - private function makeSchemaDefFromConfig($def, array $config) + private function makeSchemaDefFromConfig(Node $def, array $config) { - if (! $def) { - throw new Error('def must be defined.'); - } - switch ($def->kind) { - case NodeKind::OBJECT_TYPE_DEFINITION: + switch (true) { + case $def instanceof ObjectTypeDefinitionNode: return new ObjectType($config); - case NodeKind::INTERFACE_TYPE_DEFINITION: + case $def instanceof InterfaceTypeDefinitionNode: return new InterfaceType($config); - case NodeKind::ENUM_TYPE_DEFINITION: + case $def instanceof EnumTypeDefinitionNode: return new EnumType($config); - case NodeKind::UNION_TYPE_DEFINITION: + case $def instanceof UnionTypeDefinitionNode: return new UnionType($config); - case NodeKind::SCALAR_TYPE_DEFINITION: + case $def instanceof ScalarTypeDefinitionNode: return new CustomScalarType($config); - case NodeKind::INPUT_OBJECT_TYPE_DEFINITION: + case $def instanceof InputObjectTypeDefinitionNode: return new InputObjectType($config); default: throw new Error(sprintf('Type kind of %s not supported.', $def->kind)); } } - /** - * @param TypeNode|ListTypeNode|NonNullTypeNode $typeNode - * - * @return TypeNode - */ - private function getNamedTypeNode(TypeNode $typeNode) + private function getNamedTypeNode(TypeNode $typeNode) : TypeNode { $namedType = $typeNode; - while ($namedType->kind === NodeKind::LIST_TYPE || $namedType->kind === NodeKind::NON_NULL_TYPE) { + while ($namedType instanceof ListTypeNode || $namedType instanceof NonNullTypeNode) { $namedType = $namedType->type; } return $namedType; } - /** - * @param TypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode - * - * @return Type - */ - private function buildWrappedType(Type $innerType, TypeNode $inputTypeNode) + private function buildWrappedType(Type $innerType, TypeNode $inputTypeNode) : Type { - if ($inputTypeNode->kind === NodeKind::LIST_TYPE) { + if ($inputTypeNode instanceof ListTypeNode) { return Type::listOf($this->buildWrappedType($innerType, $inputTypeNode->type)); } - if ($inputTypeNode->kind === NodeKind::NON_NULL_TYPE) { + if ($inputTypeNode instanceof NonNullTypeNode) { $wrappedType = $this->buildWrappedType($innerType, $inputTypeNode->type); return Type::nonNull(NonNull::assertNullableType($wrappedType)); diff --git a/src/Utils/BuildSchema.php b/src/Utils/BuildSchema.php index cf0eb7bda..3039aefe3 100644 --- a/src/Utils/BuildSchema.php +++ b/src/Utils/BuildSchema.php @@ -5,10 +5,16 @@ namespace GraphQL\Utils; use GraphQL\Error\Error; +use GraphQL\Language\AST\DirectiveDefinitionNode; use GraphQL\Language\AST\DocumentNode; +use GraphQL\Language\AST\EnumTypeDefinitionNode; +use GraphQL\Language\AST\InputObjectTypeDefinitionNode; +use GraphQL\Language\AST\InterfaceTypeDefinitionNode; use GraphQL\Language\AST\Node; -use GraphQL\Language\AST\NodeKind; +use GraphQL\Language\AST\ObjectTypeDefinitionNode; +use GraphQL\Language\AST\ScalarTypeDefinitionNode; use GraphQL\Language\AST\SchemaDefinitionNode; +use GraphQL\Language\AST\UnionTypeDefinitionNode; use GraphQL\Language\Parser; use GraphQL\Language\Source; use GraphQL\Type\Definition\Directive; @@ -95,39 +101,38 @@ public static function buildAST(DocumentNode $ast, ?callable $typeConfigDecorato public function buildSchema() { - /** @var SchemaDefinitionNode $schemaDef */ $schemaDef = null; $typeDefs = []; $this->nodeMap = []; $directiveDefs = []; - foreach ($this->ast->definitions as $d) { - switch ($d->kind) { - case NodeKind::SCHEMA_DEFINITION: - if ($schemaDef) { + foreach ($this->ast->definitions as $definition) { + switch (true) { + case $definition instanceof SchemaDefinitionNode: + if ($schemaDef !== null) { throw new Error('Must provide only one schema definition.'); } - $schemaDef = $d; + $schemaDef = $definition; break; - case NodeKind::SCALAR_TYPE_DEFINITION: - case NodeKind::OBJECT_TYPE_DEFINITION: - case NodeKind::INTERFACE_TYPE_DEFINITION: - case NodeKind::ENUM_TYPE_DEFINITION: - case NodeKind::UNION_TYPE_DEFINITION: - case NodeKind::INPUT_OBJECT_TYPE_DEFINITION: - $typeName = $d->name->value; + case $definition instanceof ScalarTypeDefinitionNode: + case $definition instanceof ObjectTypeDefinitionNode: + case $definition instanceof InterfaceTypeDefinitionNode: + case $definition instanceof EnumTypeDefinitionNode: + case $definition instanceof UnionTypeDefinitionNode: + case $definition instanceof InputObjectTypeDefinitionNode: + $typeName = $definition->name->value; if (! empty($this->nodeMap[$typeName])) { throw new Error(sprintf('Type "%s" was defined more than once.', $typeName)); } - $typeDefs[] = $d; - $this->nodeMap[$typeName] = $d; + $typeDefs[] = $definition; + $this->nodeMap[$typeName] = $definition; break; - case NodeKind::DIRECTIVE_DEFINITION: - $directiveDefs[] = $d; + case $definition instanceof DirectiveDefinitionNode: + $directiveDefs[] = $definition; break; } } - $operationTypes = $schemaDef + $operationTypes = $schemaDef !== null ? $this->getOperationTypes($schemaDef) : [ 'query' => isset($this->nodeMap['Query']) ? 'Query' : null, diff --git a/src/Utils/SchemaExtender.php b/src/Utils/SchemaExtender.php index fab006ead..87411f0ca 100644 --- a/src/Utils/SchemaExtender.php +++ b/src/Utils/SchemaExtender.php @@ -7,13 +7,16 @@ use GraphQL\Error\Error; use GraphQL\Language\AST\DirectiveDefinitionNode; use GraphQL\Language\AST\DocumentNode; +use GraphQL\Language\AST\EnumTypeExtensionNode; +use GraphQL\Language\AST\InputObjectTypeExtensionNode; +use GraphQL\Language\AST\InterfaceTypeExtensionNode; use GraphQL\Language\AST\Node; -use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\ObjectTypeExtensionNode; use GraphQL\Language\AST\SchemaDefinitionNode; use GraphQL\Language\AST\SchemaTypeExtensionNode; use GraphQL\Language\AST\TypeDefinitionNode; use GraphQL\Language\AST\TypeExtensionNode; +use GraphQL\Language\AST\UnionTypeExtensionNode; use GraphQL\Type\Definition\CustomScalarType; use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\EnumType; @@ -74,8 +77,8 @@ protected static function getExtensionASTNodes(NamedType $type) : ?array */ protected static function checkExtensionNode(Type $type, Node $node) : void { - switch ($node->kind) { - case NodeKind::OBJECT_TYPE_EXTENSION: + switch (true) { + case $node instanceof ObjectTypeExtensionNode: if (! ($type instanceof ObjectType)) { throw new Error( 'Cannot extend non-object type "' . $type->name . '".', @@ -83,7 +86,7 @@ protected static function checkExtensionNode(Type $type, Node $node) : void ); } break; - case NodeKind::INTERFACE_TYPE_EXTENSION: + case $node instanceof InterfaceTypeExtensionNode: if (! ($type instanceof InterfaceType)) { throw new Error( 'Cannot extend non-interface type "' . $type->name . '".', @@ -91,7 +94,7 @@ protected static function checkExtensionNode(Type $type, Node $node) : void ); } break; - case NodeKind::ENUM_TYPE_EXTENSION: + case $node instanceof EnumTypeExtensionNode: if (! ($type instanceof EnumType)) { throw new Error( 'Cannot extend non-enum type "' . $type->name . '".', @@ -99,7 +102,7 @@ protected static function checkExtensionNode(Type $type, Node $node) : void ); } break; - case NodeKind::UNION_TYPE_EXTENSION: + case $node instanceof UnionTypeExtensionNode: if (! ($type instanceof UnionType)) { throw new Error( 'Cannot extend non-union type "' . $type->name . '".', @@ -107,7 +110,7 @@ protected static function checkExtensionNode(Type $type, Node $node) : void ); } break; - case NodeKind::INPUT_OBJECT_TYPE_EXTENSION: + case $node instanceof InputObjectTypeExtensionNode: if (! ($type instanceof InputObjectType)) { throw new Error( 'Cannot extend non-input object type "' . $type->name . '".', diff --git a/src/Utils/TypeInfo.php b/src/Utils/TypeInfo.php index b18751121..a1d6be734 100644 --- a/src/Utils/TypeInfo.php +++ b/src/Utils/TypeInfo.php @@ -6,12 +6,21 @@ use GraphQL\Error\InvariantViolation; use GraphQL\Error\Warning; +use GraphQL\Language\AST\ArgumentNode; +use GraphQL\Language\AST\DirectiveNode; +use GraphQL\Language\AST\EnumValueNode; use GraphQL\Language\AST\FieldNode; +use GraphQL\Language\AST\FragmentDefinitionNode; +use GraphQL\Language\AST\InlineFragmentNode; use GraphQL\Language\AST\ListTypeNode; +use GraphQL\Language\AST\ListValueNode; use GraphQL\Language\AST\NamedTypeNode; use GraphQL\Language\AST\Node; -use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\NonNullTypeNode; +use GraphQL\Language\AST\ObjectFieldNode; +use GraphQL\Language\AST\OperationDefinitionNode; +use GraphQL\Language\AST\SelectionSetNode; +use GraphQL\Language\AST\VariableDefinitionNode; use GraphQL\Type\Definition\CompositeType; use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\EnumType; @@ -255,13 +264,13 @@ public function enter(Node $node) // any assumptions of a valid schema to ensure runtime types are properly // checked before continuing since TypeInfo is used as part of validation // which occurs before guarantees of schema and document validity. - switch ($node->kind) { - case NodeKind::SELECTION_SET: + switch (true) { + case $node instanceof SelectionSetNode: $namedType = Type::getNamedType($this->getType()); $this->parentTypeStack[] = Type::isCompositeType($namedType) ? $namedType : null; break; - case NodeKind::FIELD: + case $node instanceof FieldNode: $parentType = $this->getParentType(); $fieldDef = null; if ($parentType) { @@ -275,11 +284,11 @@ public function enter(Node $node) $this->typeStack[] = Type::isOutputType($fieldType) ? $fieldType : null; break; - case NodeKind::DIRECTIVE: + case $node instanceof DirectiveNode: $this->directive = $schema->getDirective($node->name->value); break; - case NodeKind::OPERATION_DEFINITION: + case $node instanceof OperationDefinitionNode: $type = null; if ($node->operation === 'query') { $type = $schema->getQueryType(); @@ -291,8 +300,8 @@ public function enter(Node $node) $this->typeStack[] = Type::isOutputType($type) ? $type : null; break; - case NodeKind::INLINE_FRAGMENT: - case NodeKind::FRAGMENT_DEFINITION: + case $node instanceof InlineFragmentNode: + case $node instanceof FragmentDefinitionNode: $typeConditionNode = $node->typeCondition; $outputType = $typeConditionNode ? self::typeFromAST( $schema, @@ -301,12 +310,12 @@ public function enter(Node $node) $this->typeStack[] = Type::isOutputType($outputType) ? $outputType : null; break; - case NodeKind::VARIABLE_DEFINITION: + case $node instanceof VariableDefinitionNode: $inputType = self::typeFromAST($schema, $node->type); $this->inputTypeStack[] = Type::isInputType($inputType) ? $inputType : null; // push break; - case NodeKind::ARGUMENT: + case $node instanceof ArgumentNode: $fieldOrDirective = $this->getDirective() ?: $this->getFieldDef(); $argDef = $argType = null; if ($fieldOrDirective) { @@ -324,7 +333,7 @@ static function ($arg) use ($node) { $this->inputTypeStack[] = Type::isInputType($argType) ? $argType : null; break; - case NodeKind::LST: + case $node instanceof ListValueNode: $listType = Type::getNullableType($this->getInputType()); $itemType = $listType instanceof ListOfType ? $listType->getWrappedType() @@ -332,7 +341,7 @@ static function ($arg) use ($node) { $this->inputTypeStack[] = Type::isInputType($itemType) ? $itemType : null; break; - case NodeKind::OBJECT_FIELD: + case $node instanceof ObjectFieldNode: $objectType = Type::getNamedType($this->getInputType()); $fieldType = null; $inputFieldType = null; @@ -344,7 +353,7 @@ static function ($arg) use ($node) { $this->inputTypeStack[] = Type::isInputType($inputFieldType) ? $inputFieldType : null; break; - case NodeKind::ENUM: + case $node instanceof EnumValueNode: $enumType = Type::getNamedType($this->getInputType()); $enumValue = null; if ($enumType instanceof EnumType) { @@ -458,37 +467,37 @@ public function getInputType() public function leave(Node $node) { - switch ($node->kind) { - case NodeKind::SELECTION_SET: + switch (true) { + case $node instanceof SelectionSetNode: array_pop($this->parentTypeStack); break; - case NodeKind::FIELD: + case $node instanceof FieldNode: array_pop($this->fieldDefStack); array_pop($this->typeStack); break; - case NodeKind::DIRECTIVE: + case $node instanceof DirectiveNode: $this->directive = null; break; - case NodeKind::OPERATION_DEFINITION: - case NodeKind::INLINE_FRAGMENT: - case NodeKind::FRAGMENT_DEFINITION: + case $node instanceof OperationDefinitionNode: + case $node instanceof InlineFragmentNode: + case $node instanceof FragmentDefinitionNode: array_pop($this->typeStack); break; - case NodeKind::VARIABLE_DEFINITION: + case $node instanceof VariableDefinitionNode: array_pop($this->inputTypeStack); break; - case NodeKind::ARGUMENT: + case $node instanceof ArgumentNode: $this->argument = null; array_pop($this->inputTypeStack); break; - case NodeKind::LST: - case NodeKind::OBJECT_FIELD: + case $node instanceof ListValueNode: + case $node instanceof ObjectFieldNode: array_pop($this->inputTypeStack); break; - case NodeKind::ENUM: + case $node instanceof EnumValueNode: $this->enumValue = null; break; } diff --git a/src/Validator/Rules/KnownArgumentNames.php b/src/Validator/Rules/KnownArgumentNames.php index 96fd800de..638f11cba 100644 --- a/src/Validator/Rules/KnownArgumentNames.php +++ b/src/Validator/Rules/KnownArgumentNames.php @@ -6,6 +6,8 @@ use GraphQL\Error\Error; use GraphQL\Language\AST\ArgumentNode; +use GraphQL\Language\AST\DirectiveNode; +use GraphQL\Language\AST\FieldNode; use GraphQL\Language\AST\Node; use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\NodeList; @@ -34,7 +36,7 @@ public function getVisitor(ValidationContext $context) } $argumentOf = $ancestors[count($ancestors) - 1]; - if ($argumentOf->kind === NodeKind::FIELD) { + if ($argumentOf instanceof FieldNode) { $fieldDef = $context->getFieldDef(); $parentType = $context->getParentType(); if ($fieldDef && $parentType) { @@ -56,7 +58,7 @@ static function ($arg) { [$node] )); } - } elseif ($argumentOf->kind === NodeKind::DIRECTIVE) { + } elseif ($argumentOf instanceof DirectiveNode) { $directive = $context->getDirective(); if ($directive) { $context->reportError(new Error( diff --git a/src/Validator/Rules/KnownDirectives.php b/src/Validator/Rules/KnownDirectives.php index 927ce2657..e986c0945 100644 --- a/src/Validator/Rules/KnownDirectives.php +++ b/src/Validator/Rules/KnownDirectives.php @@ -7,10 +7,31 @@ use GraphQL\Error\Error; use GraphQL\Language\AST\DirectiveDefinitionNode; use GraphQL\Language\AST\DirectiveNode; +use GraphQL\Language\AST\EnumTypeDefinitionNode; +use GraphQL\Language\AST\EnumTypeExtensionNode; +use GraphQL\Language\AST\EnumValueDefinitionNode; +use GraphQL\Language\AST\FieldDefinitionNode; +use GraphQL\Language\AST\FieldNode; +use GraphQL\Language\AST\FragmentDefinitionNode; +use GraphQL\Language\AST\FragmentSpreadNode; +use GraphQL\Language\AST\InlineFragmentNode; use GraphQL\Language\AST\InputObjectTypeDefinitionNode; +use GraphQL\Language\AST\InputObjectTypeExtensionNode; +use GraphQL\Language\AST\InputValueDefinitionNode; +use GraphQL\Language\AST\InterfaceTypeDefinitionNode; +use GraphQL\Language\AST\InterfaceTypeExtensionNode; use GraphQL\Language\AST\Node; use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\NodeList; +use GraphQL\Language\AST\ObjectTypeDefinitionNode; +use GraphQL\Language\AST\ObjectTypeExtensionNode; +use GraphQL\Language\AST\OperationDefinitionNode; +use GraphQL\Language\AST\ScalarTypeDefinitionNode; +use GraphQL\Language\AST\ScalarTypeExtensionNode; +use GraphQL\Language\AST\SchemaDefinitionNode; +use GraphQL\Language\AST\SchemaTypeExtensionNode; +use GraphQL\Language\AST\UnionTypeDefinitionNode; +use GraphQL\Language\AST\UnionTypeExtensionNode; use GraphQL\Language\DirectiveLocation; use GraphQL\Validator\ValidationContext; use function array_map; @@ -96,8 +117,8 @@ public static function unknownDirectiveMessage($directiveName) private function getDirectiveLocationForASTPath(array $ancestors) { $appliedTo = $ancestors[count($ancestors) - 1]; - switch ($appliedTo->kind) { - case NodeKind::OPERATION_DEFINITION: + switch (true) { + case $appliedTo instanceof OperationDefinitionNode: switch ($appliedTo->operation) { case 'query': return DirectiveLocation::QUERY; @@ -107,40 +128,40 @@ private function getDirectiveLocationForASTPath(array $ancestors) return DirectiveLocation::SUBSCRIPTION; } break; - case NodeKind::FIELD: + case $appliedTo instanceof FieldNode: return DirectiveLocation::FIELD; - case NodeKind::FRAGMENT_SPREAD: + case $appliedTo instanceof FragmentSpreadNode: return DirectiveLocation::FRAGMENT_SPREAD; - case NodeKind::INLINE_FRAGMENT: + case $appliedTo instanceof InlineFragmentNode: return DirectiveLocation::INLINE_FRAGMENT; - case NodeKind::FRAGMENT_DEFINITION: + case $appliedTo instanceof FragmentDefinitionNode: return DirectiveLocation::FRAGMENT_DEFINITION; - case NodeKind::SCHEMA_DEFINITION: - case NodeKind::SCHEMA_EXTENSION: + case $appliedTo instanceof SchemaDefinitionNode: + case $appliedTo instanceof SchemaTypeExtensionNode: return DirectiveLocation::SCHEMA; - case NodeKind::SCALAR_TYPE_DEFINITION: - case NodeKind::SCALAR_TYPE_EXTENSION: + case $appliedTo instanceof ScalarTypeDefinitionNode: + case $appliedTo instanceof ScalarTypeExtensionNode: return DirectiveLocation::SCALAR; - case NodeKind::OBJECT_TYPE_DEFINITION: - case NodeKind::OBJECT_TYPE_EXTENSION: + case $appliedTo instanceof ObjectTypeDefinitionNode: + case $appliedTo instanceof ObjectTypeExtensionNode: return DirectiveLocation::OBJECT; - case NodeKind::FIELD_DEFINITION: + case $appliedTo instanceof FieldDefinitionNode: return DirectiveLocation::FIELD_DEFINITION; - case NodeKind::INTERFACE_TYPE_DEFINITION: - case NodeKind::INTERFACE_TYPE_EXTENSION: + case $appliedTo instanceof InterfaceTypeDefinitionNode: + case $appliedTo instanceof InterfaceTypeExtensionNode: return DirectiveLocation::IFACE; - case NodeKind::UNION_TYPE_DEFINITION: - case NodeKind::UNION_TYPE_EXTENSION: + case $appliedTo instanceof UnionTypeDefinitionNode: + case $appliedTo instanceof UnionTypeExtensionNode: return DirectiveLocation::UNION; - case NodeKind::ENUM_TYPE_DEFINITION: - case NodeKind::ENUM_TYPE_EXTENSION: + case $appliedTo instanceof EnumTypeDefinitionNode: + case $appliedTo instanceof EnumTypeExtensionNode: return DirectiveLocation::ENUM; - case NodeKind::ENUM_VALUE_DEFINITION: + case $appliedTo instanceof EnumValueDefinitionNode: return DirectiveLocation::ENUM_VALUE; - case NodeKind::INPUT_OBJECT_TYPE_DEFINITION: - case NodeKind::INPUT_OBJECT_TYPE_EXTENSION: + case $appliedTo instanceof InputObjectTypeDefinitionNode: + case $appliedTo instanceof InputObjectTypeExtensionNode: return DirectiveLocation::INPUT_OBJECT; - case NodeKind::INPUT_VALUE_DEFINITION: + case $appliedTo instanceof InputValueDefinitionNode: $parentNode = $ancestors[count($ancestors) - 3]; return $parentNode instanceof InputObjectTypeDefinitionNode diff --git a/src/Validator/Rules/LoneAnonymousOperation.php b/src/Validator/Rules/LoneAnonymousOperation.php index 40ff8218d..e87530955 100644 --- a/src/Validator/Rules/LoneAnonymousOperation.php +++ b/src/Validator/Rules/LoneAnonymousOperation.php @@ -30,7 +30,7 @@ public function getVisitor(ValidationContext $context) $tmp = Utils::filter( $node->definitions, static function (Node $definition) { - return $definition->kind === NodeKind::OPERATION_DEFINITION; + return $definition instanceof OperationDefinitionNode; } ); diff --git a/src/Validator/Rules/QueryComplexity.php b/src/Validator/Rules/QueryComplexity.php index 6a3da59a0..ca4df0a5b 100644 --- a/src/Validator/Rules/QueryComplexity.php +++ b/src/Validator/Rules/QueryComplexity.php @@ -110,9 +110,8 @@ private function fieldComplexity($node, $complexity = 0) private function nodeComplexity(Node $node, $complexity = 0) { - switch ($node->kind) { - case NodeKind::FIELD: - /** @var FieldNode $node */ + switch (true) { + case $node instanceof FieldNode: // default values $args = []; $complexityFn = FieldDefinition::DEFAULT_COMPLEXITY_FN; @@ -143,16 +142,14 @@ private function nodeComplexity(Node $node, $complexity = 0) $complexity += call_user_func_array($complexityFn, [$childrenComplexity, $args]); break; - case NodeKind::INLINE_FRAGMENT: - /** @var InlineFragmentNode $node */ + case $node instanceof InlineFragmentNode: // node has children? if (isset($node->selectionSet)) { $complexity = $this->fieldComplexity($node, $complexity); } break; - case NodeKind::FRAGMENT_SPREAD: - /** @var FragmentSpreadNode $node */ + case $node instanceof FragmentSpreadNode: $fragment = $this->getFragment($node); if ($fragment !== null) { diff --git a/src/Validator/Rules/QueryDepth.php b/src/Validator/Rules/QueryDepth.php index 5a35f0c88..3f74edda5 100644 --- a/src/Validator/Rules/QueryDepth.php +++ b/src/Validator/Rules/QueryDepth.php @@ -5,6 +5,9 @@ namespace GraphQL\Validator\Rules; use GraphQL\Error\Error; +use GraphQL\Language\AST\FieldNode; +use GraphQL\Language\AST\FragmentSpreadNode; +use GraphQL\Language\AST\InlineFragmentNode; use GraphQL\Language\AST\Node; use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\OperationDefinitionNode; @@ -57,9 +60,8 @@ private function fieldDepth($node, $depth = 0, $maxDepth = 0) private function nodeDepth(Node $node, $depth = 0, $maxDepth = 0) { - switch ($node->kind) { - case NodeKind::FIELD: - /** @var FieldNode $node */ + switch (true) { + case $node instanceof FieldNode: // node has children? if ($node->selectionSet !== null) { // update maxDepth if needed @@ -70,16 +72,14 @@ private function nodeDepth(Node $node, $depth = 0, $maxDepth = 0) } break; - case NodeKind::INLINE_FRAGMENT: - /** @var InlineFragmentNode $node */ + case $node instanceof InlineFragmentNode: // node has children? if ($node->selectionSet !== null) { $maxDepth = $this->fieldDepth($node, $depth, $maxDepth); } break; - case NodeKind::FRAGMENT_SPREAD: - /** @var FragmentSpreadNode $node */ + case $node instanceof FragmentSpreadNode: $fragment = $this->getFragment($node); if ($fragment !== null) { diff --git a/src/Validator/Rules/QuerySecurityRule.php b/src/Validator/Rules/QuerySecurityRule.php index c5f02603f..2837ed788 100644 --- a/src/Validator/Rules/QuerySecurityRule.php +++ b/src/Validator/Rules/QuerySecurityRule.php @@ -9,7 +9,6 @@ use GraphQL\Language\AST\FragmentDefinitionNode; use GraphQL\Language\AST\FragmentSpreadNode; use GraphQL\Language\AST\InlineFragmentNode; -use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\SelectionSetNode; use GraphQL\Type\Definition\Type; use GraphQL\Type\Introspection; @@ -114,8 +113,8 @@ protected function collectFieldASTsAndDefs( $_astAndDefs = $astAndDefs ?: new ArrayObject(); foreach ($selectionSet->selections as $selection) { - switch ($selection->kind) { - case NodeKind::FIELD: + switch (true) { + case $selection instanceof FieldNode: /** @var FieldNode $selection */ $fieldName = $selection->name->value; $fieldDef = null; @@ -142,7 +141,7 @@ protected function collectFieldASTsAndDefs( // create field context $_astAndDefs[$responseName][] = [$selection, $fieldDef]; break; - case NodeKind::INLINE_FRAGMENT: + case $selection instanceof InlineFragmentNode: /** @var InlineFragmentNode $selection */ $_astAndDefs = $this->collectFieldASTsAndDefs( $context, @@ -152,7 +151,7 @@ protected function collectFieldASTsAndDefs( $_astAndDefs ); break; - case NodeKind::FRAGMENT_SPREAD: + case $selection instanceof FragmentSpreadNode: /** @var FragmentSpreadNode $selection */ $fragName = $selection->name->value; diff --git a/src/Validator/ValidationContext.php b/src/Validator/ValidationContext.php index 71633710a..7564af457 100644 --- a/src/Validator/ValidationContext.php +++ b/src/Validator/ValidationContext.php @@ -199,7 +199,7 @@ public function getFragmentSpreads(HasSelectionSet $node) for ($i = 0, $selectionCount = count($set->selections); $i < $selectionCount; $i++) { $selection = $set->selections[$i]; - if ($selection->kind === NodeKind::FRAGMENT_SPREAD) { + if ($selection instanceof FragmentSpreadNode) { $spreads[] = $selection; } elseif ($selection->selectionSet) { $setsToVisit[] = $selection->selectionSet; @@ -223,7 +223,7 @@ public function getFragment($name) if (! $fragments) { $fragments = []; foreach ($this->getDocument()->definitions as $statement) { - if ($statement->kind !== NodeKind::FRAGMENT_DEFINITION) { + if (! ($statement instanceof FragmentDefinitionNode)) { continue; } diff --git a/tests/Experimental/Executor/CollectorTest.php b/tests/Experimental/Executor/CollectorTest.php index c078cabdb..1339e57c8 100644 --- a/tests/Experimental/Executor/CollectorTest.php +++ b/tests/Experimental/Executor/CollectorTest.php @@ -362,7 +362,7 @@ public function provideForTestCollectFields() $operationName = null; foreach ($documentNode->definitions as $definitionNode) { /** @var Node $definitionNode */ - if ($definitionNode->kind === NodeKind::OPERATION_DEFINITION) { + if ($definitionNode instanceof OperationDefinitionNode) { /** @var OperationDefinitionNode $definitionNode */ self::assertNotNull($definitionNode->name); $operationName = $definitionNode->name->value; diff --git a/tests/Language/VisitorTest.php b/tests/Language/VisitorTest.php index 4a0432674..b04b4db0b 100644 --- a/tests/Language/VisitorTest.php +++ b/tests/Language/VisitorTest.php @@ -384,7 +384,7 @@ public function testAllowsEarlyExitWhileLeaving() : void $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['leave', $node->kind, $node->value ?? null]; - if ($node->kind === NodeKind::NAME && $node->value === 'x') { + if ($node instanceof NameNode && $node->value === 'x') { return Visitor::stop(); } }, From e94db8a0452e20ff13a4c446a97fbea69d3ded2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20FIDRY?= Date: Wed, 8 May 2019 17:28:01 +0200 Subject: [PATCH 002/256] Update .gitattributes --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index 0bddf4164..d04345ede 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,6 +2,7 @@ * text eol=lf /benchmarks export-ignore /tests export-ignore +/examples export-ignore /tools export-ignore .gitattributes export-ignore .gitignore export-ignore From 2173bb969680e735e0ce2c8cf2caa250df779f60 Mon Sep 17 00:00:00 2001 From: stefania Date: Wed, 22 May 2019 15:27:40 -0400 Subject: [PATCH 003/256] Update README.md Fix "LICENCE" typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2cf46ba0d..2698b4772 100644 --- a/README.md +++ b/README.md @@ -49,4 +49,4 @@ Support this project by becoming a sponsor. Your logo will show up here with a l ## License -See [LICENCE](LICENSE). +See [LICENSE](LICENSE). From cf90a8d338cccb21ad45e77c6bf0202e42546e73 Mon Sep 17 00:00:00 2001 From: Markus Podar Date: Tue, 28 May 2019 22:15:29 +0200 Subject: [PATCH 004/256] Add Laravel GraphQL implementation It's a spiritual success to the archived https://github.com/Folkloreatelier/laravel-graphql project which was previously removed in https://github.com/webonyx/graphql-php/pull/455 --- docs/complementary-tools.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/complementary-tools.md b/docs/complementary-tools.md index 4710ebd9a..e343b1b09 100644 --- a/docs/complementary-tools.md +++ b/docs/complementary-tools.md @@ -3,6 +3,7 @@ * [Standard Server](executing-queries.md/#using-server) – Out of the box integration with any PSR-7 compatible framework (like [Slim](http://slimframework.com) or [Zend Expressive](http://zendframework.github.io/zend-expressive/)). * [Relay Library for graphql-php](https://github.com/ivome/graphql-relay-php) – Helps construct Relay related schema definitions. * [Lighthouse](https://github.com/nuwave/lighthouse) – Laravel based, uses Schema Definition Language +* [Laravel GraphQL](https://github.com/rebing/graphql-laravel) - Laravel wrapper for Facebook's GraphQL * [OverblogGraphQLBundle](https://github.com/overblog/GraphQLBundle) – Bundle for Symfony * [WP-GraphQL](https://github.com/wp-graphql/wp-graphql) - GraphQL API for WordPress From c9faa3489bfd93d9e0772b85e2c716413142b422 Mon Sep 17 00:00:00 2001 From: spawnia Date: Mon, 10 Jun 2019 22:15:23 +0200 Subject: [PATCH 005/256] Add schema validation: Input Objects must not contain non-nullable circular references Spec change: https://github.com/graphql/graphql-spec/pull/445 Reference implementation: https://github.com/graphql/graphql-js/pull/1359 --- src/Type/SchemaValidationContext.php | 12 +- .../Validation/InputObjectCircularRefs.php | 105 +++++++++++++ tests/Type/ValidationTest.php | 138 ++++++++++++++++++ 3 files changed, 253 insertions(+), 2 deletions(-) create mode 100644 src/Type/Validation/InputObjectCircularRefs.php diff --git a/src/Type/SchemaValidationContext.php b/src/Type/SchemaValidationContext.php index 63f54dd46..f00d3e588 100644 --- a/src/Type/SchemaValidationContext.php +++ b/src/Type/SchemaValidationContext.php @@ -29,6 +29,7 @@ use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\UnionType; +use GraphQL\Type\Validation\InputObjectCircularRefs; use GraphQL\Utils\TypeComparators; use GraphQL\Utils\Utils; use function array_filter; @@ -48,9 +49,13 @@ class SchemaValidationContext /** @var Schema */ private $schema; + /** @var InputObjectCircularRefs */ + private $inputObjectCircularRefs; + public function __construct(Schema $schema) { - $this->schema = $schema; + $this->schema = $schema; + $this->inputObjectCircularRefs = new InputObjectCircularRefs($this); } /** @@ -99,7 +104,7 @@ public function validateRootTypes() * @param string $message * @param Node[]|Node|TypeNode|TypeDefinitionNode|null $nodes */ - private function reportError($message, $nodes = null) + public function reportError($message, $nodes = null) { $nodes = array_filter($nodes && is_array($nodes) ? $nodes : [$nodes]); $this->addError(new Error($message, $nodes)); @@ -275,6 +280,9 @@ public function validateTypes() } elseif ($type instanceof InputObjectType) { // Ensure Input Object fields are valid. $this->validateInputFields($type); + + // Ensure Input Objects do not contain non-nullable circular references + $this->inputObjectCircularRefs->validate($type); } } } diff --git a/src/Type/Validation/InputObjectCircularRefs.php b/src/Type/Validation/InputObjectCircularRefs.php new file mode 100644 index 000000000..ace97dc14 --- /dev/null +++ b/src/Type/Validation/InputObjectCircularRefs.php @@ -0,0 +1,105 @@ + int $index] + * + * @var int[] + */ + private $fieldPathIndexByTypeName = []; + + public function __construct(SchemaValidationContext $schemaValidationContext) + { + $this->schemaValidationContext = $schemaValidationContext; + } + + /** + * This does a straight-forward DFS to find cycles. + * It does not terminate when a cycle was found but continues to explore + * the graph to find all possible cycles. + */ + public function validate(InputObjectType $inputObj) : void + { + if (isset($this->visitedTypes[$inputObj->name])) { + return; + } + + $this->visitedTypes[$inputObj->name] = true; + $this->fieldPathIndexByTypeName[$inputObj->name] = count($this->fieldPath); + + $fieldMap = $inputObj->getFields(); + foreach ($fieldMap as $fieldName => $field) { + $type = $field->getType(); + + if ($type instanceof NonNull) { + $fieldType = $type->getWrappedType(); + + // If the type of the field is anything else then a non-nullable input object, + // there is no chance of an unbreakable cycle + if ($fieldType instanceof InputObjectType) { + $this->fieldPath[] = $field; + + if (! isset($this->fieldPathIndexByTypeName[$fieldType->name])) { + $this->validate($fieldType); + } else { + $cycleIndex = $this->fieldPathIndexByTypeName[$fieldType->name]; + $cyclePath = array_slice($this->fieldPath, $cycleIndex); + $fieldNames = array_map( + static function (InputObjectField $field) : string { + return $field->name; + }, + $cyclePath + ); + + $this->schemaValidationContext->reportError( + 'Cannot reference Input Object "' . $fieldType->name . '" within itself ' + . 'through a series of non-null fields: "' . implode('.', $fieldNames) . '".', + array_map( + static function (InputObjectField $field) : ?InputValueDefinitionNode { + return $field->astNode; + }, + $cyclePath + ) + ); + } + } + } + + array_pop($this->fieldPath); + } + + unset($this->fieldPathIndexByTypeName[$inputObj->name]); + } +} diff --git a/tests/Type/ValidationTest.php b/tests/Type/ValidationTest.php index 5bafd30ec..c83bd806f 100644 --- a/tests/Type/ValidationTest.php +++ b/tests/Type/ValidationTest.php @@ -879,6 +879,144 @@ public function testRejectsAnInputObjectTypeWithMissingFields() : void ); } + /** + * @see it('accepts an Input Object with breakable circular reference') + */ + public function testAcceptsAnInputObjectWithBreakableCircularReference() : void + { + $schema = BuildSchema::build(' + input AnotherInputObject { + parent: SomeInputObject + } + + type Query { + field(arg: SomeInputObject): String + } + + input SomeInputObject { + self: SomeInputObject + arrayOfSelf: [SomeInputObject] + nonNullArrayOfSelf: [SomeInputObject]! + nonNullArrayOfNonNullSelf: [SomeInputObject!]! + intermediateSelf: AnotherInputObject + } + '); + self::assertEquals([], $schema->validate()); + } + + /** + * @see it('rejects an Input Object with non-breakable circular reference') + */ + public function testRejectsAnInputObjectWithNonBreakableCircularReference() : void + { + $schema = BuildSchema::build(' + type Query { + field(arg: SomeInputObject): String + } + + input SomeInputObject { + nonNullSelf: SomeInputObject! + } + '); + $this->assertMatchesValidationMessage( + $schema->validate(), + [ + [ + 'message' => 'Cannot reference Input Object "SomeInputObject" within itself through a series of non-null fields: "nonNullSelf".', + 'locations' => [['line' => 7, 'column' => 9]], + ], + ] + ); + } + + /** + * @see it('rejects Input Objects with non-breakable circular reference spread across them') + */ + public function testRejectsInputObjectsWithNonBreakableCircularReferenceSpreadAcrossThem() : void + { + $schema = BuildSchema::build(' + type Query { + field(arg: SomeInputObject): String + } + + input SomeInputObject { + startLoop: AnotherInputObject! + } + + input AnotherInputObject { + nextInLoop: YetAnotherInputObject! + } + + input YetAnotherInputObject { + closeLoop: SomeInputObject! + } + '); + $this->assertMatchesValidationMessage( + $schema->validate(), + [ + [ + 'message' => 'Cannot reference Input Object "SomeInputObject" within itself through a series of non-null fields: "startLoop.nextInLoop.closeLoop".', + 'locations' => [ + ['line' => 7, 'column' => 9], + ['line' => 11, 'column' => 9], + ['line' => 15, 'column' => 9], + ], + ], + ] + ); + } + + /** + * @see it('rejects Input Objects with multiple non-breakable circular reference') + */ + public function testRejectsInputObjectsWithMultipleNonBreakableCircularReferences() : void + { + $schema = BuildSchema::build(' + type Query { + field(arg: SomeInputObject): String + } + + input SomeInputObject { + startLoop: AnotherInputObject! + } + + input AnotherInputObject { + closeLoop: SomeInputObject! + startSecondLoop: YetAnotherInputObject! + } + + input YetAnotherInputObject { + closeSecondLoop: AnotherInputObject! + nonNullSelf: YetAnotherInputObject! + } + '); + $this->assertMatchesValidationMessage( + $schema->validate(), + [ + [ + 'message' => 'Cannot reference Input Object "SomeInputObject" within itself through a series of non-null fields: "startLoop.closeLoop".', + 'locations' => [ + ['line' => 7, 'column' => 9], + ['line' => 11, 'column' => 9], + ], + ], + [ + 'message' => 'Cannot reference Input Object "AnotherInputObject" within itself through a series of non-null fields: "startSecondLoop.closeSecondLoop".', + 'locations' => [ + ['line' => 12, 'column' => 9], + ['line' => 16, 'column' => 9], + ], + ], + [ + 'message' => 'Cannot reference Input Object "YetAnotherInputObject" within itself through a series of non-null fields: "nonNullSelf".', + 'locations' => [ + ['line' => 17, 'column' => 9], + ], + ], + ] + ); + } + /** * @see it('rejects an Input Object type with incorrectly typed fields') */ From e5528d14abed17776eba220311dc5096401181b1 Mon Sep 17 00:00:00 2001 From: spawnia Date: Mon, 10 Jun 2019 22:23:06 +0200 Subject: [PATCH 006/256] Add changelog entry --- CHANGELOG.md | 19 +++++++++++++++++++ CONTRIBUTING.md | 5 +++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ac09c47b..38b52f718 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,23 @@ # Changelog + +## Unreleased +- Add schema validation: Input Objects must not contain non-nullable circular references (https://github.com/webonyx/graphql-php/pull/492) + +#### v0.13.4 +- Force int when setting max query depth (https://github.com/webonyx/graphql-php/pull/477) + +#### v0.13.3 +- Reverted minor possible breaking change (https://github.com/webonyx/graphql-php/pull/476) + +#### v0.13.2 +- Added QueryPlan support (https://github.com/webonyx/graphql-php/pull/436) +- Fixed an issue with NodeList iteration over missing keys (https://github.com/webonyx/graphql-php/pull/475) + +#### v0.13.1 +- Better validation of field/directive arguments +- Support for Apollo-style client/server persisted queries +- Minor tweaks and fixes + ## v0.13.0 This release brings several breaking changes. Please refer to [UPGRADE](UPGRADE.md) document for details. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 399716c8d..636ef53a1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,8 +10,9 @@ For smaller contributions just use this workflow: * Fork the project. * Add your features and or bug fixes. * Add tests. Tests are important for us. -* Check your changes using `composer check-all` -* Send a pull request +* Check your changes using `composer check-all`. +* Add an entry to the [Changelog](CHANGELOG.md). +* Send a pull request. ## Setup the Development Environment First, copy the URL of your fork and `git clone` it to your local machine. From 6c82b85e79c4552e5e268bd9ec8997997968552e Mon Sep 17 00:00:00 2001 From: spawnia Date: Mon, 10 Jun 2019 22:24:54 +0200 Subject: [PATCH 007/256] Revert "Add changelog entry" This reverts commit e5528d14 --- CHANGELOG.md | 19 ------------------- CONTRIBUTING.md | 5 ++--- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38b52f718..0ac09c47b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,23 +1,4 @@ # Changelog - -## Unreleased -- Add schema validation: Input Objects must not contain non-nullable circular references (https://github.com/webonyx/graphql-php/pull/492) - -#### v0.13.4 -- Force int when setting max query depth (https://github.com/webonyx/graphql-php/pull/477) - -#### v0.13.3 -- Reverted minor possible breaking change (https://github.com/webonyx/graphql-php/pull/476) - -#### v0.13.2 -- Added QueryPlan support (https://github.com/webonyx/graphql-php/pull/436) -- Fixed an issue with NodeList iteration over missing keys (https://github.com/webonyx/graphql-php/pull/475) - -#### v0.13.1 -- Better validation of field/directive arguments -- Support for Apollo-style client/server persisted queries -- Minor tweaks and fixes - ## v0.13.0 This release brings several breaking changes. Please refer to [UPGRADE](UPGRADE.md) document for details. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 636ef53a1..399716c8d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,9 +10,8 @@ For smaller contributions just use this workflow: * Fork the project. * Add your features and or bug fixes. * Add tests. Tests are important for us. -* Check your changes using `composer check-all`. -* Add an entry to the [Changelog](CHANGELOG.md). -* Send a pull request. +* Check your changes using `composer check-all` +* Send a pull request ## Setup the Development Environment First, copy the URL of your fork and `git clone` it to your local machine. From 692d10c127d13e0018c907288d85fe3fefffa0bb Mon Sep 17 00:00:00 2001 From: spawnia Date: Mon, 10 Jun 2019 22:30:25 +0200 Subject: [PATCH 008/256] Add Unreleases section to the Changelog --- CHANGELOG.md | 4 ++++ CONTRIBUTING.md | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ac09c47b..8fc837bf2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ # Changelog + +## Unreleased +- Add schema validation: Input Objects must not contain non-nullable circular references (#492) + ## v0.13.0 This release brings several breaking changes. Please refer to [UPGRADE](UPGRADE.md) document for details. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 399716c8d..d51a0ac9a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,8 +10,9 @@ For smaller contributions just use this workflow: * Fork the project. * Add your features and or bug fixes. * Add tests. Tests are important for us. -* Check your changes using `composer check-all` -* Send a pull request +* Check your changes using `composer check-all`. +* Add an entry to the [Changelog's Unreleases section](CHANGELOG.md#unreleased). +* Send a pull request. ## Setup the Development Environment First, copy the URL of your fork and `git clone` it to your local machine. From 21592f8f280e592c23502f72b5c9e306f562b612 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Wed, 12 Jun 2019 10:22:18 +0200 Subject: [PATCH 009/256] Upgrade PHPStan --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 113e79d92..b68861858 100644 --- a/composer.json +++ b/composer.json @@ -16,9 +16,9 @@ "require-dev": { "doctrine/coding-standard": "^6.0", "phpbench/phpbench": "^0.14.0", - "phpstan/phpstan": "^0.11.4", - "phpstan/phpstan-phpunit": "^0.11.0", - "phpstan/phpstan-strict-rules": "^0.11.0", + "phpstan/phpstan": "^0.11.8", + "phpstan/phpstan-phpunit": "^0.11.2", + "phpstan/phpstan-strict-rules": "^0.11.1", "phpunit/phpcov": "^5.0", "phpunit/phpunit": "^7.2", "psr/http-message": "^1.0", From a22a08322096ff020b1e327743a425cc88729c67 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Wed, 12 Jun 2019 10:33:08 +0200 Subject: [PATCH 010/256] Cleanup Warning --- src/Error/Warning.php | 32 +++++++++++++++---------- src/Exception/InvalidArgument.php | 20 ++++++++++++++++ tests/Exception/InvalidArgumentTest.php | 18 ++++++++++++++ 3 files changed, 57 insertions(+), 13 deletions(-) create mode 100644 src/Exception/InvalidArgument.php create mode 100644 tests/Exception/InvalidArgumentTest.php diff --git a/src/Error/Warning.php b/src/Error/Warning.php index 9157b0554..25828b183 100644 --- a/src/Error/Warning.php +++ b/src/Error/Warning.php @@ -4,6 +4,8 @@ namespace GraphQL\Error; +use GraphQL\Exception\InvalidArgument; +use function is_int; use function trigger_error; use const E_USER_WARNING; @@ -15,12 +17,12 @@ */ final class Warning { - const WARNING_ASSIGN = 2; - const WARNING_CONFIG = 4; - const WARNING_FULL_SCHEMA_SCAN = 8; - const WARNING_CONFIG_DEPRECATION = 16; - const WARNING_NOT_A_TYPE = 32; - const ALL = 63; + public const WARNING_ASSIGN = 2; + public const WARNING_CONFIG = 4; + public const WARNING_FULL_SCHEMA_SCAN = 8; + public const WARNING_CONFIG_DEPRECATION = 16; + public const WARNING_NOT_A_TYPE = 32; + public const ALL = 63; /** @var int */ private static $enableWarnings = self::ALL; @@ -37,7 +39,7 @@ final class Warning * * @api */ - public static function setWarningHandler(?callable $warningHandler = null) + public static function setWarningHandler(?callable $warningHandler = null) : void { self::$warningHandler = $warningHandler; } @@ -54,14 +56,16 @@ public static function setWarningHandler(?callable $warningHandler = null) * * @api */ - public static function suppress($suppress = true) + public static function suppress($suppress = true) : void { if ($suppress === true) { self::$enableWarnings = 0; } elseif ($suppress === false) { self::$enableWarnings = self::ALL; - } else { + } elseif (is_int($suppress)) { self::$enableWarnings &= ~$suppress; + } else { + throw InvalidArgument::fromExpectedTypeAndArgument('bool|int', $suppress); } } @@ -77,18 +81,20 @@ public static function suppress($suppress = true) * * @api */ - public static function enable($enable = true) + public static function enable($enable = true) : void { if ($enable === true) { self::$enableWarnings = self::ALL; } elseif ($enable === false) { self::$enableWarnings = 0; - } else { + } elseif (is_int($enable)) { self::$enableWarnings |= $enable; + } else { + throw InvalidArgument::fromExpectedTypeAndArgument('bool|int', $enable); } } - public static function warnOnce($errorMessage, $warningId, $messageLevel = null) + public static function warnOnce(string $errorMessage, int $warningId, ?int $messageLevel = null) : void { if (self::$warningHandler) { $fn = self::$warningHandler; @@ -99,7 +105,7 @@ public static function warnOnce($errorMessage, $warningId, $messageLevel = null) } } - public static function warn($errorMessage, $warningId, $messageLevel = null) + public static function warn(string $errorMessage, int $warningId, ?int $messageLevel = null) : void { if (self::$warningHandler) { $fn = self::$warningHandler; diff --git a/src/Exception/InvalidArgument.php b/src/Exception/InvalidArgument.php new file mode 100644 index 000000000..eea34d562 --- /dev/null +++ b/src/Exception/InvalidArgument.php @@ -0,0 +1,20 @@ +getMessage()); + } +} From e87460880c49393fa8bb823409a0e65233f6d873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20David?= Date: Wed, 12 Jun 2019 11:59:42 +0200 Subject: [PATCH 011/256] QueryPlan can now be used on interfaces not only objects. It's often the case to use interfaces in queries: interface Pet { name: String! } Query { pets: [Pet] } --- src/Type/Definition/QueryPlan.php | 4 +- tests/Type/QueryPlanTest.php | 113 ++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 1 deletion(-) diff --git a/src/Type/Definition/QueryPlan.php b/src/Type/Definition/QueryPlan.php index 235066d60..641686d67 100644 --- a/src/Type/Definition/QueryPlan.php +++ b/src/Type/Definition/QueryPlan.php @@ -140,9 +140,11 @@ private function analyzeQueryPlan(ObjectType $parentType, iterable $fieldNodes) /** * @return mixed[] * + * $parentType InterfaceType|ObjectType. + * * @throws Error */ - private function analyzeSelectionSet(SelectionSetNode $selectionSet, ObjectType $parentType) : array + private function analyzeSelectionSet(SelectionSetNode $selectionSet, Type $parentType) : array { $fields = []; foreach ($selectionSet->selections as $selectionNode) { diff --git a/tests/Type/QueryPlanTest.php b/tests/Type/QueryPlanTest.php index 50ec6ae95..73f8ae7ca 100644 --- a/tests/Type/QueryPlanTest.php +++ b/tests/Type/QueryPlanTest.php @@ -5,6 +5,8 @@ namespace GraphQL\Tests\Type; use GraphQL\GraphQL; +use GraphQL\Tests\Executor\TestClasses\Dog; +use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\QueryPlan; use GraphQL\Type\Definition\ResolveInfo; @@ -295,6 +297,117 @@ public function testQueryPlan() : void self::assertFalse($queryPlan->hasType('Test')); } + public function testQueryPlanOnInterface() : void + { + $petType = new InterfaceType([ + 'name' => 'Pet', + 'fields' => static function () { + return [ + 'name' => ['type' => Type::string()], + ]; + }, + ]); + + $dogType = new ObjectType([ + 'name' => 'Dog', + 'interfaces' => [$petType], + 'isTypeOf' => static function ($obj) { + return $obj instanceof Dog; + }, + 'fields' => static function () { + return [ + 'name' => ['type' => Type::string()], + 'woofs' => ['type' => Type::boolean()], + ]; + }, + ]); + + $query = 'query Test { + pets { + name + ... on Dog { + woofs + } + } + }'; + + $expectedQueryPlan = [ + 'woofs' => [ + 'type' => Type::boolean(), + 'fields' => [], + 'args' => [], + ], + 'name' => [ + 'type' => Type::string(), + 'args' => [], + 'fields' => [], + ], + ]; + + $expectedReferencedTypes = [ + 'Dog', + 'Pet', + ]; + + $expectedReferencedFields = [ + 'woofs', + 'name', + ]; + + /** @var QueryPlan $queryPlan */ + $queryPlan = null; + $hasCalled = false; + + $petsQuery = new ObjectType([ + 'name' => 'Query', + 'fields' => [ + 'pets' => [ + 'type' => Type::listOf($petType), + 'resolve' => static function ( + $value, + $args, + $context, + ResolveInfo $info + ) use ( + &$hasCalled, + &$queryPlan +) { + $hasCalled = true; + $queryPlan = $info->lookAhead(); + + return []; + }, + ], + ], + ]); + + $schema = new Schema([ + 'query' => $petsQuery, + 'types' => [$dogType], + 'typeLoader' => static function ($name) use ($dogType, $petType) { + switch ($name) { + case 'Dog': + return $dogType; + case 'Pet': + return $petType; + } + }, + ]); + $result = GraphQL::executeQuery($schema, $query)->toArray(); + + self::assertTrue($hasCalled); + self::assertEquals($expectedQueryPlan, $queryPlan->queryPlan()); + self::assertEquals($expectedReferencedTypes, $queryPlan->getReferencedTypes()); + self::assertEquals($expectedReferencedFields, $queryPlan->getReferencedFields()); + self::assertEquals(['woofs'], $queryPlan->subFields('Dog')); + + self::assertTrue($queryPlan->hasField('name')); + self::assertFalse($queryPlan->hasField('test')); + + self::assertTrue($queryPlan->hasType('Dog')); + self::assertFalse($queryPlan->hasType('Test')); + } + public function testMergedFragmentsQueryPlan() : void { $image = new ObjectType([ From 6a5325a44847e55b088a857c1ec952e971932aab Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Wed, 12 Jun 2019 15:02:27 +0200 Subject: [PATCH 012/256] Another code cleanup --- src/Error/Warning.php | 4 +- src/Executor/Promise/Adapter/SyncPromise.php | 18 +++---- src/Executor/Values.php | 6 +-- src/Experimental/Executor/Collector.php | 2 +- .../Executor/CoroutineExecutor.php | 6 +-- src/Language/AST/Location.php | 2 +- src/Language/AST/Node.php | 4 +- src/Language/Lexer.php | 10 ++-- src/Language/Parser.php | 4 +- src/Language/Token.php | 53 +++++++++---------- src/Server/OperationParams.php | 3 +- src/Type/Definition/Directive.php | 15 +++--- src/Type/Definition/ObjectType.php | 2 +- src/Type/SchemaConfig.php | 2 +- 14 files changed, 64 insertions(+), 67 deletions(-) diff --git a/src/Error/Warning.php b/src/Error/Warning.php index 25828b183..672894125 100644 --- a/src/Error/Warning.php +++ b/src/Error/Warning.php @@ -96,7 +96,7 @@ public static function enable($enable = true) : void public static function warnOnce(string $errorMessage, int $warningId, ?int $messageLevel = null) : void { - if (self::$warningHandler) { + if (self::$warningHandler !== null) { $fn = self::$warningHandler; $fn($errorMessage, $warningId); } elseif ((self::$enableWarnings & $warningId) > 0 && ! isset(self::$warned[$warningId])) { @@ -107,7 +107,7 @@ public static function warnOnce(string $errorMessage, int $warningId, ?int $mess public static function warn(string $errorMessage, int $warningId, ?int $messageLevel = null) : void { - if (self::$warningHandler) { + if (self::$warningHandler !== null) { $fn = self::$warningHandler; $fn($errorMessage, $warningId); } elseif ((self::$enableWarnings & $warningId) > 0) { diff --git a/src/Executor/Promise/Adapter/SyncPromise.php b/src/Executor/Promise/Adapter/SyncPromise.php index 26aecd696..8d490ed4a 100644 --- a/src/Executor/Promise/Adapter/SyncPromise.php +++ b/src/Executor/Promise/Adapter/SyncPromise.php @@ -38,16 +38,16 @@ class SyncPromise */ private $waiting = []; - public static function runQueue() + public static function runQueue() : void { $q = self::$queue; - while ($q && ! $q->isEmpty()) { + while ($q !== null && ! $q->isEmpty()) { $task = $q->dequeue(); $task(); } } - public function resolve($value) + public function resolve($value) : self { switch ($this->state) { case self::PENDING: @@ -83,7 +83,7 @@ function ($reason) { return $this; } - public function reject($reason) + public function reject($reason) : self { if (! $reason instanceof Exception && ! $reason instanceof Throwable) { throw new Exception('SyncPromise::reject() has to be called with an instance of \Throwable'); @@ -107,7 +107,7 @@ public function reject($reason) return $this; } - private function enqueueWaitingPromises() + private function enqueueWaitingPromises() : void { Utils::invariant( $this->state !== self::PENDING, @@ -116,7 +116,7 @@ private function enqueueWaitingPromises() foreach ($this->waiting as $descriptor) { self::getQueue()->enqueue(function () use ($descriptor) { - /** @var $promise self */ + /** @var self $promise */ [$promise, $onFulfilled, $onRejected] = $descriptor; if ($this->state === self::FULFILLED) { @@ -145,17 +145,17 @@ private function enqueueWaitingPromises() $this->waiting = []; } - public static function getQueue() + public static function getQueue() : SplQueue { return self::$queue ?: self::$queue = new SplQueue(); } public function then(?callable $onFulfilled = null, ?callable $onRejected = null) { - if ($this->state === self::REJECTED && ! $onRejected) { + if ($this->state === self::REJECTED && $onRejected === null) { return $this; } - if ($this->state === self::FULFILLED && ! $onFulfilled) { + if ($this->state === self::FULFILLED && $onFulfilled === null) { return $this; } $tmp = new self(); diff --git a/src/Executor/Values.php b/src/Executor/Values.php index 2f74e8c54..82e26c8c5 100644 --- a/src/Executor/Values.php +++ b/src/Executor/Values.php @@ -92,7 +92,7 @@ public static function getVariableValues(Schema $schema, $varDefNodes, array $in ), [$varDefNode] ); - } elseif ($varDefNode->defaultValue) { + } elseif ($varDefNode->defaultValue !== null) { $coercedValues[$varName] = AST::valueFromAST($varDefNode->defaultValue, $varType); } } @@ -196,7 +196,7 @@ public static function getArgumentValuesForMap($fieldDefinition, $argumentValueM $argType = $argumentDefinition->getType(); $argumentValueNode = $argumentValueMap[$name] ?? null; - if (! $argumentValueNode) { + if ($argumentValueNode === null) { if ($argumentDefinition->defaultValueExists()) { $coercedValues[$name] = $argumentDefinition->defaultValue; } elseif ($argType instanceof NonNull) { @@ -209,7 +209,7 @@ public static function getArgumentValuesForMap($fieldDefinition, $argumentValueM } elseif ($argumentValueNode instanceof VariableNode) { $variableName = $argumentValueNode->name->value; - if ($variableValues && array_key_exists($variableName, $variableValues)) { + if ($variableValues !== null && array_key_exists($variableName, $variableValues)) { // Note: this does not check that this variable value is correct. // This assumes that this query has been validated and the variable // usage here is of the correct type. diff --git a/src/Experimental/Executor/Collector.php b/src/Experimental/Executor/Collector.php index 8639335c7..843db9b27 100644 --- a/src/Experimental/Executor/Collector.php +++ b/src/Experimental/Executor/Collector.php @@ -199,7 +199,7 @@ private function doCollectFields(ObjectType $runtimeType, ?SelectionSetNode $sel if ($selection instanceof FieldNode) { /** @var FieldNode $selection */ - $resultName = $selection->alias ? $selection->alias->value : $selection->name->value; + $resultName = $selection->alias === null ? $selection->name->value : $selection->alias->value; if (! isset($this->fields[$resultName])) { $this->fields[$resultName] = []; diff --git a/src/Experimental/Executor/CoroutineExecutor.php b/src/Experimental/Executor/CoroutineExecutor.php index 91091b40b..d90035b06 100644 --- a/src/Experimental/Executor/CoroutineExecutor.php +++ b/src/Experimental/Executor/CoroutineExecutor.php @@ -498,7 +498,7 @@ private function completeValueFast(CoroutineContext $ctx, Type $type, $value, ar if ($type !== $this->schema->getType($type->name)) { $hint = ''; - if ($this->schema->getConfig()->typeLoader) { + if ($this->schema->getConfig()->typeLoader !== null) { $hint = sprintf( 'Make sure that type loader returns the same instance as defined in %s.%s', $ctx->type, @@ -646,7 +646,7 @@ private function completeValue(CoroutineContext $ctx, Type $type, $value, array } else { if ($type !== $this->schema->getType($type->name)) { $hint = ''; - if ($this->schema->getConfig()->typeLoader) { + if ($this->schema->getConfig()->typeLoader !== null) { $hint = sprintf( 'Make sure that type loader returns the same instance as defined in %s.%s', $ctx->type, @@ -904,7 +904,7 @@ private function resolveTypeSlow(CoroutineContext $ctx, $value, AbstractType $ab return $this->schema->getType($value['__typename']); } - if ($abstractType instanceof InterfaceType && $this->schema->getConfig()->typeLoader) { + if ($abstractType instanceof InterfaceType && $this->schema->getConfig()->typeLoader !== null) { Warning::warnOnce( sprintf( 'GraphQL Interface Type `%s` returned `null` from its `resolveType` function ' . diff --git a/src/Language/AST/Location.php b/src/Language/AST/Location.php index 72135fc20..6f2e6d4af 100644 --- a/src/Language/AST/Location.php +++ b/src/Language/AST/Location.php @@ -69,7 +69,7 @@ public function __construct(?Token $startToken = null, ?Token $endToken = null, $this->endToken = $endToken; $this->source = $source; - if (! $startToken || ! $endToken) { + if ($startToken === null || $endToken === null) { return; } diff --git a/src/Language/AST/Node.php b/src/Language/AST/Node.php index 2205d6d5f..0e192af9f 100644 --- a/src/Language/AST/Node.php +++ b/src/Language/AST/Node.php @@ -106,7 +106,7 @@ public function toArray($recursive = false) $tmp = (array) $this; - if ($this->loc) { + if ($this->loc !== null) { $tmp['loc'] = [ 'start' => $this->loc->start, 'end' => $this->loc->end, @@ -125,7 +125,7 @@ private function recursiveToArray(Node $node) 'kind' => $node->kind, ]; - if ($node->loc) { + if ($node->loc !== null) { $result['loc'] = [ 'start' => $node->loc->start, 'end' => $node->loc->end, diff --git a/src/Language/Lexer.php b/src/Language/Lexer.php index 3aad4dd20..d2c1d5f8e 100644 --- a/src/Language/Lexer.php +++ b/src/Language/Lexer.php @@ -314,11 +314,11 @@ private function readName($line, $col, Token $prev) $start = $this->position; [$char, $code] = $this->readChar(); - while ($code && ( + while ($code !== null && ( $code === 95 || // _ - $code >= 48 && $code <= 57 || // 0-9 - $code >= 65 && $code <= 90 || // A-Z - $code >= 97 && $code <= 122 // a-z + ($code >= 48 && $code <= 57) || // 0-9 + ($code >= 65 && $code <= 90) || // A-Z + ($code >= 97 && $code <= 122) // a-z )) { $value .= $char; [$char, $code] = $this->moveStringCursor(1, 1)->readChar(); @@ -695,7 +695,7 @@ private function readComment($line, $col, Token $prev) do { [$char, $code, $bytes] = $this->moveStringCursor(1, $bytes)->readChar(); $value .= $char; - } while ($code && + } while ($code !== null && // SourceCharacter but not LineTerminator ($code > 0x001F || $code === 0x0009) ); diff --git a/src/Language/Parser.php b/src/Language/Parser.php index f5c928051..a0c123f7e 100644 --- a/src/Language/Parser.php +++ b/src/Language/Parser.php @@ -1655,9 +1655,7 @@ private function parseUnionTypeExtension() $name = $this->parseName(); $directives = $this->parseDirectives(true); $types = $this->parseUnionMemberTypes(); - if (count($directives) === 0 && - ! $types - ) { + if (count($directives) === 0 && count($types) === 0) { throw $this->unexpected(); } diff --git a/src/Language/Token.php b/src/Language/Token.php index 831b3adcf..eb0bbdf03 100644 --- a/src/Language/Token.php +++ b/src/Language/Token.php @@ -11,28 +11,28 @@ class Token { // Each kind of token. - const SOF = ''; - const EOF = ''; - const BANG = '!'; - const DOLLAR = '$'; - const AMP = '&'; - const PAREN_L = '('; - const PAREN_R = ')'; - const SPREAD = '...'; - const COLON = ':'; - const EQUALS = '='; - const AT = '@'; - const BRACKET_L = '['; - const BRACKET_R = ']'; - const BRACE_L = '{'; - const PIPE = '|'; - const BRACE_R = '}'; - const NAME = 'Name'; - const INT = 'Int'; - const FLOAT = 'Float'; - const STRING = 'String'; - const BLOCK_STRING = 'BlockString'; - const COMMENT = 'Comment'; + public const SOF = ''; + public const EOF = ''; + public const BANG = '!'; + public const DOLLAR = '$'; + public const AMP = '&'; + public const PAREN_L = '('; + public const PAREN_R = ')'; + public const SPREAD = '...'; + public const COLON = ':'; + public const EQUALS = '='; + public const AT = '@'; + public const BRACKET_L = '['; + public const BRACKET_R = ']'; + public const BRACE_L = '{'; + public const PIPE = '|'; + public const BRACE_R = '}'; + public const NAME = 'Name'; + public const INT = 'Int'; + public const FLOAT = 'Float'; + public const STRING = 'String'; + public const BLOCK_STRING = 'BlockString'; + public const COMMENT = 'Comment'; /** * The kind of Token (see one of constants above). @@ -104,18 +104,15 @@ public function __construct($kind, $start, $end, $line, $column, ?Token $previou $this->value = $value; } - /** - * @return string - */ - public function getDescription() + public function getDescription() : string { - return $this->kind . ($this->value ? ' "' . $this->value . '"' : ''); + return $this->kind . ($this->value === null ? '' : ' "' . $this->value . '"'); } /** * @return (string|int|null)[] */ - public function toArray() + public function toArray() : array { return [ 'kind' => $this->kind, diff --git a/src/Server/OperationParams.php b/src/Server/OperationParams.php index 3ab12de88..a976ec072 100644 --- a/src/Server/OperationParams.php +++ b/src/Server/OperationParams.php @@ -9,6 +9,7 @@ use function json_decode; use function json_last_error; use const CASE_LOWER; +use const JSON_ERROR_NONE; /** * Structure representing parsed HTTP parameters for GraphQL operation @@ -93,7 +94,7 @@ public static function create(array $params, bool $readonly = false) : Operation } $tmp = json_decode($params[$param], true); - if (json_last_error()) { + if (json_last_error() !== JSON_ERROR_NONE) { continue; } diff --git a/src/Type/Definition/Directive.php b/src/Type/Definition/Directive.php index 0ffe496ae..3ad4b8a69 100644 --- a/src/Type/Definition/Directive.php +++ b/src/Type/Definition/Directive.php @@ -9,6 +9,7 @@ use GraphQL\Utils\Utils; use function array_key_exists; use function array_keys; +use function count; use function in_array; use function is_array; @@ -16,11 +17,11 @@ class Directive { public const DEFAULT_DEPRECATION_REASON = 'No longer supported'; - const INCLUDE_NAME = 'include'; - const IF_ARGUMENT_NAME = 'if'; - const SKIP_NAME = 'skip'; - const DEPRECATED_NAME = 'deprecated'; - const REASON_ARGUMENT_NAME = 'reason'; + public const INCLUDE_NAME = 'include'; + public const IF_ARGUMENT_NAME = 'if'; + public const SKIP_NAME = 'skip'; + public const DEPRECATED_NAME = 'deprecated'; + public const REASON_ARGUMENT_NAME = 'reason'; /** @var Directive[] */ public static $internalDirectives; @@ -84,9 +85,9 @@ public static function includeDirective() /** * @return Directive[] */ - public static function getInternalDirectives() + public static function getInternalDirectives() : array { - if (! self::$internalDirectives) { + if (count(self::$internalDirectives) === 0) { self::$internalDirectives = [ 'include' => new self([ 'name' => self::INCLUDE_NAME, diff --git a/src/Type/Definition/ObjectType.php b/src/Type/Definition/ObjectType.php index f5092936d..532a9b0f0 100644 --- a/src/Type/Definition/ObjectType.php +++ b/src/Type/Definition/ObjectType.php @@ -168,7 +168,7 @@ public function implementsInterface($iface) private function getInterfaceMap() { - if (! $this->interfaceMap) { + if ($this->interfaceMap === null) { $this->interfaceMap = []; foreach ($this->getInterfaces() as $interface) { $this->interfaceMap[$interface->name] = $interface; diff --git a/src/Type/SchemaConfig.php b/src/Type/SchemaConfig.php index 9f336262b..1b2177593 100644 --- a/src/Type/SchemaConfig.php +++ b/src/Type/SchemaConfig.php @@ -42,7 +42,7 @@ class SchemaConfig /** @var Directive[] */ public $directives; - /** @var callable */ + /** @var callable|null */ public $typeLoader; /** @var SchemaDefinitionNode */ From ec6ff22b261b139795318d65b3947a6f35c3dbf0 Mon Sep 17 00:00:00 2001 From: Romain VALAT Date: Thu, 13 Jun 2019 16:56:47 +0200 Subject: [PATCH 013/256] fix query complexity validator on directives --- src/Validator/Rules/QueryComplexity.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Validator/Rules/QueryComplexity.php b/src/Validator/Rules/QueryComplexity.php index fe469cd0a..9f91e1e8b 100644 --- a/src/Validator/Rules/QueryComplexity.php +++ b/src/Validator/Rules/QueryComplexity.php @@ -206,11 +206,16 @@ static function ($error) { return ! $directiveArgsIf; } - $directive = Directive::skipDirective(); - $directiveArgsIf = Values::getArgumentValues($directive, $directiveNode, $variableValues); + if ($directiveNode->name->value === 'skip') { + $directive = Directive::skipDirective(); + /** @var bool $directiveArgsIf */ + $directiveArgsIf = Values::getArgumentValues($directive, $directiveNode, $variableValues)['if']; - return $directiveArgsIf['if']; + return $directiveArgsIf; + } } + + return false; } public function getRawVariableValues() From 862bba038d9e1713d150bbdfe2f5220996b00edd Mon Sep 17 00:00:00 2001 From: spawnia Date: Fri, 14 Jun 2019 21:19:11 +0200 Subject: [PATCH 014/256] Add proof of concept implementation --- src/Language/Parser.php | 42 +++++++++++++++++++++++++++-------- tests/Language/ParserTest.php | 16 ++++++++++++- 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/src/Language/Parser.php b/src/Language/Parser.php index f5c928051..a93360122 100644 --- a/src/Language/Parser.php +++ b/src/Language/Parser.php @@ -32,6 +32,7 @@ use GraphQL\Language\AST\Location; use GraphQL\Language\AST\NamedTypeNode; use GraphQL\Language\AST\NameNode; +use GraphQL\Language\AST\Node; use GraphQL\Language\AST\NodeList; use GraphQL\Language\AST\NonNullTypeNode; use GraphQL\Language\AST\NullValueNode; @@ -58,6 +59,12 @@ /** * Parses string containing GraphQL query or [type definition](type-system/type-language.md) to Abstract Syntax Tree. + * + * // TODO write out the rest of those magic function helpers + * + * @method static NameNode name(Source|string $source, bool[] $options = []) + * @method static NameNode directiveLocation(Source|string $source, bool[] $options = []) + * @method static ObjectTypeDefinitionNode objectTypeDefinition(Source|string $source, bool[] $options = []) */ class Parser { @@ -113,8 +120,7 @@ class Parser */ public static function parse($source, array $options = []) { - $sourceObj = $source instanceof Source ? $source : new Source($source); - $parser = new self($sourceObj, $options); + $parser = new self($source, $options); return $parser->parseDocument(); } @@ -138,8 +144,7 @@ public static function parse($source, array $options = []) */ public static function parseValue($source, array $options = []) { - $sourceObj = $source instanceof Source ? $source : new Source($source); - $parser = new Parser($sourceObj, $options); + $parser = new Parser($source, $options); $parser->expect(Token::SOF); $value = $parser->parseValueLiteral(false); $parser->expect(Token::EOF); @@ -166,8 +171,7 @@ public static function parseValue($source, array $options = []) */ public static function parseType($source, array $options = []) { - $sourceObj = $source instanceof Source ? $source : new Source($source); - $parser = new Parser($sourceObj, $options); + $parser = new Parser($source, $options); $parser->expect(Token::SOF); $type = $parser->parseTypeReference(); $parser->expect(Token::EOF); @@ -175,15 +179,35 @@ public static function parseType($source, array $options = []) return $type; } + /** + * Parse partial source by delegating calls to the internal parseX methods. + * + * @param Source|string $name + * @param bool[] $arguments + * + * @throws SyntaxError + */ + public static function __callStatic(string $name, array $arguments) : Node + { + $parser = new Parser(...$arguments); + $parser->expect(Token::SOF); + $type = $parser->{'parse' . $name}(); + $parser->expect(Token::EOF); + + return $type; + } + /** @var Lexer */ private $lexer; /** - * @param bool[] $options + * @param Source|string $source + * @param bool[] $options */ - public function __construct(Source $source, array $options = []) + public function __construct($source, array $options = []) { - $this->lexer = new Lexer($source, $options); + $sourceObj = $source instanceof Source ? $source : new Source($source); + $this->lexer = new Lexer($sourceObj, $options); } /** diff --git a/tests/Language/ParserTest.php b/tests/Language/ParserTest.php index d0769e910..33db63f48 100644 --- a/tests/Language/ParserTest.php +++ b/tests/Language/ParserTest.php @@ -12,6 +12,7 @@ use GraphQL\Language\AST\Node; use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\NodeList; +use GraphQL\Language\AST\ObjectTypeDefinitionNode; use GraphQL\Language\AST\SelectionSetNode; use GraphQL\Language\AST\StringValueNode; use GraphQL\Language\Parser; @@ -185,7 +186,7 @@ public function testDoesNotAcceptFragmentSpreadOfOn() : void */ public function testParsesMultiByteCharacters() : void { - // Note: \u0A0A could be naively interpretted as two line-feed chars. + // Note: \u0A0A could be naively interpreted as two line-feed chars. $char = Utils::chr(0x0A0A); $query = << Date: Mon, 17 Jun 2019 13:54:38 +0200 Subject: [PATCH 015/256] Drop deprecated GraphQL\Schema --- UPGRADE.md | 5 +++++ src/Schema.php | 22 ---------------------- 2 files changed, 5 insertions(+), 22 deletions(-) delete mode 100644 src/Schema.php diff --git a/UPGRADE.md b/UPGRADE.md index a9d72560c..99d21509f 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,3 +1,8 @@ +## Master + +### Breaking (major): dropped deprecations + - dropped deprecated `GraphQL\Schema`. Use `GraphQL\Type\Schema`. + ## Upgrade v0.12.x > v0.13.x ### Breaking (major): minimum supported version of PHP diff --git a/src/Schema.php b/src/Schema.php deleted file mode 100644 index 351047ac2..000000000 --- a/src/Schema.php +++ /dev/null @@ -1,22 +0,0 @@ - Date: Mon, 17 Jun 2019 14:12:21 +0200 Subject: [PATCH 016/256] Fix ReferenceExecutor --- src/Executor/ReferenceExecutor.php | 66 +++++++++++++----------------- src/Type/Definition/Directive.php | 2 +- 2 files changed, 30 insertions(+), 38 deletions(-) diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 5c0353b14..9fbafe380 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -134,16 +134,16 @@ private static function buildExecutionContext( ) { $errors = []; $fragments = []; - /** @var OperationDefinitionNode $operation */ + /** @var OperationDefinitionNode|null $operation */ $operation = null; $hasMultipleAssumedOperations = false; foreach ($documentNode->definitions as $definition) { switch (true) { case $definition instanceof OperationDefinitionNode: - if (! $operationName && $operation) { + if ($operationName === null && $operation !== null) { $hasMultipleAssumedOperations = true; } - if (! $operationName || + if ($operationName === null || (isset($definition->name) && $definition->name->value === $operationName)) { $operation = $definition; } @@ -154,10 +154,10 @@ private static function buildExecutionContext( } } if ($operation === null) { - if ($operationName) { - $errors[] = new Error(sprintf('Unknown operation named "%s".', $operationName)); - } else { + if ($operationName === null) { $errors[] = new Error('Must provide an operation.'); + } else { + $errors[] = new Error(sprintf('Unknown operation named "%s".', $operationName)); } } elseif ($hasMultipleAssumedOperations) { $errors[] = new Error( @@ -286,7 +286,7 @@ private function getOperationRootType(Schema $schema, OperationDefinitionNode $o switch ($operation->operation) { case 'query': $queryType = $schema->getQueryType(); - if (! $queryType) { + if ($queryType === null) { throw new Error( 'Schema does not define the required query root type.', [$operation] @@ -296,7 +296,7 @@ private function getOperationRootType(Schema $schema, OperationDefinitionNode $o return $queryType; case 'mutation': $mutationType = $schema->getMutationType(); - if (! $mutationType) { + if ($mutationType === null) { throw new Error( 'Schema is not configured for mutations.', [$operation] @@ -306,7 +306,7 @@ private function getOperationRootType(Schema $schema, OperationDefinitionNode $o return $mutationType; case 'subscription': $subscriptionType = $schema->getSubscriptionType(); - if (! $subscriptionType) { + if ($subscriptionType === null) { throw new Error( 'Schema is not configured for subscriptions.', [$operation] @@ -375,7 +375,7 @@ private function collectFields( $visitedFragmentNames[$fragName] = true; /** @var FragmentDefinitionNode|null $fragment */ $fragment = $exeContext->fragments[$fragName] ?? null; - if (! $fragment || ! $this->doesFragmentConditionMatch($fragment, $runtimeType)) { + if ($fragment === null || ! $this->doesFragmentConditionMatch($fragment, $runtimeType)) { break; } $this->collectFields( @@ -396,10 +396,8 @@ private function collectFields( * directives, where @skip has higher precedence than @include. * * @param FragmentSpreadNode|FieldNode|InlineFragmentNode $node - * - * @return bool */ - private function shouldIncludeNode($node) + private function shouldIncludeNode($node) : bool { $variableValues = $this->exeContext->variableValues; $skipDirective = Directive::skipDirective(); @@ -423,12 +421,10 @@ private function shouldIncludeNode($node) /** * Implements the logic to compute the key of a given fields entry - * - * @return string */ - private static function getFieldEntryKey(FieldNode $node) + private static function getFieldEntryKey(FieldNode $node) : string { - return $node->alias ? $node->alias->value : $node->name->value; + return $node->alias === null ? $node->name->value : $node->alias->value; } /** @@ -480,7 +476,7 @@ function ($results, $responseName) use ($path, $parentType, $sourceValue, $field return $results; } $promise = $this->getPromise($result); - if ($promise) { + if ($promise !== null) { return $promise->then(static function ($resolvedResult) use ($responseName, $results) { $results[$responseName] = $resolvedResult; @@ -520,7 +516,7 @@ private function resolveField(ObjectType $parentType, $source, $fieldNodes, $pat $fieldNode = $fieldNodes[0]; $fieldName = $fieldNode->name->value; $fieldDef = $this->getFieldDef($exeContext->schema, $parentType, $fieldName); - if (! $fieldDef) { + if ($fieldDef === null) { return self::$UNDEFINED; } $returnType = $fieldDef->getType(); @@ -578,12 +574,8 @@ private function resolveField(ObjectType $parentType, $source, $fieldNodes, $pat * are allowed, like on a Union. __schema could get automatically * added to the query type, but that would require mutating type * definitions, which would cause issues. - * - * @param string $fieldName - * - * @return FieldDefinition */ - private function getFieldDef(Schema $schema, ObjectType $parentType, $fieldName) + private function getFieldDef(Schema $schema, ObjectType $parentType, string $fieldName) : ?FieldDefinition { static $schemaMetaFieldDef, $typeMetaFieldDef, $typeNameMetaFieldDef; $schemaMetaFieldDef = $schemaMetaFieldDef ?: Introspection::schemaMetaFieldDef(); @@ -677,7 +669,7 @@ private function completeValueCatchingError( $result ); $promise = $this->getPromise($completed); - if ($promise) { + if ($promise !== null) { return $promise->then( null, function ($error) use ($exeContext) { @@ -726,7 +718,7 @@ public function completeValueWithLocatedError( $result ); $promise = $this->getPromise($completed); - if ($promise) { + if ($promise !== null) { return $promise->then( null, function ($error) use ($fieldNodes, $path) { @@ -786,7 +778,7 @@ private function completeValue( ) { $promise = $this->getPromise($result); // If result is a Promise, apply-lift over completeValue. - if ($promise) { + if ($promise !== null) { return $promise->then(function (&$resolved) use ($returnType, $fieldNodes, $info, $path) { return $this->completeValue($returnType, $fieldNodes, $info, $path, $resolved); }); @@ -824,7 +816,7 @@ private function completeValue( // instance than `resolveType` or $field->getType() or $arg->getType() if ($returnType !== $this->exeContext->schema->getType($returnType->name)) { $hint = ''; - if ($this->exeContext->schema->getConfig()->typeLoader) { + if ($this->exeContext->schema->getConfig()->typeLoader !== null) { $hint = sprintf( 'Make sure that type loader returns the same instance as defined in %s.%s', $info->parentType, @@ -904,7 +896,7 @@ private function getPromise($value) * @param mixed[] $values * @param Promise|mixed|null $initialValue * - * @return mixed[] + * @return mixed */ private function promiseReduce(array $values, callable $callback, $initialValue) { @@ -912,7 +904,7 @@ private function promiseReduce(array $values, callable $callback, $initialValue) $values, function ($previous, $value) use ($callback) { $promise = $this->getPromise($previous); - if ($promise) { + if ($promise !== null) { return $promise->then(static function ($resolved) use ($callback, $value) { return $callback($resolved, $value); }); @@ -950,7 +942,7 @@ private function completeListValue(ListOfType $returnType, $fieldNodes, ResolveI $fieldPath = $path; $fieldPath[] = $i++; $completedItem = $this->completeValueCatchingError($itemType, $fieldNodes, $info, $fieldPath, $item); - if (! $containsPromise && $this->getPromise($completedItem)) { + if (! $containsPromise && $this->getPromise($completedItem) !== null) { $containsPromise = true; } $completedItems[] = $completedItem; @@ -1007,7 +999,7 @@ private function completeAbstractValue(AbstractType $returnType, $fieldNodes, Re $runtimeType = self::defaultTypeResolver($result, $exeContext->contextValue, $info, $returnType); } $promise = $this->getPromise($runtimeType); - if ($promise) { + if ($promise !== null) { return $promise->then(function ($resolvedRuntimeType) use ( $returnType, $fieldNodes, @@ -1069,7 +1061,8 @@ private function defaultTypeResolver($value, $context, ResolveInfo $info, Abstra ) { return $value['__typename']; } - if ($abstractType instanceof InterfaceType && $info->schema->getConfig()->typeLoader) { + + if ($abstractType instanceof InterfaceType && $info->schema->getConfig()->typeLoader !== null) { Warning::warnOnce( sprintf( 'GraphQL Interface Type `%s` returned `null` from its `resolveType` function ' . @@ -1091,7 +1084,7 @@ private function defaultTypeResolver($value, $context, ResolveInfo $info, Abstra continue; } $promise = $this->getPromise($isTypeOfResult); - if ($promise) { + if ($promise !== null) { $promisedIsTypeOfResults[$index] = $promise; } elseif ($isTypeOfResult) { return $type; @@ -1132,7 +1125,7 @@ private function completeObjectValue(ObjectType $returnType, $fieldNodes, Resolv $isTypeOf = $returnType->isTypeOf($result, $this->exeContext->contextValue, $info); if ($isTypeOf !== null) { $promise = $this->getPromise($isTypeOf); - if ($promise) { + if ($promise !== null) { return $promise->then(function ($isTypeOfResult) use ( $returnType, $fieldNodes, @@ -1248,7 +1241,7 @@ private function executeFields(ObjectType $parentType, $source, $path, $fields) if ($result === self::$UNDEFINED) { continue; } - if (! $containsPromise && $this->getPromise($result)) { + if (! $containsPromise && $this->getPromise($result) !== null) { $containsPromise = true; } $finalResults[$responseName] = $result; @@ -1310,7 +1303,6 @@ private function promiseForAssocArray(array $assoc) /** * @param string|ObjectType|null $runtimeTypeOrName - * @param FieldNode[] $fieldNodes * @param mixed $result * * @return ObjectType diff --git a/src/Type/Definition/Directive.php b/src/Type/Definition/Directive.php index 3ad4b8a69..f77075c6f 100644 --- a/src/Type/Definition/Directive.php +++ b/src/Type/Definition/Directive.php @@ -24,7 +24,7 @@ class Directive public const REASON_ARGUMENT_NAME = 'reason'; /** @var Directive[] */ - public static $internalDirectives; + public static $internalDirectives = []; // Schema Definitions From 368a9ee2f7e45f904ed57b35a66addab196dcf72 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Mon, 17 Jun 2019 14:54:05 +0200 Subject: [PATCH 017/256] Add FieldDefinition return type --- src/Type/Introspection.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Type/Introspection.php b/src/Type/Introspection.php index f0cacee65..67b7abb55 100644 --- a/src/Type/Introspection.php +++ b/src/Type/Introspection.php @@ -693,7 +693,7 @@ public static function _directiveLocation() return self::$map['__DirectiveLocation']; } - public static function schemaMetaFieldDef() + public static function schemaMetaFieldDef() : FieldDefinition { if (! isset(self::$map[self::SCHEMA_FIELD_NAME])) { self::$map[self::SCHEMA_FIELD_NAME] = FieldDefinition::create([ @@ -715,7 +715,7 @@ public static function schemaMetaFieldDef() return self::$map[self::SCHEMA_FIELD_NAME]; } - public static function typeMetaFieldDef() + public static function typeMetaFieldDef() : FieldDefinition { if (! isset(self::$map[self::TYPE_FIELD_NAME])) { self::$map[self::TYPE_FIELD_NAME] = FieldDefinition::create([ @@ -734,7 +734,7 @@ public static function typeMetaFieldDef() return self::$map[self::TYPE_FIELD_NAME]; } - public static function typeNameMetaFieldDef() + public static function typeNameMetaFieldDef() : FieldDefinition { if (! isset(self::$map[self::TYPE_NAME_FIELD_NAME])) { self::$map[self::TYPE_NAME_FIELD_NAME] = FieldDefinition::create([ From fe64f2fe319bb3108af2962c0b6af5970501b4d0 Mon Sep 17 00:00:00 2001 From: Romain VALAT Date: Tue, 18 Jun 2019 17:50:38 +0200 Subject: [PATCH 018/256] add test --- tests/Validator/QueryComplexityTest.php | 16 +++++++++++++ tests/Validator/QuerySecuritySchema.php | 31 ++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/tests/Validator/QueryComplexityTest.php b/tests/Validator/QueryComplexityTest.php index 998b751ff..846faf818 100644 --- a/tests/Validator/QueryComplexityTest.php +++ b/tests/Validator/QueryComplexityTest.php @@ -146,6 +146,22 @@ public function testQueryWithMultipleDirectives() : void $this->assertDocumentValidators($query, 2, 3); } + public function testQueryWithCustomDirective() : void + { + $query = 'query MyQuery { human { ... on Human { firstName @foo(bar: false) } } }'; + + $this->assertDocumentValidators($query, 2, 3); + } + + public function testQueryWithCustomAndSkipDirective() : void + { + $query = 'query MyQuery($withoutDogs: Boolean!) { human { dogs(name: "Root") @skip(if:$withoutDogs) { name @foo(bar: true) } } }'; + + $this->getRule()->setRawVariableValues(['withoutDogs' => true]); + + $this->assertDocumentValidators($query, 1, 2); + } + public function testComplexityIntrospectionQuery() : void { $this->assertIntrospectionQuery(181); diff --git a/tests/Validator/QuerySecuritySchema.php b/tests/Validator/QuerySecuritySchema.php index 64fcf4558..898ae3e92 100644 --- a/tests/Validator/QuerySecuritySchema.php +++ b/tests/Validator/QuerySecuritySchema.php @@ -4,15 +4,23 @@ namespace GraphQL\Tests\Validator; +use GraphQL\GraphQL; +use GraphQL\Language\DirectiveLocation; +use GraphQL\Type\Definition\Directive; +use GraphQL\Type\Definition\FieldArgument; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Schema; +use function array_merge; class QuerySecuritySchema { /** @var Schema */ private static $schema; + /** @var Directive */ + private static $fooDirective; + /** @var ObjectType */ private static $dogType; @@ -32,7 +40,8 @@ public static function buildSchema() } self::$schema = new Schema([ - 'query' => static::buildQueryRootType(), + 'query' => static::buildQueryRootType(), + 'directives' => array_merge(GraphQL::getStandardDirectives(), [static::buildFooDirective()]), ]); return self::$schema; @@ -110,4 +119,24 @@ public static function buildDogType() return self::$dogType; } + + public static function buildFooDirective() + { + if (self::$fooDirective !== null) { + return self::$fooDirective; + } + + self::$fooDirective = new Directive([ + 'name' => 'foo', + 'locations' => [DirectiveLocation::FIELD], + 'args' => [new FieldArgument([ + 'name' => 'bar', + 'type' => Type::nonNull(Type::boolean()), + 'defaultValue' => ' ', + ]), + ], + ]); + + return self::$fooDirective; + } } From 752010b341cb3e97e8de39d75b08362305238200 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Wed, 19 Jun 2019 15:06:45 +0700 Subject: [PATCH 019/256] Documentation fix (#499) --- docs/type-system/input-types.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/type-system/input-types.md b/docs/type-system/input-types.md index 1b7ffc814..7138557bf 100644 --- a/docs/type-system/input-types.md +++ b/docs/type-system/input-types.md @@ -131,7 +131,7 @@ $queryType = new ObjectType([ 'type' => Type::listOf($storyType), 'args' => [ 'filters' => [ - 'type' => Type::nonNull($filters), + 'type' => $filters, 'defaultValue' => [ 'popular' => true ] From c336d01bd24dee0b2230cad244206c3c1fd7e8b0 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Wed, 19 Jun 2019 15:08:51 +0700 Subject: [PATCH 020/256] Added .idea to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index fa07e1e1f..2bf79fe99 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ composer.phar phpcs.xml phpstan.neon vendor/ +/.idea From d1d4455eaaf328507402a81e61fef8fdc2ca4a1b Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Wed, 19 Jun 2019 15:47:14 +0700 Subject: [PATCH 021/256] Fixed warning added by one of the previous commits --- src/Type/Definition/Directive.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Type/Definition/Directive.php b/src/Type/Definition/Directive.php index 3ad4b8a69..cb8071365 100644 --- a/src/Type/Definition/Directive.php +++ b/src/Type/Definition/Directive.php @@ -8,9 +8,6 @@ use GraphQL\Language\DirectiveLocation; use GraphQL\Utils\Utils; use function array_key_exists; -use function array_keys; -use function count; -use function in_array; use function is_array; class Directive @@ -87,7 +84,7 @@ public static function includeDirective() */ public static function getInternalDirectives() : array { - if (count(self::$internalDirectives) === 0) { + if (self::$internalDirectives === null) { self::$internalDirectives = [ 'include' => new self([ 'name' => self::INCLUDE_NAME, From 261f8f5ebd433605f0fc3981c0e3db12f8c63be2 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Wed, 19 Jun 2019 15:52:16 +0700 Subject: [PATCH 022/256] AST: Replaced array with NodeList where missing --- src/Utils/AST.php | 4 ++-- tests/Utils/AstFromValueTest.php | 17 +++++++++-------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/Utils/AST.php b/src/Utils/AST.php index 4fab80335..24bd77c84 100644 --- a/src/Utils/AST.php +++ b/src/Utils/AST.php @@ -183,7 +183,7 @@ public static function astFromValue($value, InputType $type) $valuesNodes[] = $itemNode; } - return new ListValueNode(['values' => $valuesNodes]); + return new ListValueNode(['values' => new NodeList($valuesNodes)]); } return self::astFromValue($value, $itemType); @@ -235,7 +235,7 @@ public static function astFromValue($value, InputType $type) ]); } - return new ObjectValueNode(['fields' => $fieldNodes]); + return new ObjectValueNode(['fields' => new NodeList($fieldNodes)]); } if ($type instanceof ScalarType || $type instanceof EnumType) { diff --git a/tests/Utils/AstFromValueTest.php b/tests/Utils/AstFromValueTest.php index b01a73cf7..02e758289 100644 --- a/tests/Utils/AstFromValueTest.php +++ b/tests/Utils/AstFromValueTest.php @@ -10,6 +10,7 @@ use GraphQL\Language\AST\IntValueNode; use GraphQL\Language\AST\ListValueNode; use GraphQL\Language\AST\NameNode; +use GraphQL\Language\AST\NodeList; use GraphQL\Language\AST\NullValueNode; use GraphQL\Language\AST\ObjectFieldNode; use GraphQL\Language\AST\ObjectValueNode; @@ -179,18 +180,18 @@ private function complexValue() public function testConvertsArrayValuesToListASTs() : void { $value1 = new ListValueNode([ - 'values' => [ + 'values' => new NodeList([ new StringValueNode(['value' => 'FOO']), new StringValueNode(['value' => 'BAR']), - ], + ]), ]); self::assertEquals($value1, AST::astFromValue(['FOO', 'BAR'], Type::listOf(Type::string()))); $value2 = new ListValueNode([ - 'values' => [ + 'values' => new NodeList([ new EnumValueNode(['value' => 'HELLO']), new EnumValueNode(['value' => 'GOODBYE']), - ], + ]), ]); self::assertEquals($value2, AST::astFromValue(['HELLO', 'GOODBYE'], Type::listOf($this->myEnum()))); } @@ -220,10 +221,10 @@ public function testConvertsInputObjects() : void ]); $expected = new ObjectValueNode([ - 'fields' => [ + 'fields' => new NodeList([ $this->objectField('foo', new IntValueNode(['value' => '3'])), $this->objectField('bar', new EnumValueNode(['value' => 'HELLO'])), - ], + ]), ]); $data = ['foo' => 3, 'bar' => 'HELLO']; @@ -259,9 +260,9 @@ public function testConvertsInputObjectsWithExplicitNulls() : void self::assertEquals( new ObjectValueNode([ - 'fields' => [ + 'fields' => new NodeList([ $this->objectField('foo', new NullValueNode([])), - ], + ]), ]), AST::astFromValue(['foo' => null], $inputObj) ); From 84a52c6c760ca39780d02702d98b350105ada215 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Wed, 19 Jun 2019 17:11:24 +0700 Subject: [PATCH 023/256] Provide a path with a correct list index to `resolveType` callback of the union and interface types (fixes #396) --- src/Executor/ReferenceExecutor.php | 1 + .../Executor/CoroutineExecutor.php | 5 +- tests/Regression/Issue396Test.php | 156 ++++++++++++++++++ 3 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 tests/Regression/Issue396Test.php diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 5c0353b14..001dbd50d 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -949,6 +949,7 @@ private function completeListValue(ListOfType $returnType, $fieldNodes, ResolveI foreach ($result as $item) { $fieldPath = $path; $fieldPath[] = $i++; + $info->path = $fieldPath; $completedItem = $this->completeValueCatchingError($itemType, $fieldNodes, $info, $fieldPath, $item); if (! $containsPromise && $this->getPromise($completedItem)) { $containsPromise = true; diff --git a/src/Experimental/Executor/CoroutineExecutor.php b/src/Experimental/Executor/CoroutineExecutor.php index d90035b06..3aba3e2a4 100644 --- a/src/Experimental/Executor/CoroutineExecutor.php +++ b/src/Experimental/Executor/CoroutineExecutor.php @@ -620,8 +620,9 @@ private function completeValue(CoroutineContext $ctx, Type $type, $value, array foreach ($value as $itemValue) { ++$index; - $itemPath = $path; - $itemPath[] = $index; // !!! use arrays COW semantics + $itemPath = $path; + $itemPath[] = $index; // !!! use arrays COW semantics + $ctx->resolveInfo->path = $itemPath; try { if (! $this->completeValueFast($ctx, $itemType, $itemValue, $itemPath, $itemReturnValue)) { diff --git a/tests/Regression/Issue396Test.php b/tests/Regression/Issue396Test.php new file mode 100644 index 000000000..41d0b7961 --- /dev/null +++ b/tests/Regression/Issue396Test.php @@ -0,0 +1,156 @@ + 'A', 'fields' => ['name' => Type::string()]]); + $b = new ObjectType(['name' => 'B', 'fields' => ['name' => Type::string()]]); + $c = new ObjectType(['name' => 'C', 'fields' => ['name' => Type::string()]]); + + $log = []; + + $unionResult = new UnionType([ + 'name' => 'UnionResult', + 'types' => [$a, $b, $c], + 'resolveType' => static function ($result, $root, ResolveInfo $info) use ($a, $b, $c, &$log) : Type { + $log[] = [$result, $info->path]; + if (stristr($result['name'], 'A')) { + return $a; + } + if (stristr($result['name'], 'B')) { + return $b; + } + if (stristr($result['name'], 'C')) { + return $c; + } + }, + ]); + + $exampleType = new ObjectType([ + 'name' => 'Example', + 'fields' => [ + 'field' => [ + 'type' => Type::nonNull(Type::listOf(Type::nonNull($unionResult))), + 'resolve' => static function () : array { + return [ + ['name' => 'A 1'], + ['name' => 'B 2'], + ['name' => 'C 3'], + ]; + }, + ], + ], + ]); + + $schema = new Schema(['query' => $exampleType]); + + $query = ' + query { + field { + ... on A { + name + } + ... on B { + name + } + ... on C { + name + } + } + } + '; + + GraphQL::executeQuery($schema, $query); + + $expected = [ + [['name' => 'A 1'], ['field', 0]], + [['name' => 'B 2'], ['field', 1]], + [['name' => 'C 3'], ['field', 2]], + ]; + self::assertEquals($expected, $log); + } + + public function testInterfaceResolveType() + { + $log = []; + + $interfaceResult = new InterfaceType([ + 'name' => 'InterfaceResult', + 'fields' => [ + 'name' => Type::string(), + ], + 'resolveType' => static function ($result, $root, ResolveInfo $info) use (&$a, &$b, &$c, &$log) : Type { + $log[] = [$result, $info->path]; + if (stristr($result['name'], 'A')) { + return $a; + } + if (stristr($result['name'], 'B')) { + return $b; + } + if (stristr($result['name'], 'C')) { + return $c; + } + }, + ]); + + $a = new ObjectType(['name' => 'A', 'fields' => ['name' => Type::string()], 'interfaces' => [$interfaceResult]]); + $b = new ObjectType(['name' => 'B', 'fields' => ['name' => Type::string()], 'interfaces' => [$interfaceResult]]); + $c = new ObjectType(['name' => 'C', 'fields' => ['name' => Type::string()], 'interfaces' => [$interfaceResult]]); + + $exampleType = new ObjectType([ + 'name' => 'Example', + 'fields' => [ + 'field' => [ + 'type' => Type::nonNull(Type::listOf(Type::nonNull($interfaceResult))), + 'resolve' => static function () : array { + return [ + ['name' => 'A 1'], + ['name' => 'B 2'], + ['name' => 'C 3'], + ]; + }, + ], + ], + ]); + + $schema = new Schema([ + 'query' => $exampleType, + 'types' => [$a, $b, $c], + ]); + + $query = ' + query { + field { + name + } + } + '; + + GraphQL::executeQuery($schema, $query); + + $expected = [ + [['name' => 'A 1'], ['field', 0]], + [['name' => 'B 2'], ['field', 1]], + [['name' => 'C 3'], ['field', 2]], + ]; + self::assertEquals($expected, $log); + } +} From ed1746e800637eebf8529530ab208fcafb7214ac Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Wed, 19 Jun 2019 18:58:51 +0700 Subject: [PATCH 024/256] Error handling and schema validation improvements (#404) --- src/Executor/ReferenceExecutor.php | 6 ++++-- src/Type/SchemaValidationContext.php | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 001dbd50d..6e35f7ca2 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -259,9 +259,11 @@ private function executeOperation(OperationDefinitionNode $operation, $rootValue return $result->then( null, function ($error) { - $this->exeContext->addError($error); + if ($error instanceof Error) { + $this->exeContext->addError($error); - return $this->exeContext->promises->createFulfilled(null); + return $this->exeContext->promises->createFulfilled(null); + } } ); } diff --git a/src/Type/SchemaValidationContext.php b/src/Type/SchemaValidationContext.php index f00d3e588..5e5216420 100644 --- a/src/Type/SchemaValidationContext.php +++ b/src/Type/SchemaValidationContext.php @@ -252,7 +252,7 @@ public function validateTypes() if (! $type instanceof NamedType) { $this->reportError( 'Expected GraphQL named type but got: ' . Utils::printSafe($type) . '.', - is_object($type) ? $type->astNode : null + $type instanceof Type ? $type->astNode : null ); continue; } From 93ccd7351d2acceae23e4abffd1d4196d0d849d3 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Wed, 19 Jun 2019 19:29:03 +0700 Subject: [PATCH 025/256] Array in variables in place of object shouldn't cause fatal error (fixes #467) --- src/Utils/Value.php | 2 +- tests/Regression/Issue467Test.php | 45 +++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 tests/Regression/Issue467Test.php diff --git a/src/Utils/Value.php b/src/Utils/Value.php index 2bb053cda..a427d3e24 100644 --- a/src/Utils/Value.php +++ b/src/Utils/Value.php @@ -199,7 +199,7 @@ static function ($enumValue) { } $suggestions = Utils::suggestionList( - $fieldName, + (string) $fieldName, array_keys($fields) ); $didYouMean = $suggestions diff --git a/tests/Regression/Issue467Test.php b/tests/Regression/Issue467Test.php new file mode 100644 index 000000000..988b4ac23 --- /dev/null +++ b/tests/Regression/Issue467Test.php @@ -0,0 +1,45 @@ + ['my message']]; + + $schema = BuildSchema::build($schemaStr); + $result = GraphQL::executeQuery($schema, $query, null, null, $variables); + + $expectedError = 'Variable "$msg" got invalid value ["my message"]; Field "0" is not defined by type MsgInput.'; + self::assertCount(1, $result->errors); + self::assertEquals($expectedError, $result->errors[0]->getMessage()); + } +} From 03c33c9dc22cc95e605298876eeab931ae3aeb4b Mon Sep 17 00:00:00 2001 From: spawnia Date: Sun, 23 Jun 2019 17:30:57 +0200 Subject: [PATCH 026/256] Consistently name the $rootValue argument --- docs/data-fetching.md | 18 ++++++++--------- docs/reference.md | 2 +- src/Executor/ExecutionContext.php | 4 ++-- src/Executor/Executor.php | 20 +++++++++---------- src/Executor/ReferenceExecutor.php | 31 +++++++++++++++--------------- 5 files changed, 38 insertions(+), 37 deletions(-) diff --git a/docs/data-fetching.md b/docs/data-fetching.md index 4bc0cfbd8..346cd9a67 100644 --- a/docs/data-fetching.md +++ b/docs/data-fetching.md @@ -103,22 +103,22 @@ for a field you simply override this default resolver. **graphql-php** provides following default field resolver: ```php fieldName; - $property = null; + $property = null; - if (is_array($source) || $source instanceof \ArrayAccess) { - if (isset($source[$fieldName])) { - $property = $source[$fieldName]; + if (is_array($rootValue) || $rootValue instanceof ArrayAccess) { + if (isset($rootValue[$fieldName])) { + $property = $rootValue[$fieldName]; } - } else if (is_object($source)) { - if (isset($source->{$fieldName})) { - $property = $source->{$fieldName}; + } elseif (is_object($rootValue)) { + if (isset($rootValue->{$fieldName})) { + $property = $rootValue->{$fieldName}; } } - return $property instanceof Closure ? $property($source, $args, $context, $info) : $property; + return $property instanceof Closure ? $property($rootValue, $args, $context, $info) : $property; } ``` diff --git a/docs/reference.md b/docs/reference.md index 7ea770950..c9f4a1118 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -33,7 +33,7 @@ See [related documentation](executing-queries.md). * fieldResolver: * A resolver function to use when one is not provided by the schema. * If not provided, the default field resolver is used (which looks for a - * value on the source value with the field's name). + * value on the root value with the field's name). * validationRules: * A set of rules for query validation step. Default value is all available rules. * Empty array would allow to skip query validation (may be convenient for persisted diff --git a/src/Executor/ExecutionContext.php b/src/Executor/ExecutionContext.php index 3de1a2e64..6bd731866 100644 --- a/src/Executor/ExecutionContext.php +++ b/src/Executor/ExecutionContext.php @@ -50,7 +50,7 @@ class ExecutionContext public function __construct( $schema, $fragments, - $root, + $rootValue, $contextValue, $operation, $variables, @@ -60,7 +60,7 @@ public function __construct( ) { $this->schema = $schema; $this->fragments = $fragments; - $this->rootValue = $root; + $this->rootValue = $rootValue; $this->contextValue = $contextValue; $this->operation = $operation; $this->variableValues = $variables; diff --git a/src/Executor/Executor.php b/src/Executor/Executor.php index 30ccad427..cc76e4ccf 100644 --- a/src/Executor/Executor.php +++ b/src/Executor/Executor.php @@ -157,31 +157,31 @@ public static function promiseToExecute( /** * If a resolve function is not given, then a default resolve behavior is used - * which takes the property of the source object of the same name as the field + * which takes the property of the root value of the same name as the field * and returns it as the result, or if it's a function, returns the result * of calling that function while passing along args and context. * - * @param mixed $source + * @param mixed $rootValue * @param mixed[] $args * @param mixed|null $context * * @return mixed|null */ - public static function defaultFieldResolver($source, $args, $context, ResolveInfo $info) + public static function defaultFieldResolver($rootValue, $args, $context, ResolveInfo $info) { $fieldName = $info->fieldName; $property = null; - if (is_array($source) || $source instanceof ArrayAccess) { - if (isset($source[$fieldName])) { - $property = $source[$fieldName]; + if (is_array($rootValue) || $rootValue instanceof ArrayAccess) { + if (isset($rootValue[$fieldName])) { + $property = $rootValue[$fieldName]; } - } elseif (is_object($source)) { - if (isset($source->{$fieldName})) { - $property = $source->{$fieldName}; + } elseif (is_object($rootValue)) { + if (isset($rootValue->{$fieldName})) { + $property = $rootValue->{$fieldName}; } } - return $property instanceof Closure ? $property($source, $args, $context, $info) : $property; + return $property instanceof Closure ? $property($rootValue, $args, $context, $info) : $property; } } diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 6e35f7ca2..6c4a00c3f 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -237,7 +237,7 @@ private function buildResponse($data) /** * Implements the "Evaluating operations" section of the spec. * - * @param mixed[] $rootValue + * @param mixed $rootValue * * @return Promise|stdClass|mixed[] */ @@ -463,21 +463,21 @@ private function doesFragmentConditionMatch( * Implements the "Evaluating selection sets" section of the spec * for "write" mode. * - * @param mixed[] $sourceValue + * @param mixed $rootValue * @param mixed[] $path * @param ArrayObject $fields * * @return Promise|stdClass|mixed[] */ - private function executeFieldsSerially(ObjectType $parentType, $sourceValue, $path, $fields) + private function executeFieldsSerially(ObjectType $parentType, $rootValue, $path, $fields) { $result = $this->promiseReduce( array_keys($fields->getArrayCopy()), - function ($results, $responseName) use ($path, $parentType, $sourceValue, $fields) { + function ($results, $responseName) use ($path, $parentType, $rootValue, $fields) { $fieldNodes = $fields[$responseName]; $fieldPath = $path; $fieldPath[] = $responseName; - $result = $this->resolveField($parentType, $sourceValue, $fieldNodes, $fieldPath); + $result = $this->resolveField($parentType, $rootValue, $fieldNodes, $fieldPath); if ($result === self::$UNDEFINED) { return $results; } @@ -505,18 +505,19 @@ function ($results, $responseName) use ($path, $parentType, $sourceValue, $field } /** - * Resolves the field on the given source object. In particular, this - * figures out the value that the field returns by calling its resolve function, - * then calls completeValue to complete promises, serialize scalars, or execute - * the sub-selection-set for objects. + * Resolves the field on the given root value. * - * @param object|null $source + * In particular, this figures out the value that the field returns + * by calling its resolve function, then calls completeValue to complete promises, + * serialize scalars, or execute the sub-selection-set for objects. + * + * @param mixed $rootValue * @param FieldNode[] $fieldNodes * @param mixed[] $path * * @return mixed[]|Exception|mixed|null */ - private function resolveField(ObjectType $parentType, $source, $fieldNodes, $path) + private function resolveField(ObjectType $parentType, $rootValue, $fieldNodes, $path) { $exeContext = $this->exeContext; $fieldNode = $fieldNodes[0]; @@ -557,7 +558,7 @@ private function resolveField(ObjectType $parentType, $source, $fieldNodes, $pat $fieldDef, $fieldNode, $resolveFn, - $source, + $rootValue, $context, $info ); @@ -614,13 +615,13 @@ private function getFieldDef(Schema $schema, ObjectType $parentType, $fieldName) * @param FieldDefinition $fieldDef * @param FieldNode $fieldNode * @param callable $resolveFn - * @param mixed $source + * @param mixed $rootValue * @param mixed $context * @param ResolveInfo $info * * @return Throwable|Promise|mixed */ - private function resolveOrError($fieldDef, $fieldNode, $resolveFn, $source, $context, $info) + private function resolveOrError($fieldDef, $fieldNode, $resolveFn, $rootValue, $context, $info) { try { // Build hash of arguments from the field.arguments AST, using the @@ -631,7 +632,7 @@ private function resolveOrError($fieldDef, $fieldNode, $resolveFn, $source, $con $this->exeContext->variableValues ); - return $resolveFn($source, $args, $context, $info); + return $resolveFn($rootValue, $args, $context, $info); } catch (Exception $error) { return $error; } catch (Throwable $error) { From 6e91e2181cc1db5b189b5ae48a32d898cb2eff0c Mon Sep 17 00:00:00 2001 From: spawnia Date: Sun, 23 Jun 2019 18:04:30 +0200 Subject: [PATCH 027/256] Spread ternary expressions across multiple lines --- docs/data-fetching.md | 4 ++- src/Executor/Executor.php | 4 ++- src/Executor/ReferenceExecutor.php | 6 ++-- src/Executor/Values.php | 3 +- src/Language/Parser.php | 35 ++++++++++--------- src/Language/Visitor.php | 14 ++++++-- src/Server/Helper.php | 12 +++++-- src/Type/Definition/InputObjectType.php | 4 ++- src/Type/Definition/ListOfType.php | 4 ++- src/Type/Definition/NonNull.php | 4 ++- src/Type/Definition/ObjectType.php | 18 ++++++---- src/Type/Definition/Type.php | 4 ++- src/Type/Introspection.php | 4 ++- src/Type/SchemaValidationContext.php | 10 +++--- src/Utils/ASTDefinitionBuilder.php | 4 +-- src/Utils/SchemaExtender.php | 4 ++- src/Utils/SchemaPrinter.php | 7 ++-- src/Utils/TypeInfo.php | 10 +++--- src/Validator/Rules/LoneSchemaDefinition.php | 14 ++++---- .../Rules/OverlappingFieldsCanBeMerged.php | 24 ++++++------- .../ProvidedRequiredArgumentsOnDirectives.php | 16 +++++---- tests/Language/VisitorTest.php | 11 ++++-- tests/Utils/SchemaExtenderTest.php | 4 ++- tools/gendocs.php | 4 ++- 24 files changed, 141 insertions(+), 83 deletions(-) diff --git a/docs/data-fetching.md b/docs/data-fetching.md index 4bc0cfbd8..9b6f8e755 100644 --- a/docs/data-fetching.md +++ b/docs/data-fetching.md @@ -118,7 +118,9 @@ function defaultFieldResolver($source, $args, $context, \GraphQL\Type\Definition } } - return $property instanceof Closure ? $property($source, $args, $context, $info) : $property; + return $property instanceof Closure + ? $property($source, $args, $context, $info) + : $property; } ``` diff --git a/src/Executor/Executor.php b/src/Executor/Executor.php index 30ccad427..db23e50b0 100644 --- a/src/Executor/Executor.php +++ b/src/Executor/Executor.php @@ -182,6 +182,8 @@ public static function defaultFieldResolver($source, $args, $context, ResolveInf } } - return $property instanceof Closure ? $property($source, $args, $context, $info) : $property; + return $property instanceof Closure + ? $property($source, $args, $context, $info) + : $property; } } diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 6e35f7ca2..08911e46d 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -252,9 +252,9 @@ private function executeOperation(OperationDefinitionNode $operation, $rootValue // // Similar to completeValueCatchingError. try { - $result = $operation->operation === 'mutation' ? - $this->executeFieldsSerially($type, $rootValue, $path, $fields) : - $this->executeFields($type, $rootValue, $path, $fields); + $result = $operation->operation === 'mutation' + ? $this->executeFieldsSerially($type, $rootValue, $path, $fields) + : $this->executeFields($type, $rootValue, $path, $fields); if ($this->isPromise($result)) { return $result->then( null, diff --git a/src/Executor/Values.php b/src/Executor/Values.php index 82e26c8c5..e80f868cf 100644 --- a/src/Executor/Values.php +++ b/src/Executor/Values.php @@ -273,6 +273,7 @@ static function (Throwable $error) { return $error->getMessage(); }, $errors - ) : []; + ) + : []; } } diff --git a/src/Language/Parser.php b/src/Language/Parser.php index a0c123f7e..2a68ffafb 100644 --- a/src/Language/Parser.php +++ b/src/Language/Parser.php @@ -512,15 +512,15 @@ private function parseOperationType() */ private function parseVariableDefinitions() { - return $this->peek(Token::PAREN_L) ? - $this->many( + return $this->peek(Token::PAREN_L) + ? $this->many( Token::PAREN_L, function () { return $this->parseVariableDefinition(); }, Token::PAREN_R - ) : - new NodeList([]); + ) + : new NodeList([]); } /** @@ -592,9 +592,9 @@ function () { */ private function parseSelection() { - return $this->peek(Token::SPREAD) ? - $this->parseFragment() : - $this->parseField(); + return $this->peek(Token::SPREAD) + ? $this->parseFragment() + : $this->parseField(); } /** @@ -634,17 +634,17 @@ private function parseField() */ private function parseArguments($isConst) { - $parseFn = $isConst ? - function () { + $parseFn = $isConst + ? function () { return $this->parseConstArgument(); - } : - function () { + } + : function () { return $this->parseArgument(); }; - return $this->peek(Token::PAREN_L) ? - $this->many(Token::PAREN_L, $parseFn, Token::PAREN_R) : - new NodeList([]); + return $this->peek(Token::PAREN_L) + ? $this->many(Token::PAREN_L, $parseFn, Token::PAREN_R) + : new NodeList([]); } /** @@ -1208,8 +1208,8 @@ private function parseImplementsInterfaces() do { $types[] = $this->parseNamedType(); } while ($this->skip(Token::AMP) || - // Legacy support for the SDL? - (! empty($this->lexer->options['allowLegacySDLImplementsInterfaces']) && $this->peek(Token::NAME)) + // Legacy support for the SDL? + (! empty($this->lexer->options['allowLegacySDLImplementsInterfaces']) && $this->peek(Token::NAME)) ); } @@ -1545,7 +1545,8 @@ private function parseSchemaTypeExtension() Token::BRACE_L, [$this, 'parseOperationTypeDefinition'], Token::BRACE_R - ) : []; + ) + : []; if (count($directives) === 0 && count($operationTypes) === 0) { $this->unexpected(); } diff --git a/src/Language/Visitor.php b/src/Language/Visitor.php index 45f8fff13..43c59edb8 100644 --- a/src/Language/Visitor.php +++ b/src/Language/Visitor.php @@ -251,8 +251,18 @@ public static function visit($root, $visitor, $keyMap = null) $inArray = $stack['inArray']; $stack = $stack['prev']; } else { - $key = $parent !== null ? ($inArray ? $index : $keys[$index]) : $UNDEFINED; - $node = $parent !== null ? ($parent instanceof NodeList || is_array($parent) ? $parent[$key] : $parent->{$key}) : $newRoot; + $key = $parent !== null + ? ($inArray + ? $index + : $keys[$index] + ) + : $UNDEFINED; + $node = $parent !== null + ? ($parent instanceof NodeList || is_array($parent) + ? $parent[$key] + : $parent->{$key} + ) + : $newRoot; if ($node === null || $node === $UNDEFINED) { continue; } diff --git a/src/Server/Helper.php b/src/Server/Helper.php index ff62b2b71..8816f9fe7 100644 --- a/src/Server/Helper.php +++ b/src/Server/Helper.php @@ -73,10 +73,14 @@ public function parseHttpRequest(?callable $readRawBodyFn = null) } if (stripos($contentType, 'application/graphql') !== false) { - $rawBody = $readRawBodyFn ? $readRawBodyFn() : $this->readRawBody(); + $rawBody = $readRawBodyFn + ? $readRawBodyFn() + : $this->readRawBody(); $bodyParams = ['query' => $rawBody ?: '']; } elseif (stripos($contentType, 'application/json') !== false) { - $rawBody = $readRawBodyFn ? $readRawBodyFn() : $this->readRawBody(); + $rawBody = $readRawBodyFn ? + $readRawBodyFn() + : $this->readRawBody(); $bodyParams = json_decode($rawBody ?: '', true); if (json_last_error()) { @@ -272,7 +276,9 @@ static function (RequestError $err) { ); } - $doc = $op->queryId ? $this->loadPersistedQuery($config, $op) : $op->query; + $doc = $op->queryId + ? $this->loadPersistedQuery($config, $op) + : $op->query; if (! $doc instanceof DocumentNode) { $doc = Parser::parse($doc); diff --git a/src/Type/Definition/InputObjectType.php b/src/Type/Definition/InputObjectType.php index c921dda00..e31b99821 100644 --- a/src/Type/Definition/InputObjectType.php +++ b/src/Type/Definition/InputObjectType.php @@ -69,7 +69,9 @@ public function getFields() if ($this->fields === null) { $this->fields = []; $fields = $this->config['fields'] ?? []; - $fields = is_callable($fields) ? call_user_func($fields) : $fields; + $fields = is_callable($fields) + ? call_user_func($fields) + : $fields; if (! is_array($fields)) { throw new InvariantViolation( diff --git a/src/Type/Definition/ListOfType.php b/src/Type/Definition/ListOfType.php index eb8200d13..e81db88a8 100644 --- a/src/Type/Definition/ListOfType.php +++ b/src/Type/Definition/ListOfType.php @@ -31,6 +31,8 @@ public function getWrappedType($recurse = false) { $type = $this->ofType; - return $recurse && $type instanceof WrappingType ? $type->getWrappedType($recurse) : $type; + return $recurse && $type instanceof WrappingType + ? $type->getWrappedType($recurse) + : $type; } } diff --git a/src/Type/Definition/NonNull.php b/src/Type/Definition/NonNull.php index 6799523b8..4b6fa487f 100644 --- a/src/Type/Definition/NonNull.php +++ b/src/Type/Definition/NonNull.php @@ -66,6 +66,8 @@ public function getWrappedType($recurse = false) { $type = $this->ofType; - return $recurse && $type instanceof WrappingType ? $type->getWrappedType($recurse) : $type; + return $recurse && $type instanceof WrappingType + ? $type->getWrappedType($recurse) + : $type; } } diff --git a/src/Type/Definition/ObjectType.php b/src/Type/Definition/ObjectType.php index 532a9b0f0..327699a03 100644 --- a/src/Type/Definition/ObjectType.php +++ b/src/Type/Definition/ObjectType.php @@ -185,7 +185,9 @@ public function getInterfaces() { if ($this->interfaces === null) { $interfaces = $this->config['interfaces'] ?? []; - $interfaces = is_callable($interfaces) ? call_user_func($interfaces) : $interfaces; + $interfaces = is_callable($interfaces) + ? call_user_func($interfaces) + : $interfaces; if ($interfaces !== null && ! is_array($interfaces)) { throw new InvariantViolation( @@ -207,12 +209,14 @@ public function getInterfaces() */ public function isTypeOf($value, $context, ResolveInfo $info) { - return isset($this->config['isTypeOf']) ? call_user_func( - $this->config['isTypeOf'], - $value, - $context, - $info - ) : null; + return isset($this->config['isTypeOf']) + ? call_user_func( + $this->config['isTypeOf'], + $value, + $context, + $info + ) + : null; } /** diff --git a/src/Type/Definition/Type.php b/src/Type/Definition/Type.php index 69faa1cc5..65da9e761 100644 --- a/src/Type/Definition/Type.php +++ b/src/Type/Definition/Type.php @@ -345,7 +345,9 @@ public static function isType($type) */ public static function getNullableType($type) { - return $type instanceof NonNull ? $type->getWrappedType() : $type; + return $type instanceof NonNull + ? $type->getWrappedType() + : $type; } /** diff --git a/src/Type/Introspection.php b/src/Type/Introspection.php index f0cacee65..3943b3ef6 100644 --- a/src/Type/Introspection.php +++ b/src/Type/Introspection.php @@ -488,7 +488,9 @@ public static function _inputValue() 'type' => [ 'type' => Type::nonNull(self::_type()), 'resolve' => static function ($value) { - return method_exists($value, 'getType') ? $value->getType() : $value->type; + return method_exists($value, 'getType') + ? $value->getType() + : $value->type; }, ], 'defaultValue' => [ diff --git a/src/Type/SchemaValidationContext.php b/src/Type/SchemaValidationContext.php index 5e5216420..a6630189d 100644 --- a/src/Type/SchemaValidationContext.php +++ b/src/Type/SchemaValidationContext.php @@ -768,8 +768,9 @@ static function (NamedTypeNode $value) use ($typeName) { ); } - return $union->astNode ? - $union->astNode->types : null; + return $union->astNode + ? $union->astNode->types + : null; } private function validateEnumValues(EnumType $enumType) @@ -824,8 +825,9 @@ static function (EnumValueDefinitionNode $value) use ($valueName) { ); } - return $enum->astNode ? - $enum->astNode->values : null; + return $enum->astNode + ? $enum->astNode->values + : null; } private function validateInputFields(InputObjectType $inputObj) diff --git a/src/Utils/ASTDefinitionBuilder.php b/src/Utils/ASTDefinitionBuilder.php index b743213a6..7cd96d003 100644 --- a/src/Utils/ASTDefinitionBuilder.php +++ b/src/Utils/ASTDefinitionBuilder.php @@ -394,8 +394,8 @@ private function makeUnionDef(UnionTypeDefinitionNode $def) function ($typeNode) { return $this->buildType($typeNode); } - ) : - [], + ) + : [], 'astNode' => $def, ]); } diff --git a/src/Utils/SchemaExtender.php b/src/Utils/SchemaExtender.php index 4fefc61e5..6188beb1b 100644 --- a/src/Utils/SchemaExtender.php +++ b/src/Utils/SchemaExtender.php @@ -609,7 +609,9 @@ static function (string $typeName) use ($schema) { } $schemaExtensionASTNodes = count($schemaExtensions) > 0 - ? ($schema->extensionASTNodes ? array_merge($schema->extensionASTNodes, $schemaExtensions) : $schemaExtensions) + ? ($schema->extensionASTNodes + ? array_merge($schema->extensionASTNodes, $schemaExtensions) + : $schemaExtensions) : $schema->extensionASTNodes; $types = array_merge( diff --git a/src/Utils/SchemaPrinter.php b/src/Utils/SchemaPrinter.php index 9e9e1ca9b..a45c99cf4 100644 --- a/src/Utils/SchemaPrinter.php +++ b/src/Utils/SchemaPrinter.php @@ -353,8 +353,8 @@ private static function printScalar(ScalarType $type, array $options) : string private static function printObject(ObjectType $type, array $options) : string { $interfaces = $type->getInterfaces(); - $implementedInterfaces = ! empty($interfaces) ? - ' implements ' . implode( + $implementedInterfaces = ! empty($interfaces) + ? ' implements ' . implode( ' & ', array_map( static function ($i) { @@ -362,7 +362,8 @@ static function ($i) { }, $interfaces ) - ) : ''; + ) + : ''; return self::printDescription($options, $type) . sprintf("type %s%s {\n%s\n}", $type->name, $implementedInterfaces, self::printFields($options, $type)); diff --git a/src/Utils/TypeInfo.php b/src/Utils/TypeInfo.php index 13892fcaa..846840794 100644 --- a/src/Utils/TypeInfo.php +++ b/src/Utils/TypeInfo.php @@ -302,10 +302,12 @@ public function enter(Node $node) case $node instanceof InlineFragmentNode: case $node instanceof FragmentDefinitionNode: $typeConditionNode = $node->typeCondition; - $outputType = $typeConditionNode ? self::typeFromAST( - $schema, - $typeConditionNode - ) : Type::getNamedType($this->getType()); + $outputType = $typeConditionNode + ? self::typeFromAST( + $schema, + $typeConditionNode + ) + : Type::getNamedType($this->getType()); $this->typeStack[] = Type::isOutputType($outputType) ? $outputType : null; break; diff --git a/src/Validator/Rules/LoneSchemaDefinition.php b/src/Validator/Rules/LoneSchemaDefinition.php index 1a8da6711..72e5832df 100644 --- a/src/Validator/Rules/LoneSchemaDefinition.php +++ b/src/Validator/Rules/LoneSchemaDefinition.php @@ -19,12 +19,14 @@ class LoneSchemaDefinition extends ValidationRule public function getVisitor(ValidationContext $context) { $oldSchema = $context->getSchema(); - $alreadyDefined = $oldSchema !== null ? ( - $oldSchema->getAstNode() || - $oldSchema->getQueryType() || - $oldSchema->getMutationType() || - $oldSchema->getSubscriptionType() - ) : false; + $alreadyDefined = $oldSchema !== null + ? ( + $oldSchema->getAstNode() || + $oldSchema->getQueryType() || + $oldSchema->getMutationType() || + $oldSchema->getSubscriptionType() + ) + : false; $schemaDefinitionsCount = 0; diff --git a/src/Validator/Rules/OverlappingFieldsCanBeMerged.php b/src/Validator/Rules/OverlappingFieldsCanBeMerged.php index 8dfa7f193..efd9038e8 100644 --- a/src/Validator/Rules/OverlappingFieldsCanBeMerged.php +++ b/src/Validator/Rules/OverlappingFieldsCanBeMerged.php @@ -473,24 +473,24 @@ private function sameValue(Node $value1, Node $value2) private function doTypesConflict(OutputType $type1, OutputType $type2) { if ($type1 instanceof ListOfType) { - return $type2 instanceof ListOfType ? - $this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType()) : - true; + return $type2 instanceof ListOfType + ? $this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType()) + : true; } if ($type2 instanceof ListOfType) { - return $type1 instanceof ListOfType ? - $this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType()) : - true; + return $type1 instanceof ListOfType + ? $this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType()) + : true; } if ($type1 instanceof NonNull) { - return $type2 instanceof NonNull ? - $this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType()) : - true; + return $type2 instanceof NonNull + ? $this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType()) + : true; } if ($type2 instanceof NonNull) { - return $type1 instanceof NonNull ? - $this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType()) : - true; + return $type1 instanceof NonNull + ? $this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType()) + : true; } if (Type::isLeafType($type1) || Type::isLeafType($type2)) { return $type1 !== $type2; diff --git a/src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php b/src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php index 875071147..080d53e60 100644 --- a/src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php +++ b/src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php @@ -66,13 +66,15 @@ static function (FieldArgument $arg) : string { } $requiredArgsMap[$def->name->value] = Utils::keyMap( - $arguments ? array_filter($arguments, static function (Node $argument) : bool { - return $argument instanceof NonNullTypeNode && - ( - ! isset($argument->defaultValue) || - $argument->defaultValue === null - ); - }) : [], + $arguments + ? array_filter($arguments, static function (Node $argument) : bool { + return $argument instanceof NonNullTypeNode && + ( + ! isset($argument->defaultValue) || + $argument->defaultValue === null + ); + }) + : [], static function (NamedTypeNode $argument) : string { return $argument->name->value; } diff --git a/tests/Language/VisitorTest.php b/tests/Language/VisitorTest.php index b04b4db0b..0a2c6b367 100644 --- a/tests/Language/VisitorTest.php +++ b/tests/Language/VisitorTest.php @@ -70,7 +70,12 @@ private function checkVisitorFnArgs($ast, $args, $isEdited = false) /** @var Node $node */ [$node, $key, $parent, $path, $ancestors] = $args; - $parentArray = $parent && ! is_array($parent) ? ($parent instanceof NodeList ? iterator_to_array($parent) : $parent->toArray()) : $parent; + $parentArray = $parent && ! is_array($parent) + ? ($parent instanceof NodeList + ? iterator_to_array($parent) + : $parent->toArray() + ) + : $parent; self::assertInstanceOf(Node::class, $node); self::assertContains($node->kind, array_keys(NodeKind::$classMap)); @@ -114,7 +119,9 @@ private function getNodeByPath(DocumentNode $ast, $path) { $result = $ast; foreach ($path as $key) { - $resultArray = $result instanceof NodeList ? iterator_to_array($result) : $result->toArray(); + $resultArray = $result instanceof NodeList + ? iterator_to_array($result) + : $result->toArray(); self::assertArrayHasKey($key, $resultArray); $result = $resultArray[$key]; } diff --git a/tests/Utils/SchemaExtenderTest.php b/tests/Utils/SchemaExtenderTest.php index 341a38142..03529a41c 100644 --- a/tests/Utils/SchemaExtenderTest.php +++ b/tests/Utils/SchemaExtenderTest.php @@ -219,7 +219,9 @@ protected function printTestSchemaChanges(Schema $extendedSchema) : string { $ast = Parser::parse(SchemaPrinter::doPrint($extendedSchema)); $ast->definitions = array_values(array_filter( - $ast->definitions instanceof NodeList ? iterator_to_array($ast->definitions->getIterator()) : $ast->definitions, + $ast->definitions instanceof NodeList + ? iterator_to_array($ast->definitions->getIterator()) + : $ast->definitions, function (Node $node) : bool { return ! in_array(Printer::doPrint($node), $this->testSchemaDefinitions, true); } diff --git a/tools/gendocs.php b/tools/gendocs.php index d28b0c93d..7f7484254 100644 --- a/tools/gendocs.php +++ b/tools/gendocs.php @@ -40,7 +40,9 @@ function renderClassMethod(ReflectionMethod $method) { $def = $type . '$' . $p->getName(); if ($p->isDefaultValueAvailable()) { - $val = $p->isDefaultValueConstant() ? $p->getDefaultValueConstantName() : $p->getDefaultValue(); + $val = $p->isDefaultValueConstant() + ? $p->getDefaultValueConstantName() + : $p->getDefaultValue(); $def .= " = " . Utils::printSafeJson($val); } From 91b72f145d0e91eaaff4e1d7fd8c264d2eb51cf2 Mon Sep 17 00:00:00 2001 From: spawnia Date: Sun, 23 Jun 2019 18:25:02 +0200 Subject: [PATCH 028/256] Context is the 3rd, ResolveInfo the 4th resolver argument --- docs/reference.md | 2 +- src/Executor/ReferenceExecutor.php | 10 +++++----- src/Type/Definition/ResolveInfo.php | 3 ++- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/reference.md b/docs/reference.md index 7ea770950..f2d828e36 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -299,7 +299,7 @@ static function getNullableType($type) ``` # GraphQL\Type\Definition\ResolveInfo Structure containing information useful for field resolution process. -Passed as 3rd argument to every field resolver. See [docs on field resolving (data fetching)](data-fetching.md). +Passed as 4th argument to every field resolver. See [docs on field resolving (data fetching)](data-fetching.md). **Class Props:** ```php diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 6e35f7ca2..91009cf66 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -526,7 +526,11 @@ private function resolveField(ObjectType $parentType, $source, $fieldNodes, $pat return self::$UNDEFINED; } $returnType = $fieldDef->getType(); - // The resolve function's optional third argument is a collection of + // The resolve function's optional 3rd argument is a context value that + // is provided to every resolve function within an execution. It is commonly + // used to represent an authenticated user, or request-specific caches. + $context = $exeContext->contextValue; + // The resolve function's optional 4th argument is a collection of // information about the current execution state. $info = new ResolveInfo( $fieldName, @@ -547,10 +551,6 @@ private function resolveField(ObjectType $parentType, $source, $fieldNodes, $pat } else { $resolveFn = $this->exeContext->fieldResolver; } - // The resolve function's optional third argument is a context value that - // is provided to every resolve function within an execution. It is commonly - // used to represent an authenticated user, or request-specific caches. - $context = $exeContext->contextValue; // Get the resolve function, regardless of if its result is normal // or abrupt (error). $result = $this->resolveOrError( diff --git a/src/Type/Definition/ResolveInfo.php b/src/Type/Definition/ResolveInfo.php index f2525dea5..7684e930e 100644 --- a/src/Type/Definition/ResolveInfo.php +++ b/src/Type/Definition/ResolveInfo.php @@ -15,7 +15,8 @@ /** * Structure containing information useful for field resolution process. - * Passed as 3rd argument to every field resolver. See [docs on field resolving (data fetching)](data-fetching.md). + * + * Passed as 4th argument to every field resolver. See [docs on field resolving (data fetching)](data-fetching.md). */ class ResolveInfo { From 8381f67bd8f2f2d5539435cdcbfc15a0e3409d93 Mon Sep 17 00:00:00 2001 From: spawnia Date: Sun, 23 Jun 2019 18:34:19 +0200 Subject: [PATCH 029/256] Add one more --- src/Executor/ReferenceExecutor.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 08911e46d..b7784e1a0 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -1324,9 +1324,9 @@ private function ensureValidRuntimeType( ResolveInfo $info, &$result ) { - $runtimeType = is_string($runtimeTypeOrName) ? - $this->exeContext->schema->getType($runtimeTypeOrName) : - $runtimeTypeOrName; + $runtimeType = is_string($runtimeTypeOrName) + ? $this->exeContext->schema->getType($runtimeTypeOrName) + : $runtimeTypeOrName; if (! $runtimeType instanceof ObjectType) { throw new InvariantViolation( sprintf( From 65a3a8d13efa75a9cb732abfc2d970a22f4a50ef Mon Sep 17 00:00:00 2001 From: spawnia Date: Sun, 23 Jun 2019 18:36:07 +0200 Subject: [PATCH 030/256] Add missing renaming in ReferenceExecutor --- src/Executor/ReferenceExecutor.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 6c4a00c3f..82824c4be 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -1235,20 +1235,20 @@ private function collectSubFields(ObjectType $returnType, $fieldNodes) : ArrayOb * Implements the "Evaluating selection sets" section of the spec * for "read" mode. * - * @param mixed|null $source + * @param mixed $rootValue * @param mixed[] $path * @param ArrayObject $fields * * @return Promise|stdClass|mixed[] */ - private function executeFields(ObjectType $parentType, $source, $path, $fields) + private function executeFields(ObjectType $parentType, $rootValue, $path, $fields) { $containsPromise = false; $finalResults = []; foreach ($fields as $responseName => $fieldNodes) { $fieldPath = $path; $fieldPath[] = $responseName; - $result = $this->resolveField($parentType, $source, $fieldNodes, $fieldPath); + $result = $this->resolveField($parentType, $rootValue, $fieldNodes, $fieldPath); if ($result === self::$UNDEFINED) { continue; } From bc66034f400d76ac961035555102a93ae579a0f6 Mon Sep 17 00:00:00 2001 From: spawnia Date: Sun, 23 Jun 2019 18:40:56 +0200 Subject: [PATCH 031/256] Rename parameters and private fields to match what they contain --- composer.json | 2 +- src/Executor/ExecutionContext.php | 8 ++--- src/Executor/ReferenceExecutor.php | 48 ++++++++++++++++-------------- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/composer.json b/composer.json index b68861858..5b63d0b37 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "API" ], "require": { - "php": "^7.1||^8.0", + "php": "^7.1", "ext-json": "*", "ext-mbstring": "*" }, diff --git a/src/Executor/ExecutionContext.php b/src/Executor/ExecutionContext.php index 3de1a2e64..13e675ac9 100644 --- a/src/Executor/ExecutionContext.php +++ b/src/Executor/ExecutionContext.php @@ -45,7 +45,7 @@ class ExecutionContext public $errors; /** @var PromiseAdapter */ - public $promises; + public $promiseAdapter; public function __construct( $schema, @@ -53,7 +53,7 @@ public function __construct( $root, $contextValue, $operation, - $variables, + $variableValues, $errors, $fieldResolver, $promiseAdapter @@ -63,10 +63,10 @@ public function __construct( $this->rootValue = $root; $this->contextValue = $contextValue; $this->operation = $operation; - $this->variableValues = $variables; + $this->variableValues = $variableValues; $this->errors = $errors ?: []; $this->fieldResolver = $fieldResolver; - $this->promises = $promiseAdapter; + $this->promiseAdapter = $promiseAdapter; } public function addError(Error $error) diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 6e35f7ca2..3f285a9d0 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -212,7 +212,7 @@ public function doExecute() : Promise // But for the "sync" case it is always fulfilled return $this->isPromise($result) ? $result - : $this->exeContext->promises->createFulfilled($result); + : $this->exeContext->promiseAdapter->createFulfilled($result); } /** @@ -262,7 +262,7 @@ function ($error) { if ($error instanceof Error) { $this->exeContext->addError($error); - return $this->exeContext->promises->createFulfilled(null); + return $this->exeContext->promiseAdapter->createFulfilled(null); } } ); @@ -574,6 +574,7 @@ private function resolveField(ObjectType $parentType, $source, $fieldNodes, $pat /** * This method looks up the field on the given type definition. + * * It has special casing for the two introspection fields, __schema * and __typename. __typename is special because it can always be * queried as a field, even in situations where no other fields @@ -608,8 +609,8 @@ private function getFieldDef(Schema $schema, ObjectType $parentType, $fieldName) } /** - * Isolates the "ReturnOrAbrupt" behavior to not de-opt the `resolveField` - * function. Returns the result of resolveFn or the abrupt-return Error object. + * Isolates the "ReturnOrAbrupt" behavior to not de-opt the `resolveField` function. + * Returns the result of resolveFn or the abrupt-return Error object. * * @param FieldDefinition $fieldDef * @param FieldNode $fieldNode @@ -623,7 +624,7 @@ private function getFieldDef(Schema $schema, ObjectType $parentType, $fieldName) private function resolveOrError($fieldDef, $fieldNode, $resolveFn, $source, $context, $info) { try { - // Build hash of arguments from the field.arguments AST, using the + // Build a map of arguments from the field.arguments AST, using the // variables scope to fulfill any variable references. $args = Values::getArgumentValues( $fieldDef, @@ -685,7 +686,7 @@ private function completeValueCatchingError( function ($error) use ($exeContext) { $exeContext->addError($error); - return $this->exeContext->promises->createFulfilled(null); + return $this->exeContext->promiseAdapter->createFulfilled(null); } ); } @@ -732,7 +733,7 @@ public function completeValueWithLocatedError( return $promise->then( null, function ($error) use ($fieldNodes, $path) { - return $this->exeContext->promises->createRejected(Error::createLocatedError( + return $this->exeContext->promiseAdapter->createRejected(Error::createLocatedError( $error, $fieldNodes, $path @@ -864,7 +865,7 @@ private function completeValue( */ private function isPromise($value) { - return $value instanceof Promise || $this->exeContext->promises->isThenable($value); + return $value instanceof Promise || $this->exeContext->promiseAdapter->isThenable($value); } /** @@ -880,12 +881,12 @@ private function getPromise($value) if ($value === null || $value instanceof Promise) { return $value; } - if ($this->exeContext->promises->isThenable($value)) { - $promise = $this->exeContext->promises->convertThenable($value); + if ($this->exeContext->promiseAdapter->isThenable($value)) { + $promise = $this->exeContext->promiseAdapter->convertThenable($value); if (! $promise instanceof Promise) { throw new InvariantViolation(sprintf( '%s::convertThenable is expected to return instance of GraphQL\Executor\Promise\Promise, got: %s', - get_class($this->exeContext->promises), + get_class($this->exeContext->promiseAdapter), Utils::printSafe($promise) )); } @@ -927,28 +928,27 @@ function ($previous, $value) use ($callback) { } /** - * Complete a list value by completing each item in the list with the - * inner type + * Complete a list value by completing each item in the list with the inner type. * - * @param FieldNode[] $fieldNodes - * @param mixed[] $path - * @param mixed $result + * @param FieldNode[] $fieldNodes + * @param mixed[] $path + * @param mixed[]|Traversable &$results * * @return mixed[]|Promise * * @throws Exception */ - private function completeListValue(ListOfType $returnType, $fieldNodes, ResolveInfo $info, $path, &$result) + private function completeListValue(ListOfType $returnType, $fieldNodes, ResolveInfo $info, $path, &$results) { $itemType = $returnType->getWrappedType(); Utils::invariant( - is_array($result) || $result instanceof Traversable, + is_array($results) || $results instanceof Traversable, 'User Error: expected iterable, but did not find one for field ' . $info->parentType . '.' . $info->fieldName . '.' ); $containsPromise = false; $i = 0; $completedItems = []; - foreach ($result as $item) { + foreach ($results as $item) { $fieldPath = $path; $fieldPath[] = $i++; $info->path = $fieldPath; @@ -959,7 +959,9 @@ private function completeListValue(ListOfType $returnType, $fieldNodes, ResolveI $completedItems[] = $completedItem; } - return $containsPromise ? $this->exeContext->promises->all($completedItems) : $completedItems; + return $containsPromise + ? $this->exeContext->promiseAdapter->all($completedItems) + : $completedItems; } /** @@ -1101,7 +1103,7 @@ private function defaultTypeResolver($value, $context, ResolveInfo $info, Abstra } } if (! empty($promisedIsTypeOfResults)) { - return $this->exeContext->promises->all($promisedIsTypeOfResults) + return $this->exeContext->promiseAdapter->all($promisedIsTypeOfResults) ->then(static function ($isTypeOfResults) use ($possibleTypes) { foreach ($isTypeOfResults as $index => $result) { if ($result) { @@ -1187,7 +1189,7 @@ private function invalidReturnTypeError( /** * @param FieldNode[] $fieldNodes * @param mixed[] $path - * @param mixed[] $result + * @param mixed $result * * @return mixed[]|Promise|stdClass * @@ -1299,7 +1301,7 @@ private function promiseForAssocArray(array $assoc) { $keys = array_keys($assoc); $valuesAndPromises = array_values($assoc); - $promise = $this->exeContext->promises->all($valuesAndPromises); + $promise = $this->exeContext->promiseAdapter->all($valuesAndPromises); return $promise->then(static function ($values) use ($keys) { $resolvedResults = []; From 65e4488ce80878dee2bc059837196c91bf2451b8 Mon Sep 17 00:00:00 2001 From: spawnia Date: Sun, 23 Jun 2019 18:41:47 +0200 Subject: [PATCH 032/256] Reformat some comments --- src/Executor/ExecutionContext.php | 2 +- src/Executor/ReferenceExecutor.php | 2 +- src/Type/Definition/ResolveInfo.php | 22 +++++++++++----------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Executor/ExecutionContext.php b/src/Executor/ExecutionContext.php index 13e675ac9..4e541f2d7 100644 --- a/src/Executor/ExecutionContext.php +++ b/src/Executor/ExecutionContext.php @@ -14,7 +14,7 @@ * Data that must be available at all points during query execution. * * Namely, schema of the type system that is currently executing, - * and the fragments defined in the query document + * and the fragments defined in the query document. * * @internal */ diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 3f285a9d0..e4f83d52a 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -199,7 +199,7 @@ private static function buildExecutionContext( public function doExecute() : Promise { // Return a Promise that will eventually resolve to the data described by - // The "Response" section of the GraphQL specification. + // the "Response" section of the GraphQL specification. // // If errors are encountered while executing a GraphQL field, only that // field and its descendants will be omitted, and sibling fields will still diff --git a/src/Type/Definition/ResolveInfo.php b/src/Type/Definition/ResolveInfo.php index f2525dea5..8ab54867d 100644 --- a/src/Type/Definition/ResolveInfo.php +++ b/src/Type/Definition/ResolveInfo.php @@ -20,7 +20,7 @@ class ResolveInfo { /** - * The name of the field being resolved + * The name of the field being resolved. * * @api * @var string @@ -36,7 +36,7 @@ class ResolveInfo public $fieldNodes = []; /** - * Expected return type of the field being resolved + * Expected return type of the field being resolved. * * @api * @var ScalarType|ObjectType|InterfaceType|UnionType|EnumType|ListOfType|NonNull @@ -44,7 +44,7 @@ class ResolveInfo public $returnType; /** - * Parent type of the field being resolved + * Parent type of the field being resolved. * * @api * @var ObjectType @@ -52,7 +52,7 @@ class ResolveInfo public $parentType; /** - * Path to this field from the very root value + * Path to this field from the very root value. * * @api * @var string[][] @@ -60,7 +60,7 @@ class ResolveInfo public $path; /** - * Instance of a schema used for execution + * Instance of a schema used for execution. * * @api * @var Schema @@ -68,7 +68,7 @@ class ResolveInfo public $schema; /** - * AST of all fragments defined in query + * AST of all fragments defined in query. * * @api * @var FragmentDefinitionNode[] @@ -76,15 +76,15 @@ class ResolveInfo public $fragments = []; /** - * Root value passed to query execution + * Root value passed to query execution. * * @api - * @var mixed|null + * @var mixed */ public $rootValue; /** - * AST of operation definition node (query, mutation) + * AST of operation definition node (query, mutation). * * @api * @var OperationDefinitionNode|null @@ -92,7 +92,7 @@ class ResolveInfo public $operation; /** - * Array of variables passed to query execution + * Array of variables passed to query execution. * * @api * @var mixed[] @@ -136,7 +136,7 @@ public function __construct( /** * Helper method that returns names of all fields selected in query for - * $this->fieldName up to $depth levels + * $this->fieldName up to $depth levels. * * Example: * query MyQuery{ From 9ca7bb6ea1f7dbd5d3f3ad8dc8a0a6474a2b3f70 Mon Sep 17 00:00:00 2001 From: spawnia Date: Sun, 23 Jun 2019 18:42:25 +0200 Subject: [PATCH 033/256] Expand one letter variable names --- tests/Executor/DeferredFieldsTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Executor/DeferredFieldsTest.php b/tests/Executor/DeferredFieldsTest.php index e68388c24..439d11789 100644 --- a/tests/Executor/DeferredFieldsTest.php +++ b/tests/Executor/DeferredFieldsTest.php @@ -401,7 +401,7 @@ public function testComplexRecursiveDeferredFields() : void return [ 'sync' => [ 'type' => Type::string(), - 'resolve' => function ($v, $a, $c, ResolveInfo $info) { + 'resolve' => function ($val, $args, $context, ResolveInfo $info) { $this->paths[] = $info->path; return 'sync'; @@ -409,7 +409,7 @@ public function testComplexRecursiveDeferredFields() : void ], 'deferred' => [ 'type' => Type::string(), - 'resolve' => function ($v, $a, $c, ResolveInfo $info) { + 'resolve' => function ($val, $args, $context, ResolveInfo $info) { $this->paths[] = $info->path; return new Deferred(function () use ($info) { @@ -421,7 +421,7 @@ public function testComplexRecursiveDeferredFields() : void ], 'nest' => [ 'type' => $complexType, - 'resolve' => function ($v, $a, $c, ResolveInfo $info) { + 'resolve' => function ($val, $args, $context, ResolveInfo $info) { $this->paths[] = $info->path; return []; @@ -429,7 +429,7 @@ public function testComplexRecursiveDeferredFields() : void ], 'deferredNest' => [ 'type' => $complexType, - 'resolve' => function ($v, $a, $c, ResolveInfo $info) { + 'resolve' => function ($val, $args, $context, ResolveInfo $info) { $this->paths[] = $info->path; return new Deferred(function () use ($info) { From 218e02a88ca0e51932526c2db2be0209918b1a5b Mon Sep 17 00:00:00 2001 From: spawnia Date: Sun, 23 Jun 2019 18:46:27 +0200 Subject: [PATCH 034/256] Revert composer.json changes --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 5b63d0b37..b68861858 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "API" ], "require": { - "php": "^7.1", + "php": "^7.1||^8.0", "ext-json": "*", "ext-mbstring": "*" }, From 9d4a6430f060c0cda434eebf91c9a4227e8e45c7 Mon Sep 17 00:00:00 2001 From: spawnia Date: Sun, 23 Jun 2019 20:15:43 +0200 Subject: [PATCH 035/256] Add the easy fixes towards PHPStan level 2 --- phpstan.neon.dist | 2 +- src/Language/AST/Node.php | 3 +++ src/Type/Definition/LeafType.php | 10 +++++--- src/Validator/Rules/ExecutableDefinitions.php | 2 -- .../Rules/VariablesInAllowedPosition.php | 6 ++++- src/Validator/ValidationContext.php | 3 ++- tests/Executor/DeferredFieldsTest.php | 2 +- tests/Executor/UnionInterfaceTest.php | 2 +- tests/Language/ParserTest.php | 4 ++-- tests/Server/RequestParsingTest.php | 2 +- tests/Type/DefinitionTest.php | 23 +++++++++++++------ tests/Utils/BuildSchemaTest.php | 15 +++++++++++- tests/Utils/SchemaPrinterTest.php | 3 +++ 13 files changed, 56 insertions(+), 21 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 02a7a9a80..5558a3c05 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,5 +1,5 @@ parameters: - level: 1 + level: 2 paths: - %currentWorkingDirectory%/src diff --git a/src/Language/AST/Node.php b/src/Language/AST/Node.php index 0e192af9f..4e6c54368 100644 --- a/src/Language/AST/Node.php +++ b/src/Language/AST/Node.php @@ -39,6 +39,9 @@ abstract class Node /** @var Location */ public $loc; + /** @var string */ + public $kind; + /** * @param (NameNode|NodeList|SelectionSetNode|Location|string|int|bool|float|null)[] $vars */ diff --git a/src/Type/Definition/LeafType.php b/src/Type/Definition/LeafType.php index ece73554b..cf563db7a 100644 --- a/src/Type/Definition/LeafType.php +++ b/src/Type/Definition/LeafType.php @@ -6,7 +6,11 @@ use Exception; use GraphQL\Error\Error; -use GraphQL\Language\AST\Node; +use GraphQL\Language\AST\BooleanValueNode; +use GraphQL\Language\AST\FloatValueNode; +use GraphQL\Language\AST\IntValueNode; +use GraphQL\Language\AST\NullValueNode; +use GraphQL\Language\AST\StringValueNode; /* export type GraphQLLeafType = @@ -45,8 +49,8 @@ public function parseValue($value); * * In the case of an invalid node or value this method must throw an Exception * - * @param Node $valueNode - * @param mixed[]|null $variables + * @param IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|NullValueNode $valueNode + * @param mixed[]|null $variables * * @return mixed * diff --git a/src/Validator/Rules/ExecutableDefinitions.php b/src/Validator/Rules/ExecutableDefinitions.php index e626861dd..6a7e534ab 100644 --- a/src/Validator/Rules/ExecutableDefinitions.php +++ b/src/Validator/Rules/ExecutableDefinitions.php @@ -7,7 +7,6 @@ use GraphQL\Error\Error; use GraphQL\Language\AST\DocumentNode; use GraphQL\Language\AST\FragmentDefinitionNode; -use GraphQL\Language\AST\Node; use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\OperationDefinitionNode; use GraphQL\Language\Visitor; @@ -26,7 +25,6 @@ public function getVisitor(ValidationContext $context) { return [ NodeKind::DOCUMENT => static function (DocumentNode $node) use ($context) { - /** @var Node $definition */ foreach ($node->definitions as $definition) { if ($definition instanceof OperationDefinitionNode || $definition instanceof FragmentDefinitionNode diff --git a/src/Validator/Rules/VariablesInAllowedPosition.php b/src/Validator/Rules/VariablesInAllowedPosition.php index 2deb0e964..44570740c 100644 --- a/src/Validator/Rules/VariablesInAllowedPosition.php +++ b/src/Validator/Rules/VariablesInAllowedPosition.php @@ -17,7 +17,11 @@ class VariablesInAllowedPosition extends ValidationRule { - /** @var */ + /** + * A map from variable names to their definition nodes. + * + * @var VariableDefinitionNode[] + */ public $varDefMap; public function getVisitor(ValidationContext $context) diff --git a/src/Validator/ValidationContext.php b/src/Validator/ValidationContext.php index 7564af457..b5dd55d5c 100644 --- a/src/Validator/ValidationContext.php +++ b/src/Validator/ValidationContext.php @@ -185,9 +185,10 @@ public function getRecursivelyReferencedFragments(OperationDefinitionNode $opera } /** + * @param HasSelectionSet|OperationDefinitionNode|FragmentDefinitionNode $node * @return FragmentSpreadNode[] */ - public function getFragmentSpreads(HasSelectionSet $node) + public function getFragmentSpreads(HasSelectionSet $node): array { $spreads = $this->fragmentSpreads[$node] ?? null; if ($spreads === null) { diff --git a/tests/Executor/DeferredFieldsTest.php b/tests/Executor/DeferredFieldsTest.php index e68388c24..a8eb30d68 100644 --- a/tests/Executor/DeferredFieldsTest.php +++ b/tests/Executor/DeferredFieldsTest.php @@ -28,7 +28,7 @@ class DeferredFieldsTest extends TestCase /** @var ObjectType */ private $categoryType; - /** @var */ + /** @var string[] */ private $paths; /** @var mixed[][] */ diff --git a/tests/Executor/UnionInterfaceTest.php b/tests/Executor/UnionInterfaceTest.php index f8ce41a2f..e24ce610e 100644 --- a/tests/Executor/UnionInterfaceTest.php +++ b/tests/Executor/UnionInterfaceTest.php @@ -20,7 +20,7 @@ class UnionInterfaceTest extends TestCase { - /** @var */ + /** @var Schema */ public $schema; /** @var Cat */ diff --git a/tests/Language/ParserTest.php b/tests/Language/ParserTest.php index d0769e910..ac0709fec 100644 --- a/tests/Language/ParserTest.php +++ b/tests/Language/ParserTest.php @@ -333,7 +333,7 @@ public function testParseCreatesAst() : void '); $result = Parser::parse($source); - $loc = static function ($start, $end) { + $loc = static function (int $start, int $end) { return [ 'start' => $start, 'end' => $end, @@ -377,7 +377,7 @@ public function testParseCreatesAst() : void 'loc' => $loc(13, 14), 'value' => '4', ], - 'loc' => $loc(9, 14, $source), + 'loc' => $loc(9, 14), ], ], 'directives' => [], diff --git a/tests/Server/RequestParsingTest.php b/tests/Server/RequestParsingTest.php index 9de6eab7d..edb4b31be 100644 --- a/tests/Server/RequestParsingTest.php +++ b/tests/Server/RequestParsingTest.php @@ -178,7 +178,7 @@ public function testParsesGetRequest() : void foreach ($parsed as $method => $parsedBody) { self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method); - self::assertTrue($parsedBody->isReadonly(), $method); + self::assertTrue($parsedBody->isReadOnly(), $method); } } diff --git a/tests/Type/DefinitionTest.php b/tests/Type/DefinitionTest.php index 5bfcb829d..78d91d5a3 100644 --- a/tests/Type/DefinitionTest.php +++ b/tests/Type/DefinitionTest.php @@ -603,10 +603,14 @@ public function testAllowsRecursiveDefinitions() : void self::assertEquals([$node], $user->getInterfaces()); self::assertNotNull($user->getField('blogs')); - self::assertSame($blog, $user->getField('blogs')->getType()->getWrappedType(true)); + /** @var NonNull $blogFieldReturnType */ + $blogFieldReturnType = $user->getField('blogs')->getType(); + self::assertSame($blog, $blogFieldReturnType->getWrappedType(true)); self::assertNotNull($blog->getField('owner')); - self::assertSame($user, $blog->getField('owner')->getType()->getWrappedType(true)); + /** @var NonNull $ownerFieldReturnType */ + $ownerFieldReturnType = $blog->getField('owner')->getType(); + self::assertSame($user, $ownerFieldReturnType->getWrappedType(true)); } public function testInputObjectTypeAllowsRecursiveDefinitions() : void @@ -702,19 +706,24 @@ public function testAllowsShorthandFieldDefinition() : void $schema = new Schema(['query' => $query]); - $valueField = $schema->getType('SomeInterface')->getField('value'); - $nestedField = $schema->getType('SomeInterface')->getField('nested'); + /** @var InterfaceType $SomeInterface */ + $SomeInterface = $schema->getType('SomeInterface'); + $valueField = $SomeInterface->getField('value'); self::assertEquals(Type::string(), $valueField->getType()); + + $nestedField = $SomeInterface->getField('nested'); self::assertEquals($interface, $nestedField->getType()); - $withArg = $schema->getType('SomeInterface')->getField('withArg'); + $withArg = $SomeInterface->getField('withArg'); self::assertEquals(Type::string(), $withArg->getType()); self::assertEquals('arg1', $withArg->args[0]->name); self::assertEquals(Type::int(), $withArg->args[0]->getType()); - $testField = $schema->getType('Query')->getField('test'); + /** @var ObjectType $Query */ + $Query = $schema->getType('Query'); + $testField = $Query->getField('test'); self::assertEquals($interface, $testField->getType()); self::assertEquals('test', $testField->name); } @@ -763,7 +772,7 @@ public function testAcceptsAnObjectTypeWithAFieldFunction() : void ]; }, ]); - $objType->assertValid(true); + $objType->assertValid(); self::assertSame(Type::string(), $objType->getField('f')->getType()); } diff --git a/tests/Utils/BuildSchemaTest.php b/tests/Utils/BuildSchemaTest.php index e8e328029..89baaf6ed 100644 --- a/tests/Utils/BuildSchemaTest.php +++ b/tests/Utils/BuildSchemaTest.php @@ -14,7 +14,11 @@ use GraphQL\Language\Printer; use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\EnumType; +use GraphQL\Type\Definition\InputObjectType; +use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\ObjectType; +use GraphQL\Type\Definition\ScalarType; +use GraphQL\Type\Definition\UnionType; use GraphQL\Utils\BuildSchema; use GraphQL\Utils\SchemaPrinter; use PHPUnit\Framework\TestCase; @@ -747,7 +751,9 @@ enum: MyEnum self::assertTrue($otherValue->isDeprecated()); self::assertEquals('Terrible reasons', $otherValue->deprecationReason); - $rootFields = $schema->getType('Query')->getFields(); + /** @var ObjectType $queryType */ + $queryType = $schema->getType('Query'); + $rootFields = $queryType->getFields(); self::assertEquals($rootFields['field1']->isDeprecated(), true); self::assertEquals($rootFields['field1']->deprecationReason, 'No longer supported'); @@ -792,13 +798,20 @@ interfaceField: String directive @test(arg: TestScalar) on FIELD '); $schema = BuildSchema::buildAST($schemaAST); + /** @var ObjectType $query */ $query = $schema->getType('Query'); + /** @var InputObjectType $testInput */ $testInput = $schema->getType('TestInput'); + /** @var EnumType $testEnum */ $testEnum = $schema->getType('TestEnum'); + /** @var UnionType $testUnion */ $testUnion = $schema->getType('TestUnion'); + /** @var InterfaceType $testInterface */ $testInterface = $schema->getType('TestInterface'); + /** @var ObjectType $testType */ $testType = $schema->getType('TestType'); + /** @var ScalarType $testScalar */ $testScalar = $schema->getType('TestScalar'); $testDirective = $schema->getDirective('test'); diff --git a/tests/Utils/SchemaPrinterTest.php b/tests/Utils/SchemaPrinterTest.php index afc851ba4..fc2049f1f 100644 --- a/tests/Utils/SchemaPrinterTest.php +++ b/tests/Utils/SchemaPrinterTest.php @@ -713,6 +713,7 @@ public function testOneLinePrintsAShortDescription() : void $output ); + /** @var ObjectType $recreatedRoot */ $recreatedRoot = BuildSchema::build($output)->getTypeMap()['Query']; $recreatedField = $recreatedRoot->getFields()['singleField']; self::assertEquals($description, $recreatedField->description); @@ -741,6 +742,7 @@ public function testDoesNotOneLinePrintADescriptionThatEndsWithAQuote() : void $output ); + /** @var ObjectType $recreatedRoot */ $recreatedRoot = BuildSchema::build($output)->getTypeMap()['Query']; $recreatedField = $recreatedRoot->getFields()['singleField']; self::assertEquals($description, $recreatedField->description); @@ -768,6 +770,7 @@ public function testPReservesLeadingSpacesWhenPrintingADescription() : void $output ); + /** @var ObjectType $recreatedRoot */ $recreatedRoot = BuildSchema::build($output)->getTypeMap()['Query']; $recreatedField = $recreatedRoot->getFields()['singleField']; self::assertEquals($description, $recreatedField->description); From 0b8e4e0739170c63ffbf458270d03aefcc1fc76b Mon Sep 17 00:00:00 2001 From: spawnia Date: Sun, 23 Jun 2019 20:33:33 +0200 Subject: [PATCH 036/256] Remove skipped tests for unsupported allowedLegacyNames schema option --- tests/Utils/SchemaExtenderTest.php | 62 ------------------------------ 1 file changed, 62 deletions(-) diff --git a/tests/Utils/SchemaExtenderTest.php b/tests/Utils/SchemaExtenderTest.php index 341a38142..0910a5b2d 100644 --- a/tests/Utils/SchemaExtenderTest.php +++ b/tests/Utils/SchemaExtenderTest.php @@ -1575,68 +1575,6 @@ public function testDoesNotAllowExtendingAnUnknownType() } } - /** - * @see it('maintains configuration of the original schema object') - */ - public function testMaintainsConfigurationOfTheOriginalSchemaObject() - { - self::markTestSkipped('allowedLegacyNames currently not supported'); - - $testSchemaWithLegacyNames = new Schema( - [ - 'query' => new ObjectType([ - 'name' => 'Query', - 'fields' => static function () { - return ['id' => ['type' => Type::id()]]; - }, - ]), - ]/*, - [ 'allowedLegacyNames' => ['__badName'] ] - */ - ); - - $ast = Parser::parse(' - extend type Query { - __badName: String - } - '); - $schema = SchemaExtender::extend($testSchemaWithLegacyNames, $ast); - self::assertEquals(['__badName'], $schema->__allowedLegacyNames); - } - - /** - * @see it('adds to the configuration of the original schema object') - */ - public function testAddsToTheConfigurationOfTheOriginalSchemaObject() - { - self::markTestSkipped('allowedLegacyNames currently not supported'); - - $testSchemaWithLegacyNames = new Schema( - [ - 'query' => new ObjectType([ - 'name' => 'Query', - 'fields' => static function () { - return ['__badName' => ['type' => Type::string()]]; - }, - ]), - ]/*, - ['allowedLegacyNames' => ['__badName']] - */ - ); - - $ast = Parser::parse(' - extend type Query { - __anotherBadName: String - } - '); - - $schema = SchemaExtender::extend($testSchemaWithLegacyNames, $ast, [ - 'allowedLegacyNames' => ['__anotherBadName'], - ]); - - self::assertEquals(['__badName', '__anotherBadName'], $schema->__allowedLegacyNames); - } - /** * @see it('does not allow extending a mismatch type') */ From 0d13484918a1ee43cd53bbf3bc1086d78999ecb4 Mon Sep 17 00:00:00 2001 From: spawnia Date: Sun, 23 Jun 2019 20:35:26 +0200 Subject: [PATCH 037/256] Add typehint fix in ReferenceExecutor --- src/Executor/ReferenceExecutor.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 6e35f7ca2..c3363229d 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -495,6 +495,7 @@ function ($results, $responseName) use ($path, $parentType, $sourceValue, $field }, [] ); + if ($this->isPromise($result)) { return $result->then(static function ($resolvedResults) { return self::fixResultsIfEmptyArray($resolvedResults); @@ -906,7 +907,7 @@ private function getPromise($value) * @param mixed[] $values * @param Promise|mixed|null $initialValue * - * @return mixed[] + * @return Promise|mixed|null */ private function promiseReduce(array $values, callable $callback, $initialValue) { @@ -1313,7 +1314,6 @@ private function promiseForAssocArray(array $assoc) /** * @param string|ObjectType|null $runtimeTypeOrName - * @param FieldNode[] $fieldNodes * @param mixed $result * * @return ObjectType From a34bb68d65833491b958e3026ac5dd064210bc0a Mon Sep 17 00:00:00 2001 From: spawnia Date: Sun, 23 Jun 2019 21:03:17 +0200 Subject: [PATCH 038/256] Resolve todo in Boolean coercion, add explanation, update test names to match reference implementation --- src/Type/Definition/BooleanType.php | 9 ++++--- tests/Type/ScalarSerializationTest.php | 34 ++++++++++++++------------ 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/Type/Definition/BooleanType.php b/src/Type/Definition/BooleanType.php index 56b5f0fed..f9c91bb00 100644 --- a/src/Type/Definition/BooleanType.php +++ b/src/Type/Definition/BooleanType.php @@ -20,11 +20,14 @@ class BooleanType extends ScalarType public $description = 'The `Boolean` scalar type represents `true` or `false`.'; /** - * @param mixed $value + * Coerce the given value to a boolean. * - * @return bool + * The GraphQL spec leaves this up to the implementations, so we just do what + * PHP does natively to make this intuitive for developers. + * + * @param mixed $value */ - public function serialize($value) + public function serialize($value) : bool { return (bool) $value; } diff --git a/tests/Type/ScalarSerializationTest.php b/tests/Type/ScalarSerializationTest.php index 17f236843..f7b3c6d59 100644 --- a/tests/Type/ScalarSerializationTest.php +++ b/tests/Type/ScalarSerializationTest.php @@ -13,9 +13,9 @@ class ScalarSerializationTest extends TestCase { // Type System: Scalar coercion /** - * @see it('serializes output int') + * @see it('serializes output as Int') */ - public function testSerializesOutputInt() : void + public function testSerializesOutputAsInt() : void { $intType = Type::int(); @@ -114,9 +114,9 @@ public function testSerializesOutputIntCannotRepresentEmptyString() : void } /** - * @see it('serializes output float') + * @see it('serializes output as Float') */ - public function testSerializesOutputFloat() : void + public function testSerializesOutputAsFloat() : void { $floatType = Type::float(); @@ -149,9 +149,9 @@ public function testSerializesOutputFloatCannotRepresentEmptyString() : void } /** - * @see it('serializes output strings') + * @see it('serializes output as String') */ - public function testSerializesOutputStrings() : void + public function testSerializesOutputAsString() : void { $stringType = Type::string(); @@ -181,23 +181,27 @@ public function testSerializesOutputStringsCannotRepresentObject() : void } /** - * @see it('serializes output boolean') + * @see it('serializes output as Boolean') */ - public function testSerializesOutputBoolean() : void + public function testSerializesOutputAsBoolean() : void { $boolType = Type::boolean(); - self::assertTrue($boolType->serialize('string')); - self::assertFalse($boolType->serialize('')); - self::assertTrue($boolType->serialize('1')); - self::assertTrue($boolType->serialize(1)); - self::assertFalse($boolType->serialize(0)); self::assertTrue($boolType->serialize(true)); + self::assertTrue($boolType->serialize(1)); + self::assertTrue($boolType->serialize('1')); + self::assertTrue($boolType->serialize('string')); + self::assertFalse($boolType->serialize(false)); - // TODO: how should it behave on '0'? + self::assertFalse($boolType->serialize(0)); + self::assertFalse($boolType->serialize('0')); + self::assertFalse($boolType->serialize('')); } - public function testSerializesOutputID() : void + /** + * @see it('serializes output as ID') + */ + public function testSerializesOutputAsID() : void { $idType = Type::id(); From e704f8cc5c15c9f87d494efa352d20686cb966c6 Mon Sep 17 00:00:00 2001 From: spawnia Date: Sun, 23 Jun 2019 20:36:30 +0200 Subject: [PATCH 039/256] Split some long lines into multiples --- src/Experimental/Executor/CoroutineExecutor.php | 5 ++++- src/Type/Definition/ResolveInfo.php | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Experimental/Executor/CoroutineExecutor.php b/src/Experimental/Executor/CoroutineExecutor.php index 3aba3e2a4..f823aeae1 100644 --- a/src/Experimental/Executor/CoroutineExecutor.php +++ b/src/Experimental/Executor/CoroutineExecutor.php @@ -821,7 +821,10 @@ private function completeValue(CoroutineContext $ctx, Type $type, $value, array } else { $childContexts = []; - foreach ($this->collector->collectFields($objectType, $ctx->shared->mergedSelectionSet ?? $this->mergeSelectionSets($ctx)) as $childShared) { + foreach ($this->collector->collectFields( + $objectType, + $ctx->shared->mergedSelectionSet ?? $this->mergeSelectionSets($ctx) + ) as $childShared) { /** @var CoroutineContextShared $childShared */ $childPath = $path; diff --git a/src/Type/Definition/ResolveInfo.php b/src/Type/Definition/ResolveInfo.php index f2525dea5..ee2aa7b12 100644 --- a/src/Type/Definition/ResolveInfo.php +++ b/src/Type/Definition/ResolveInfo.php @@ -177,7 +177,10 @@ public function getFieldSelection($depth = 0) /** @var FieldNode $fieldNode */ foreach ($this->fieldNodes as $fieldNode) { - $fields = array_merge_recursive($fields, $this->foldSelectionSet($fieldNode->selectionSet, $depth)); + $fields = array_merge_recursive( + $fields, + $this->foldSelectionSet($fieldNode->selectionSet, $depth) + ); } return $fields; From a222cc9137580769caaa564f500f59472c518693 Mon Sep 17 00:00:00 2001 From: spawnia Date: Sun, 23 Jun 2019 21:08:49 +0200 Subject: [PATCH 040/256] Reword a comment --- src/Utils/MixedStore.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Utils/MixedStore.php b/src/Utils/MixedStore.php index 469abaa9c..8513fd569 100644 --- a/src/Utils/MixedStore.php +++ b/src/Utils/MixedStore.php @@ -21,7 +21,7 @@ * Similar to PHP array, but allows any type of data to act as key (including arrays, objects, scalars) * * Note: unfortunately when storing array as key - access and modification is O(N) - * (yet this should be really rare case and should be avoided when possible) + * (yet this should rarely be the case and should be avoided when possible) */ class MixedStore implements ArrayAccess { From 3a7bd49f68170a16d84fce4a3cc5e7dee3c17588 Mon Sep 17 00:00:00 2001 From: spawnia Date: Sun, 23 Jun 2019 21:12:52 +0200 Subject: [PATCH 041/256] Add exception for strict rule to allow loose boolean comparisons --- phpstan.neon.dist | 1 + 1 file changed, 1 insertion(+) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 5558a3c05..979a382b0 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -10,6 +10,7 @@ parameters: - "~(Method|Property) .+::.+(\\(\\))? (has parameter \\$\\w+ with no|has no return|has no) typehint specified~" - "~Variable property access on .+~" - "~Variable method call on static\\(GraphQL\\\\Server\\\\ServerConfig\\)~" # TODO get rid of + - "~Only booleans are allowed in .*~" # TODO https://github.com/phpstan/phpstan-strict-rules/issues/2 includes: - vendor/phpstan/phpstan-phpunit/extension.neon From df5fcb1937dac39bf7aa84d595e1a2488ebcd6b8 Mon Sep 17 00:00:00 2001 From: spawnia Date: Sun, 23 Jun 2019 21:13:17 +0200 Subject: [PATCH 042/256] Add comment about union type related errors --- phpstan.neon.dist | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 979a382b0..17e534822 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -12,6 +12,12 @@ parameters: - "~Variable method call on static\\(GraphQL\\\\Server\\\\ServerConfig\\)~" # TODO get rid of - "~Only booleans are allowed in .*~" # TODO https://github.com/phpstan/phpstan-strict-rules/issues/2 + # A whole class of errors in PHPStan is a result of PHP's lack of union types. + # This commonly happens in the parts of the code that deal with the GraphQL + # type system where we can currently use interfaces and lose type safety. + # Until we find a better way, we can list related error's here. + - Call to an undefined method GraphQL\Type\Definition\Type::getField() + includes: - vendor/phpstan/phpstan-phpunit/extension.neon - vendor/phpstan/phpstan-phpunit/rules.neon From 8da3043702ddcfaa097d16b782da465a012be150 Mon Sep 17 00:00:00 2001 From: spawnia Date: Sun, 23 Jun 2019 21:26:38 +0200 Subject: [PATCH 043/256] Rename some test variable names --- docs/type-system/enum-types.md | 2 +- tests/Executor/DeferredFieldsTest.php | 4 ++-- tests/Executor/ExecutorSchemaTest.php | 2 +- tests/Executor/ExecutorTest.php | 2 +- tests/Executor/TestClasses/Adder.php | 2 +- tests/GraphQLTest.php | 2 +- tests/Type/EnumTypeTest.php | 12 ++++++------ tests/Type/IntrospectionTest.php | 2 +- tests/Type/QueryPlanTest.php | 2 +- 9 files changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/type-system/enum-types.md b/docs/type-system/enum-types.md index 4cebdec70..6170c0cbd 100644 --- a/docs/type-system/enum-types.md +++ b/docs/type-system/enum-types.md @@ -158,7 +158,7 @@ $heroType = new ObjectType([ 'args' => [ 'episode' => Type::nonNull($enumType) ], - 'resolve' => function($_value, $args) { + 'resolve' => function($root, $args) { return $args['episode'] === 5 ? true : false; } ] diff --git a/tests/Executor/DeferredFieldsTest.php b/tests/Executor/DeferredFieldsTest.php index e68388c24..a4021d21f 100644 --- a/tests/Executor/DeferredFieldsTest.php +++ b/tests/Executor/DeferredFieldsTest.php @@ -109,10 +109,10 @@ static function ($entry) use ($user) { 'fields' => [ 'title' => [ 'type' => Type::string(), - 'resolve' => function ($entry, $args, $context, ResolveInfo $info) { + 'resolve' => function ($story, $args, $context, ResolveInfo $info) { $this->paths[] = $info->path; - return $entry['title']; + return $story['title']; }, ], 'author' => [ diff --git a/tests/Executor/ExecutorSchemaTest.php b/tests/Executor/ExecutorSchemaTest.php index 4946c593a..2a0fd997f 100644 --- a/tests/Executor/ExecutorSchemaTest.php +++ b/tests/Executor/ExecutorSchemaTest.php @@ -75,7 +75,7 @@ public function testExecutesUsingASchema() : void 'article' => [ 'type' => $BlogArticle, 'args' => ['id' => ['type' => Type::id()]], - 'resolve' => function ($_, $args) { + 'resolve' => function ($root, $args) { return $this->article($args['id']); }, ], diff --git a/tests/Executor/ExecutorTest.php b/tests/Executor/ExecutorTest.php index 9f5480f3a..7e7082bd5 100644 --- a/tests/Executor/ExecutorTest.php +++ b/tests/Executor/ExecutorTest.php @@ -273,7 +273,7 @@ public function testProvidesInfoAboutCurrentExecutionState() : void 'fields' => [ 'test' => [ 'type' => Type::string(), - 'resolve' => static function ($val, $args, $ctx, $_info) use (&$info) { + 'resolve' => static function ($root, $args, $ctx, $_info) use (&$info) { $info = $_info; }, ], diff --git a/tests/Executor/TestClasses/Adder.php b/tests/Executor/TestClasses/Adder.php index b086b51a1..804617b00 100644 --- a/tests/Executor/TestClasses/Adder.php +++ b/tests/Executor/TestClasses/Adder.php @@ -16,7 +16,7 @@ public function __construct(float $num) { $this->num = $num; - $this->test = function ($source, $args, $context) { + $this->test = function ($root, $args, $context) { return $this->num + $args['addend1'] + $context['addend2']; }; } diff --git a/tests/GraphQLTest.php b/tests/GraphQLTest.php index 13193b5a5..15da589b7 100644 --- a/tests/GraphQLTest.php +++ b/tests/GraphQLTest.php @@ -30,7 +30,7 @@ public function testPromiseToExecute() : void 'type' => Type::nonNull(Type::string()), ], ], - 'resolve' => static function ($value, $args) use ($promiseAdapter) { + 'resolve' => static function ($root, $args) use ($promiseAdapter) { return $promiseAdapter->createFulfilled(sprintf('Hi %s!', $args['name'])); }, ], diff --git a/tests/Type/EnumTypeTest.php b/tests/Type/EnumTypeTest.php index 9b2e8e62d..8e579ded8 100644 --- a/tests/Type/EnumTypeTest.php +++ b/tests/Type/EnumTypeTest.php @@ -74,7 +74,7 @@ public function setUp() 'fromInt' => ['type' => Type::int()], 'fromString' => ['type' => Type::string()], ], - 'resolve' => static function ($value, $args) { + 'resolve' => static function ($root, $args) { if (isset($args['fromInt'])) { return $args['fromInt']; } @@ -92,7 +92,7 @@ public function setUp() 'fromName' => ['type' => Type::string()], 'fromValue' => ['type' => Type::string()], ], - 'resolve' => static function ($value, $args) { + 'resolve' => static function ($root, $args) { if (isset($args['fromName'])) { return $args['fromName']; } @@ -107,7 +107,7 @@ public function setUp() 'fromEnum' => ['type' => $ColorType], 'fromInt' => ['type' => Type::int()], ], - 'resolve' => static function ($value, $args) { + 'resolve' => static function ($root, $args) { if (isset($args['fromInt'])) { return $args['fromInt']; } @@ -132,7 +132,7 @@ public function setUp() 'type' => Type::boolean(), ], ], - 'resolve' => static function ($value, $args) use ($Complex2) { + 'resolve' => static function ($root, $args) use ($Complex2) { if (! empty($args['provideGoodValue'])) { // Note: this is one of the references of the internal values which // ComplexEnum allows. @@ -156,7 +156,7 @@ public function setUp() 'favoriteEnum' => [ 'type' => $ColorType, 'args' => ['color' => ['type' => $ColorType]], - 'resolve' => static function ($value, $args) { + 'resolve' => static function ($root, $args) { return $args['color'] ?? null; }, ], @@ -169,7 +169,7 @@ public function setUp() 'subscribeToEnum' => [ 'type' => $ColorType, 'args' => ['color' => ['type' => $ColorType]], - 'resolve' => static function ($value, $args) { + 'resolve' => static function ($root, $args) { return $args['color'] ?? null; }, ], diff --git a/tests/Type/IntrospectionTest.php b/tests/Type/IntrospectionTest.php index 51feb401b..d92bba008 100644 --- a/tests/Type/IntrospectionTest.php +++ b/tests/Type/IntrospectionTest.php @@ -1049,7 +1049,7 @@ public function testIntrospectsOnInputObject() : void 'field' => [ 'type' => Type::string(), 'args' => ['complex' => ['type' => $TestInputObject]], - 'resolve' => static function ($_, $args) { + 'resolve' => static function ($root, $args) { return json_encode($args['complex']); }, ], diff --git a/tests/Type/QueryPlanTest.php b/tests/Type/QueryPlanTest.php index 73f8ae7ca..a0d05fd40 100644 --- a/tests/Type/QueryPlanTest.php +++ b/tests/Type/QueryPlanTest.php @@ -393,7 +393,7 @@ public function testQueryPlanOnInterface() : void } }, ]); - $result = GraphQL::executeQuery($schema, $query)->toArray(); + GraphQL::executeQuery($schema, $query)->toArray(); self::assertTrue($hasCalled); self::assertEquals($expectedQueryPlan, $queryPlan->queryPlan()); From 965fed88aaaf470962cad9accd04734e3e09339e Mon Sep 17 00:00:00 2001 From: spawnia Date: Sun, 23 Jun 2019 21:43:06 +0200 Subject: [PATCH 044/256] Fix phpstan config --- phpstan.neon.dist | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 17e534822..c870aca34 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -11,12 +11,11 @@ parameters: - "~Variable property access on .+~" - "~Variable method call on static\\(GraphQL\\\\Server\\\\ServerConfig\\)~" # TODO get rid of - "~Only booleans are allowed in .*~" # TODO https://github.com/phpstan/phpstan-strict-rules/issues/2 - # A whole class of errors in PHPStan is a result of PHP's lack of union types. # This commonly happens in the parts of the code that deal with the GraphQL # type system where we can currently use interfaces and lose type safety. # Until we find a better way, we can list related error's here. - - Call to an undefined method GraphQL\Type\Definition\Type::getField() + - "~Call to an undefined method GraphQL\\\\Type\\\\Definition\\\\Type::getField()~" includes: - vendor/phpstan/phpstan-phpunit/extension.neon From 24f236403a7ffa653a560e47edfd10f3469dcba6 Mon Sep 17 00:00:00 2001 From: spawnia Date: Sun, 30 Jun 2019 20:54:56 +0200 Subject: [PATCH 045/256] More consistent naming --- docs/getting-started.md | 4 ++-- docs/reference.md | 2 +- docs/type-system/enum-types.md | 2 +- examples/00-hello-world/graphql.php | 6 +++--- examples/02-shorthand/rootvalue.php | 16 ++++++++-------- examples/03-server/graphql.php | 6 +++--- src/Server/Helper.php | 8 ++++---- tests/Executor/ExecutorSchemaTest.php | 2 +- tests/Executor/ExecutorTest.php | 2 +- tests/Executor/TestClasses/Adder.php | 2 +- tests/GraphQLTest.php | 2 +- tests/Regression/Issue396Test.php | 4 ++-- tests/Server/ServerTestCase.php | 12 ++++++------ tests/StarWarsSchema.php | 6 +++--- tests/Type/EnumTypeTest.php | 12 ++++++------ tests/Type/IntrospectionTest.php | 2 +- tests/Utils/BuildSchemaTest.php | 22 +++++++++++----------- 17 files changed, 55 insertions(+), 55 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 4477cda85..69abf8eea 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -54,8 +54,8 @@ $queryType = new ObjectType([ 'args' => [ 'message' => Type::nonNull(Type::string()), ], - 'resolve' => function ($root, $args) { - return $root['prefix'] . $args['message']; + 'resolve' => function ($rootValue, $args) { + return $rootValue['prefix'] . $args['message']; } ], ], diff --git a/docs/reference.md b/docs/reference.md index c9f4a1118..b5791791d 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -998,7 +998,7 @@ visitor API: * * @api */ -static function visit($root, $visitor, $keyMap = null) +static function visit($rootValue, $visitor, $keyMap = null) ``` ```php diff --git a/docs/type-system/enum-types.md b/docs/type-system/enum-types.md index 6170c0cbd..ea62c2bf8 100644 --- a/docs/type-system/enum-types.md +++ b/docs/type-system/enum-types.md @@ -158,7 +158,7 @@ $heroType = new ObjectType([ 'args' => [ 'episode' => Type::nonNull($enumType) ], - 'resolve' => function($root, $args) { + 'resolve' => function($rootValue, $args) { return $args['episode'] === 5 ? true : false; } ] diff --git a/examples/00-hello-world/graphql.php b/examples/00-hello-world/graphql.php index 30b1f9c0e..167b44de3 100644 --- a/examples/00-hello-world/graphql.php +++ b/examples/00-hello-world/graphql.php @@ -19,8 +19,8 @@ 'args' => [ 'message' => ['type' => Type::string()], ], - 'resolve' => function ($root, $args) { - return $root['prefix'] . $args['message']; + 'resolve' => function ($rootValue, $args) { + return $rootValue['prefix'] . $args['message']; } ], ], @@ -35,7 +35,7 @@ 'x' => ['type' => Type::int()], 'y' => ['type' => Type::int()], ], - 'resolve' => function ($root, $args) { + 'resolve' => function ($rootValue, $args) { return $args['x'] + $args['y']; }, ], diff --git a/examples/02-shorthand/rootvalue.php b/examples/02-shorthand/rootvalue.php index 97e0a82c1..c16d62bca 100644 --- a/examples/02-shorthand/rootvalue.php +++ b/examples/02-shorthand/rootvalue.php @@ -1,12 +1,12 @@ function($root, $args, $context) { + 'sum' => function($rootValue, $args, $context) { $sum = new Addition(); - return $sum->resolve($root, $args, $context); + return $sum->resolve($rootValue, $args, $context); }, - 'echo' => function($root, $args, $context) { + 'echo' => function($rootValue, $args, $context) { $echo = new Echoer(); - return $echo->resolve($root, $args, $context); + return $echo->resolve($rootValue, $args, $context); }, 'prefix' => 'You said: ', ]; diff --git a/examples/03-server/graphql.php b/examples/03-server/graphql.php index 0c0119596..8b73f4422 100644 --- a/examples/03-server/graphql.php +++ b/examples/03-server/graphql.php @@ -19,8 +19,8 @@ 'args' => [ 'message' => ['type' => Type::string()], ], - 'resolve' => function ($root, $args) { - return $root['prefix'] . $args['message']; + 'resolve' => function ($rootValue, $args) { + return $rootValue['prefix'] . $args['message']; } ], ], @@ -35,7 +35,7 @@ 'x' => ['type' => Type::int()], 'y' => ['type' => Type::int()], ], - 'resolve' => function ($root, $args) { + 'resolve' => function ($rootValue, $args) { return $args['x'] + $args['y']; }, ], diff --git a/src/Server/Helper.php b/src/Server/Helper.php index ff62b2b71..9c522c0eb 100644 --- a/src/Server/Helper.php +++ b/src/Server/Helper.php @@ -385,13 +385,13 @@ private function resolveValidationRules( */ private function resolveRootValue(ServerConfig $config, OperationParams $params, DocumentNode $doc, $operationType) { - $root = $config->getRootValue(); + $rootValue = $config->getRootValue(); - if (is_callable($root)) { - $root = $root($params, $doc, $operationType); + if (is_callable($rootValue)) { + $rootValue = $rootValue($params, $doc, $operationType); } - return $root; + return $rootValue; } /** diff --git a/tests/Executor/ExecutorSchemaTest.php b/tests/Executor/ExecutorSchemaTest.php index 2a0fd997f..ae5a3c180 100644 --- a/tests/Executor/ExecutorSchemaTest.php +++ b/tests/Executor/ExecutorSchemaTest.php @@ -75,7 +75,7 @@ public function testExecutesUsingASchema() : void 'article' => [ 'type' => $BlogArticle, 'args' => ['id' => ['type' => Type::id()]], - 'resolve' => function ($root, $args) { + 'resolve' => function ($rootValue, $args) { return $this->article($args['id']); }, ], diff --git a/tests/Executor/ExecutorTest.php b/tests/Executor/ExecutorTest.php index 7e7082bd5..76b4beb15 100644 --- a/tests/Executor/ExecutorTest.php +++ b/tests/Executor/ExecutorTest.php @@ -273,7 +273,7 @@ public function testProvidesInfoAboutCurrentExecutionState() : void 'fields' => [ 'test' => [ 'type' => Type::string(), - 'resolve' => static function ($root, $args, $ctx, $_info) use (&$info) { + 'resolve' => static function ($rootValue, $args, $ctx, $_info) use (&$info) { $info = $_info; }, ], diff --git a/tests/Executor/TestClasses/Adder.php b/tests/Executor/TestClasses/Adder.php index 804617b00..416cd5e73 100644 --- a/tests/Executor/TestClasses/Adder.php +++ b/tests/Executor/TestClasses/Adder.php @@ -16,7 +16,7 @@ public function __construct(float $num) { $this->num = $num; - $this->test = function ($root, $args, $context) { + $this->test = function ($rootValue, $args, $context) { return $this->num + $args['addend1'] + $context['addend2']; }; } diff --git a/tests/GraphQLTest.php b/tests/GraphQLTest.php index 15da589b7..6d28e0970 100644 --- a/tests/GraphQLTest.php +++ b/tests/GraphQLTest.php @@ -30,7 +30,7 @@ public function testPromiseToExecute() : void 'type' => Type::nonNull(Type::string()), ], ], - 'resolve' => static function ($root, $args) use ($promiseAdapter) { + 'resolve' => static function ($rootValue, $args) use ($promiseAdapter) { return $promiseAdapter->createFulfilled(sprintf('Hi %s!', $args['name'])); }, ], diff --git a/tests/Regression/Issue396Test.php b/tests/Regression/Issue396Test.php index 41d0b7961..f7c26a9ee 100644 --- a/tests/Regression/Issue396Test.php +++ b/tests/Regression/Issue396Test.php @@ -30,7 +30,7 @@ public function testUnionResolveType() $unionResult = new UnionType([ 'name' => 'UnionResult', 'types' => [$a, $b, $c], - 'resolveType' => static function ($result, $root, ResolveInfo $info) use ($a, $b, $c, &$log) : Type { + 'resolveType' => static function ($result, $rootValue, ResolveInfo $info) use ($a, $b, $c, &$log) : Type { $log[] = [$result, $info->path]; if (stristr($result['name'], 'A')) { return $a; @@ -97,7 +97,7 @@ public function testInterfaceResolveType() 'fields' => [ 'name' => Type::string(), ], - 'resolveType' => static function ($result, $root, ResolveInfo $info) use (&$a, &$b, &$c, &$log) : Type { + 'resolveType' => static function ($result, $rootValue, ResolveInfo $info) use (&$a, &$b, &$c, &$log) : Type { $log[] = [$result, $info->path]; if (stristr($result['name'], 'A')) { return $a; diff --git a/tests/Server/ServerTestCase.php b/tests/Server/ServerTestCase.php index 072acba46..d94ac340d 100644 --- a/tests/Server/ServerTestCase.php +++ b/tests/Server/ServerTestCase.php @@ -25,13 +25,13 @@ protected function buildSchema() 'fields' => [ 'f1' => [ 'type' => Type::string(), - 'resolve' => static function ($root, $args, $context, $info) { + 'resolve' => static function ($rootValue, $args, $context, $info) { return $info->fieldName; }, ], 'fieldWithPhpError' => [ 'type' => Type::string(), - 'resolve' => static function ($root, $args, $context, $info) { + 'resolve' => static function ($rootValue, $args, $context, $info) { trigger_error('deprecated', E_USER_DEPRECATED); trigger_error('notice', E_USER_NOTICE); trigger_error('warning', E_USER_WARNING); @@ -55,8 +55,8 @@ protected function buildSchema() ], 'testContextAndRootValue' => [ 'type' => Type::string(), - 'resolve' => static function ($root, $args, $context, $info) { - $context->testedRootValue = $root; + 'resolve' => static function ($rootValue, $args, $context, $info) { + $context->testedRootValue = $rootValue; return $info->fieldName; }, @@ -68,7 +68,7 @@ protected function buildSchema() 'type' => Type::nonNull(Type::string()), ], ], - 'resolve' => static function ($root, $args) { + 'resolve' => static function ($rootValue, $args) { return $args['arg']; }, ], @@ -79,7 +79,7 @@ protected function buildSchema() 'type' => Type::nonNull(Type::int()), ], ], - 'resolve' => static function ($root, $args, $context) { + 'resolve' => static function ($rootValue, $args, $context) { $context['buffer']($args['num']); return new Deferred(static function () use ($args, $context) { diff --git a/tests/StarWarsSchema.php b/tests/StarWarsSchema.php index b9240aff1..86221e698 100644 --- a/tests/StarWarsSchema.php +++ b/tests/StarWarsSchema.php @@ -273,7 +273,7 @@ static function ($friend) use ($fieldSelection) { 'type' => $episodeEnum, ], ], - 'resolve' => static function ($root, $args) { + 'resolve' => static function ($rootValue, $args) { return StarWarsData::getHero($args['episode'] ?? null); }, ], @@ -286,7 +286,7 @@ static function ($friend) use ($fieldSelection) { 'type' => Type::nonNull(Type::string()), ], ], - 'resolve' => static function ($root, $args) { + 'resolve' => static function ($rootValue, $args) { $humans = StarWarsData::humans(); return $humans[$args['id']] ?? null; @@ -301,7 +301,7 @@ static function ($friend) use ($fieldSelection) { 'type' => Type::nonNull(Type::string()), ], ], - 'resolve' => static function ($root, $args) { + 'resolve' => static function ($rootValue, $args) { $droids = StarWarsData::droids(); return $droids[$args['id']] ?? null; diff --git a/tests/Type/EnumTypeTest.php b/tests/Type/EnumTypeTest.php index 8e579ded8..b1649bed2 100644 --- a/tests/Type/EnumTypeTest.php +++ b/tests/Type/EnumTypeTest.php @@ -74,7 +74,7 @@ public function setUp() 'fromInt' => ['type' => Type::int()], 'fromString' => ['type' => Type::string()], ], - 'resolve' => static function ($root, $args) { + 'resolve' => static function ($rootValue, $args) { if (isset($args['fromInt'])) { return $args['fromInt']; } @@ -92,7 +92,7 @@ public function setUp() 'fromName' => ['type' => Type::string()], 'fromValue' => ['type' => Type::string()], ], - 'resolve' => static function ($root, $args) { + 'resolve' => static function ($rootValue, $args) { if (isset($args['fromName'])) { return $args['fromName']; } @@ -107,7 +107,7 @@ public function setUp() 'fromEnum' => ['type' => $ColorType], 'fromInt' => ['type' => Type::int()], ], - 'resolve' => static function ($root, $args) { + 'resolve' => static function ($rootValue, $args) { if (isset($args['fromInt'])) { return $args['fromInt']; } @@ -132,7 +132,7 @@ public function setUp() 'type' => Type::boolean(), ], ], - 'resolve' => static function ($root, $args) use ($Complex2) { + 'resolve' => static function ($rootValue, $args) use ($Complex2) { if (! empty($args['provideGoodValue'])) { // Note: this is one of the references of the internal values which // ComplexEnum allows. @@ -156,7 +156,7 @@ public function setUp() 'favoriteEnum' => [ 'type' => $ColorType, 'args' => ['color' => ['type' => $ColorType]], - 'resolve' => static function ($root, $args) { + 'resolve' => static function ($rootValue, $args) { return $args['color'] ?? null; }, ], @@ -169,7 +169,7 @@ public function setUp() 'subscribeToEnum' => [ 'type' => $ColorType, 'args' => ['color' => ['type' => $ColorType]], - 'resolve' => static function ($root, $args) { + 'resolve' => static function ($rootValue, $args) { return $args['color'] ?? null; }, ], diff --git a/tests/Type/IntrospectionTest.php b/tests/Type/IntrospectionTest.php index d92bba008..afea3d98c 100644 --- a/tests/Type/IntrospectionTest.php +++ b/tests/Type/IntrospectionTest.php @@ -1049,7 +1049,7 @@ public function testIntrospectsOnInputObject() : void 'field' => [ 'type' => Type::string(), 'args' => ['complex' => ['type' => $TestInputObject]], - 'resolve' => static function ($root, $args) { + 'resolve' => static function ($rootValue, $args) { return json_encode($args['complex']); }, ], diff --git a/tests/Utils/BuildSchemaTest.php b/tests/Utils/BuildSchemaTest.php index e8e328029..0ca56c905 100644 --- a/tests/Utils/BuildSchemaTest.php +++ b/tests/Utils/BuildSchemaTest.php @@ -51,7 +51,7 @@ public function testBuildSchemaDirectlyFromSource() : void '); $root = [ - 'add' => static function ($root, $args) { + 'add' => static function ($rootValue, $args) { return $args['x'] + $args['y']; }, ]; @@ -437,7 +437,7 @@ public function testMultipleUnion() : void */ public function testSpecifyingUnionTypeUsingTypename() : void { - $schema = BuildSchema::buildAST(Parser::parse(' + $schema = BuildSchema::buildAST(Parser::parse(' type Query { fruits: [Fruit] } @@ -452,7 +452,7 @@ public function testSpecifyingUnionTypeUsingTypename() : void length: Int } ')); - $query = ' + $query = ' { fruits { ... on Apple { @@ -464,7 +464,7 @@ public function testSpecifyingUnionTypeUsingTypename() : void } } '; - $root = [ + $rootValue = [ 'fruits' => [ [ 'color' => 'green', @@ -476,7 +476,7 @@ public function testSpecifyingUnionTypeUsingTypename() : void ], ], ]; - $expected = [ + $expected = [ 'data' => [ 'fruits' => [ ['color' => 'green'], @@ -485,7 +485,7 @@ public function testSpecifyingUnionTypeUsingTypename() : void ], ]; - $result = GraphQL::executeQuery($schema, $query, $root); + $result = GraphQL::executeQuery($schema, $query, $rootValue); self::assertEquals($expected, $result->toArray(true)); } @@ -494,7 +494,7 @@ public function testSpecifyingUnionTypeUsingTypename() : void */ public function testSpecifyingInterfaceUsingTypename() : void { - $schema = BuildSchema::buildAST(Parser::parse(' + $schema = BuildSchema::buildAST(Parser::parse(' type Query { characters: [Character] } @@ -513,7 +513,7 @@ interface Character { primaryFunction: String } ')); - $query = ' + $query = ' { characters { name @@ -526,7 +526,7 @@ interface Character { } } '; - $root = [ + $rootValue = [ 'characters' => [ [ 'name' => 'Han Solo', @@ -540,7 +540,7 @@ interface Character { ], ], ]; - $expected = [ + $expected = [ 'data' => [ 'characters' => [ ['name' => 'Han Solo', 'totalCredits' => 10], @@ -549,7 +549,7 @@ interface Character { ], ]; - $result = GraphQL::executeQuery($schema, $query, $root); + $result = GraphQL::executeQuery($schema, $query, $rootValue); self::assertEquals($expected, $result->toArray(true)); } From 99453076b520f0ebdac7d392d2b35b053dd272cf Mon Sep 17 00:00:00 2001 From: spawnia Date: Sun, 30 Jun 2019 20:57:08 +0200 Subject: [PATCH 046/256] Remove &$ in phpdoc --- src/Executor/ReferenceExecutor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index e4f83d52a..f2e85ff3f 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -932,7 +932,7 @@ function ($previous, $value) use ($callback) { * * @param FieldNode[] $fieldNodes * @param mixed[] $path - * @param mixed[]|Traversable &$results + * @param mixed[]|Traversable $results * * @return mixed[]|Promise * From 51b24102e4e53b9db9ce171249bf18b82a9b2a74 Mon Sep 17 00:00:00 2001 From: spawnia Date: Sun, 30 Jun 2019 20:58:28 +0200 Subject: [PATCH 047/256] Add return type in ParserTest --- tests/Language/ParserTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Language/ParserTest.php b/tests/Language/ParserTest.php index ac0709fec..84c2b2371 100644 --- a/tests/Language/ParserTest.php +++ b/tests/Language/ParserTest.php @@ -333,7 +333,7 @@ public function testParseCreatesAst() : void '); $result = Parser::parse($source); - $loc = static function (int $start, int $end) { + $loc = static function (int $start, int $end): array { return [ 'start' => $start, 'end' => $end, From 6979d68db60f02db5aecb352df17aa6611c31fc6 Mon Sep 17 00:00:00 2001 From: spawnia Date: Sun, 30 Jun 2019 21:39:44 +0200 Subject: [PATCH 048/256] Add the rest of the methods --- src/Language/Parser.php | 94 ++++++++++++++++++++++++++++++++--------- 1 file changed, 74 insertions(+), 20 deletions(-) diff --git a/src/Language/Parser.php b/src/Language/Parser.php index a93360122..f383b3c13 100644 --- a/src/Language/Parser.php +++ b/src/Language/Parser.php @@ -60,11 +60,65 @@ /** * Parses string containing GraphQL query or [type definition](type-system/type-language.md) to Abstract Syntax Tree. * - * // TODO write out the rest of those magic function helpers - * * @method static NameNode name(Source|string $source, bool[] $options = []) - * @method static NameNode directiveLocation(Source|string $source, bool[] $options = []) + * @method static DocumentNode document(Source|string $source, bool[] $options = []) + * @method static ExecutableDefinitionNode|TypeSystemDefinitionNode definition(Source|string $source, bool[] $options = []) + * @method static ExecutableDefinitionNode executableDefinition(Source|string $source, bool[] $options = []) + * @method static OperationDefinitionNode operationDefinition(Source|string $source, bool[] $options = []) + * @method static string operationType(Source|string $source, bool[] $options = []) + * @method static NodeList|VariableDefinitionNode[] variableDefinitions(Source|string $source, bool[] $options = []) + * @method static VariableDefinitionNode variableDefinition(Source|string $source, bool[] $options = []) + * @method static VariableNode variable(Source|string $source, bool[] $options = []) + * @method static SelectionSetNode selectionSet(Source|string $source, bool[] $options = []) + * @method static mixed selection(Source|string $source, bool[] $options = []) + * @method static FieldNode field(Source|string $source, bool[] $options = []) + * @method static NodeList|ArgumentNode[] arguments(Source|string $source, bool[] $options = []) + * @method static ArgumentNode argument(Source|string $source, bool[] $options = []) + * @method static ArgumentNode constArgument(Source|string $source, bool[] $options = []) + * @method static FragmentSpreadNode|InlineFragmentNode fragment(Source|string $source, bool[] $options = []) + * @method static FragmentDefinitionNode fragmentDefinition(Source|string $source, bool[] $options = []) + * @method static NameNode fragmentName(Source|string $source, bool[] $options = []) + * @method static BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|ListValueNode|NullValueNode|ObjectValueNode|StringValueNode|VariableNode valueLiteral(Source|string $source, bool[] $options = []) + * @method static StringValueNode stringLiteral(Source|string $source, bool[] $options = []) + * @method static BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|StringValueNode|VariableNode constValue(Source|string $source, bool[] $options = []) + * @method static BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|ListValueNode|ObjectValueNode|StringValueNode|VariableNode variableValue(Source|string $source, bool[] $options = []) + * @method static ListValueNode array(Source|string $source, bool[] $options = []) + * @method static ObjectValueNode object(Source|string $source, bool[] $options = []) + * @method static ObjectFieldNode objectField(Source|string $source, bool[] $options = []) + * @method static NodeList|DirectiveNode[] directives(Source|string $source, bool[] $options = []) + * @method static DirectiveNode directive(Source|string $source, bool[] $options = []) + * @method static ListTypeNode|NameNode|NonNullTypeNode typeReference(Source|string $source, bool[] $options = []) + * @method static NamedTypeNode namedType(Source|string $source, bool[] $options = []) + * @method static TypeSystemDefinitionNode typeSystemDefinition(Source|string $source, bool[] $options = []) + * @method static StringValueNode|null description(Source|string $source, bool[] $options = []) + * @method static SchemaDefinitionNode schemaDefinition(Source|string $source, bool[] $options = []) + * @method static OperationTypeDefinitionNode operationTypeDefinition(Source|string $source, bool[] $options = []) + * @method static ScalarTypeDefinitionNode scalarTypeDefinition(Source|string $source, bool[] $options = []) * @method static ObjectTypeDefinitionNode objectTypeDefinition(Source|string $source, bool[] $options = []) + * @method static NamedTypeNode[] implementsInterfaces(Source|string $source, bool[] $options = []) + * @method static FieldDefinitionNode[] fieldDefinitions(Source|string $source, bool[] $options = []) + * @method static FieldDefinitionNode fieldDefinition(Source|string $source, bool[] $options = []) + * @method static InputValueDefinitionNode[] argumentDefinitions(Source|string $source, bool[] $options = []) + * @method static InputValueDefinitionNode inputValueDefinition(Source|string $source, bool[] $options = []) + * @method static InterfaceTypeDefinitionNode interfaceTypeDefinition(Source|string $source, bool[] $options = []) + * @method static UnionTypeDefinitionNode unionTypeDefinition(Source|string $source, bool[] $options = []) + * @method static NamedTypeNode[] unionMemberTypes(Source|string $source, bool[] $options = []) + * @method static EnumTypeDefinitionNode enumTypeDefinition(Source|string $source, bool[] $options = []) + * @method static EnumValueDefinitionNode[] enumValueDefinitions(Source|string $source, bool[] $options = []) + * @method static EnumValueDefinitionNode enumValueDefinition(Source|string $source, bool[] $options = []) + * @method static InputObjectTypeDefinitionNode inputObjectTypeDefinition(Source|string $source, bool[] $options = []) + * @method static InputValueDefinitionNode[] inputFieldDefinitions(Source|string $source, bool[] $options = []) + * @method static TypeExtensionNode typeExtension(Source|string $source, bool[] $options = []) + * @method static SchemaTypeExtensionNode schemaTypeExtension(Source|string $source, bool[] $options = []) + * @method static ScalarTypeExtensionNode scalarTypeExtension(Source|string $source, bool[] $options = []) + * @method static ObjectTypeExtensionNode objectTypeExtension(Source|string $source, bool[] $options = []) + * @method static InterfaceTypeExtensionNode interfaceTypeExtension(Source|string $source, bool[] $options = []) + * @method static UnionTypeExtensionNode unionTypeExtension(Source|string $source, bool[] $options = []) + * @method static EnumTypeExtensionNode enumTypeExtension(Source|string $source, bool[] $options = []) + * @method static InputObjectTypeExtensionNode inputObjectTypeExtension(Source|string $source, bool[] $options = []) + * @method static DirectiveDefinitionNode directiveDefinition(Source|string $source, bool[] $options = []) + * @method static DirectiveLocation[] directiveLocations(Source|string $source, bool[] $options = []) + * @method static DirectiveLocation directiveLocation(Source|string $source, bool[] $options = []) */ class Parser { @@ -1203,7 +1257,7 @@ private function parseObjectTypeDefinition() $name = $this->parseName(); $interfaces = $this->parseImplementsInterfaces(); $directives = $this->parseDirectives(true); - $fields = $this->parseFieldsDefinition(); + $fields = $this->parseFieldDefinitions(); return new ObjectTypeDefinitionNode([ 'name' => $name, @@ -1245,7 +1299,7 @@ private function parseImplementsInterfaces() * * @throws SyntaxError */ - private function parseFieldsDefinition() + private function parseFieldDefinitions() { // Legacy support for the SDL? if (! empty($this->lexer->options['allowLegacySDLEmptyFields']) && @@ -1279,7 +1333,7 @@ private function parseFieldDefinition() $start = $this->lexer->token; $description = $this->parseDescription(); $name = $this->parseName(); - $args = $this->parseArgumentDefs(); + $args = $this->parseArgumentDefinitions(); $this->expect(Token::COLON); $type = $this->parseTypeReference(); $directives = $this->parseDirectives(true); @@ -1299,7 +1353,7 @@ private function parseFieldDefinition() * * @throws SyntaxError */ - private function parseArgumentDefs() + private function parseArgumentDefinitions() { if (! $this->peek(Token::PAREN_L)) { return new NodeList([]); @@ -1308,7 +1362,7 @@ private function parseArgumentDefs() return $this->many( Token::PAREN_L, function () { - return $this->parseInputValueDef(); + return $this->parseInputValueDefinition(); }, Token::PAREN_R ); @@ -1319,7 +1373,7 @@ function () { * * @throws SyntaxError */ - private function parseInputValueDef() + private function parseInputValueDefinition() { $start = $this->lexer->token; $description = $this->parseDescription(); @@ -1354,7 +1408,7 @@ private function parseInterfaceTypeDefinition() $this->expectKeyword('interface'); $name = $this->parseName(); $directives = $this->parseDirectives(true); - $fields = $this->parseFieldsDefinition(); + $fields = $this->parseFieldDefinitions(); return new InterfaceTypeDefinitionNode([ 'name' => $name, @@ -1424,7 +1478,7 @@ private function parseEnumTypeDefinition() $this->expectKeyword('enum'); $name = $this->parseName(); $directives = $this->parseDirectives(true); - $values = $this->parseEnumValuesDefinition(); + $values = $this->parseEnumValueDefinitions(); return new EnumTypeDefinitionNode([ 'name' => $name, @@ -1440,7 +1494,7 @@ private function parseEnumTypeDefinition() * * @throws SyntaxError */ - private function parseEnumValuesDefinition() + private function parseEnumValueDefinitions() { return $this->peek(Token::BRACE_L) ? $this->many( @@ -1485,7 +1539,7 @@ private function parseInputObjectTypeDefinition() $this->expectKeyword('input'); $name = $this->parseName(); $directives = $this->parseDirectives(true); - $fields = $this->parseInputFieldsDefinition(); + $fields = $this->parseInputFieldDefinitions(); return new InputObjectTypeDefinitionNode([ 'name' => $name, @@ -1501,13 +1555,13 @@ private function parseInputObjectTypeDefinition() * * @throws SyntaxError */ - private function parseInputFieldsDefinition() + private function parseInputFieldDefinitions() { return $this->peek(Token::BRACE_L) ? $this->many( Token::BRACE_L, function () { - return $this->parseInputValueDef(); + return $this->parseInputValueDefinition(); }, Token::BRACE_R ) @@ -1617,7 +1671,7 @@ private function parseObjectTypeExtension() $name = $this->parseName(); $interfaces = $this->parseImplementsInterfaces(); $directives = $this->parseDirectives(true); - $fields = $this->parseFieldsDefinition(); + $fields = $this->parseFieldDefinitions(); if (count($interfaces) === 0 && count($directives) === 0 && @@ -1647,7 +1701,7 @@ private function parseInterfaceTypeExtension() $this->expectKeyword('interface'); $name = $this->parseName(); $directives = $this->parseDirectives(true); - $fields = $this->parseFieldsDefinition(); + $fields = $this->parseFieldDefinitions(); if (count($directives) === 0 && count($fields) === 0 ) { @@ -1705,7 +1759,7 @@ private function parseEnumTypeExtension() $this->expectKeyword('enum'); $name = $this->parseName(); $directives = $this->parseDirectives(true); - $values = $this->parseEnumValuesDefinition(); + $values = $this->parseEnumValueDefinitions(); if (count($directives) === 0 && count($values) === 0 ) { @@ -1732,7 +1786,7 @@ private function parseInputObjectTypeExtension() $this->expectKeyword('input'); $name = $this->parseName(); $directives = $this->parseDirectives(true); - $fields = $this->parseInputFieldsDefinition(); + $fields = $this->parseInputFieldDefinitions(); if (count($directives) === 0 && count($fields) === 0 ) { @@ -1762,7 +1816,7 @@ private function parseDirectiveDefinition() $this->expectKeyword('directive'); $this->expect(Token::AT); $name = $this->parseName(); - $args = $this->parseArgumentDefs(); + $args = $this->parseArgumentDefinitions(); $this->expectKeyword('on'); $locations = $this->parseDirectiveLocations(); From 719bebc1469bbcc5744c182c5dadc47560c1f05b Mon Sep 17 00:00:00 2001 From: spawnia Date: Sun, 30 Jun 2019 22:45:59 +0200 Subject: [PATCH 049/256] Add phpstan exception --- phpstan.neon.dist | 1 + 1 file changed, 1 insertion(+) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 02a7a9a80..94ceb22a7 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -9,6 +9,7 @@ parameters: - "~Construct empty\\(\\) is not allowed\\. Use more strict comparison~" - "~(Method|Property) .+::.+(\\(\\))? (has parameter \\$\\w+ with no|has no return|has no) typehint specified~" - "~Variable property access on .+~" + - "~Variable method call on GraphQL\\\\Language\\\\Parser\\.~" - "~Variable method call on static\\(GraphQL\\\\Server\\\\ServerConfig\\)~" # TODO get rid of includes: From 19a37609f4ba53d6e7ce13ee0644b65b821fb154 Mon Sep 17 00:00:00 2001 From: spawnia Date: Sun, 30 Jun 2019 23:08:24 +0200 Subject: [PATCH 050/256] Add a few more --- benchmarks/Utils/SchemaGenerator.php | 2 +- docs/type-system/object-types.md | 4 ++-- examples/01-blog/Blog/Type/CommentType.php | 6 +++--- examples/01-blog/Blog/Type/StoryType.php | 6 +++--- examples/01-blog/Blog/Type/UserType.php | 6 +++--- src/Type/Definition/ObjectType.php | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/benchmarks/Utils/SchemaGenerator.php b/benchmarks/Utils/SchemaGenerator.php index 8ee39e7e3..495ad8f5d 100644 --- a/benchmarks/Utils/SchemaGenerator.php +++ b/benchmarks/Utils/SchemaGenerator.php @@ -152,7 +152,7 @@ protected function createFieldArgs($fieldName, $typeName) ]; } - public function resolveField($value, $args, $context, $resolveInfo) + public function resolveField($rootValue, $args, $context, $resolveInfo) { return $resolveInfo->fieldName . '-value'; } diff --git a/docs/type-system/object-types.md b/docs/type-system/object-types.md index 9c1f2fbc4..c12595ad3 100644 --- a/docs/type-system/object-types.md +++ b/docs/type-system/object-types.md @@ -69,7 +69,7 @@ name | `string` | **Required.** Unique name of this object type within S fields | `array` or `callable` | **Required**. An array describing object fields or callable returning such an array. See [Fields](#field-definitions) section below for expected structure of each array entry. See also the section on [Circular types](#recurring-and-circular-types) for an explanation of when to use callable for this option. description | `string` | Plain-text description of this type for clients (e.g. used by [GraphiQL](https://github.com/graphql/graphiql) for auto-generated documentation) interfaces | `array` or `callable` | List of interfaces implemented by this type or callable returning such a list. See [Interface Types](interfaces.md) for details. See also the section on [Circular types](#recurring-and-circular-types) for an explanation of when to use callable for this option. -isTypeOf | `callable` | **function($value, $context, [ResolveInfo](../reference.md#graphqltypedefinitionresolveinfo) $info)**
Expected to return **true** if **$value** qualifies for this type (see section about [Abstract Type Resolution](interfaces.md#interface-role-in-data-fetching) for explanation). +isTypeOf | `callable` | **function($rootValue, $context, [ResolveInfo](../reference.md#graphqltypedefinitionresolveinfo) $info)**
Expected to return **true** if **$value** qualifies for this type (see section about [Abstract Type Resolution](interfaces.md#interface-role-in-data-fetching) for explanation). resolveField | `callable` | **function($value, $args, $context, [ResolveInfo](../reference.md#graphqltypedefinitionresolveinfo) $info)**
Given the **$value** of this type, it is expected to return value for a field defined in **$info->fieldName**. A good place to define a type-specific strategy for field resolution. See section on [Data Fetching](../data-fetching.md) for details. # Field configuration options @@ -80,7 +80,7 @@ Option | Type | Notes name | `string` | **Required.** Name of the field. When not set - inferred from **fields** array key (read about [shorthand field definition](#shorthand-field-definitions) below) type | `Type` | **Required.** An instance of internal or custom type. Note: type must be represented by a single instance within one schema (see also [Type Registry](index.md#type-registry)) args | `array` | An array of possible type arguments. Each entry is expected to be an array with keys: **name**, **type**, **description**, **defaultValue**. See [Field Arguments](#field-arguments) section below. -resolve | `callable` | **function($value, $args, $context, [ResolveInfo](../reference.md#graphqltypedefinitionresolveinfo) $info)**
Given the **$value** of this type, it is expected to return actual value of the current field. See section on [Data Fetching](../data-fetching.md) for details +resolve | `callable` | **function($rootValue, $args, $context, [ResolveInfo](../reference.md#graphqltypedefinitionresolveinfo) $info)**
Given the **$value** of this type, it is expected to return actual value of the current field. See section on [Data Fetching](../data-fetching.md) for details complexity | `callable` | **function($childrenComplexity, $args)**
Used to restrict query complexity. The feature is disabled by default, read about [Security](../security.md#query-complexity-analysis) to use it. description | `string` | Plain-text description of this field for clients (e.g. used by [GraphiQL](https://github.com/graphql/graphiql) for auto-generated documentation) deprecationReason | `string` | Text describing why this field is deprecated. When not empty - field will not be returned by introspection queries (unless forced) diff --git a/examples/01-blog/Blog/Type/CommentType.php b/examples/01-blog/Blog/Type/CommentType.php index cbcad4dac..c61e3384a 100644 --- a/examples/01-blog/Blog/Type/CommentType.php +++ b/examples/01-blog/Blog/Type/CommentType.php @@ -35,12 +35,12 @@ public function __construct() Types::htmlField('body') ]; }, - 'resolveField' => function($value, $args, $context, ResolveInfo $info) { + 'resolveField' => function($rootValue, $args, $context, ResolveInfo $info) { $method = 'resolve' . ucfirst($info->fieldName); if (method_exists($this, $method)) { - return $this->{$method}($value, $args, $context, $info); + return $this->{$method}($rootValue, $args, $context, $info); } else { - return $value->{$info->fieldName}; + return $rootValue->{$info->fieldName}; } } ]; diff --git a/examples/01-blog/Blog/Type/StoryType.php b/examples/01-blog/Blog/Type/StoryType.php index 32cea4e70..1df1f37e5 100644 --- a/examples/01-blog/Blog/Type/StoryType.php +++ b/examples/01-blog/Blog/Type/StoryType.php @@ -75,12 +75,12 @@ public function __construct() 'interfaces' => [ Types::node() ], - 'resolveField' => function($value, $args, $context, ResolveInfo $info) { + 'resolveField' => function($rootValue, $args, $context, ResolveInfo $info) { $method = 'resolve' . ucfirst($info->fieldName); if (method_exists($this, $method)) { - return $this->{$method}($value, $args, $context, $info); + return $this->{$method}($rootValue, $args, $context, $info); } else { - return $value->{$info->fieldName}; + return $rootValue->{$info->fieldName}; } } ]; diff --git a/examples/01-blog/Blog/Type/UserType.php b/examples/01-blog/Blog/Type/UserType.php index 9960b6265..cb5922d1f 100644 --- a/examples/01-blog/Blog/Type/UserType.php +++ b/examples/01-blog/Blog/Type/UserType.php @@ -44,12 +44,12 @@ public function __construct() 'interfaces' => [ Types::node() ], - 'resolveField' => function($value, $args, $context, ResolveInfo $info) { + 'resolveField' => function($rootValue, $args, $context, ResolveInfo $info) { $method = 'resolve' . ucfirst($info->fieldName); if (method_exists($this, $method)) { - return $this->{$method}($value, $args, $context, $info); + return $this->{$method}($rootValue, $args, $context, $info); } else { - return $value->{$info->fieldName}; + return $rootValue->{$info->fieldName}; } } ]; diff --git a/src/Type/Definition/ObjectType.php b/src/Type/Definition/ObjectType.php index 532a9b0f0..d464b4574 100644 --- a/src/Type/Definition/ObjectType.php +++ b/src/Type/Definition/ObjectType.php @@ -200,7 +200,7 @@ public function getInterfaces() } /** - * @param mixed[] $value + * @param mixed $value * @param mixed[]|null $context * * @return bool|null From c069d20ca751933483b056dd808f0476ed4e97c3 Mon Sep 17 00:00:00 2001 From: spawnia Date: Sun, 30 Jun 2019 23:09:32 +0200 Subject: [PATCH 051/256] And a few more in tests --- docs/type-system/schema.md | 2 +- examples/01-blog/Blog/Type/QueryType.php | 4 ++-- tests/Executor/DeferredFieldsTest.php | 14 +++++++------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/type-system/schema.md b/docs/type-system/schema.md index 08ee12fce..e3ae6e052 100644 --- a/docs/type-system/schema.md +++ b/docs/type-system/schema.md @@ -62,7 +62,7 @@ $mutationType = new ObjectType([ 'episode' => $episodeEnum, 'review' => $reviewInputObject ], - 'resolve' => function($val, $args) { + 'resolve' => function($rootValue, $args) { // TODOC } ] diff --git a/examples/01-blog/Blog/Type/QueryType.php b/examples/01-blog/Blog/Type/QueryType.php index ff8557783..b7d807568 100644 --- a/examples/01-blog/Blog/Type/QueryType.php +++ b/examples/01-blog/Blog/Type/QueryType.php @@ -57,8 +57,8 @@ public function __construct() ], 'hello' => Type::string() ], - 'resolveField' => function($val, $args, $context, ResolveInfo $info) { - return $this->{$info->fieldName}($val, $args, $context, $info); + 'resolveField' => function($rootValue, $args, $context, ResolveInfo $info) { + return $this->{$info->fieldName}($rootValue, $args, $context, $info); } ]; parent::__construct($config); diff --git a/tests/Executor/DeferredFieldsTest.php b/tests/Executor/DeferredFieldsTest.php index a4021d21f..6879e8de0 100644 --- a/tests/Executor/DeferredFieldsTest.php +++ b/tests/Executor/DeferredFieldsTest.php @@ -185,7 +185,7 @@ static function ($story) use ($category) { 'fields' => [ 'topStories' => [ 'type' => Type::listOf($this->storyType), - 'resolve' => function ($val, $args, $context, ResolveInfo $info) { + 'resolve' => function ($rootValue, $args, $context, ResolveInfo $info) { $this->paths[] = $info->path; return Utils::filter( @@ -198,7 +198,7 @@ static function ($story) { ], 'featuredCategory' => [ 'type' => $this->categoryType, - 'resolve' => function ($val, $args, $context, ResolveInfo $info) { + 'resolve' => function ($rootValue, $args, $context, ResolveInfo $info) { $this->paths[] = $info->path; return $this->categoryDataSource[0]; @@ -206,7 +206,7 @@ static function ($story) { ], 'categories' => [ 'type' => Type::listOf($this->categoryType), - 'resolve' => function ($val, $args, $context, ResolveInfo $info) { + 'resolve' => function ($rootValue, $args, $context, ResolveInfo $info) { $this->paths[] = $info->path; return $this->categoryDataSource; @@ -401,7 +401,7 @@ public function testComplexRecursiveDeferredFields() : void return [ 'sync' => [ 'type' => Type::string(), - 'resolve' => function ($v, $a, $c, ResolveInfo $info) { + 'resolve' => function ($rootValue, $a, $c, ResolveInfo $info) { $this->paths[] = $info->path; return 'sync'; @@ -409,7 +409,7 @@ public function testComplexRecursiveDeferredFields() : void ], 'deferred' => [ 'type' => Type::string(), - 'resolve' => function ($v, $a, $c, ResolveInfo $info) { + 'resolve' => function ($rootValue, $a, $c, ResolveInfo $info) { $this->paths[] = $info->path; return new Deferred(function () use ($info) { @@ -421,7 +421,7 @@ public function testComplexRecursiveDeferredFields() : void ], 'nest' => [ 'type' => $complexType, - 'resolve' => function ($v, $a, $c, ResolveInfo $info) { + 'resolve' => function ($rootValue, $a, $c, ResolveInfo $info) { $this->paths[] = $info->path; return []; @@ -429,7 +429,7 @@ public function testComplexRecursiveDeferredFields() : void ], 'deferredNest' => [ 'type' => $complexType, - 'resolve' => function ($v, $a, $c, ResolveInfo $info) { + 'resolve' => function ($rootValue, $a, $c, ResolveInfo $info) { $this->paths[] = $info->path; return new Deferred(function () use ($info) { From 3b33167c871429ee86a2cee8b45b4ec802ab9dd7 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Mon, 1 Jul 2019 12:12:01 +0200 Subject: [PATCH 052/256] Rename $rootValue where applicable --- benchmarks/Utils/SchemaGenerator.php | 2 +- docs/type-system/enum-types.md | 2 +- docs/type-system/object-types.md | 4 ++-- examples/00-hello-world/graphql.php | 2 +- examples/01-blog/Blog/Type/CommentType.php | 6 +++--- examples/01-blog/Blog/Type/StoryType.php | 6 +++--- examples/01-blog/Blog/Type/UserType.php | 6 +++--- examples/03-server/graphql.php | 2 +- tests/Executor/DeferredFieldsTest.php | 8 ++++---- tests/Executor/ExecutorTest.php | 2 +- tests/Executor/TestClasses/Adder.php | 2 +- tests/Regression/Issue396Test.php | 4 ++-- tests/Type/IntrospectionTest.php | 2 +- 13 files changed, 24 insertions(+), 24 deletions(-) diff --git a/benchmarks/Utils/SchemaGenerator.php b/benchmarks/Utils/SchemaGenerator.php index 495ad8f5d..1dce8f68d 100644 --- a/benchmarks/Utils/SchemaGenerator.php +++ b/benchmarks/Utils/SchemaGenerator.php @@ -152,7 +152,7 @@ protected function createFieldArgs($fieldName, $typeName) ]; } - public function resolveField($rootValue, $args, $context, $resolveInfo) + public function resolveField($objectValue, $args, $context, $resolveInfo) { return $resolveInfo->fieldName . '-value'; } diff --git a/docs/type-system/enum-types.md b/docs/type-system/enum-types.md index ea62c2bf8..bfab3a549 100644 --- a/docs/type-system/enum-types.md +++ b/docs/type-system/enum-types.md @@ -158,7 +158,7 @@ $heroType = new ObjectType([ 'args' => [ 'episode' => Type::nonNull($enumType) ], - 'resolve' => function($rootValue, $args) { + 'resolve' => function($hero, $args) { return $args['episode'] === 5 ? true : false; } ] diff --git a/docs/type-system/object-types.md b/docs/type-system/object-types.md index c12595ad3..0b950c021 100644 --- a/docs/type-system/object-types.md +++ b/docs/type-system/object-types.md @@ -69,7 +69,7 @@ name | `string` | **Required.** Unique name of this object type within S fields | `array` or `callable` | **Required**. An array describing object fields or callable returning such an array. See [Fields](#field-definitions) section below for expected structure of each array entry. See also the section on [Circular types](#recurring-and-circular-types) for an explanation of when to use callable for this option. description | `string` | Plain-text description of this type for clients (e.g. used by [GraphiQL](https://github.com/graphql/graphiql) for auto-generated documentation) interfaces | `array` or `callable` | List of interfaces implemented by this type or callable returning such a list. See [Interface Types](interfaces.md) for details. See also the section on [Circular types](#recurring-and-circular-types) for an explanation of when to use callable for this option. -isTypeOf | `callable` | **function($rootValue, $context, [ResolveInfo](../reference.md#graphqltypedefinitionresolveinfo) $info)**
Expected to return **true** if **$value** qualifies for this type (see section about [Abstract Type Resolution](interfaces.md#interface-role-in-data-fetching) for explanation). +isTypeOf | `callable` | **function($value, $context, [ResolveInfo](../reference.md#graphqltypedefinitionresolveinfo) $info)**
Expected to return **true** if **$value** qualifies for this type (see section about [Abstract Type Resolution](interfaces.md#interface-role-in-data-fetching) for explanation). resolveField | `callable` | **function($value, $args, $context, [ResolveInfo](../reference.md#graphqltypedefinitionresolveinfo) $info)**
Given the **$value** of this type, it is expected to return value for a field defined in **$info->fieldName**. A good place to define a type-specific strategy for field resolution. See section on [Data Fetching](../data-fetching.md) for details. # Field configuration options @@ -80,7 +80,7 @@ Option | Type | Notes name | `string` | **Required.** Name of the field. When not set - inferred from **fields** array key (read about [shorthand field definition](#shorthand-field-definitions) below) type | `Type` | **Required.** An instance of internal or custom type. Note: type must be represented by a single instance within one schema (see also [Type Registry](index.md#type-registry)) args | `array` | An array of possible type arguments. Each entry is expected to be an array with keys: **name**, **type**, **description**, **defaultValue**. See [Field Arguments](#field-arguments) section below. -resolve | `callable` | **function($rootValue, $args, $context, [ResolveInfo](../reference.md#graphqltypedefinitionresolveinfo) $info)**
Given the **$value** of this type, it is expected to return actual value of the current field. See section on [Data Fetching](../data-fetching.md) for details +resolve | `callable` | **function($objectValue, $args, $context, [ResolveInfo](../reference.md#graphqltypedefinitionresolveinfo) $info)**
Given the **$objectValue** of this type, it is expected to return actual value of the current field. See section on [Data Fetching](../data-fetching.md) for details complexity | `callable` | **function($childrenComplexity, $args)**
Used to restrict query complexity. The feature is disabled by default, read about [Security](../security.md#query-complexity-analysis) to use it. description | `string` | Plain-text description of this field for clients (e.g. used by [GraphiQL](https://github.com/graphql/graphiql) for auto-generated documentation) deprecationReason | `string` | Text describing why this field is deprecated. When not empty - field will not be returned by introspection queries (unless forced) diff --git a/examples/00-hello-world/graphql.php b/examples/00-hello-world/graphql.php index 167b44de3..7f933ad16 100644 --- a/examples/00-hello-world/graphql.php +++ b/examples/00-hello-world/graphql.php @@ -35,7 +35,7 @@ 'x' => ['type' => Type::int()], 'y' => ['type' => Type::int()], ], - 'resolve' => function ($rootValue, $args) { + 'resolve' => function ($calc, $args) { return $args['x'] + $args['y']; }, ], diff --git a/examples/01-blog/Blog/Type/CommentType.php b/examples/01-blog/Blog/Type/CommentType.php index c61e3384a..8a97a3a8f 100644 --- a/examples/01-blog/Blog/Type/CommentType.php +++ b/examples/01-blog/Blog/Type/CommentType.php @@ -35,12 +35,12 @@ public function __construct() Types::htmlField('body') ]; }, - 'resolveField' => function($rootValue, $args, $context, ResolveInfo $info) { + 'resolveField' => function($comment, $args, $context, ResolveInfo $info) { $method = 'resolve' . ucfirst($info->fieldName); if (method_exists($this, $method)) { - return $this->{$method}($rootValue, $args, $context, $info); + return $this->{$method}($comment, $args, $context, $info); } else { - return $rootValue->{$info->fieldName}; + return $comment->{$info->fieldName}; } } ]; diff --git a/examples/01-blog/Blog/Type/StoryType.php b/examples/01-blog/Blog/Type/StoryType.php index 1df1f37e5..1abb8972a 100644 --- a/examples/01-blog/Blog/Type/StoryType.php +++ b/examples/01-blog/Blog/Type/StoryType.php @@ -75,12 +75,12 @@ public function __construct() 'interfaces' => [ Types::node() ], - 'resolveField' => function($rootValue, $args, $context, ResolveInfo $info) { + 'resolveField' => function($story, $args, $context, ResolveInfo $info) { $method = 'resolve' . ucfirst($info->fieldName); if (method_exists($this, $method)) { - return $this->{$method}($rootValue, $args, $context, $info); + return $this->{$method}($story, $args, $context, $info); } else { - return $rootValue->{$info->fieldName}; + return $story->{$info->fieldName}; } } ]; diff --git a/examples/01-blog/Blog/Type/UserType.php b/examples/01-blog/Blog/Type/UserType.php index cb5922d1f..b185f74a5 100644 --- a/examples/01-blog/Blog/Type/UserType.php +++ b/examples/01-blog/Blog/Type/UserType.php @@ -44,12 +44,12 @@ public function __construct() 'interfaces' => [ Types::node() ], - 'resolveField' => function($rootValue, $args, $context, ResolveInfo $info) { + 'resolveField' => function($user, $args, $context, ResolveInfo $info) { $method = 'resolve' . ucfirst($info->fieldName); if (method_exists($this, $method)) { - return $this->{$method}($rootValue, $args, $context, $info); + return $this->{$method}($user, $args, $context, $info); } else { - return $rootValue->{$info->fieldName}; + return $user->{$info->fieldName}; } } ]; diff --git a/examples/03-server/graphql.php b/examples/03-server/graphql.php index 8b73f4422..c0240da57 100644 --- a/examples/03-server/graphql.php +++ b/examples/03-server/graphql.php @@ -35,7 +35,7 @@ 'x' => ['type' => Type::int()], 'y' => ['type' => Type::int()], ], - 'resolve' => function ($rootValue, $args) { + 'resolve' => function ($calc, $args) { return $args['x'] + $args['y']; }, ], diff --git a/tests/Executor/DeferredFieldsTest.php b/tests/Executor/DeferredFieldsTest.php index 787e2c92c..957b5a85b 100644 --- a/tests/Executor/DeferredFieldsTest.php +++ b/tests/Executor/DeferredFieldsTest.php @@ -401,7 +401,7 @@ public function testComplexRecursiveDeferredFields() : void return [ 'sync' => [ 'type' => Type::string(), - 'resolve' => function ($rootValue, $args, $context, ResolveInfo $info) { + 'resolve' => function ($complexType, $args, $context, ResolveInfo $info) { $this->paths[] = $info->path; return 'sync'; @@ -409,7 +409,7 @@ public function testComplexRecursiveDeferredFields() : void ], 'deferred' => [ 'type' => Type::string(), - 'resolve' => function ($rootValue, $args, $context, ResolveInfo $info) { + 'resolve' => function ($complexType, $args, $context, ResolveInfo $info) { $this->paths[] = $info->path; return new Deferred(function () use ($info) { @@ -421,7 +421,7 @@ public function testComplexRecursiveDeferredFields() : void ], 'nest' => [ 'type' => $complexType, - 'resolve' => function ($rootValue, $args, $context, ResolveInfo $info) { + 'resolve' => function ($complexType, $args, $context, ResolveInfo $info) { $this->paths[] = $info->path; return []; @@ -429,7 +429,7 @@ public function testComplexRecursiveDeferredFields() : void ], 'deferredNest' => [ 'type' => $complexType, - 'resolve' => function ($rootValue, $args, $context, ResolveInfo $info) { + 'resolve' => function ($complexType, $args, $context, ResolveInfo $info) { $this->paths[] = $info->path; return new Deferred(function () use ($info) { diff --git a/tests/Executor/ExecutorTest.php b/tests/Executor/ExecutorTest.php index 76b4beb15..eb8e4a12e 100644 --- a/tests/Executor/ExecutorTest.php +++ b/tests/Executor/ExecutorTest.php @@ -273,7 +273,7 @@ public function testProvidesInfoAboutCurrentExecutionState() : void 'fields' => [ 'test' => [ 'type' => Type::string(), - 'resolve' => static function ($rootValue, $args, $ctx, $_info) use (&$info) { + 'resolve' => static function ($test, $args, $ctx, $_info) use (&$info) { $info = $_info; }, ], diff --git a/tests/Executor/TestClasses/Adder.php b/tests/Executor/TestClasses/Adder.php index 416cd5e73..562aba6e0 100644 --- a/tests/Executor/TestClasses/Adder.php +++ b/tests/Executor/TestClasses/Adder.php @@ -16,7 +16,7 @@ public function __construct(float $num) { $this->num = $num; - $this->test = function ($rootValue, $args, $context) { + $this->test = function ($objectValue, $args, $context) { return $this->num + $args['addend1'] + $context['addend2']; }; } diff --git a/tests/Regression/Issue396Test.php b/tests/Regression/Issue396Test.php index f7c26a9ee..ca5e5c006 100644 --- a/tests/Regression/Issue396Test.php +++ b/tests/Regression/Issue396Test.php @@ -30,7 +30,7 @@ public function testUnionResolveType() $unionResult = new UnionType([ 'name' => 'UnionResult', 'types' => [$a, $b, $c], - 'resolveType' => static function ($result, $rootValue, ResolveInfo $info) use ($a, $b, $c, &$log) : Type { + 'resolveType' => static function ($result, $value, ResolveInfo $info) use ($a, $b, $c, &$log) : Type { $log[] = [$result, $info->path]; if (stristr($result['name'], 'A')) { return $a; @@ -97,7 +97,7 @@ public function testInterfaceResolveType() 'fields' => [ 'name' => Type::string(), ], - 'resolveType' => static function ($result, $rootValue, ResolveInfo $info) use (&$a, &$b, &$c, &$log) : Type { + 'resolveType' => static function ($result, $value, ResolveInfo $info) use (&$a, &$b, &$c, &$log) : Type { $log[] = [$result, $info->path]; if (stristr($result['name'], 'A')) { return $a; diff --git a/tests/Type/IntrospectionTest.php b/tests/Type/IntrospectionTest.php index afea3d98c..d38bda1bd 100644 --- a/tests/Type/IntrospectionTest.php +++ b/tests/Type/IntrospectionTest.php @@ -1049,7 +1049,7 @@ public function testIntrospectsOnInputObject() : void 'field' => [ 'type' => Type::string(), 'args' => ['complex' => ['type' => $TestInputObject]], - 'resolve' => static function ($rootValue, $args) { + 'resolve' => static function ($testType, $args) { return json_encode($args['complex']); }, ], From 8c4e7b178d6c744de72c93b47e8a71d97e7fb862 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Mon, 1 Jul 2019 12:17:04 +0200 Subject: [PATCH 053/256] Fix up some overly eager renamings in the docs --- docs/data-fetching.md | 35 +++++++++++++++++------------------ docs/reference.md | 4 ++-- src/Executor/Executor.php | 18 +++++++++--------- 3 files changed, 28 insertions(+), 29 deletions(-) diff --git a/docs/data-fetching.md b/docs/data-fetching.md index 349e702c3..c5152ba5f 100644 --- a/docs/data-fetching.md +++ b/docs/data-fetching.md @@ -103,25 +103,25 @@ for a field you simply override this default resolver. **graphql-php** provides following default field resolver: ```php fieldName; - $property = null; - - if (is_array($rootValue) || $rootValue instanceof ArrayAccess) { - if (isset($rootValue[$fieldName])) { - $property = $rootValue[$fieldName]; +function defaultFieldResolver($objectValue, $args, $context, \GraphQL\Type\Definition\ResolveInfo $info) + { + $fieldName = $info->fieldName; + $property = null; + + if (is_array($objectValue) || $objectValue instanceof \ArrayAccess) { + if (isset($objectValue[$fieldName])) { + $property = $objectValue[$fieldName]; + } + } elseif (is_object($objectValue)) { + if (isset($objectValue->{$fieldName})) { + $property = $objectValue->{$fieldName}; + } } - } elseif (is_object($rootValue)) { - if (isset($rootValue->{$fieldName})) { - $property = $rootValue->{$fieldName}; - } - } - return $property instanceof Closure - ? $property($rootValue, $args, $context, $info) - : $property; -} + return $property instanceof Closure + ? $property($objectValue, $args, $context, $info) + : $property; + } ``` As you see it returns value by key (for arrays) or property (for objects). @@ -163,7 +163,6 @@ $userType = new ObjectType([ Keep in mind that **field resolver** has precedence over **default field resolver per type** which in turn has precedence over **default field resolver**. - # Solving N+1 Problem Since: 0.9.0 diff --git a/docs/reference.md b/docs/reference.md index 36d4d1ab8..49f2372f5 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -33,7 +33,7 @@ See [related documentation](executing-queries.md). * fieldResolver: * A resolver function to use when one is not provided by the schema. * If not provided, the default field resolver is used (which looks for a - * value on the root value with the field's name). + * value on the object value with the field's name). * validationRules: * A set of rules for query validation step. Default value is all available rules. * Empty array would allow to skip query validation (may be convenient for persisted @@ -998,7 +998,7 @@ visitor API: * * @api */ -static function visit($rootValue, $visitor, $keyMap = null) +static function visit($root, $visitor, $keyMap = null) ``` ```php diff --git a/src/Executor/Executor.php b/src/Executor/Executor.php index 2877f9d48..764fa1c59 100644 --- a/src/Executor/Executor.php +++ b/src/Executor/Executor.php @@ -161,29 +161,29 @@ public static function promiseToExecute( * and returns it as the result, or if it's a function, returns the result * of calling that function while passing along args and context. * - * @param mixed $rootValue + * @param mixed $objectValue * @param mixed[] $args * @param mixed|null $context * * @return mixed|null */ - public static function defaultFieldResolver($rootValue, $args, $context, ResolveInfo $info) + public static function defaultFieldResolver($objectValue, $args, $context, ResolveInfo $info) { $fieldName = $info->fieldName; $property = null; - if (is_array($rootValue) || $rootValue instanceof ArrayAccess) { - if (isset($rootValue[$fieldName])) { - $property = $rootValue[$fieldName]; + if (is_array($objectValue) || $objectValue instanceof ArrayAccess) { + if (isset($objectValue[$fieldName])) { + $property = $objectValue[$fieldName]; } - } elseif (is_object($rootValue)) { - if (isset($rootValue->{$fieldName})) { - $property = $rootValue->{$fieldName}; + } elseif (is_object($objectValue)) { + if (isset($objectValue->{$fieldName})) { + $property = $objectValue->{$fieldName}; } } return $property instanceof Closure - ? $property($rootValue, $args, $context, $info) + ? $property($objectValue, $args, $context, $info) : $property; } } From a502c3325480aa506a83b519f578e868fcb92d82 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Wed, 10 Jul 2019 17:46:07 +0300 Subject: [PATCH 054/256] Upgrade PHPStan to 0.11.12 --- composer.json | 2 +- src/Utils/BuildSchema.php | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index b68861858..918cbcaca 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ "require-dev": { "doctrine/coding-standard": "^6.0", "phpbench/phpbench": "^0.14.0", - "phpstan/phpstan": "^0.11.8", + "phpstan/phpstan": "^0.11.12", "phpstan/phpstan-phpunit": "^0.11.2", "phpstan/phpstan-strict-rules": "^0.11.1", "phpunit/phpcov": "^5.0", diff --git a/src/Utils/BuildSchema.php b/src/Utils/BuildSchema.php index 3039aefe3..1aa4f68a7 100644 --- a/src/Utils/BuildSchema.php +++ b/src/Utils/BuildSchema.php @@ -159,9 +159,10 @@ static function ($def) use ($DefinitionBuilder) { // If specified directives were not explicitly declared, add them. $skip = array_reduce( $directives, - static function ($hasSkip, $directive) { + static function (bool $hasSkip, Directive $directive) : bool { return $hasSkip || $directive->name === 'skip'; - } + }, + false ); if (! $skip) { $directives[] = Directive::skipDirective(); @@ -169,9 +170,10 @@ static function ($hasSkip, $directive) { $include = array_reduce( $directives, - static function ($hasInclude, $directive) { + static function (bool $hasInclude, Directive $directive) : bool { return $hasInclude || $directive->name === 'include'; - } + }, + false ); if (! $include) { $directives[] = Directive::includeDirective(); @@ -179,9 +181,10 @@ static function ($hasInclude, $directive) { $deprecated = array_reduce( $directives, - static function ($hasDeprecated, $directive) { + static function (bool $hasDeprecated, Directive $directive) : bool { return $hasDeprecated || $directive->name === 'deprecated'; - } + }, + false ); if (! $deprecated) { $directives[] = Directive::deprecatedDirective(); From 1ac5af1d8b4d1b4c96973cbc1a0900ce28ed2535 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Fri, 12 Jul 2019 21:01:10 +0300 Subject: [PATCH 055/256] Fix internal directives --- src/Type/Definition/Directive.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Type/Definition/Directive.php b/src/Type/Definition/Directive.php index 3f71e61c4..75bc03407 100644 --- a/src/Type/Definition/Directive.php +++ b/src/Type/Definition/Directive.php @@ -20,8 +20,8 @@ class Directive public const DEPRECATED_NAME = 'deprecated'; public const REASON_ARGUMENT_NAME = 'reason'; - /** @var Directive[] */ - public static $internalDirectives = []; + /** @var Directive[]|null */ + public static $internalDirectives; // Schema Definitions From 5f19bd39239ffb7710e829ac2d691bfe0c8ff27c Mon Sep 17 00:00:00 2001 From: Romain VALAT <37904498+sukano@users.noreply.github.com> Date: Fri, 12 Jul 2019 20:35:58 +0200 Subject: [PATCH 056/256] use constant for directive name Co-Authored-By: Jeremiah VALERIE --- src/Validator/Rules/QueryComplexity.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Validator/Rules/QueryComplexity.php b/src/Validator/Rules/QueryComplexity.php index 9f91e1e8b..5b04d29a5 100644 --- a/src/Validator/Rules/QueryComplexity.php +++ b/src/Validator/Rules/QueryComplexity.php @@ -206,7 +206,7 @@ static function ($error) { return ! $directiveArgsIf; } - if ($directiveNode->name->value === 'skip') { + if (Directive::SKIP_NAME === $directiveNode->name->value) { $directive = Directive::skipDirective(); /** @var bool $directiveArgsIf */ $directiveArgsIf = Values::getArgumentValues($directive, $directiveNode, $variableValues)['if']; From 01e2aa369c578540e1c8c4a0e269088fd90fbdc7 Mon Sep 17 00:00:00 2001 From: spawnia Date: Sun, 14 Jul 2019 12:28:16 +0200 Subject: [PATCH 057/256] Fix codestyle --- src/Validator/ValidationContext.php | 3 ++- tests/Language/ParserTest.php | 2 +- tests/Type/DefinitionTest.php | 4 ++-- tests/Utils/BuildSchemaTest.php | 12 ++++++------ 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/Validator/ValidationContext.php b/src/Validator/ValidationContext.php index b5dd55d5c..aea1909e6 100644 --- a/src/Validator/ValidationContext.php +++ b/src/Validator/ValidationContext.php @@ -186,9 +186,10 @@ public function getRecursivelyReferencedFragments(OperationDefinitionNode $opera /** * @param HasSelectionSet|OperationDefinitionNode|FragmentDefinitionNode $node + * * @return FragmentSpreadNode[] */ - public function getFragmentSpreads(HasSelectionSet $node): array + public function getFragmentSpreads(HasSelectionSet $node) : array { $spreads = $this->fragmentSpreads[$node] ?? null; if ($spreads === null) { diff --git a/tests/Language/ParserTest.php b/tests/Language/ParserTest.php index 84c2b2371..5d6655f91 100644 --- a/tests/Language/ParserTest.php +++ b/tests/Language/ParserTest.php @@ -333,7 +333,7 @@ public function testParseCreatesAst() : void '); $result = Parser::parse($source); - $loc = static function (int $start, int $end): array { + $loc = static function (int $start, int $end) : array { return [ 'start' => $start, 'end' => $end, diff --git a/tests/Type/DefinitionTest.php b/tests/Type/DefinitionTest.php index 78d91d5a3..d1967ea54 100644 --- a/tests/Type/DefinitionTest.php +++ b/tests/Type/DefinitionTest.php @@ -709,7 +709,7 @@ public function testAllowsShorthandFieldDefinition() : void /** @var InterfaceType $SomeInterface */ $SomeInterface = $schema->getType('SomeInterface'); - $valueField = $SomeInterface->getField('value'); + $valueField = $SomeInterface->getField('value'); self::assertEquals(Type::string(), $valueField->getType()); $nestedField = $SomeInterface->getField('nested'); @@ -722,7 +722,7 @@ public function testAllowsShorthandFieldDefinition() : void self::assertEquals(Type::int(), $withArg->args[0]->getType()); /** @var ObjectType $Query */ - $Query = $schema->getType('Query'); + $Query = $schema->getType('Query'); $testField = $Query->getField('test'); self::assertEquals($interface, $testField->getType()); self::assertEquals('test', $testField->name); diff --git a/tests/Utils/BuildSchemaTest.php b/tests/Utils/BuildSchemaTest.php index 1a4ba0d3d..951090bbf 100644 --- a/tests/Utils/BuildSchemaTest.php +++ b/tests/Utils/BuildSchemaTest.php @@ -752,7 +752,7 @@ enum: MyEnum self::assertEquals('Terrible reasons', $otherValue->deprecationReason); /** @var ObjectType $queryType */ - $queryType = $schema->getType('Query'); + $queryType = $schema->getType('Query'); $rootFields = $queryType->getFields(); self::assertEquals($rootFields['field1']->isDeprecated(), true); self::assertEquals($rootFields['field1']->deprecationReason, 'No longer supported'); @@ -800,17 +800,17 @@ interfaceField: String $schema = BuildSchema::buildAST($schemaAST); /** @var ObjectType $query */ - $query = $schema->getType('Query'); + $query = $schema->getType('Query'); /** @var InputObjectType $testInput */ - $testInput = $schema->getType('TestInput'); + $testInput = $schema->getType('TestInput'); /** @var EnumType $testEnum */ - $testEnum = $schema->getType('TestEnum'); + $testEnum = $schema->getType('TestEnum'); /** @var UnionType $testUnion */ - $testUnion = $schema->getType('TestUnion'); + $testUnion = $schema->getType('TestUnion'); /** @var InterfaceType $testInterface */ $testInterface = $schema->getType('TestInterface'); /** @var ObjectType $testType */ - $testType = $schema->getType('TestType'); + $testType = $schema->getType('TestType'); /** @var ScalarType $testScalar */ $testScalar = $schema->getType('TestScalar'); $testDirective = $schema->getDirective('test'); From 52a3be0489a7e4642f95fff0dbc858d4dc518332 Mon Sep 17 00:00:00 2001 From: spawnia Date: Tue, 16 Jul 2019 18:22:55 +0200 Subject: [PATCH 058/256] Reduce overall amount of errors and ignore some that are hard to get rid of --- docs/reference.md | 2 +- phpstan.neon.dist | 7 ++++- src/Executor/ReferenceExecutor.php | 11 ++++--- src/Executor/Values.php | 21 ++++++++++++-- src/Experimental/Executor/Collector.php | 5 ++-- .../Executor/CoroutineExecutor.php | 10 +++++++ src/Experimental/Executor/Runtime.php | 8 +++++ src/Language/AST/ArgumentNode.php | 2 +- src/Language/AST/DefinitionNode.php | 2 +- src/Language/AST/FieldDefinitionNode.php | 2 +- src/Language/AST/HasSelectionSet.php | 10 ++++--- src/Language/AST/InputValueDefinitionNode.php | 4 +-- src/Language/AST/ListTypeNode.php | 2 +- src/Language/AST/ObjectFieldNode.php | 2 +- src/Language/AST/TypeSystemDefinitionNode.php | 2 ++ src/Language/AST/VariableDefinitionNode.php | 4 +-- src/Type/Definition/AbstractType.php | 11 ++++--- src/Type/Definition/EnumType.php | 10 +++++-- src/Type/Definition/FieldArgument.php | 4 +-- src/Type/Definition/InputType.php | 23 +++++++-------- src/Type/Definition/NamedType.php | 17 +++++------ src/Type/Definition/QueryPlan.php | 4 +-- src/Type/Definition/Type.php | 4 +-- src/Type/Schema.php | 8 +++-- src/Type/SchemaValidationContext.php | 16 +++++----- src/Utils/AST.php | 14 ++++----- src/Utils/ASTDefinitionBuilder.php | 8 +++++ src/Utils/BreakingChangesFinder.php | 29 +++++++++++++------ src/Utils/BuildSchema.php | 5 ++-- src/Utils/SchemaExtender.php | 3 +- src/Utils/TypeComparators.php | 6 ++-- src/Utils/TypeInfo.php | 6 ++-- src/Utils/Value.php | 3 +- src/Validator/Rules/ExecutableDefinitions.php | 2 ++ src/Validator/Rules/ValuesOfCorrectType.php | 8 ++++- src/Validator/ValidationContext.php | 24 ++++++++++----- tests/Experimental/Executor/CollectorTest.php | 1 - tests/Language/VisitorTest.php | 25 +++++++++++----- 38 files changed, 212 insertions(+), 113 deletions(-) diff --git a/docs/reference.md b/docs/reference.md index 49f2372f5..e4b182e4e 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -2240,7 +2240,7 @@ static function astFromValue($value, GraphQL\Type\Definition\InputType $type) * | Enum Value | Mixed | * | Null Value | null | * - * @param ValueNode|null $valueNode + * @param VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode|null $valueNode * @param mixed[]|null $variables * * @return mixed[]|stdClass|null diff --git a/phpstan.neon.dist b/phpstan.neon.dist index c870aca34..0b9fc06d0 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -15,7 +15,12 @@ parameters: # This commonly happens in the parts of the code that deal with the GraphQL # type system where we can currently use interfaces and lose type safety. # Until we find a better way, we can list related error's here. - - "~Call to an undefined method GraphQL\\\\Type\\\\Definition\\\\Type::getField()~" + - "~Access to an undefined property GraphQL\\\\Type\\\\Definition\\\\NamedType::\\$name~" + - "~Access to an undefined property GraphQL\\\\Language\\\\AST\\\\DefinitionNode::\\$name~" + # Those come from graphql-php\tests\Language\VisitorTest.php + - "~Access to an undefined property GraphQL\\\\Language\\\\AST\\\\.+::\\$didEnter~" + - "~Access to an undefined property GraphQL\\\\Language\\\\AST\\\\.+::\\$didLeave~" + - "~Access to an undefined property GraphQL\\\\Language\\\\AST\\\\Node::\\$value~" includes: - vendor/phpstan/phpstan-phpunit/extension.neon diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 9727a526e..f51219aa8 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -30,6 +30,7 @@ use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ResolveInfo; use GraphQL\Type\Definition\Type; +use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Introspection; use GraphQL\Type\Schema; use GraphQL\Utils\TypeInfo; @@ -1053,8 +1054,9 @@ private function completeAbstractValue(AbstractType $returnType, $fieldNodes, Re * Otherwise, test each possible type for the abstract type by calling * isTypeOf for the object being coerced, returning the first type that matches. * - * @param mixed|null $value - * @param mixed|null $context + * @param mixed|null $value + * @param mixed|null $context + * @param InterfaceType|UnionType $abstractType * * @return ObjectType|Promise|null */ @@ -1309,8 +1311,9 @@ private function promiseForAssocArray(array $assoc) } /** - * @param string|ObjectType|null $runtimeTypeOrName - * @param mixed $result + * @param string|ObjectType|null $runtimeTypeOrName + * @param InterfaceType|UnionType $returnType + * @param mixed $result * * @return ObjectType */ diff --git a/src/Executor/Values.php b/src/Executor/Values.php index e80f868cf..a7dee1b13 100644 --- a/src/Executor/Values.php +++ b/src/Executor/Values.php @@ -6,22 +6,34 @@ use GraphQL\Error\Error; use GraphQL\Language\AST\ArgumentNode; +use GraphQL\Language\AST\BooleanValueNode; use GraphQL\Language\AST\DirectiveNode; use GraphQL\Language\AST\EnumValueDefinitionNode; +use GraphQL\Language\AST\EnumValueNode; use GraphQL\Language\AST\FieldDefinitionNode; use GraphQL\Language\AST\FieldNode; +use GraphQL\Language\AST\FloatValueNode; use GraphQL\Language\AST\FragmentSpreadNode; use GraphQL\Language\AST\InlineFragmentNode; +use GraphQL\Language\AST\IntValueNode; +use GraphQL\Language\AST\ListValueNode; use GraphQL\Language\AST\Node; use GraphQL\Language\AST\NodeList; +use GraphQL\Language\AST\NullValueNode; +use GraphQL\Language\AST\ObjectValueNode; +use GraphQL\Language\AST\StringValueNode; use GraphQL\Language\AST\ValueNode; use GraphQL\Language\AST\VariableDefinitionNode; use GraphQL\Language\AST\VariableNode; use GraphQL\Language\Printer; use GraphQL\Type\Definition\Directive; +use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\FieldDefinition; +use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\InputType; +use GraphQL\Type\Definition\ListOfType; use GraphQL\Type\Definition\NonNull; +use GraphQL\Type\Definition\ScalarType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Schema; use GraphQL\Utils\AST; @@ -246,12 +258,13 @@ public static function getArgumentValuesForMap($fieldDefinition, $argumentValueM /** * @deprecated as of 8.0 (Moved to \GraphQL\Utils\AST::valueFromAST) * - * @param ValueNode $valueNode - * @param mixed[]|null $variables + * @param VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode $valueNode + * @param ScalarType|EnumType|InputObjectType|ListOfType|NonNull $type + * @param mixed[]|null $variables * * @return mixed[]|stdClass|null */ - public static function valueFromAST($valueNode, InputType $type, ?array $variables = null) + public static function valueFromAST(ValueNode $valueNode, InputType $type, ?array $variables = null) { return AST::valueFromAST($valueNode, $type, $variables); } @@ -262,6 +275,8 @@ public static function valueFromAST($valueNode, InputType $type, ?array $variabl * @param mixed[] $value * * @return string[] + * + * @paarm ScalarType|EnumType|InputObjectType|ListOfType|NonNull $type */ public static function isValidPHPValue($value, InputType $type) { diff --git a/src/Experimental/Executor/Collector.php b/src/Experimental/Executor/Collector.php index 843db9b27..61413d667 100644 --- a/src/Experimental/Executor/Collector.php +++ b/src/Experimental/Executor/Collector.php @@ -149,11 +149,10 @@ private function doCollectFields(ObjectType $runtimeType, ?SelectionSetNode $sel foreach ($selectionSet->selections as $selection) { /** @var FieldNode|FragmentSpreadNode|InlineFragmentNode $selection */ - if (! empty($selection->directives)) { foreach ($selection->directives as $directiveNode) { if ($directiveNode->name->value === Directive::SKIP_NAME) { - /** @var ValueNode|null $condition */ + /** @var VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode|null $condition */ $condition = null; foreach ($directiveNode->arguments as $argumentNode) { if ($argumentNode->name->value === Directive::IF_ARGUMENT_NAME) { @@ -173,7 +172,7 @@ private function doCollectFields(ObjectType $runtimeType, ?SelectionSetNode $sel } } } elseif ($directiveNode->name->value === Directive::INCLUDE_NAME) { - /** @var ValueNode|null $condition */ + /** @var VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode|null $condition */ $condition = null; foreach ($directiveNode->arguments as $argumentNode) { if ($argumentNode->name->value === Directive::IF_ARGUMENT_NAME) { diff --git a/src/Experimental/Executor/CoroutineExecutor.php b/src/Experimental/Executor/CoroutineExecutor.php index f823aeae1..b754427cc 100644 --- a/src/Experimental/Executor/CoroutineExecutor.php +++ b/src/Experimental/Executor/CoroutineExecutor.php @@ -18,6 +18,8 @@ use GraphQL\Language\AST\ValueNode; use GraphQL\Type\Definition\AbstractType; use GraphQL\Type\Definition\CompositeType; +use GraphQL\Type\Definition\EnumType; +use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\InputType; use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\LeafType; @@ -25,6 +27,7 @@ use GraphQL\Type\Definition\NonNull; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ResolveInfo; +use GraphQL\Type\Definition\ScalarType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Introspection; @@ -250,6 +253,8 @@ private function finishExecute($value, array $errors) : ExecutionResult /** * @internal + * + * @param ScalarType|EnumType|InputObjectType|ListOfType|NonNull $type */ public function evaluate(ValueNode $valueNode, InputType $type) { @@ -898,6 +903,11 @@ private function mergeSelectionSets(CoroutineContext $ctx) return $ctx->shared->mergedSelectionSet = new SelectionSetNode(['selections' => $selections]); } + /** + * @param InterfaceType|UnionType $abstractType + * + * @return Generator|ObjectType|Type|null + */ private function resolveTypeSlow(CoroutineContext $ctx, $value, AbstractType $abstractType) { if ($value !== null && diff --git a/src/Experimental/Executor/Runtime.php b/src/Experimental/Executor/Runtime.php index f8dc14ad8..a5c8fa6d7 100644 --- a/src/Experimental/Executor/Runtime.php +++ b/src/Experimental/Executor/Runtime.php @@ -5,13 +5,21 @@ namespace GraphQL\Experimental\Executor; use GraphQL\Language\AST\ValueNode; +use GraphQL\Type\Definition\EnumType; +use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\InputType; +use GraphQL\Type\Definition\ListOfType; +use GraphQL\Type\Definition\NonNull; +use GraphQL\Type\Definition\ScalarType; /** * @internal */ interface Runtime { + /** + * @param ScalarType|EnumType|InputObjectType|ListOfType|NonNull $type + */ public function evaluate(ValueNode $valueNode, InputType $type); public function addError($error); diff --git a/src/Language/AST/ArgumentNode.php b/src/Language/AST/ArgumentNode.php index 545a855ce..0abfb6fdc 100644 --- a/src/Language/AST/ArgumentNode.php +++ b/src/Language/AST/ArgumentNode.php @@ -9,7 +9,7 @@ class ArgumentNode extends Node /** @var string */ public $kind = NodeKind::ARGUMENT; - /** @var ValueNode */ + /** @var VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode */ public $value; /** @var NameNode */ diff --git a/src/Language/AST/DefinitionNode.php b/src/Language/AST/DefinitionNode.php index b3255d42d..c331e2342 100644 --- a/src/Language/AST/DefinitionNode.php +++ b/src/Language/AST/DefinitionNode.php @@ -7,7 +7,7 @@ /** * export type DefinitionNode = * | ExecutableDefinitionNode - * | TypeSystemDefinitionNode; // experimental non-spec addition. + * | TypeSystemDefinitionNode; */ interface DefinitionNode { diff --git a/src/Language/AST/FieldDefinitionNode.php b/src/Language/AST/FieldDefinitionNode.php index 9e48f1980..950db0fd5 100644 --- a/src/Language/AST/FieldDefinitionNode.php +++ b/src/Language/AST/FieldDefinitionNode.php @@ -15,7 +15,7 @@ class FieldDefinitionNode extends Node /** @var InputValueDefinitionNode[]|NodeList */ public $arguments; - /** @var TypeNode */ + /** @var NamedTypeNode|ListTypeNode|NonNullTypeNode */ public $type; /** @var DirectiveNode[]|NodeList */ diff --git a/src/Language/AST/HasSelectionSet.php b/src/Language/AST/HasSelectionSet.php index a80bcaee0..a412cf7d4 100644 --- a/src/Language/AST/HasSelectionSet.php +++ b/src/Language/AST/HasSelectionSet.php @@ -4,10 +4,12 @@ namespace GraphQL\Language\AST; +/** + * export type DefinitionNode = OperationDefinitionNode + * | FragmentDefinitionNode + * + * @property SelectionSetNode $selectionSet + */ interface HasSelectionSet { - /** - * export type DefinitionNode = OperationDefinitionNode - * | FragmentDefinitionNode - */ } diff --git a/src/Language/AST/InputValueDefinitionNode.php b/src/Language/AST/InputValueDefinitionNode.php index afbaabbfa..647de0923 100644 --- a/src/Language/AST/InputValueDefinitionNode.php +++ b/src/Language/AST/InputValueDefinitionNode.php @@ -12,10 +12,10 @@ class InputValueDefinitionNode extends Node /** @var NameNode */ public $name; - /** @var TypeNode */ + /** @var NamedTypeNode|ListTypeNode|NonNullTypeNode */ public $type; - /** @var ValueNode */ + /** @var VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode */ public $defaultValue; /** @var DirectiveNode[] */ diff --git a/src/Language/AST/ListTypeNode.php b/src/Language/AST/ListTypeNode.php index 29e81f9de..6bb3902b4 100644 --- a/src/Language/AST/ListTypeNode.php +++ b/src/Language/AST/ListTypeNode.php @@ -9,6 +9,6 @@ class ListTypeNode extends Node implements TypeNode /** @var string */ public $kind = NodeKind::LIST_TYPE; - /** @var TypeNode */ + /** @var NamedTypeNode|ListTypeNode|NonNullTypeNode */ public $type; } diff --git a/src/Language/AST/ObjectFieldNode.php b/src/Language/AST/ObjectFieldNode.php index cd458e8dc..bcced05a3 100644 --- a/src/Language/AST/ObjectFieldNode.php +++ b/src/Language/AST/ObjectFieldNode.php @@ -12,6 +12,6 @@ class ObjectFieldNode extends Node /** @var NameNode */ public $name; - /** @var ValueNode */ + /** @var VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode */ public $value; } diff --git a/src/Language/AST/TypeSystemDefinitionNode.php b/src/Language/AST/TypeSystemDefinitionNode.php index 65553ab90..4a31f2e28 100644 --- a/src/Language/AST/TypeSystemDefinitionNode.php +++ b/src/Language/AST/TypeSystemDefinitionNode.php @@ -10,6 +10,8 @@ * | TypeDefinitionNode * | TypeExtensionNode * | DirectiveDefinitionNode + * + * @property NameNode $name */ interface TypeSystemDefinitionNode extends DefinitionNode { diff --git a/src/Language/AST/VariableDefinitionNode.php b/src/Language/AST/VariableDefinitionNode.php index eb76b666c..f46d5b667 100644 --- a/src/Language/AST/VariableDefinitionNode.php +++ b/src/Language/AST/VariableDefinitionNode.php @@ -12,9 +12,9 @@ class VariableDefinitionNode extends Node implements DefinitionNode /** @var VariableNode */ public $variable; - /** @var TypeNode */ + /** @var NamedTypeNode|ListTypeNode|NonNullTypeNode */ public $type; - /** @var ValueNode|null */ + /** @var VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode|null */ public $defaultValue; } diff --git a/src/Type/Definition/AbstractType.php b/src/Type/Definition/AbstractType.php index 356dceb76..e02571549 100644 --- a/src/Type/Definition/AbstractType.php +++ b/src/Type/Definition/AbstractType.php @@ -4,12 +4,11 @@ namespace GraphQL\Type\Definition; -/* -export type GraphQLAbstractType = -GraphQLInterfaceType | -GraphQLUnionType; -*/ - +/** +export type AbstractType = +InterfaceType | +UnionType; + */ interface AbstractType { /** diff --git a/src/Type/Definition/EnumType.php b/src/Type/Definition/EnumType.php index dc83d6907..a24d04990 100644 --- a/src/Type/Definition/EnumType.php +++ b/src/Type/Definition/EnumType.php @@ -27,7 +27,11 @@ class EnumType extends Type implements InputType, OutputType, LeafType, Nullable /** @var EnumValueDefinition[] */ private $values; - /** @var MixedStore */ + /** + * Actually a MixedStore, PHPStan won't let us type it that way + * + * @var MixedStore + */ private $valueLookup; /** @var ArrayObject */ @@ -139,9 +143,9 @@ public function serialize($value) } /** - * @return MixedStore + * Actually returns a MixedStore, PHPStan won't let us type it that way */ - private function getValueLookup() + private function getValueLookup() : MixedStore { if ($this->valueLookup === null) { $this->valueLookup = new MixedStore(); diff --git a/src/Type/Definition/FieldArgument.php b/src/Type/Definition/FieldArgument.php index 3bffe57d7..ed2d37955 100644 --- a/src/Type/Definition/FieldArgument.php +++ b/src/Type/Definition/FieldArgument.php @@ -81,9 +81,9 @@ public static function createMap(array $config) } /** - * @return InputType + * @return ScalarType|EnumType|InputObjectType|ListOfType|NonNull */ - public function getType() + public function getType() : InputType { return $this->type; } diff --git a/src/Type/Definition/InputType.php b/src/Type/Definition/InputType.php index 043765753..d970bff3d 100644 --- a/src/Type/Definition/InputType.php +++ b/src/Type/Definition/InputType.php @@ -4,20 +4,19 @@ namespace GraphQL\Type\Definition; -/* -export type GraphQLInputType = - | GraphQLScalarType - | GraphQLEnumType - | GraphQLInputObjectType - | GraphQLList - | GraphQLNonNull< - | GraphQLScalarType - | GraphQLEnumType - | GraphQLInputObjectType - | GraphQLList, +/** +export type InputType = + | ScalarType + | EnumType + | InputObjectType + | ListOfType + | NonNull< + | ScalarType + | EnumType + | InputObjectType + | ListOfType, >; */ - interface InputType { } diff --git a/src/Type/Definition/NamedType.php b/src/Type/Definition/NamedType.php index 372bcc5ba..d36dff5ab 100644 --- a/src/Type/Definition/NamedType.php +++ b/src/Type/Definition/NamedType.php @@ -4,16 +4,15 @@ namespace GraphQL\Type\Definition; -/* -export type GraphQLNamedType = - | GraphQLScalarType - | GraphQLObjectType - | GraphQLInterfaceType - | GraphQLUnionType - | GraphQLEnumType - | GraphQLInputObjectType; +/** +export type NamedType = + | ScalarType + | ObjectType + | InterfaceType + | UnionType + | EnumType + | InputObjectType; */ - interface NamedType { } diff --git a/src/Type/Definition/QueryPlan.php b/src/Type/Definition/QueryPlan.php index 641686d67..9cd0aec00 100644 --- a/src/Type/Definition/QueryPlan.php +++ b/src/Type/Definition/QueryPlan.php @@ -138,9 +138,9 @@ private function analyzeQueryPlan(ObjectType $parentType, iterable $fieldNodes) } /** - * @return mixed[] + * @param InterfaceType|ObjectType $parentType * - * $parentType InterfaceType|ObjectType. + * @return mixed[] * * @throws Error */ diff --git a/src/Type/Definition/Type.php b/src/Type/Definition/Type.php index 65da9e761..eac87a77f 100644 --- a/src/Type/Definition/Type.php +++ b/src/Type/Definition/Type.php @@ -300,11 +300,9 @@ public static function isCompositeType($type) /** * @param Type $type * - * @return bool - * * @api */ - public static function isAbstractType($type) + public static function isAbstractType($type) : bool { return $type instanceof AbstractType; } diff --git a/src/Type/Schema.php b/src/Type/Schema.php index 7e480e824..4e59a3335 100644 --- a/src/Type/Schema.php +++ b/src/Type/Schema.php @@ -371,11 +371,13 @@ private function defaultTypeLoader($typeName) * * This operation requires full schema scan. Do not use in production environment. * + * @param InterfaceType|UnionType $abstractType + * * @return ObjectType[] * * @api */ - public function getPossibleTypes(AbstractType $abstractType) + public function getPossibleTypes(AbstractType $abstractType) : array { $possibleTypeMap = $this->getPossibleTypeMap(); @@ -413,11 +415,11 @@ private function getPossibleTypeMap() * Returns true if object type is concrete type of given abstract type * (implementation for interfaces and members of union type for unions) * - * @return bool + * @param InterfaceType|UnionType $abstractType * * @api */ - public function isPossibleType(AbstractType $abstractType, ObjectType $possibleType) + public function isPossibleType(AbstractType $abstractType, ObjectType $possibleType) : bool { if ($abstractType instanceof InterfaceType) { return $possibleType->implementsInterface($abstractType); diff --git a/src/Type/SchemaValidationContext.php b/src/Type/SchemaValidationContext.php index a6630189d..e60ea2f3c 100644 --- a/src/Type/SchemaValidationContext.php +++ b/src/Type/SchemaValidationContext.php @@ -10,8 +10,10 @@ use GraphQL\Language\AST\InputValueDefinitionNode; use GraphQL\Language\AST\InterfaceTypeDefinitionNode; use GraphQL\Language\AST\InterfaceTypeExtensionNode; +use GraphQL\Language\AST\ListTypeNode; use GraphQL\Language\AST\NamedTypeNode; use GraphQL\Language\AST\Node; +use GraphQL\Language\AST\NonNullTypeNode; use GraphQL\Language\AST\ObjectTypeDefinitionNode; use GraphQL\Language\AST\ObjectTypeExtensionNode; use GraphQL\Language\AST\SchemaDefinitionNode; @@ -122,7 +124,7 @@ private function addError($error) * @param Type $type * @param string $operation * - * @return TypeNode|TypeDefinitionNode + * @return NamedTypeNode|ListTypeNode|NonNullTypeNode|TypeDefinitionNode */ private function getOperationTypeNode($type, $operation) { @@ -235,9 +237,9 @@ private function getAllDirectiveArgNodes(Directive $directive, $argName) /** * @param string $argName * - * @return TypeNode|null + * @return NamedTypeNode|ListTypeNode|NonNullTypeNode|null */ - private function getDirectiveArgTypeNode(Directive $directive, $argName) + private function getDirectiveArgTypeNode(Directive $directive, $argName) : ?TypeNode { $argNode = $this->getAllDirectiveArgNodes($directive, $argName)[0]; @@ -414,9 +416,9 @@ private function getAllFieldNodes($type, $fieldName) * @param ObjectType|InterfaceType $type * @param string $fieldName * - * @return TypeNode|null + * @return NamedTypeNode|ListTypeNode|NonNullTypeNode|null */ - private function getFieldTypeNode($type, $fieldName) + private function getFieldTypeNode($type, $fieldName) : ?TypeNode { $fieldNode = $this->getFieldNode($type, $fieldName); @@ -465,9 +467,9 @@ private function getAllFieldArgNodes($type, $fieldName, $argName) * @param string $fieldName * @param string $argName * - * @return TypeNode|null + * @return NamedTypeNode|ListTypeNode|NonNullTypeNode|null */ - private function getFieldArgTypeNode($type, $fieldName, $argName) + private function getFieldArgTypeNode($type, $fieldName, $argName) : ?TypeNode { $fieldArgNode = $this->getFieldArgNode($type, $fieldName, $argName); diff --git a/src/Utils/AST.php b/src/Utils/AST.php index 24bd77c84..9e54300a1 100644 --- a/src/Utils/AST.php +++ b/src/Utils/AST.php @@ -313,8 +313,8 @@ public static function astFromValue($value, InputType $type) * | Enum Value | Mixed | * | Null Value | null | * - * @param ValueNode|null $valueNode - * @param mixed[]|null $variables + * @param VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode|null $valueNode + * @param mixed[]|null $variables * * @return mixed[]|stdClass|null * @@ -322,7 +322,7 @@ public static function astFromValue($value, InputType $type) * * @api */ - public static function valueFromAST($valueNode, Type $type, ?array $variables = null) + public static function valueFromAST(?ValueNode $valueNode, Type $type, ?array $variables = null) { $undefined = Utils::undefined(); @@ -411,7 +411,7 @@ static function ($field) { } ); foreach ($fields as $field) { - /** @var ValueNode $fieldNode */ + /** @var VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode $fieldNode */ $fieldName = $field->name; $fieldNode = $fieldNodes[$fieldName] ?? null; @@ -473,12 +473,12 @@ static function ($field) { * Returns true if the provided valueNode is a variable which is not defined * in the set of variables. * - * @param ValueNode $valueNode - * @param mixed[] $variables + * @param VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode $valueNode + * @param mixed[] $variables * * @return bool */ - private static function isMissingVariable($valueNode, $variables) + private static function isMissingVariable(ValueNode $valueNode, $variables) { return $valueNode instanceof VariableNode && (count($variables) === 0 || ! array_key_exists($valueNode->name->value, $variables)); diff --git a/src/Utils/ASTDefinitionBuilder.php b/src/Utils/ASTDefinitionBuilder.php index 7cd96d003..0ffac1923 100644 --- a/src/Utils/ASTDefinitionBuilder.php +++ b/src/Utils/ASTDefinitionBuilder.php @@ -453,6 +453,11 @@ private function makeSchemaDefFromConfig(Node $def, array $config) } } + /** + * @param NamedTypeNode|ListTypeNode|NonNullTypeNode $typeNode + * + * @return NamedTypeNode|ListTypeNode|NonNullTypeNode + */ private function getNamedTypeNode(TypeNode $typeNode) : TypeNode { $namedType = $typeNode; @@ -463,6 +468,9 @@ private function getNamedTypeNode(TypeNode $typeNode) : TypeNode return $namedType; } + /** + * @param NamedTypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode + */ private function buildWrappedType(Type $innerType, TypeNode $inputTypeNode) : Type { if ($inputTypeNode instanceof ListTypeNode) { diff --git a/src/Utils/BreakingChangesFinder.php b/src/Utils/BreakingChangesFinder.php index 533abf6f0..460052ccf 100644 --- a/src/Utils/BreakingChangesFinder.php +++ b/src/Utils/BreakingChangesFinder.php @@ -304,13 +304,19 @@ public static function findFieldsThatChangedTypeOnInputObjectTypes( $newFieldType ); if (! $isSafe) { - $oldFieldTypeString = $oldFieldType instanceof NamedType - ? $oldFieldType->name - : $oldFieldType; - $newFieldTypeString = $newFieldType instanceof NamedType - ? $newFieldType->name - : $newFieldType; - $breakingChanges[] = [ + if ($oldFieldType instanceof NamedType) { + /** @var ScalarType|ObjectType|InterfaceType|UnionType|EnumType|InputObjectType $oldFieldType */ + $oldFieldTypeString = $oldFieldType->name; + } else { + $oldFieldTypeString = $oldFieldType; + } + if ($newFieldType instanceof NamedType) { + /** @var ScalarType|ObjectType|InterfaceType|UnionType|EnumType|InputObjectType $newFieldType */ + $newFieldTypeString = $newFieldType->name; + } else { + $newFieldTypeString = $newFieldType; + } + $breakingChanges[] = [ 'type' => self::BREAKING_CHANGE_FIELD_CHANGED_KIND, 'description' => "${typeName}.${fieldName} changed type from ${oldFieldTypeString} to ${newFieldTypeString}.", ]; @@ -352,8 +358,12 @@ private static function isChangeSafeForInputObjectFieldOrFieldArg( Type $newType ) { if ($oldType instanceof NamedType) { + if (! $newType instanceof NamedType) { + return false; + } + // if they're both named types, see if their names are equivalent - return $newType instanceof NamedType && $oldType->name === $newType->name; + return $oldType->name === $newType->name; } if ($oldType instanceof ListOfType) { @@ -501,10 +511,11 @@ static function ($arg) use ($oldArgDef) { } ); if ($newArgDef !== null) { - $isSafe = self::isChangeSafeForInputObjectFieldOrFieldArg( + $isSafe = self::isChangeSafeForInputObjectFieldOrFieldArg( $oldArgDef->getType(), $newArgDef->getType() ); + /** @var ScalarType|EnumType|InputObjectType|ListOfType|NonNull $oldArgType */ $oldArgType = $oldArgDef->getType(); $oldArgName = $oldArgDef->name; if (! $isSafe) { diff --git a/src/Utils/BuildSchema.php b/src/Utils/BuildSchema.php index 1aa4f68a7..62f610164 100644 --- a/src/Utils/BuildSchema.php +++ b/src/Utils/BuildSchema.php @@ -10,10 +10,10 @@ use GraphQL\Language\AST\EnumTypeDefinitionNode; use GraphQL\Language\AST\InputObjectTypeDefinitionNode; use GraphQL\Language\AST\InterfaceTypeDefinitionNode; -use GraphQL\Language\AST\Node; use GraphQL\Language\AST\ObjectTypeDefinitionNode; use GraphQL\Language\AST\ScalarTypeDefinitionNode; use GraphQL\Language\AST\SchemaDefinitionNode; +use GraphQL\Language\AST\TypeDefinitionNode; use GraphQL\Language\AST\UnionTypeDefinitionNode; use GraphQL\Language\Parser; use GraphQL\Language\Source; @@ -32,7 +32,7 @@ class BuildSchema /** @var DocumentNode */ private $ast; - /** @var Node[] */ + /** @var TypeDefinitionNode[] */ private $nodeMap; /** @var callable|null */ @@ -211,6 +211,7 @@ static function (bool $hasDeprecated, Directive $directive) : bool { 'astNode' => $schemaDef, 'types' => function () use ($DefinitionBuilder) { $types = []; + /** @var ScalarTypeDefinitionNode|ObjectTypeDefinitionNode|InterfaceTypeDefinitionNode|UnionTypeDefinitionNode|EnumTypeDefinitionNode|InputObjectTypeDefinitionNode $def */ foreach ($this->nodeMap as $name => $def) { $types[] = $DefinitionBuilder->buildType($def->name->value); } diff --git a/src/Utils/SchemaExtender.php b/src/Utils/SchemaExtender.php index 6188beb1b..d20df4df7 100644 --- a/src/Utils/SchemaExtender.php +++ b/src/Utils/SchemaExtender.php @@ -28,6 +28,7 @@ use GraphQL\Type\Definition\NamedType; use GraphQL\Type\Definition\NonNull; use GraphQL\Type\Definition\ObjectType; +use GraphQL\Type\Definition\ScalarType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Introspection; @@ -563,7 +564,7 @@ public static function extend(Schema $schema, DocumentNode $documentAST, ?array $typeDefinitionMap, $options, static function (string $typeName) use ($schema) { - /** @var NamedType $existingType */ + /** @var ScalarType|ObjectType|InterfaceType|UnionType|EnumType|InputObjectType $existingType */ $existingType = $schema->getType($typeName); if ($existingType !== null) { return static::extendNamedType($existingType); diff --git a/src/Utils/TypeComparators.php b/src/Utils/TypeComparators.php index 2beb942f7..2555c1f97 100644 --- a/src/Utils/TypeComparators.php +++ b/src/Utils/TypeComparators.php @@ -6,10 +6,12 @@ use GraphQL\Type\Definition\AbstractType; use GraphQL\Type\Definition\CompositeType; +use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\ListOfType; use GraphQL\Type\Definition\NonNull; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; +use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Schema; class TypeComparators @@ -44,8 +46,8 @@ public static function isEqualType(Type $typeA, Type $typeB) * Provided a type and a super type, return true if the first type is either * equal or a subset of the second super type (covariant). * - * @param AbstractType $maybeSubType - * @param AbstractType $superType + * @param InterfaceType|UnionType $maybeSubType + * @param InterfaceType|UnionType $superType * * @return bool */ diff --git a/src/Utils/TypeInfo.php b/src/Utils/TypeInfo.php index 846840794..8a6ebcf0f 100644 --- a/src/Utils/TypeInfo.php +++ b/src/Utils/TypeInfo.php @@ -30,8 +30,10 @@ use GraphQL\Type\Definition\InputType; use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\ListOfType; +use GraphQL\Type\Definition\NonNull; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\OutputType; +use GraphQL\Type\Definition\ScalarType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Definition\WrappingType; @@ -455,9 +457,9 @@ public function getFieldDef() } /** - * @return InputType + * @return ScalarType|EnumType|InputObjectType|ListOfType|NonNull|null */ - public function getInputType() + public function getInputType() : ?InputType { if (! empty($this->inputTypeStack)) { return $this->inputTypeStack[count($this->inputTypeStack) - 1]; diff --git a/src/Utils/Value.php b/src/Utils/Value.php index a427d3e24..532e4afd5 100644 --- a/src/Utils/Value.php +++ b/src/Utils/Value.php @@ -35,7 +35,8 @@ class Value /** * Given a type and any value, return a runtime value coerced to match the type. * - * @param mixed[] $path + * @param ScalarType|EnumType|InputObjectType|ListOfType|NonNull $type + * @param mixed[] $path */ public static function coerceValue($value, InputType $type, $blameNode = null, ?array $path = null) { diff --git a/src/Validator/Rules/ExecutableDefinitions.php b/src/Validator/Rules/ExecutableDefinitions.php index 6a7e534ab..a20bb7b39 100644 --- a/src/Validator/Rules/ExecutableDefinitions.php +++ b/src/Validator/Rules/ExecutableDefinitions.php @@ -9,6 +9,7 @@ use GraphQL\Language\AST\FragmentDefinitionNode; use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\OperationDefinitionNode; +use GraphQL\Language\AST\TypeSystemDefinitionNode; use GraphQL\Language\Visitor; use GraphQL\Validator\ValidationContext; use function sprintf; @@ -32,6 +33,7 @@ public function getVisitor(ValidationContext $context) continue; } + /** @var TypeSystemDefinitionNode $definition */ $context->reportError(new Error( self::nonExecutableDefinitionMessage($definition->name->value), [$definition->name] diff --git a/src/Validator/Rules/ValuesOfCorrectType.php b/src/Validator/Rules/ValuesOfCorrectType.php index 4e8199b60..0bf603531 100644 --- a/src/Validator/Rules/ValuesOfCorrectType.php +++ b/src/Validator/Rules/ValuesOfCorrectType.php @@ -18,11 +18,11 @@ use GraphQL\Language\AST\ObjectValueNode; use GraphQL\Language\AST\StringValueNode; use GraphQL\Language\AST\ValueNode; +use GraphQL\Language\AST\VariableNode; use GraphQL\Language\Printer; use GraphQL\Language\Visitor; use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\EnumValueDefinition; -use GraphQL\Type\Definition\FieldArgument; use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\ListOfType; use GraphQL\Type\Definition\NonNull; @@ -177,6 +177,9 @@ public static function badValueMessage($typeName, $valueName, $message = null) ($message ? "; ${message}" : '.'); } + /** + * @param VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode $node + */ private function isValidScalar(ValidationContext $context, ValueNode $node, $fieldName) { // Report any error at the full type expected by the location. @@ -240,6 +243,9 @@ private function isValidScalar(ValidationContext $context, ValueNode $node, $fie } } + /** + * @param VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode $node + */ private function enumTypeSuggestion($type, ValueNode $node) { if ($type instanceof EnumType) { diff --git a/src/Validator/ValidationContext.php b/src/Validator/ValidationContext.php index aea1909e6..344a3c04c 100644 --- a/src/Validator/ValidationContext.php +++ b/src/Validator/ValidationContext.php @@ -6,16 +6,23 @@ use GraphQL\Error\Error; use GraphQL\Language\AST\DocumentNode; +use GraphQL\Language\AST\FieldNode; use GraphQL\Language\AST\FragmentDefinitionNode; use GraphQL\Language\AST\FragmentSpreadNode; use GraphQL\Language\AST\HasSelectionSet; +use GraphQL\Language\AST\InlineFragmentNode; use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\OperationDefinitionNode; use GraphQL\Language\AST\SelectionSetNode; use GraphQL\Language\AST\VariableNode; use GraphQL\Language\Visitor; +use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\FieldDefinition; +use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\InputType; +use GraphQL\Type\Definition\ListOfType; +use GraphQL\Type\Definition\NonNull; +use GraphQL\Type\Definition\ScalarType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Schema; use GraphQL\Utils\TypeInfo; @@ -185,7 +192,7 @@ public function getRecursivelyReferencedFragments(OperationDefinitionNode $opera } /** - * @param HasSelectionSet|OperationDefinitionNode|FragmentDefinitionNode $node + * @param OperationDefinitionNode|FragmentDefinitionNode $node * * @return FragmentSpreadNode[] */ @@ -203,8 +210,11 @@ public function getFragmentSpreads(HasSelectionSet $node) : array $selection = $set->selections[$i]; if ($selection instanceof FragmentSpreadNode) { $spreads[] = $selection; - } elseif ($selection->selectionSet) { - $setsToVisit[] = $selection->selectionSet; + } else { + /** @var FieldNode|InlineFragmentNode $selection*/ + if ($selection->selectionSet) { + $setsToVisit[] = $selection->selectionSet; + } } } } @@ -264,17 +274,17 @@ public function getParentType() } /** - * @return InputType + * @return ScalarType|EnumType|InputObjectType|ListOfType|NonNull */ - public function getInputType() + public function getInputType() : InputType { return $this->typeInfo->getInputType(); } /** - * @return InputType + * @return ScalarType|EnumType|InputObjectType|ListOfType|NonNull */ - public function getParentInputType() + public function getParentInputType() : InputType { return $this->typeInfo->getParentInputType(); } diff --git a/tests/Experimental/Executor/CollectorTest.php b/tests/Experimental/Executor/CollectorTest.php index 1339e57c8..62dec4feb 100644 --- a/tests/Experimental/Executor/CollectorTest.php +++ b/tests/Experimental/Executor/CollectorTest.php @@ -9,7 +9,6 @@ use GraphQL\Experimental\Executor\Runtime; use GraphQL\Language\AST\DocumentNode; use GraphQL\Language\AST\Node; -use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\OperationDefinitionNode; use GraphQL\Language\AST\ValueNode; use GraphQL\Language\Parser; diff --git a/tests/Language/VisitorTest.php b/tests/Language/VisitorTest.php index 0a2c6b367..bdc135241 100644 --- a/tests/Language/VisitorTest.php +++ b/tests/Language/VisitorTest.php @@ -16,6 +16,11 @@ use GraphQL\Language\Printer; use GraphQL\Language\Visitor; use GraphQL\Tests\Validator\ValidatorTestCase; +use GraphQL\Type\Definition\EnumType; +use GraphQL\Type\Definition\InputObjectType; +use GraphQL\Type\Definition\ListOfType; +use GraphQL\Type\Definition\NonNull; +use GraphQL\Type\Definition\ScalarType; use GraphQL\Type\Definition\Type; use GraphQL\Utils\TypeInfo; use function array_keys; @@ -1385,8 +1390,9 @@ public function testMaintainsTypeInfoDuringVisit() : void $this->checkVisitorFnArgs($ast, func_get_args()); $parentType = $typeInfo->getParentType(); $type = $typeInfo->getType(); - $inputType = $typeInfo->getInputType(); - $visited[] = [ + /** @var ScalarType|EnumType|InputObjectType|ListOfType|NonNull|null $inputType */ + $inputType = $typeInfo->getInputType(); + $visited[] = [ 'enter', $node->kind, $node->kind === 'Name' ? $node->value : null, @@ -1399,8 +1405,9 @@ public function testMaintainsTypeInfoDuringVisit() : void $this->checkVisitorFnArgs($ast, func_get_args()); $parentType = $typeInfo->getParentType(); $type = $typeInfo->getType(); - $inputType = $typeInfo->getInputType(); - $visited[] = [ + /** @var ScalarType|EnumType|InputObjectType|ListOfType|NonNull|null $inputType */ + $inputType = $typeInfo->getInputType(); + $visited[] = [ 'leave', $node->kind, $node->kind === 'Name' ? $node->value : null, @@ -1477,8 +1484,9 @@ public function testMaintainsTypeInfoDuringEdit() : void $this->checkVisitorFnArgs($ast, func_get_args(), true); $parentType = $typeInfo->getParentType(); $type = $typeInfo->getType(); - $inputType = $typeInfo->getInputType(); - $visited[] = [ + /** @var ScalarType|EnumType|InputObjectType|ListOfType|NonNull|null $inputType */ + $inputType = $typeInfo->getInputType(); + $visited[] = [ 'enter', $node->kind, $node->kind === 'Name' ? $node->value : null, @@ -1511,8 +1519,9 @@ public function testMaintainsTypeInfoDuringEdit() : void $this->checkVisitorFnArgs($ast, func_get_args(), true); $parentType = $typeInfo->getParentType(); $type = $typeInfo->getType(); - $inputType = $typeInfo->getInputType(); - $visited[] = [ + /** @var ScalarType|EnumType|InputObjectType|ListOfType|NonNull|null $inputType */ + $inputType = $typeInfo->getInputType(); + $visited[] = [ 'leave', $node->kind, $node->kind === 'Name' ? $node->value : null, From 9ac999e22c979d856e5520527761ff885a70b6fd Mon Sep 17 00:00:00 2001 From: spawnia Date: Tue, 16 Jul 2019 18:42:37 +0200 Subject: [PATCH 059/256] Fix tests --- src/Type/Definition/FieldArgument.php | 4 +++- src/Validator/ValidationContext.php | 4 ++-- tests/Utils/BreakingChangesFinderTest.php | 5 ++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Type/Definition/FieldArgument.php b/src/Type/Definition/FieldArgument.php index ed2d37955..e99828f72 100644 --- a/src/Type/Definition/FieldArgument.php +++ b/src/Type/Definition/FieldArgument.php @@ -81,9 +81,11 @@ public static function createMap(array $config) } /** + * Returns an InputType + * * @return ScalarType|EnumType|InputObjectType|ListOfType|NonNull */ - public function getType() : InputType + public function getType() { return $this->type; } diff --git a/src/Validator/ValidationContext.php b/src/Validator/ValidationContext.php index 344a3c04c..60ced9a7f 100644 --- a/src/Validator/ValidationContext.php +++ b/src/Validator/ValidationContext.php @@ -276,7 +276,7 @@ public function getParentType() /** * @return ScalarType|EnumType|InputObjectType|ListOfType|NonNull */ - public function getInputType() : InputType + public function getInputType() : ?InputType { return $this->typeInfo->getInputType(); } @@ -284,7 +284,7 @@ public function getInputType() : InputType /** * @return ScalarType|EnumType|InputObjectType|ListOfType|NonNull */ - public function getParentInputType() : InputType + public function getParentInputType() : ?InputType { return $this->typeInfo->getParentInputType(); } diff --git a/tests/Utils/BreakingChangesFinderTest.php b/tests/Utils/BreakingChangesFinderTest.php index 898bc2924..5e46d330b 100644 --- a/tests/Utils/BreakingChangesFinderTest.php +++ b/tests/Utils/BreakingChangesFinderTest.php @@ -1177,7 +1177,10 @@ public function testShouldDetectAllBreakingChanges() : void 'name' => 'DirectiveThatRemovesArg', 'locations' => [DirectiveLocation::FIELD_DEFINITION], 'args' => FieldArgument::createMap([ - 'arg1' => ['name' => 'arg1'], + 'arg1' => [ + 'name' => 'arg1', + 'type' => Type::boolean(), + ], ]), ]); $directiveThatRemovesArgNew = new Directive([ From cdb5bf88dc3ca27f4d229b7b0ba7e0a2d3872edb Mon Sep 17 00:00:00 2001 From: spawnia Date: Tue, 16 Jul 2019 19:03:56 +0200 Subject: [PATCH 060/256] Fix one more static analysis issue --- src/Validator/Rules/ValuesOfCorrectType.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Validator/Rules/ValuesOfCorrectType.php b/src/Validator/Rules/ValuesOfCorrectType.php index 0bf603531..d4e6e345d 100644 --- a/src/Validator/Rules/ValuesOfCorrectType.php +++ b/src/Validator/Rules/ValuesOfCorrectType.php @@ -117,6 +117,7 @@ static function ($field) { }, NodeKind::OBJECT_FIELD => static function (ObjectFieldNode $node) use ($context) { $parentType = Type::getNamedType($context->getParentInputType()); + /** @var ScalarType|EnumType|InputObjectType|ListOfType|NonNull $fieldType */ $fieldType = $context->getInputType(); if ($fieldType || ! ($parentType instanceof InputObjectType)) { return; @@ -183,6 +184,7 @@ public static function badValueMessage($typeName, $valueName, $message = null) private function isValidScalar(ValidationContext $context, ValueNode $node, $fieldName) { // Report any error at the full type expected by the location. + /** @var ScalarType|EnumType|InputObjectType|ListOfType|NonNull $locationType */ $locationType = $context->getInputType(); if (! $locationType) { From f2eb60a094defc02df7f63e949be0224767eee4a Mon Sep 17 00:00:00 2001 From: spawnia Date: Tue, 16 Jul 2019 19:18:07 +0200 Subject: [PATCH 061/256] Fix codestyle --- src/Validator/Rules/ValuesOfCorrectType.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Validator/Rules/ValuesOfCorrectType.php b/src/Validator/Rules/ValuesOfCorrectType.php index d4e6e345d..2f496dd3c 100644 --- a/src/Validator/Rules/ValuesOfCorrectType.php +++ b/src/Validator/Rules/ValuesOfCorrectType.php @@ -118,7 +118,7 @@ static function ($field) { NodeKind::OBJECT_FIELD => static function (ObjectFieldNode $node) use ($context) { $parentType = Type::getNamedType($context->getParentInputType()); /** @var ScalarType|EnumType|InputObjectType|ListOfType|NonNull $fieldType */ - $fieldType = $context->getInputType(); + $fieldType = $context->getInputType(); if ($fieldType || ! ($parentType instanceof InputObjectType)) { return; } From bd1425487a03510e0bb9a11efc81c9dbd8345c3a Mon Sep 17 00:00:00 2001 From: spawnia Date: Wed, 17 Jul 2019 21:33:52 +0200 Subject: [PATCH 062/256] Add FieldDefinition to ResolveInfo --- src/Executor/ReferenceExecutor.php | 2 +- src/Type/Definition/ResolveInfo.php | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 057a69dbf..854281ba0 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -532,7 +532,7 @@ private function resolveField(ObjectType $parentType, $rootValue, $fieldNodes, $ $info = new ResolveInfo( $fieldName, $fieldNodes, - $returnType, + $fieldDef, $parentType, $path, $exeContext->schema, diff --git a/src/Type/Definition/ResolveInfo.php b/src/Type/Definition/ResolveInfo.php index 0f453e03b..95bab5e75 100644 --- a/src/Type/Definition/ResolveInfo.php +++ b/src/Type/Definition/ResolveInfo.php @@ -36,6 +36,14 @@ class ResolveInfo */ public $fieldNodes = []; + /** + * The definition of the field being resolved. + * + * @api + * @var FieldDefinition + */ + public $fieldDefinition; + /** * Expected return type of the field being resolved. * @@ -105,7 +113,6 @@ class ResolveInfo /** * @param FieldNode[] $fieldNodes - * @param ScalarType|ObjectType|InterfaceType|UnionType|EnumType|ListOfType|NonNull $returnType * @param string[][] $path * @param FragmentDefinitionNode[] $fragments * @param mixed|null $rootValue @@ -114,7 +121,7 @@ class ResolveInfo public function __construct( string $fieldName, iterable $fieldNodes, - $returnType, + FieldDefinition $fieldDefinition, ObjectType $parentType, array $path, Schema $schema, @@ -125,7 +132,8 @@ public function __construct( ) { $this->fieldName = $fieldName; $this->fieldNodes = $fieldNodes; - $this->returnType = $returnType; + $this->fieldDefinition = $fieldDefinition; + $this->returnType = $fieldDefinition->getType(); $this->parentType = $parentType; $this->path = $path; $this->schema = $schema; From 7f97ee43e83c08e1bead0b7cdbafb3ede4bf0569 Mon Sep 17 00:00:00 2001 From: spawnia Date: Wed, 24 Jul 2019 20:35:26 +0200 Subject: [PATCH 063/256] Simplify and reorder constructor --- src/Executor/ReferenceExecutor.php | 3 +- .../Executor/CoroutineExecutor.php | 3 +- src/Type/Definition/ResolveInfo.php | 57 +++++++++---------- 3 files changed, 30 insertions(+), 33 deletions(-) diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 854281ba0..6d828459e 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -530,9 +530,8 @@ private function resolveField(ObjectType $parentType, $rootValue, $fieldNodes, $ // The resolve function's optional 4th argument is a collection of // information about the current execution state. $info = new ResolveInfo( - $fieldName, - $fieldNodes, $fieldDef, + $fieldNodes, $parentType, $path, $exeContext->schema, diff --git a/src/Experimental/Executor/CoroutineExecutor.php b/src/Experimental/Executor/CoroutineExecutor.php index f823aeae1..b9d2c6f3f 100644 --- a/src/Experimental/Executor/CoroutineExecutor.php +++ b/src/Experimental/Executor/CoroutineExecutor.php @@ -396,9 +396,8 @@ private function spawn(CoroutineContext $ctx) $returnType = $fieldDefinition->getType(); $ctx->resolveInfo = new ResolveInfo( - $ctx->shared->fieldName, + $fieldDefinition, $ctx->shared->fieldNodes, - $returnType, $ctx->type, $ctx->path, $this->schema, diff --git a/src/Type/Definition/ResolveInfo.php b/src/Type/Definition/ResolveInfo.php index 95bab5e75..6e6287164 100644 --- a/src/Type/Definition/ResolveInfo.php +++ b/src/Type/Definition/ResolveInfo.php @@ -21,36 +21,36 @@ class ResolveInfo { /** - * The name of the field being resolved. + * The definition of the field being resolved. * * @api - * @var string + * @var FieldDefinition */ - public $fieldName; + public $fieldDefinition; /** - * AST of all nodes referencing this field in the query. + * The name of the field being resolved. * * @api - * @var FieldNode[] + * @var string */ - public $fieldNodes = []; + public $fieldName; /** - * The definition of the field being resolved. + * Expected return type of the field being resolved. * * @api - * @var FieldDefinition + * @var ScalarType|ObjectType|InterfaceType|UnionType|EnumType|ListOfType|NonNull */ - public $fieldDefinition; + public $returnType; /** - * Expected return type of the field being resolved. + * AST of all nodes referencing this field in the query. * * @api - * @var ScalarType|ObjectType|InterfaceType|UnionType|EnumType|ListOfType|NonNull + * @var FieldNode[] */ - public $returnType; + public $fieldNodes = []; /** * Parent type of the field being resolved. @@ -112,16 +112,15 @@ class ResolveInfo private $queryPlan; /** - * @param FieldNode[] $fieldNodes - * @param string[][] $path - * @param FragmentDefinitionNode[] $fragments - * @param mixed|null $rootValue - * @param mixed[] $variableValues + * @param FieldNode[] $fieldNodes + * @param string[][] $path + * @param FragmentDefinitionNode[] $fragments + * @param mixed|null $rootValue + * @param mixed[] $variableValues */ public function __construct( - string $fieldName, - iterable $fieldNodes, FieldDefinition $fieldDefinition, + iterable $fieldNodes, ObjectType $parentType, array $path, Schema $schema, @@ -130,17 +129,17 @@ public function __construct( ?OperationDefinitionNode $operation, array $variableValues ) { - $this->fieldName = $fieldName; - $this->fieldNodes = $fieldNodes; $this->fieldDefinition = $fieldDefinition; - $this->returnType = $fieldDefinition->getType(); - $this->parentType = $parentType; - $this->path = $path; - $this->schema = $schema; - $this->fragments = $fragments; - $this->rootValue = $rootValue; - $this->operation = $operation; - $this->variableValues = $variableValues; + $this->fieldName = $fieldDefinition->name; + $this->returnType = $fieldDefinition->getType(); + $this->fieldNodes = $fieldNodes; + $this->parentType = $parentType; + $this->path = $path; + $this->schema = $schema; + $this->fragments = $fragments; + $this->rootValue = $rootValue; + $this->operation = $operation; + $this->variableValues = $variableValues; } /** From 95400910f80f1445ae90de72d79d5a745b92ed23 Mon Sep 17 00:00:00 2001 From: spawnia Date: Wed, 24 Jul 2019 21:10:30 +0200 Subject: [PATCH 064/256] Revert renamings --- src/Language/Parser.php | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Language/Parser.php b/src/Language/Parser.php index 57d4d707c..2c7fbeefb 100644 --- a/src/Language/Parser.php +++ b/src/Language/Parser.php @@ -96,18 +96,18 @@ * @method static ScalarTypeDefinitionNode scalarTypeDefinition(Source|string $source, bool[] $options = []) * @method static ObjectTypeDefinitionNode objectTypeDefinition(Source|string $source, bool[] $options = []) * @method static NamedTypeNode[] implementsInterfaces(Source|string $source, bool[] $options = []) - * @method static FieldDefinitionNode[] fieldDefinitions(Source|string $source, bool[] $options = []) + * @method static FieldDefinitionNode[] fieldsDefinition(Source|string $source, bool[] $options = []) * @method static FieldDefinitionNode fieldDefinition(Source|string $source, bool[] $options = []) - * @method static InputValueDefinitionNode[] argumentDefinitions(Source|string $source, bool[] $options = []) + * @method static InputValueDefinitionNode[] argumentsDefinition(Source|string $source, bool[] $options = []) * @method static InputValueDefinitionNode inputValueDefinition(Source|string $source, bool[] $options = []) * @method static InterfaceTypeDefinitionNode interfaceTypeDefinition(Source|string $source, bool[] $options = []) * @method static UnionTypeDefinitionNode unionTypeDefinition(Source|string $source, bool[] $options = []) * @method static NamedTypeNode[] unionMemberTypes(Source|string $source, bool[] $options = []) * @method static EnumTypeDefinitionNode enumTypeDefinition(Source|string $source, bool[] $options = []) - * @method static EnumValueDefinitionNode[] enumValueDefinitions(Source|string $source, bool[] $options = []) + * @method static EnumValueDefinitionNode[] enumValuesDefinition(Source|string $source, bool[] $options = []) * @method static EnumValueDefinitionNode enumValueDefinition(Source|string $source, bool[] $options = []) * @method static InputObjectTypeDefinitionNode inputObjectTypeDefinition(Source|string $source, bool[] $options = []) - * @method static InputValueDefinitionNode[] inputFieldDefinitions(Source|string $source, bool[] $options = []) + * @method static InputValueDefinitionNode[] inputFieldsDefinition(Source|string $source, bool[] $options = []) * @method static TypeExtensionNode typeExtension(Source|string $source, bool[] $options = []) * @method static SchemaTypeExtensionNode schemaTypeExtension(Source|string $source, bool[] $options = []) * @method static ScalarTypeExtensionNode scalarTypeExtension(Source|string $source, bool[] $options = []) @@ -1257,7 +1257,7 @@ private function parseObjectTypeDefinition() $name = $this->parseName(); $interfaces = $this->parseImplementsInterfaces(); $directives = $this->parseDirectives(true); - $fields = $this->parseFieldDefinitions(); + $fields = $this->parseFieldsDefinition(); return new ObjectTypeDefinitionNode([ 'name' => $name, @@ -1299,7 +1299,7 @@ private function parseImplementsInterfaces() * * @throws SyntaxError */ - private function parseFieldDefinitions() + private function parseFieldsDefinition() { // Legacy support for the SDL? if (! empty($this->lexer->options['allowLegacySDLEmptyFields']) && @@ -1333,7 +1333,7 @@ private function parseFieldDefinition() $start = $this->lexer->token; $description = $this->parseDescription(); $name = $this->parseName(); - $args = $this->parseArgumentDefinitions(); + $args = $this->parseArgumentsDefinition(); $this->expect(Token::COLON); $type = $this->parseTypeReference(); $directives = $this->parseDirectives(true); @@ -1353,7 +1353,7 @@ private function parseFieldDefinition() * * @throws SyntaxError */ - private function parseArgumentDefinitions() + private function parseArgumentsDefinition() { if (! $this->peek(Token::PAREN_L)) { return new NodeList([]); @@ -1408,7 +1408,7 @@ private function parseInterfaceTypeDefinition() $this->expectKeyword('interface'); $name = $this->parseName(); $directives = $this->parseDirectives(true); - $fields = $this->parseFieldDefinitions(); + $fields = $this->parseFieldsDefinition(); return new InterfaceTypeDefinitionNode([ 'name' => $name, @@ -1478,7 +1478,7 @@ private function parseEnumTypeDefinition() $this->expectKeyword('enum'); $name = $this->parseName(); $directives = $this->parseDirectives(true); - $values = $this->parseEnumValueDefinitions(); + $values = $this->parseEnumValuesDefinition(); return new EnumTypeDefinitionNode([ 'name' => $name, @@ -1494,7 +1494,7 @@ private function parseEnumTypeDefinition() * * @throws SyntaxError */ - private function parseEnumValueDefinitions() + private function parseEnumValuesDefinition() { return $this->peek(Token::BRACE_L) ? $this->many( @@ -1539,7 +1539,7 @@ private function parseInputObjectTypeDefinition() $this->expectKeyword('input'); $name = $this->parseName(); $directives = $this->parseDirectives(true); - $fields = $this->parseInputFieldDefinitions(); + $fields = $this->parseInputFieldsDefinition(); return new InputObjectTypeDefinitionNode([ 'name' => $name, @@ -1555,7 +1555,7 @@ private function parseInputObjectTypeDefinition() * * @throws SyntaxError */ - private function parseInputFieldDefinitions() + private function parseInputFieldsDefinition() { return $this->peek(Token::BRACE_L) ? $this->many( @@ -1672,7 +1672,7 @@ private function parseObjectTypeExtension() $name = $this->parseName(); $interfaces = $this->parseImplementsInterfaces(); $directives = $this->parseDirectives(true); - $fields = $this->parseFieldDefinitions(); + $fields = $this->parseFieldsDefinition(); if (count($interfaces) === 0 && count($directives) === 0 && @@ -1702,7 +1702,7 @@ private function parseInterfaceTypeExtension() $this->expectKeyword('interface'); $name = $this->parseName(); $directives = $this->parseDirectives(true); - $fields = $this->parseFieldDefinitions(); + $fields = $this->parseFieldsDefinition(); if (count($directives) === 0 && count($fields) === 0 ) { @@ -1758,7 +1758,7 @@ private function parseEnumTypeExtension() $this->expectKeyword('enum'); $name = $this->parseName(); $directives = $this->parseDirectives(true); - $values = $this->parseEnumValueDefinitions(); + $values = $this->parseEnumValuesDefinition(); if (count($directives) === 0 && count($values) === 0 ) { @@ -1785,7 +1785,7 @@ private function parseInputObjectTypeExtension() $this->expectKeyword('input'); $name = $this->parseName(); $directives = $this->parseDirectives(true); - $fields = $this->parseInputFieldDefinitions(); + $fields = $this->parseInputFieldsDefinition(); if (count($directives) === 0 && count($fields) === 0 ) { @@ -1815,7 +1815,7 @@ private function parseDirectiveDefinition() $this->expectKeyword('directive'); $this->expect(Token::AT); $name = $this->parseName(); - $args = $this->parseArgumentDefinitions(); + $args = $this->parseArgumentsDefinition(); $this->expectKeyword('on'); $locations = $this->parseDirectiveLocations(); From 90c52370049713640a08b8004cccbea7e7f8d545 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Fri, 2 Aug 2019 13:07:44 +0200 Subject: [PATCH 065/256] Let's a bit wait more for coverage in Scrutinizer --- .scrutinizer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 9cef7f5f9..de038a3fd 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -20,7 +20,7 @@ build: tools: external_code_coverage: - timeout: 900 + timeout: 3600 build_failure_conditions: - 'elements.rating(<= C).new.exists' # No new classes/methods with a rating of C or worse allowed From a023b02b22915e937e100cf4229aad2a903f8612 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Fri, 2 Aug 2019 13:11:13 +0200 Subject: [PATCH 066/256] Remove invalid typehint --- src/Language/Parser.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Language/Parser.php b/src/Language/Parser.php index 2c7fbeefb..35fbcf32e 100644 --- a/src/Language/Parser.php +++ b/src/Language/Parser.php @@ -236,7 +236,6 @@ public static function parseType($source, array $options = []) /** * Parse partial source by delegating calls to the internal parseX methods. * - * @param Source|string $name * @param bool[] $arguments * * @throws SyntaxError From f27eb3571ed0d1f499f2c0ab348bbbde7b75d3f3 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Fri, 2 Aug 2019 18:53:38 +0700 Subject: [PATCH 067/256] Created FUNDING.yml --- .github/FUNDING.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..0306338fd --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +# These are supported funding model platforms +open_collective: webonyx-graphql-php From 76c229b8ad22f4a7d83d755296b989504ea95b9c Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Fri, 2 Aug 2019 18:54:52 +0700 Subject: [PATCH 068/256] Minor codestyle fix --- src/Language/Parser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Language/Parser.php b/src/Language/Parser.php index 35fbcf32e..8946d4817 100644 --- a/src/Language/Parser.php +++ b/src/Language/Parser.php @@ -236,7 +236,7 @@ public static function parseType($source, array $options = []) /** * Parse partial source by delegating calls to the internal parseX methods. * - * @param bool[] $arguments + * @param bool[] $arguments * * @throws SyntaxError */ From e01b6e0a93fa2dd8a2cdedd8a26ddbd19bad8bdb Mon Sep 17 00:00:00 2001 From: Steve Lacey Date: Tue, 6 Aug 2019 23:13:16 +0700 Subject: [PATCH 069/256] Scalar type ResolverInfo::getFieldSelection support (#529) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Scalar type ResolveInfo::getFieldSelection support Co-Authored-By: Šimon Podlipský --- src/Type/Definition/ResolveInfo.php | 4 ++++ tests/Type/ResolveInfoTest.php | 28 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/Type/Definition/ResolveInfo.php b/src/Type/Definition/ResolveInfo.php index 0f453e03b..822a0fa2b 100644 --- a/src/Type/Definition/ResolveInfo.php +++ b/src/Type/Definition/ResolveInfo.php @@ -178,6 +178,10 @@ public function getFieldSelection($depth = 0) /** @var FieldNode $fieldNode */ foreach ($this->fieldNodes as $fieldNode) { + if ($fieldNode->selectionSet === null) { + continue; + } + $fields = array_merge_recursive( $fields, $this->foldSelectionSet($fieldNode->selectionSet, $depth) diff --git a/tests/Type/ResolveInfoTest.php b/tests/Type/ResolveInfoTest.php index d228a82b8..2ad6127c1 100644 --- a/tests/Type/ResolveInfoTest.php +++ b/tests/Type/ResolveInfoTest.php @@ -181,6 +181,34 @@ public function testFieldSelection() : void self::assertEquals($expectedDeepSelection, $actualDeepSelection); } + public function testFieldSelectionOnScalarTypes() : void + { + $query = ' + query Ping { + ping + } + '; + + $pingPongQuery = new ObjectType([ + 'name' => 'Query', + 'fields' => [ + 'ping' => [ + 'type' => Type::string(), + 'resolve' => static function ($value, $args, $context, ResolveInfo $info) : string { + self::assertEquals([], $info->getFieldSelection()); + + return 'pong'; + }, + ], + ], + ]); + + $schema = new Schema(['query' => $pingPongQuery]); + $result = GraphQL::executeQuery($schema, $query)->toArray(); + + self::assertEquals(['data' => ['ping' => 'pong']], $result); + } + public function testMergedFragmentsFieldSelection() : void { $image = new ObjectType([ From 259daaa94128f6d0f819766009b41fa3fc2f61ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Podlipsk=C3=BD?= Date: Tue, 6 Aug 2019 18:16:56 +0200 Subject: [PATCH 070/256] Update ResolveInfoTest.php --- tests/Type/ResolveInfoTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Type/ResolveInfoTest.php b/tests/Type/ResolveInfoTest.php index 2ad6127c1..d05a44c35 100644 --- a/tests/Type/ResolveInfoTest.php +++ b/tests/Type/ResolveInfoTest.php @@ -205,7 +205,7 @@ public function testFieldSelectionOnScalarTypes() : void $schema = new Schema(['query' => $pingPongQuery]); $result = GraphQL::executeQuery($schema, $query)->toArray(); - + self::assertEquals(['data' => ['ping' => 'pong']], $result); } From fb52ab0d9ba12aeec0217f8e0714265edccf663c Mon Sep 17 00:00:00 2001 From: Nicholas Clark Date: Thu, 8 Aug 2019 16:45:21 +1000 Subject: [PATCH 071/256] Added ability to retrieve the query complexity once the query has been completed --- CHANGELOG.md | 1 + src/Validator/Rules/QueryComplexity.php | 16 ++++++++++++---- tests/Validator/QueryComplexityTest.php | 15 +++++++++++++++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e89faf71d..e1078834f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased - Add schema validation: Input Objects must not contain non-nullable circular references (#492) +- Added retrieving query complexity once query has been completed (#316) #### v0.13.5 - Fix coroutine executor when using with promise (#486) diff --git a/src/Validator/Rules/QueryComplexity.php b/src/Validator/Rules/QueryComplexity.php index fe469cd0a..972eff18f 100644 --- a/src/Validator/Rules/QueryComplexity.php +++ b/src/Validator/Rules/QueryComplexity.php @@ -41,6 +41,9 @@ class QueryComplexity extends QuerySecurityRule /** @var ValidationContext */ private $context; + /** @var int */ + private $complexity; + public function __construct($maxQueryComplexity) { $this->setMaxQueryComplexity($maxQueryComplexity); @@ -52,7 +55,7 @@ public function getVisitor(ValidationContext $context) $this->variableDefs = new ArrayObject(); $this->fieldNodeAndDefs = new ArrayObject(); - $complexity = 0; + $this->complexity = 0; return $this->invokeIfNeeded( $context, @@ -79,16 +82,16 @@ public function getVisitor(ValidationContext $context) return; } - $complexity = $this->fieldComplexity($operationDefinition, $complexity); + $this->complexity = $this->fieldComplexity($operationDefinition, $complexity); - if ($complexity <= $this->getMaxQueryComplexity()) { + if ($this->getQueryComplexity() <= $this->getMaxQueryComplexity()) { return; } $context->reportError( new Error(self::maxQueryComplexityErrorMessage( $this->getMaxQueryComplexity(), - $complexity + $this->getQueryComplexity() )) ); }, @@ -259,6 +262,11 @@ static function ($error) { return $args; } + public function getQueryComplexity() + { + return $this->complexity; + } + public function getMaxQueryComplexity() { return $this->maxQueryComplexity; diff --git a/tests/Validator/QueryComplexityTest.php b/tests/Validator/QueryComplexityTest.php index 998b751ff..24836d04b 100644 --- a/tests/Validator/QueryComplexityTest.php +++ b/tests/Validator/QueryComplexityTest.php @@ -25,6 +25,21 @@ public function testSimpleQueries() : void $this->assertDocumentValidators($query, 2, 3); } + public function testGetQueryComplexity() : void + { + $query = 'query MyQuery { human { firstName } }'; + + $rule = $this->getRule(5); + + DocumentValidator::validate( + QuerySecuritySchema::buildSchema(), + Parser::parse($query), + [$rule] + ); + + self::assertEquals(2, $rule->getQueryComplexity(), $query); + } + private function assertDocumentValidators($query, $queryComplexity, $startComplexity) { for ($maxComplexity = $startComplexity; $maxComplexity >= 0; --$maxComplexity) { From 07c9ad67e29461f820a46daaa965e5ff21e99b88 Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 13 Aug 2019 22:03:03 +1000 Subject: [PATCH 072/256] Allow \stdClass for input types --- CHANGELOG.md | 1 + docs/executing-queries.md | 44 ++++++++++++++++---------------- src/Utils/Value.php | 6 +++++ tests/Executor/VariablesTest.php | 18 +++++++++++++ tests/Utils/CoerceValueTest.php | 7 +++++ 5 files changed, 54 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1078834f..475936ec5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Add schema validation: Input Objects must not contain non-nullable circular references (#492) - Added retrieving query complexity once query has been completed (#316) +- Allow input types to be passed in from variables using \stdClass instead of associative arrays #### v0.13.5 - Fix coroutine executor when using with promise (#486) diff --git a/docs/executing-queries.md b/docs/executing-queries.md index 5cfea251d..29388a2d1 100644 --- a/docs/executing-queries.md +++ b/docs/executing-queries.md @@ -1,8 +1,8 @@ # Using Facade Method -Query execution is a complex process involving multiple steps, including query **parsing**, +Query execution is a complex process involving multiple steps, including query **parsing**, **validating** and finally **executing** against your [schema](type-system/schema.md). -**graphql-php** provides a convenient facade for this process in class +**graphql-php** provides a convenient facade for this process in class [`GraphQL\GraphQL`](reference.md#graphqlgraphql): ```php @@ -10,26 +10,26 @@ Query execution is a complex process involving multiple steps, including query * use GraphQL\GraphQL; $result = GraphQL::executeQuery( - $schema, - $queryString, - $rootValue = null, - $context = null, - $variableValues = null, + $schema, + $queryString, + $rootValue = null, + $context = null, + $variableValues = null, $operationName = null, $fieldResolver = null, $validationRules = null ); ``` -It returns an instance of [`GraphQL\Executor\ExecutionResult`](reference.md#graphqlexecutorexecutionresult) +It returns an instance of [`GraphQL\Executor\ExecutionResult`](reference.md#graphqlexecutorexecutionresult) which can be easily converted to array: ```php $serializableResult = $result->toArray(); ``` -Returned array contains **data** and **errors** keys, as described by the -[GraphQL spec](http://facebook.github.io/graphql/#sec-Response-Format). +Returned array contains **data** and **errors** keys, as described by the +[GraphQL spec](http://facebook.github.io/graphql/#sec-Response-Format). This array is suitable for further serialization (e.g. using **json_encode**). See also the section on [error handling and formatting](error-handling.md). @@ -41,14 +41,14 @@ schema | [`GraphQL\Type\Schema`](#) | **Required.** Instance of your appli queryString | `string` or `GraphQL\Language\AST\DocumentNode` | **Required.** Actual GraphQL query string to be parsed, validated and executed. If you parse query elsewhere before executing - pass corresponding AST document here to avoid new parsing. rootValue | `mixed` | Any value that represents a root of your data graph. It is passed as the 1st argument to field resolvers of [Query type](type-system/schema.md#query-and-mutation-types). Can be omitted or set to null if actual root values are fetched by Query type itself. context | `mixed` | Any value that holds information shared between all field resolvers. Most often they use it to pass currently logged in user, locale details, etc.

It will be available as the 3rd argument in all field resolvers. (see section on [Field Definitions](type-system/object-types.md#field-configuration-options) for reference) **graphql-php** never modifies this value and passes it *as is* to all underlying resolvers. -variableValues | `array` | Map of variable values passed along with query string. See section on [query variables on official GraphQL website](http://graphql.org/learn/queries/#variables) +variableValues | `array` | Map of variable values passed along with query string. See section on [query variables on official GraphQL website](http://graphql.org/learn/queries/#variables). Note that while variableValues must be an associative array, the values inside it can be nested using \stdClass if desired. operationName | `string` | Allows the caller to specify which operation in queryString will be run, in cases where queryString contains multiple top-level operations. fieldResolver | `callable` | A resolver function to use when one is not provided by the schema. If not provided, the [default field resolver is used](data-fetching.md#default-field-resolver). validationRules | `array` | A set of rules for query validation step. The default value is all available rules. Empty array would allow skipping query validation (may be convenient for persisted queries which are validated before persisting and assumed valid during execution) # Using Server -If you are building HTTP GraphQL API, you may prefer our Standard Server -(compatible with [express-graphql](https://github.com/graphql/express-graphql)). +If you are building HTTP GraphQL API, you may prefer our Standard Server +(compatible with [express-graphql](https://github.com/graphql/express-graphql)). It supports more features out of the box, including parsing HTTP requests, producing a spec-compliant response; [batched queries](#query-batching); persisted queries. Usage example (with plain PHP): @@ -105,11 +105,11 @@ debug | `int` | Debug flags. See [docs on error debugging](error-handling.md#deb persistentQueryLoader | `callable` | A function which is called to fetch actual query when server encounters **queryId** in request vs **query**.

The server does not implement persistence part (which you will have to build on your own), but it allows you to execute queries which were persisted previously.

Expected function signature:
**function ($queryId, [OperationParams](reference.md#graphqlserveroperationparams) $params)**

Function is expected to return query **string** or parsed **DocumentNode**

[Read more about persisted queries](https://dev-blog.apollodata.com/persisted-graphql-queries-with-apollo-client-119fd7e6bba5). errorFormatter | `callable` | Custom error formatter. See [error handling docs](error-handling.md#custom-error-handling-and-formatting). errorsHandler | `callable` | Custom errors handler. See [error handling docs](error-handling.md#custom-error-handling-and-formatting). -promiseAdapter | [`PromiseAdapter`](reference.md#graphqlexecutorpromisepromiseadapter) | Required for [Async PHP](data-fetching/#async-php) only. +promiseAdapter | [`PromiseAdapter`](reference.md#graphqlexecutorpromisepromiseadapter) | Required for [Async PHP](data-fetching/#async-php) only. **Server config instance** -If you prefer fluid interface for config with autocomplete in IDE and static time validation, +If you prefer fluid interface for config with autocomplete in IDE and static time validation, use [`GraphQL\Server\ServerConfig`](reference.md#graphqlserverserverconfig) instead of an array: ```php @@ -129,7 +129,7 @@ $server = new StandardServer($config); ## Query batching Standard Server supports query batching ([apollo-style](https://dev-blog.apollodata.com/query-batching-in-apollo-63acfd859862)). -One of the major benefits of Server over a sequence of **executeQuery()** calls is that +One of the major benefits of Server over a sequence of **executeQuery()** calls is that [Deferred resolvers](data-fetching.md#solving-n1-problem) won't be isolated in queries. So for example following batch will require single DB request (if user field is deferred): @@ -186,11 +186,11 @@ $myValiationRules = array_merge( ); $result = GraphQL::executeQuery( - $schema, - $queryString, - $rootValue = null, - $context = null, - $variableValues = null, + $schema, + $queryString, + $rootValue = null, + $context = null, + $variableValues = null, $operationName = null, $fieldResolver = null, $myValiationRules // <-- this will override global validation rules for this request @@ -199,7 +199,7 @@ $result = GraphQL::executeQuery( Or with a standard server: ```php -getFields(); diff --git a/tests/Executor/VariablesTest.php b/tests/Executor/VariablesTest.php index 533beb5ea..079e81685 100644 --- a/tests/Executor/VariablesTest.php +++ b/tests/Executor/VariablesTest.php @@ -309,6 +309,24 @@ public function testUsingVariables() : void self::assertEquals($expected, $result->toArray()); } + public function testUsingStdClassVariables() : void + { + $doc = ' + query q($input:TestNestedInputObject) { + fieldWithNestedInputObject(input: $input) + } + '; + + // executes with complex input: + $params = ['input' => (object) ['na' => (object) ['a' => 'foo', 'b' => ['bar'], 'c' => 'baz'], 'nb' => 'test']]; + $result = $this->executeQuery($doc, $params); + + self::assertEquals( + ['data' => ['fieldWithNestedInputObject' => '{"na":{"a":"foo","b":["bar"],"c":"baz"},"nb":"test"}']], + $result->toArray() + ); + } + /** * @see it('allows nullable inputs to be omitted') */ diff --git a/tests/Utils/CoerceValueTest.php b/tests/Utils/CoerceValueTest.php index 831c98f66..3d30fc87b 100644 --- a/tests/Utils/CoerceValueTest.php +++ b/tests/Utils/CoerceValueTest.php @@ -292,6 +292,13 @@ public function testReturnsErrorForNonObjectType() : void $this->expectError($result, 'Expected type TestInputObject to be an object.'); } + public function testReturnsNoErrorForStdClassInput() : void + { + $result = Value::coerceValue((object) ['foo' => 123], $this->testInputObject); + $this->expectNoErrors($result); + self::assertEquals(['foo' => 123], $result['value']); + } + /** * @see it('returns no error for an invalid field') */ From 008e4667262192de6cae722e1e4ed98e785b2c18 Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 14 Aug 2019 06:44:34 +1000 Subject: [PATCH 073/256] Add PR number to changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 475936ec5..e33fff9f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## Unreleased - Add schema validation: Input Objects must not contain non-nullable circular references (#492) - Added retrieving query complexity once query has been completed (#316) -- Allow input types to be passed in from variables using \stdClass instead of associative arrays +- Allow input types to be passed in from variables using \stdClass instead of associative arrays (#535) #### v0.13.5 - Fix coroutine executor when using with promise (#486) From 953178fafcd96ffcda60f49392779c12b089198a Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Wed, 14 Aug 2019 16:19:23 +0700 Subject: [PATCH 074/256] Additional test for single-value to list coercion (in variables) --- tests/Executor/VariablesTest.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/Executor/VariablesTest.php b/tests/Executor/VariablesTest.php index 079e81685..2ef4690d1 100644 --- a/tests/Executor/VariablesTest.php +++ b/tests/Executor/VariablesTest.php @@ -45,6 +45,17 @@ public function testUsingInlineStructs() : void self::assertEquals($expected, $result->toArray()); + $result = $this->executeQuery(' + query ($input: TestInputObject) { + fieldWithObjectInput(input: $input) + } + ', + ['input' => ['a' => 'foo', 'b' => 'bar', 'c' => 'baz']] + ); + $expected = ['data' => ['fieldWithObjectInput' => '{"a":"foo","b":["bar"],"c":"baz"}']]; + + self::assertEquals($expected, $result->toArray()); + // properly parses null value to null $result = $this->executeQuery(' { From bf4e7d4a9f078fb926f92a32725a6ccf0c0e09a2 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Wed, 14 Aug 2019 20:55:32 +0700 Subject: [PATCH 075/256] SPEC/BUG: Ambiguity with null variable values and default values (fixes #274) --- CHANGELOG.md | 2 + src/Executor/Values.php | 180 ++++++++------ src/Utils/AST.php | 11 +- src/Utils/TypeInfo.php | 47 +++- src/Validator/DocumentValidator.php | 6 +- ...ents.php => ProvidedRequiredArguments.php} | 8 +- src/Validator/Rules/ValuesOfCorrectType.php | 2 +- .../Rules/VariablesDefaultValueAllowed.php | 65 ------ .../Rules/VariablesInAllowedPosition.php | 55 ++--- src/Validator/ValidationContext.php | 6 +- tests/Executor/NonNullTest.php | 219 +++++++++++++++++- tests/Executor/VariablesTest.php | 118 ++++++++-- tests/Type/IntrospectionTest.php | 4 +- ....php => ProvidedRequiredArgumentsTest.php} | 59 +++-- tests/Validator/ValidatorTestCase.php | 7 + tests/Validator/ValuesOfCorrectTypeTest.php | 28 ++- .../VariablesDefaultValueAllowedTest.php | 132 ----------- .../VariablesInAllowedPositionTest.php | 105 ++++++--- 18 files changed, 659 insertions(+), 395 deletions(-) rename src/Validator/Rules/{ProvidedNonNullArguments.php => ProvidedRequiredArguments.php} (91%) delete mode 100644 src/Validator/Rules/VariablesDefaultValueAllowed.php rename tests/Validator/{ProvidedNonNullArgumentsTest.php => ProvidedRequiredArgumentsTest.php} (81%) delete mode 100644 tests/Validator/VariablesDefaultValueAllowedTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index e33fff9f0..49b8fc8fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog ## Unreleased +- **BREAKING:** Removal of `VariablesDefaultValueAllowed` validation rule. All variables may now specify a default value. +- **BREAKING:** renamed `ProvidedNonNullArguments` to `ProvidedRequiredArguments` (no longer require values to be provided to non-null arguments which provide a default value). - Add schema validation: Input Objects must not contain non-nullable circular references (#492) - Added retrieving query complexity once query has been completed (#316) - Allow input types to be passed in from variables using \stdClass instead of associative arrays (#535) diff --git a/src/Executor/Values.php b/src/Executor/Values.php index a7dee1b13..653abce24 100644 --- a/src/Executor/Values.php +++ b/src/Executor/Values.php @@ -67,48 +67,9 @@ public static function getVariableValues(Schema $schema, $varDefNodes, array $in /** @var InputType|Type $varType */ $varType = TypeInfo::typeFromAST($schema, $varDefNode->type); - if (Type::isInputType($varType)) { - if (array_key_exists($varName, $inputs)) { - $value = $inputs[$varName]; - $coerced = Value::coerceValue($value, $varType, $varDefNode); - /** @var Error[] $coercionErrors */ - $coercionErrors = $coerced['errors']; - if (empty($coercionErrors)) { - $coercedValues[$varName] = $coerced['value']; - } else { - $messagePrelude = sprintf( - 'Variable "$%s" got invalid value %s; ', - $varName, - Utils::printSafeJson($value) - ); - - foreach ($coercionErrors as $error) { - $errors[] = new Error( - $messagePrelude . $error->getMessage(), - $error->getNodes(), - $error->getSource(), - $error->getPositions(), - $error->getPath(), - $error, - $error->getExtensions() - ); - } - } - } else { - if ($varType instanceof NonNull) { - $errors[] = new Error( - sprintf( - 'Variable "$%s" of required type "%s" was not provided.', - $varName, - $varType - ), - [$varDefNode] - ); - } elseif ($varDefNode->defaultValue !== null) { - $coercedValues[$varName] = AST::valueFromAST($varDefNode->defaultValue, $varType); - } - } - } else { + if (! Type::isInputType($varType)) { + // Must use input types for variables. This should be caught during + // validation, however is checked again here for safety. $errors[] = new Error( sprintf( 'Variable "$%s" expected value of type "%s" which cannot be used as an input type.', @@ -117,6 +78,61 @@ public static function getVariableValues(Schema $schema, $varDefNodes, array $in ), [$varDefNode->type] ); + } else { + $hasValue = array_key_exists($varName, $inputs); + $value = $hasValue ? $inputs[$varName] : Utils::undefined(); + + if (! $hasValue && $varDefNode->defaultValue) { + // If no value was provided to a variable with a default value, + // use the default value. + $coercedValues[$varName] = AST::valueFromAST($varDefNode->defaultValue, $varType); + } elseif ((! $hasValue || $value === null) && ($varType instanceof NonNull)) { + // If no value or a nullish value was provided to a variable with a + // non-null type (required), produce an error. + $errors[] = new Error( + sprintf( + $hasValue + ? 'Variable "$%s" of non-null type "%s" must not be null.' + : 'Variable "$%s" of required type "%s" was not provided.', + $varName, + Utils::printSafe($varType) + ), + [$varDefNode] + ); + } elseif ($hasValue) { + if ($value === null) { + // If the explicit value `null` was provided, an entry in the coerced + // values must exist as the value `null`. + $coercedValues[$varName] = null; + } else { + // Otherwise, a non-null value was provided, coerce it to the expected + // type or report an error if coercion fails. + $coerced = Value::coerceValue($value, $varType, $varDefNode); + /** @var Error[] $coercionErrors */ + $coercionErrors = $coerced['errors']; + if ($coercionErrors) { + $messagePrelude = sprintf( + 'Variable "$%s" got invalid value %s; ', + $varName, + Utils::printSafeJson($value) + ); + + foreach ($coercionErrors as $error) { + $errors[] = new Error( + $messagePrelude . $error->getMessage(), + $error->getNodes(), + $error->getSource(), + $error->getPositions(), + $error->getPath(), + $error, + $error->getExtensions() + ); + } + } else { + $coercedValues[$varName] = $coerced['value']; + } + } + } } } @@ -208,27 +224,32 @@ public static function getArgumentValuesForMap($fieldDefinition, $argumentValueM $argType = $argumentDefinition->getType(); $argumentValueNode = $argumentValueMap[$name] ?? null; - if ($argumentValueNode === null) { - if ($argumentDefinition->defaultValueExists()) { - $coercedValues[$name] = $argumentDefinition->defaultValue; - } elseif ($argType instanceof NonNull) { + if ($argumentValueNode instanceof VariableNode) { + $variableName = $argumentValueNode->name->value; + $hasValue = $variableValues ? array_key_exists($variableName, $variableValues) : false; + $isNull = $hasValue ? $variableValues[$variableName] === null : false; + } else { + $hasValue = $argumentValueNode !== null; + $isNull = $argumentValueNode instanceof NullValueNode; + } + + if (! $hasValue && $argumentDefinition->defaultValueExists()) { + // If no argument was provided where the definition has a default value, + // use the default value. + $coercedValues[$name] = $argumentDefinition->defaultValue; + } elseif ((! $hasValue || $isNull) && ($argType instanceof NonNull)) { + // If no argument or a null value was provided to an argument with a + // non-null type (required), produce a field error. + if ($isNull) { throw new Error( - 'Argument "' . $name . '" of required type ' . - '"' . Utils::printSafe($argType) . '" was not provided.', + 'Argument "' . $name . '" of non-null type ' . + '"' . Utils::printSafe($argType) . '" must not be null.', $referenceNode ); } - } elseif ($argumentValueNode instanceof VariableNode) { - $variableName = $argumentValueNode->name->value; - if ($variableValues !== null && array_key_exists($variableName, $variableValues)) { - // Note: this does not check that this variable value is correct. - // This assumes that this query has been validated and the variable - // usage here is of the correct type. - $coercedValues[$name] = $variableValues[$variableName]; - } elseif ($argumentDefinition->defaultValueExists()) { - $coercedValues[$name] = $argumentDefinition->defaultValue; - } elseif ($argType instanceof NonNull) { + if ($argumentValueNode instanceof VariableNode) { + $variableName = $argumentValueNode->name->value; throw new Error( 'Argument "' . $name . '" of required type "' . Utils::printSafe($argType) . '" was ' . 'provided the variable "$' . $variableName . '" which was not provided ' . @@ -236,19 +257,38 @@ public static function getArgumentValuesForMap($fieldDefinition, $argumentValueM [$argumentValueNode] ); } - } else { - $valueNode = $argumentValueNode; - $coercedValue = AST::valueFromAST($valueNode, $argType, $variableValues); - if (Utils::isInvalid($coercedValue)) { - // Note: ValuesOfCorrectType validation should catch this before - // execution. This is a runtime check to ensure execution does not - // continue with an invalid argument value. - throw new Error( - 'Argument "' . $name . '" has invalid value ' . Printer::doPrint($valueNode) . '.', - [$argumentValueNode] - ); + + throw new Error( + 'Argument "' . $name . '" of required type ' . + '"' . Utils::printSafe($argType) . '" was not provided.', + $referenceNode + ); + } elseif ($hasValue) { + if ($argumentValueNode instanceof NullValueNode) { + // If the explicit value `null` was provided, an entry in the coerced + // values must exist as the value `null`. + $coercedValues[$name] = null; + } elseif ($argumentValueNode instanceof VariableNode) { + $variableName = $argumentValueNode->name->value; + Utils::invariant($variableValues !== null, 'Must exist for hasValue to be true.'); + // Note: This does no further checking that this variable is correct. + // This assumes that this query has been validated and the variable + // usage here is of the correct type. + $coercedValues[$name] = $variableValues[$variableName] ?? null; + } else { + $valueNode = $argumentValueNode; + $coercedValue = AST::valueFromAST($valueNode, $argType, $variableValues); + if (Utils::isInvalid($coercedValue)) { + // Note: ValuesOfCorrectType validation should catch this before + // execution. This is a runtime check to ensure execution does not + // continue with an invalid argument value. + throw new Error( + 'Argument "' . $name . '" has invalid value ' . Printer::doPrint($valueNode) . '.', + [$argumentValueNode] + ); + } + $coercedValues[$name] = $coercedValue; } - $coercedValues[$name] = $coercedValue; } } diff --git a/src/Utils/AST.php b/src/Utils/AST.php index 9e54300a1..2626c16c7 100644 --- a/src/Utils/AST.php +++ b/src/Utils/AST.php @@ -354,9 +354,14 @@ public static function valueFromAST(?ValueNode $valueNode, Type $type, ?array $v return $undefined; } - // Note: we're not doing any checking that this variable is correct. We're - // assuming that this query has been validated and the variable usage here - // is of the correct type. + $variableValue = $variables[$variableName] ?? null; + if ($variableValue === null && $type instanceof NonNull) { + return $undefined; // Invalid: intentionally return no value. + } + + // Note: This does no further checking that this variable is correct. + // This assumes that this query has been validated and the variable + // usage here is of the correct type. return $variables[$variableName]; } diff --git a/src/Utils/TypeInfo.php b/src/Utils/TypeInfo.php index 8a6ebcf0f..6f26fd2c6 100644 --- a/src/Utils/TypeInfo.php +++ b/src/Utils/TypeInfo.php @@ -64,6 +64,9 @@ class TypeInfo /** @var SplStack */ private $fieldDefStack; + /** @var SplStack */ + private $defaultValueStack; + /** @var Directive */ private $directive; @@ -78,11 +81,13 @@ class TypeInfo */ public function __construct(Schema $schema, $initialType = null) { - $this->schema = $schema; - $this->typeStack = []; - $this->parentTypeStack = []; - $this->inputTypeStack = []; - $this->fieldDefStack = []; + $this->schema = $schema; + $this->typeStack = []; + $this->parentTypeStack = []; + $this->inputTypeStack = []; + $this->fieldDefStack = []; + $this->defaultValueStack = []; + if (! $initialType) { return; } @@ -322,6 +327,7 @@ public function enter(Node $node) $fieldOrDirective = $this->getDirective() ?: $this->getFieldDef(); $argDef = $argType = null; if ($fieldOrDirective) { + /** @var FieldArgument $argDef */ $argDef = Utils::find( $fieldOrDirective->args, static function ($arg) use ($node) { @@ -332,28 +338,33 @@ static function ($arg) use ($node) { $argType = $argDef->getType(); } } - $this->argument = $argDef; - $this->inputTypeStack[] = Type::isInputType($argType) ? $argType : null; + $this->argument = $argDef; + $this->defaultValueStack[] = $argDef && $argDef->defaultValueExists() ? $argDef->defaultValue : Utils::undefined(); + $this->inputTypeStack[] = Type::isInputType($argType) ? $argType : null; break; case $node instanceof ListValueNode: - $listType = Type::getNullableType($this->getInputType()); - $itemType = $listType instanceof ListOfType + $listType = Type::getNullableType($this->getInputType()); + $itemType = $listType instanceof ListOfType ? $listType->getWrappedType() : $listType; - $this->inputTypeStack[] = Type::isInputType($itemType) ? $itemType : null; + // List positions never have a default value. + $this->defaultValueStack[] = Utils::undefined(); + $this->inputTypeStack[] = Type::isInputType($itemType) ? $itemType : null; break; case $node instanceof ObjectFieldNode: $objectType = Type::getNamedType($this->getInputType()); $fieldType = null; + $inputField = null; $inputFieldType = null; if ($objectType instanceof InputObjectType) { $tmp = $objectType->getFields(); $inputField = $tmp[$node->name->value] ?? null; $inputFieldType = $inputField ? $inputField->getType() : null; } - $this->inputTypeStack[] = Type::isInputType($inputFieldType) ? $inputFieldType : null; + $this->defaultValueStack[] = $inputField && $inputField->defaultValueExists() ? $inputField->defaultValue : Utils::undefined(); + $this->inputTypeStack[] = Type::isInputType($inputFieldType) ? $inputFieldType : null; break; case $node instanceof EnumValueNode: @@ -456,6 +467,18 @@ public function getFieldDef() return null; } + /** + * @return mixed|null + */ + public function getDefaultValue() + { + if (! empty($this->defaultValueStack)) { + return $this->defaultValueStack[count($this->defaultValueStack) - 1]; + } + + return null; + } + /** * @return ScalarType|EnumType|InputObjectType|ListOfType|NonNull|null */ @@ -494,10 +517,12 @@ public function leave(Node $node) break; case $node instanceof ArgumentNode: $this->argument = null; + array_pop($this->defaultValueStack); array_pop($this->inputTypeStack); break; case $node instanceof ListValueNode: case $node instanceof ObjectFieldNode: + array_pop($this->defaultValueStack); array_pop($this->inputTypeStack); break; case $node instanceof EnumValueNode: diff --git a/src/Validator/DocumentValidator.php b/src/Validator/DocumentValidator.php index 0364839e2..65daa763e 100644 --- a/src/Validator/DocumentValidator.php +++ b/src/Validator/DocumentValidator.php @@ -28,7 +28,7 @@ use GraphQL\Validator\Rules\NoUnusedVariables; use GraphQL\Validator\Rules\OverlappingFieldsCanBeMerged; use GraphQL\Validator\Rules\PossibleFragmentSpreads; -use GraphQL\Validator\Rules\ProvidedNonNullArguments; +use GraphQL\Validator\Rules\ProvidedRequiredArguments; use GraphQL\Validator\Rules\ProvidedRequiredArgumentsOnDirectives; use GraphQL\Validator\Rules\QueryComplexity; use GraphQL\Validator\Rules\QueryDepth; @@ -43,7 +43,6 @@ use GraphQL\Validator\Rules\ValidationRule; use GraphQL\Validator\Rules\ValuesOfCorrectType; use GraphQL\Validator\Rules\VariablesAreInputTypes; -use GraphQL\Validator\Rules\VariablesDefaultValueAllowed; use GraphQL\Validator\Rules\VariablesInAllowedPosition; use Throwable; use function array_filter; @@ -160,8 +159,7 @@ public static function defaultRules() KnownArgumentNames::class => new KnownArgumentNames(), UniqueArgumentNames::class => new UniqueArgumentNames(), ValuesOfCorrectType::class => new ValuesOfCorrectType(), - ProvidedNonNullArguments::class => new ProvidedNonNullArguments(), - VariablesDefaultValueAllowed::class => new VariablesDefaultValueAllowed(), + ProvidedRequiredArguments::class => new ProvidedRequiredArguments(), VariablesInAllowedPosition::class => new VariablesInAllowedPosition(), OverlappingFieldsCanBeMerged::class => new OverlappingFieldsCanBeMerged(), UniqueInputFieldNames::class => new UniqueInputFieldNames(), diff --git a/src/Validator/Rules/ProvidedNonNullArguments.php b/src/Validator/Rules/ProvidedRequiredArguments.php similarity index 91% rename from src/Validator/Rules/ProvidedNonNullArguments.php rename to src/Validator/Rules/ProvidedRequiredArguments.php index 976f38b61..2099f3c48 100644 --- a/src/Validator/Rules/ProvidedNonNullArguments.php +++ b/src/Validator/Rules/ProvidedRequiredArguments.php @@ -13,7 +13,7 @@ use GraphQL\Validator\ValidationContext; use function sprintf; -class ProvidedNonNullArguments extends ValidationRule +class ProvidedRequiredArguments extends ValidationRule { public function getVisitor(ValidationContext $context) { @@ -29,11 +29,11 @@ public function getVisitor(ValidationContext $context) $argNodeMap = []; foreach ($argNodes as $argNode) { - $argNodeMap[$argNode->name->value] = $argNodes; + $argNodeMap[$argNode->name->value] = $argNode; } foreach ($fieldDef->args as $argDef) { $argNode = $argNodeMap[$argDef->name] ?? null; - if ($argNode || ! ($argDef->getType() instanceof NonNull)) { + if ($argNode || (! ($argDef->getType() instanceof NonNull)) || $argDef->defaultValueExists()) { continue; } @@ -58,7 +58,7 @@ public function getVisitor(ValidationContext $context) foreach ($directiveDef->args as $argDef) { $argNode = $argNodeMap[$argDef->name] ?? null; - if ($argNode || ! ($argDef->getType() instanceof NonNull)) { + if ($argNode || (! ($argDef->getType() instanceof NonNull)) || $argDef->defaultValueExists()) { continue; } diff --git a/src/Validator/Rules/ValuesOfCorrectType.php b/src/Validator/Rules/ValuesOfCorrectType.php index 2f496dd3c..d64ea7562 100644 --- a/src/Validator/Rules/ValuesOfCorrectType.php +++ b/src/Validator/Rules/ValuesOfCorrectType.php @@ -103,7 +103,7 @@ static function ($field) { ); foreach ($inputFields as $fieldName => $fieldDef) { $fieldType = $fieldDef->getType(); - if (isset($fieldNodeMap[$fieldName]) || ! ($fieldType instanceof NonNull)) { + if (isset($fieldNodeMap[$fieldName]) || ! ($fieldType instanceof NonNull) || ($fieldDef->defaultValueExists())) { continue; } diff --git a/src/Validator/Rules/VariablesDefaultValueAllowed.php b/src/Validator/Rules/VariablesDefaultValueAllowed.php deleted file mode 100644 index a19c14d2f..000000000 --- a/src/Validator/Rules/VariablesDefaultValueAllowed.php +++ /dev/null @@ -1,65 +0,0 @@ - static function (VariableDefinitionNode $node) use ($context) { - $name = $node->variable->name->value; - $defaultValue = $node->defaultValue; - $type = $context->getInputType(); - if ($type instanceof NonNull && $defaultValue) { - $context->reportError( - new Error( - self::defaultForRequiredVarMessage( - $name, - $type, - $type->getWrappedType() - ), - [$defaultValue] - ) - ); - } - - return Visitor::skipNode(); - }, - NodeKind::SELECTION_SET => static function (SelectionSetNode $node) { - return Visitor::skipNode(); - }, - NodeKind::FRAGMENT_DEFINITION => static function (FragmentDefinitionNode $node) { - return Visitor::skipNode(); - }, - ]; - } - - public static function defaultForRequiredVarMessage($varName, $type, $guessType) - { - return sprintf( - 'Variable "$%s" of type "%s" is required and will not use the default value. Perhaps you meant to use type "%s".', - $varName, - $type, - $guessType - ); - } -} diff --git a/src/Validator/Rules/VariablesInAllowedPosition.php b/src/Validator/Rules/VariablesInAllowedPosition.php index 44570740c..04a89bdf6 100644 --- a/src/Validator/Rules/VariablesInAllowedPosition.php +++ b/src/Validator/Rules/VariablesInAllowedPosition.php @@ -6,12 +6,17 @@ use GraphQL\Error\Error; use GraphQL\Language\AST\NodeKind; +use GraphQL\Language\AST\NullValueNode; use GraphQL\Language\AST\OperationDefinitionNode; +use GraphQL\Language\AST\ValueNode; use GraphQL\Language\AST\VariableDefinitionNode; use GraphQL\Type\Definition\ListOfType; use GraphQL\Type\Definition\NonNull; +use GraphQL\Type\Definition\Type; +use GraphQL\Type\Schema; use GraphQL\Utils\TypeComparators; use GraphQL\Utils\TypeInfo; +use GraphQL\Utils\Utils; use GraphQL\Validator\ValidationContext; use function sprintf; @@ -35,10 +40,11 @@ public function getVisitor(ValidationContext $context) $usages = $context->getRecursiveVariableUsages($operation); foreach ($usages as $usage) { - $node = $usage['node']; - $type = $usage['type']; - $varName = $node->name->value; - $varDef = $this->varDefMap[$varName] ?? null; + $node = $usage['node']; + $type = $usage['type']; + $defaultValue = $usage['defaultValue']; + $varName = $node->name->value; + $varDef = $this->varDefMap[$varName] ?? null; if ($varDef === null || $type === null) { continue; @@ -52,11 +58,7 @@ public function getVisitor(ValidationContext $context) $schema = $context->getSchema(); $varType = TypeInfo::typeFromAST($schema, $varDef->type); - if (! $varType || TypeComparators::isTypeSubTypeOf( - $schema, - $this->effectiveType($varType, $varDef), - $type - )) { + if (! $varType || $this->allowedVariableUsage($schema, $varType, $varDef->defaultValue, $type, $defaultValue)) { continue; } @@ -73,11 +75,6 @@ public function getVisitor(ValidationContext $context) ]; } - private function effectiveType($varType, $varDef) - { - return ! $varDef->defaultValue || $varType instanceof NonNull ? $varType : new NonNull($varType); - } - /** * A var type is allowed if it is the same or more strict than the expected * type. It can be more strict if the variable type is non-null when the @@ -94,23 +91,27 @@ public static function badVarPosMessage($varName, $varType, $expectedType) ); } - /** If a variable definition has a default value, it's effectively non-null. */ - private function varTypeAllowedForType($varType, $expectedType) + /** + * Returns true if the variable is allowed in the location it was found, + * which includes considering if default values exist for either the variable + * or the location at which it is located. + * + * @param ValueNode|null $varDefaultValue + * @param mixed $locationDefaultValue + */ + private function allowedVariableUsage(Schema $schema, Type $varType, $varDefaultValue, Type $locationType, $locationDefaultValue) : bool { - if ($expectedType instanceof NonNull) { - if ($varType instanceof NonNull) { - return $this->varTypeAllowedForType($varType->getWrappedType(), $expectedType->getWrappedType()); + if ($locationType instanceof NonNull && ! $varType instanceof NonNull) { + $hasNonNullVariableDefaultValue = $varDefaultValue && ! $varDefaultValue instanceof NullValueNode; + $hasLocationDefaultValue = ! Utils::isInvalid($locationDefaultValue); + if (! $hasNonNullVariableDefaultValue && ! $hasLocationDefaultValue) { + return false; } + $nullableLocationType = $locationType->getWrappedType(); - return false; - } - if ($varType instanceof NonNull) { - return $this->varTypeAllowedForType($varType->getWrappedType(), $expectedType); - } - if ($varType instanceof ListOfType && $expectedType instanceof ListOfType) { - return $this->varTypeAllowedForType($varType->getWrappedType(), $expectedType->getWrappedType()); + return TypeComparators::isTypeSubTypeOf($schema, $varType, $nullableLocationType); } - return $varType === $expectedType; + return TypeComparators::isTypeSubTypeOf($schema, $varType, $locationType); } } diff --git a/src/Validator/ValidationContext.php b/src/Validator/ValidationContext.php index 60ced9a7f..4a67d20e6 100644 --- a/src/Validator/ValidationContext.php +++ b/src/Validator/ValidationContext.php @@ -142,7 +142,11 @@ private function getVariableUsages(HasSelectionSet $node) &$newUsages, $typeInfo ) { - $newUsages[] = ['node' => $variable, 'type' => $typeInfo->getInputType()]; + $newUsages[] = [ + 'node' => $variable, + 'type' => $typeInfo->getInputType(), + 'defaultValue' => $typeInfo->getDefaultValue(), + ]; }, ] ) diff --git a/tests/Executor/NonNullTest.php b/tests/Executor/NonNullTest.php index 86c7084d5..3801c888e 100644 --- a/tests/Executor/NonNullTest.php +++ b/tests/Executor/NonNullTest.php @@ -9,6 +9,7 @@ use GraphQL\Error\FormattedError; use GraphQL\Error\UserError; use GraphQL\Executor\Executor; +use GraphQL\GraphQL; use GraphQL\Language\Parser; use GraphQL\Language\SourceLocation; use GraphQL\Type\Definition\ObjectType; @@ -17,6 +18,7 @@ use PHPUnit\Framework\ExpectationFailedException; use PHPUnit\Framework\TestCase; use function count; +use function is_string; use function json_encode; class NonNullTest extends TestCase @@ -42,6 +44,9 @@ class NonNullTest extends TestCase /** @var Schema */ public $schema; + /** @var Schema */ + public $schemaWithNonNullArg; + public function setUp() { $this->syncError = new UserError('sync'); @@ -136,6 +141,27 @@ public function setUp() ]); $this->schema = new Schema(['query' => $dataType]); + + $this->schemaWithNonNullArg = new Schema([ + 'query' => new ObjectType([ + 'name' => 'Query', + 'fields' => [ + 'withNonNullArg' => [ + 'type' => Type::string(), + 'args' => [ + 'cannotBeNull' => [ + 'type' => Type::nonNull(Type::string()), + ], + ], + 'resolve' => static function ($value, $args) { + if (is_string($args['cannotBeNull'])) { + return 'Passed: ' . $args['cannotBeNull']; + } + }, + ], + ], + ]), + ]); } // Execute: handles non-nullable types @@ -683,6 +709,9 @@ public function testNullsAComplexTreeOfNullableFieldsThatReturnNull() : void self::assertEquals($expected, $actual); } + /** + * @see it('nulls the first nullable object after a field in a long chain of non-null fields') + */ public function testNullsTheFirstNullableObjectAfterAFieldReturnsNullInALongChainOfFieldsThatAreNonNull() : void { $doc = ' @@ -758,7 +787,7 @@ public function testNullsTheFirstNullableObjectAfterAFieldReturnsNullInALongChai } /** - * @see it('nulls the top level if sync non-nullable field throws') + * @see it('nulls the top level if non-nullable field') */ public function testNullsTheTopLevelIfSyncNonNullableFieldThrows() : void { @@ -775,6 +804,194 @@ public function testNullsTheTopLevelIfSyncNonNullableFieldThrows() : void self::assertArraySubset($expected, $actual); } + /** + * @see describe('Handles non-null argument') + * @see it('succeeds when passed non-null literal value') + */ + public function succeedsWhenPassedNonNullLiteralValue() : void + { + $result = Executor::execute( + $this->schemaWithNonNullArg, + Parser::parse(' + query { + withNonNullArg (cannotBeNull: "literal value") + } + ') + ); + + $expected = ['data' => ['withNonNullArg' => 'Passed: literal value']]; + self::assertEquals($expected, $result->toArray()); + } + + /** + * @see it('succeeds when passed non-null variable value') + */ + public function succeedsWhenPassedNonNullVariableValue() + { + $result = Executor::execute( + $this->schemaWithNonNullArg, + Parser::parse(' + query ($testVar: String!) { + withNonNullArg (cannotBeNull: $testVar) + } + '), + null, + null, + ['testVar' => 'variable value'] + ); + + $expected = ['data' => ['withNonNullArg' => 'Passed: variable value']]; + self::assertEquals($expected, $result->toArray()); + } + + /** + * @see it('succeeds when missing variable has default value') + */ + public function testSucceedsWhenMissingVariableHasDefaultValue() + { + $result = Executor::execute( + $this->schemaWithNonNullArg, + Parser::parse(' + query ($testVar: String = "default value") { + withNonNullArg (cannotBeNull: $testVar) + } + '), + null, + null, + [] // Intentionally missing variable + ); + + $expected = ['data' => ['withNonNullArg' => 'Passed: default value']]; + self::assertEquals($expected, $result->toArray()); + } + + /** + * @see it('field error when missing non-null arg') + */ + public function testFieldErrorWhenMissingNonNullArg() + { + // Note: validation should identify this issue first (missing args rule) + // however execution should still protect against this. + $result = Executor::execute( + $this->schemaWithNonNullArg, + Parser::parse(' + query { + withNonNullArg + } + ') + ); + + $expected = [ + 'data' => ['withNonNullArg' => null], + 'errors' => [ + [ + 'message' => 'Argument "cannotBeNull" of required type "String!" was not provided.', + 'locations' => [['line' => 3, 'column' => 13]], + 'path' => ['withNonNullArg'], + 'extensions' => ['category' => 'graphql'], + ], + ], + ]; + self::assertEquals($expected, $result->toArray()); + } + + /** + * @see it('field error when non-null arg provided null') + */ + public function testFieldErrorWhenNonNullArgProvidedNull() + { + // Note: validation should identify this issue first (values of correct + // type rule) however execution should still protect against this. + $result = Executor::execute( + $this->schemaWithNonNullArg, + Parser::parse(' + query { + withNonNullArg(cannotBeNull: null) + } + ') + ); + + $expected = [ + 'data' => ['withNonNullArg' => null], + 'errors' => [ + [ + 'message' => 'Argument "cannotBeNull" of non-null type "String!" must not be null.', + 'locations' => [['line' => 3, 'column' => 13]], + 'path' => ['withNonNullArg'], + 'extensions' => ['category' => 'graphql'], + ], + ], + ]; + self::assertEquals($expected, $result->toArray()); + } + + /** + * @see it('field error when non-null arg not provided variable value') + */ + public function testFieldErrorWhenNonNullArgNotProvidedVariableValue() : void + { + // Note: validation should identify this issue first (variables in allowed + // position rule) however execution should still protect against this. + $result = Executor::execute( + $this->schemaWithNonNullArg, + Parser::parse(' + query ($testVar: String) { + withNonNullArg(cannotBeNull: $testVar) + } + '), + null, + null, + [] // Intentionally missing variable + ); + + $expected = [ + 'data' => ['withNonNullArg' => null], + 'errors' => [ + [ + 'message' => 'Argument "cannotBeNull" of required type "String!" was ' . + 'provided the variable "$testVar" which was not provided a ' . + 'runtime value.', + 'locations' => [['line' => 3, 'column' => 42]], + 'path' => ['withNonNullArg'], + 'extensions' => ['category' => 'graphql'], + ], + ], + ]; + self::assertEquals($expected, $result->toArray()); + } + + /** + * @see it('field error when non-null arg provided variable with explicit null value') + */ + public function testFieldErrorWhenNonNullArgProvidedVariableWithExplicitNullValue() + { + $result = Executor::execute( + $this->schemaWithNonNullArg, + Parser::parse(' + query ($testVar: String = "default value") { + withNonNullArg (cannotBeNull: $testVar) + } + '), + null, + null, + ['testVar' => null] + ); + + $expected = [ + 'data' => ['withNonNullArg' => null], + 'errors' => [ + [ + 'message' => 'Argument "cannotBeNull" of non-null type "String!" must not be null.', + 'locations' => [['line' => 3, 'column' => 13]], + 'path' => ['withNonNullArg'], + 'extensions' => ['category' => 'graphql'], + ], + ], + ]; + + self::assertEquals($expected, $result->toArray()); + } + public function testNullsTheTopLevelIfAsyncNonNullableFieldErrors() : void { $doc = ' diff --git a/tests/Executor/VariablesTest.php b/tests/Executor/VariablesTest.php index 2ef4690d1..be5f2d781 100644 --- a/tests/Executor/VariablesTest.php +++ b/tests/Executor/VariablesTest.php @@ -13,6 +13,7 @@ use GraphQL\Type\Definition\Type; use GraphQL\Type\Schema; use PHPUnit\Framework\TestCase; +use function array_key_exists; use function json_encode; /** @@ -45,7 +46,8 @@ public function testUsingInlineStructs() : void self::assertEquals($expected, $result->toArray()); - $result = $this->executeQuery(' + $result = $this->executeQuery( + ' query ($input: TestInputObject) { fieldWithObjectInput(input: $input) } @@ -148,6 +150,10 @@ public function schema() : Schema 'type' => Type::string(), 'defaultValue' => 'Hello World', ]), + 'fieldWithNonNullableStringInputAndDefaultArgumentValue' => $this->fieldWithInputArg([ + 'type' => Type::nonNull(Type::string()), + 'defaultValue' => 'Hello World', + ]), 'fieldWithNestedInputObject' => $this->fieldWithInputArg([ 'type' => $TestNestedInputObject, 'defaultValue' => 'Hello World', @@ -171,6 +177,9 @@ private function fieldWithInputArg($inputArg) if (isset($args['input'])) { return json_encode($args['input']); } + if (array_key_exists('input', $args) && $args['input'] === null) { + return 'null'; + } return null; }, @@ -194,6 +203,33 @@ public function testUsingVariables() : void $result->toArray() ); + // uses undefined when variable not provided + $result = $this->executeQuery( + ' + query q($input: String) { + fieldWithNullableStringInput(input: $input) + }', + [] // Intentionally missing variable values. + ); + $expected = [ + 'data' => ['fieldWithNullableStringInput' => null], + ]; + self::assertEquals($expected, $result->toArray()); + + // uses null when variable provided explicit null value + $result = $this->executeQuery( + ' + query q($input: String) { + fieldWithNullableStringInput(input: $input) + }', + [ 'input' => null ] + ); + + $expected = [ + 'data' => ['fieldWithNullableStringInput' => 'null'], + ]; + self::assertEquals($expected, $result->toArray()); + // uses default value when not provided: $result = $this->executeQuery(' query ($input: TestInputObject = {a: "foo", b: ["bar"], c: "baz"}) { @@ -206,6 +242,47 @@ public function testUsingVariables() : void ]; self::assertEquals($expected, $result->toArray()); + // does not use default value when provided + $result = $this->executeQuery( + 'query q($input: String = "Default value") { + fieldWithNullableStringInput(input: $input) + }', + [ 'input' => 'Variable value' ] + ); + + $expected = [ + 'data' => ['fieldWithNullableStringInput' => '"Variable value"'], + ]; + self::assertEquals($expected, $result->toArray()); + + // uses explicit null value instead of default value + $result = $this->executeQuery( + ' + query q($input: String = "Default value") { + fieldWithNullableStringInput(input: $input) + }', + [ 'input' => null ] + ); + + $expected = [ + 'data' => ['fieldWithNullableStringInput' => 'null'], + ]; + self::assertEquals($expected, $result->toArray()); + + // uses null default value when not provided + $result = $this->executeQuery( + ' + query q($input: String = null) { + fieldWithNullableStringInput(input: $input) + }', + [] // Intentionally missing variable values. + ); + + $expected = [ + 'data' => ['fieldWithNullableStringInput' => 'null'], + ]; + self::assertEquals($expected, $result->toArray()); + // properly parses single value to list: $params = ['input' => ['a' => 'foo', 'b' => 'bar', 'c' => 'baz']]; $result = $this->executeQuery($doc, $params); @@ -483,10 +560,8 @@ public function testDoesNotAllowNonNullableInputsToBeSetToNullInAVariable() : vo $expected = [ 'errors' => [ [ - 'message' => - 'Variable "$value" got invalid value null; ' . - 'Expected non-nullable type String! not to be null.', - 'locations' => [['line' => 2, 'column' => 31]], + 'message' => 'Variable "$value" of non-null type "String!" must not be null.', + 'locations' => [['line' => 2, 'column' => 31]], 'extensions' => ['category' => 'graphql'], ], ], @@ -627,7 +702,7 @@ public function testAllowsListsToBeNull() : void } '; $result = $this->executeQuery($doc, ['input' => null]); - $expected = ['data' => ['list' => null]]; + $expected = ['data' => ['list' => 'null']]; self::assertEquals($expected, $result->toArray()); } @@ -676,10 +751,8 @@ public function testDoesNotAllowNonNullListsToBeNull() : void $expected = [ 'errors' => [ [ - 'message' => - 'Variable "$input" got invalid value null; ' . - 'Expected non-nullable type [String]! not to be null.', - 'locations' => [['line' => 2, 'column' => 17]], + 'message' => 'Variable "$input" of non-null type "[String]!" must not be null.', + 'locations' => [['line' => 2, 'column' => 17]], 'extensions' => ['category' => 'graphql'], ], ], @@ -728,7 +801,7 @@ public function testAllowsListsOfNonNullsToBeNull() : void } '; $result = $this->executeQuery($doc, ['input' => null]); - $expected = ['data' => ['listNN' => null]]; + $expected = ['data' => ['listNN' => 'null']]; self::assertEquals($expected, $result->toArray()); } @@ -786,10 +859,8 @@ public function testDoesNotAllowNonNullListsOfNonNullsToBeNull() : void $expected = [ 'errors' => [ [ - 'message' => - 'Variable "$input" got invalid value null; ' . - 'Expected non-nullable type [String!]! not to be null.', - 'locations' => [['line' => 2, 'column' => 17]], + 'message' => 'Variable "$input" of non-null type "[String!]!" must not be null.', + 'locations' => [['line' => 2, 'column' => 17]], 'extensions' => ['category' => 'graphql'], ], ], @@ -945,4 +1016,21 @@ public function testNotWhenArgumentCannotBeCoerced() : void self::assertEquals($expected, $result->toArray()); } + + /** + * @see it('when no runtime value is provided to a non-null argument') + */ + public function testWhenNoRuntimeValueIsProvidedToANonNullArgument() + { + $result = $this->executeQuery(' + query optionalVariable($optional: String) { + fieldWithNonNullableStringInputAndDefaultArgumentValue(input: $optional) + } + '); + + $expected = [ + 'data' => ['fieldWithNonNullableStringInputAndDefaultArgumentValue' => '"Hello World"'], + ]; + self::assertEquals($expected, $result->toArray()); + } } diff --git a/tests/Type/IntrospectionTest.php b/tests/Type/IntrospectionTest.php index d38bda1bd..781a64eda 100644 --- a/tests/Type/IntrospectionTest.php +++ b/tests/Type/IntrospectionTest.php @@ -13,7 +13,7 @@ use GraphQL\Type\Definition\Type; use GraphQL\Type\Introspection; use GraphQL\Type\Schema; -use GraphQL\Validator\Rules\ProvidedNonNullArguments; +use GraphQL\Validator\Rules\ProvidedRequiredArguments; use PHPUnit\Framework\TestCase; use function json_encode; @@ -1418,7 +1418,7 @@ public function testFailsAsExpectedOnTheTypeRootFieldWithoutAnArg() : void $expected = [ 'errors' => [ FormattedError::create( - ProvidedNonNullArguments::missingFieldArgMessage('__type', 'name', 'String!'), + ProvidedRequiredArguments::missingFieldArgMessage('__type', 'name', 'String!'), [new SourceLocation(3, 9)] ), ], diff --git a/tests/Validator/ProvidedNonNullArgumentsTest.php b/tests/Validator/ProvidedRequiredArgumentsTest.php similarity index 81% rename from tests/Validator/ProvidedNonNullArgumentsTest.php rename to tests/Validator/ProvidedRequiredArgumentsTest.php index 803fcda59..7a5c2d759 100644 --- a/tests/Validator/ProvidedNonNullArgumentsTest.php +++ b/tests/Validator/ProvidedRequiredArgumentsTest.php @@ -6,9 +6,9 @@ use GraphQL\Error\FormattedError; use GraphQL\Language\SourceLocation; -use GraphQL\Validator\Rules\ProvidedNonNullArguments; +use GraphQL\Validator\Rules\ProvidedRequiredArguments; -class ProvidedNonNullArgumentsTest extends ValidatorTestCase +class ProvidedRequiredArgumentsTest extends ValidatorTestCase { // Validate: Provided required arguments /** @@ -18,7 +18,7 @@ public function testIgnoresUnknownArguments() : void { // ignores unknown arguments $this->expectPassesRule( - new ProvidedNonNullArguments(), + new ProvidedRequiredArguments(), ' { dog { @@ -37,7 +37,7 @@ public function testIgnoresUnknownArguments() : void public function testArgOnOptionalArg() : void { $this->expectPassesRule( - new ProvidedNonNullArguments(), + new ProvidedRequiredArguments(), ' { dog { @@ -54,7 +54,7 @@ public function testArgOnOptionalArg() : void public function testNoArgOnOptionalArg() : void { $this->expectPassesRule( - new ProvidedNonNullArguments(), + new ProvidedRequiredArguments(), ' { dog { @@ -65,13 +65,30 @@ public function testNoArgOnOptionalArg() : void ); } + /** + * @see it('No arg on non-null field with default') + */ + public function testNoArgOnNonNullFieldWithDefault() + { + $this->expectPassesRule( + new ProvidedRequiredArguments(), + ' + { + complicatedArgs { + nonNullFieldWithDefault + } + } + ' + ); + } + /** * @see it('Multiple args') */ public function testMultipleArgs() : void { $this->expectPassesRule( - new ProvidedNonNullArguments(), + new ProvidedRequiredArguments(), ' { complicatedArgs { @@ -88,7 +105,7 @@ public function testMultipleArgs() : void public function testMultipleArgsReverseOrder() : void { $this->expectPassesRule( - new ProvidedNonNullArguments(), + new ProvidedRequiredArguments(), ' { complicatedArgs { @@ -105,7 +122,7 @@ public function testMultipleArgsReverseOrder() : void public function testNoArgsOnMultipleOptional() : void { $this->expectPassesRule( - new ProvidedNonNullArguments(), + new ProvidedRequiredArguments(), ' { complicatedArgs { @@ -122,7 +139,7 @@ public function testNoArgsOnMultipleOptional() : void public function testOneArgOnMultipleOptional() : void { $this->expectPassesRule( - new ProvidedNonNullArguments(), + new ProvidedRequiredArguments(), ' { complicatedArgs { @@ -139,7 +156,7 @@ public function testOneArgOnMultipleOptional() : void public function testSecondArgOnMultipleOptional() : void { $this->expectPassesRule( - new ProvidedNonNullArguments(), + new ProvidedRequiredArguments(), ' { complicatedArgs { @@ -156,7 +173,7 @@ public function testSecondArgOnMultipleOptional() : void public function testMultipleReqsOnMixedList() : void { $this->expectPassesRule( - new ProvidedNonNullArguments(), + new ProvidedRequiredArguments(), ' { complicatedArgs { @@ -173,7 +190,7 @@ public function testMultipleReqsOnMixedList() : void public function testMultipleReqsAndOneOptOnMixedList() : void { $this->expectPassesRule( - new ProvidedNonNullArguments(), + new ProvidedRequiredArguments(), ' { complicatedArgs { @@ -190,7 +207,7 @@ public function testMultipleReqsAndOneOptOnMixedList() : void public function testAllReqsAndOptsOnMixedList() : void { $this->expectPassesRule( - new ProvidedNonNullArguments(), + new ProvidedRequiredArguments(), ' { complicatedArgs { @@ -209,7 +226,7 @@ public function testAllReqsAndOptsOnMixedList() : void public function testMissingOneNonNullableArgument() : void { $this->expectFailsRule( - new ProvidedNonNullArguments(), + new ProvidedRequiredArguments(), ' { complicatedArgs { @@ -224,7 +241,7 @@ public function testMissingOneNonNullableArgument() : void private function missingFieldArg($fieldName, $argName, $typeName, $line, $column) { return FormattedError::create( - ProvidedNonNullArguments::missingFieldArgMessage($fieldName, $argName, $typeName), + ProvidedRequiredArguments::missingFieldArgMessage($fieldName, $argName, $typeName), [new SourceLocation($line, $column)] ); } @@ -235,7 +252,7 @@ private function missingFieldArg($fieldName, $argName, $typeName, $line, $column public function testMissingMultipleNonNullableArguments() : void { $this->expectFailsRule( - new ProvidedNonNullArguments(), + new ProvidedRequiredArguments(), ' { complicatedArgs { @@ -258,7 +275,7 @@ public function testMissingMultipleNonNullableArguments() : void public function testIncorrectValueAndMissingArgument() : void { $this->expectFailsRule( - new ProvidedNonNullArguments(), + new ProvidedRequiredArguments(), ' { complicatedArgs { @@ -278,7 +295,7 @@ public function testIncorrectValueAndMissingArgument() : void public function testIgnoresUnknownDirectives() : void { $this->expectPassesRule( - new ProvidedNonNullArguments(), + new ProvidedRequiredArguments(), ' { dog @unknown @@ -293,7 +310,7 @@ public function testIgnoresUnknownDirectives() : void public function testWithDirectivesOfValidTypes() : void { $this->expectPassesRule( - new ProvidedNonNullArguments(), + new ProvidedRequiredArguments(), ' { dog @include(if: true) { @@ -313,7 +330,7 @@ public function testWithDirectivesOfValidTypes() : void public function testWithDirectiveWithMissingTypes() : void { $this->expectFailsRule( - new ProvidedNonNullArguments(), + new ProvidedRequiredArguments(), ' { dog @include { @@ -331,7 +348,7 @@ public function testWithDirectiveWithMissingTypes() : void private function missingDirectiveArg($directiveName, $argName, $typeName, $line, $column) { return FormattedError::create( - ProvidedNonNullArguments::missingDirectiveArgMessage($directiveName, $argName, $typeName), + ProvidedRequiredArguments::missingDirectiveArgMessage($directiveName, $argName, $typeName), [new SourceLocation($line, $column)] ); } diff --git a/tests/Validator/ValidatorTestCase.php b/tests/Validator/ValidatorTestCase.php index 55e913666..e315d9190 100644 --- a/tests/Validator/ValidatorTestCase.php +++ b/tests/Validator/ValidatorTestCase.php @@ -193,6 +193,7 @@ public static function getTestSchema() 'name' => 'ComplexInput', 'fields' => [ 'requiredField' => ['type' => Type::nonNull(Type::boolean())], + 'nonNullField' => ['type' => Type::nonNull(Type::boolean()), 'defaultValue' => false], 'intField' => ['type' => Type::int()], 'stringField' => ['type' => Type::string()], 'booleanField' => ['type' => Type::boolean()], @@ -257,6 +258,12 @@ public static function getTestSchema() 'req2' => ['type' => Type::nonNull(Type::int())], ], ], + 'nonNullFieldWithDefault' => [ + 'type' => Type::string(), + 'args' => [ + 'arg' => [ 'type' => Type::nonNull(Type::int()), 'defaultValue' => 0 ], + ], + ], 'multipleOpts' => [ 'type' => Type::string(), 'args' => [ diff --git a/tests/Validator/ValuesOfCorrectTypeTest.php b/tests/Validator/ValuesOfCorrectTypeTest.php index ac927084e..721f01f6b 100644 --- a/tests/Validator/ValuesOfCorrectTypeTest.php +++ b/tests/Validator/ValuesOfCorrectTypeTest.php @@ -1060,9 +1060,9 @@ public function testIncorrectValueType() : void } /** - * @see it('Incorrect value and missing argument (ProvidedNonNullArguments)') + * @see it('Incorrect value and missing argument (ProvidedRequiredArguments)') */ - public function testIncorrectValueAndMissingArgumentProvidedNonNullArguments() : void + public function testIncorrectValueAndMissingArgumentProvidedRequiredArguments() : void { $this->expectFailsRule( new ValuesOfCorrectType(), @@ -1274,6 +1274,27 @@ public function testPartialObjectInvalidFieldType() : void ); } + /** + * @see it('Partial object, null to non-null field') + */ + public function testPartialObjectNullToNonNullField() + { + $this->expectFailsRule( + new ValuesOfCorrectType(), + ' + { + complicatedArgs { + complexArgField(complexArg: { + requiredField: true, + nonNullField: null, + }) + } + } + ', + [$this->badValueWithMessage('Field "complexArgField" argument "complexArg" requires type Boolean!, found null.', 6, 29)] + ); + } + /** * @see it('Partial object, unknown field arg') * @@ -1301,7 +1322,7 @@ public function testPartialObjectUnknownFieldArg() : void 'unknownField', 6, 15, - 'Did you mean intField or booleanField?' + 'Did you mean nonNullField, intField, or booleanField?' ), ] ); @@ -1417,6 +1438,7 @@ public function testVariablesWithValidDefaultValues() : void $a: Int = 1, $b: String = "ok", $c: ComplexInput = { requiredField: true, intField: 3 } + $d: Int! = 123 ) { dog { name } } diff --git a/tests/Validator/VariablesDefaultValueAllowedTest.php b/tests/Validator/VariablesDefaultValueAllowedTest.php deleted file mode 100644 index ddebae2b8..000000000 --- a/tests/Validator/VariablesDefaultValueAllowedTest.php +++ /dev/null @@ -1,132 +0,0 @@ -expectPassesRule( - new VariablesDefaultValueAllowed(), - ' - query NullableValues($a: Int, $b: String, $c: ComplexInput) { - dog { name } - } - ' - ); - } - - // DESCRIBE: Validate: Variable default value is allowed - - /** - * @see it('required variables without default values') - */ - public function testRequiredVariablesWithoutDefaultValues() : void - { - $this->expectPassesRule( - new VariablesDefaultValueAllowed(), - ' - query RequiredValues($a: Int!, $b: String!) { - dog { name } - } - ' - ); - } - - /** - * @see it('variables with valid default values') - */ - public function testVariablesWithValidDefaultValues() : void - { - $this->expectPassesRule( - new VariablesDefaultValueAllowed(), - ' - query WithDefaultValues( - $a: Int = 1, - $b: String = "ok", - $c: ComplexInput = { requiredField: true, intField: 3 } - ) { - dog { name } - } - ' - ); - } - - /** - * @see it('variables with valid default null values') - */ - public function testVariablesWithValidDefaultNullValues() : void - { - $this->expectPassesRule( - new VariablesDefaultValueAllowed(), - ' - query WithDefaultValues( - $a: Int = null, - $b: String = null, - $c: ComplexInput = { requiredField: true, intField: null } - ) { - dog { name } - } - ' - ); - } - - /** - * @see it('no required variables with default values') - */ - public function testNoRequiredVariablesWithDefaultValues() : void - { - $this->expectFailsRule( - new VariablesDefaultValueAllowed(), - ' - query UnreachableDefaultValues($a: Int! = 3, $b: String! = "default") { - dog { name } - } - ', - [ - $this->defaultForRequiredVar('a', 'Int!', 'Int', 2, 49), - $this->defaultForRequiredVar('b', 'String!', 'String', 2, 66), - ] - ); - } - - private function defaultForRequiredVar($varName, $typeName, $guessTypeName, $line, $column) - { - return FormattedError::create( - VariablesDefaultValueAllowed::defaultForRequiredVarMessage( - $varName, - $typeName, - $guessTypeName - ), - [new SourceLocation($line, $column)] - ); - } - - /** - * @see it('variables with invalid default null values') - */ - public function testNullIntoNullableType() : void - { - $this->expectFailsRule( - new VariablesDefaultValueAllowed(), - ' - query WithDefaultValues($a: Int! = null, $b: String! = null) { - dog { name } - } - ', - [ - $this->defaultForRequiredVar('a', 'Int!', 'Int', 2, 42), - $this->defaultForRequiredVar('b', 'String!', 'String', 2, 62), - ] - ); - } -} diff --git a/tests/Validator/VariablesInAllowedPositionTest.php b/tests/Validator/VariablesInAllowedPositionTest.php index b1a1edd5c..9f7cc2a24 100644 --- a/tests/Validator/VariablesInAllowedPositionTest.php +++ b/tests/Validator/VariablesInAllowedPositionTest.php @@ -109,25 +109,6 @@ public function testBooleanNonNullXBooleanWithinFragment() : void ); } - /** - * @see it('Int => Int! with default') - */ - public function testIntXIntNonNullWithDefault() : void - { - // Int => Int! with default - $this->expectPassesRule( - new VariablesInAllowedPosition(), - ' - query Query($intArg: Int = 1) - { - complicatedArgs { - nonNullIntArgField(nonNullIntArg: $intArg) - } - } - ' - ); - } - /** * @see it('[String] => [String]') */ @@ -252,22 +233,6 @@ public function testBooleanNonNullXBooleanNonNullInDirective() : void ); } - /** - * @see it('Boolean => Boolean! in directive with default') - */ - public function testBooleanXBooleanNonNullInDirectiveWithDefault() : void - { - $this->expectPassesRule( - new VariablesInAllowedPosition(), - ' - query Query($boolVar: Boolean = false) - { - dog @include(if: $boolVar) - } - ' - ); - } - /** * @see it('Int => Int!') */ @@ -463,4 +428,74 @@ public function testStringArrayXStringNonNullArray() : void ] ); } + + // Allows optional (nullable) variables with default values + + /** + * @see it('Int => Int! fails when variable provides null default value') + */ + public function testIntXIntNonNullFailsWhenVariableProvidesNullDefaultValue() + { + $this->expectFailsRule( + new VariablesInAllowedPosition(), + ' + query Query($intVar: Int = null) { + complicatedArgs { + nonNullIntArgField(nonNullIntArg: $intVar) + } + } + ', + [FormattedError::create( + VariablesInAllowedPosition::badVarPosMessage('intVar', 'Int', 'Int!'), + [new SourceLocation(2, 21), new SourceLocation(4, 47)] + ), + ] + ); + } + + /** + * @see it('Int => Int! when variable provides non-null default value') + */ + public function testIntXIntNonNullWhenVariableProvidesNonNullDefaultValue() + { + $this->expectPassesRule( + new VariablesInAllowedPosition(), + ' + query Query($intVar: Int = 1) { + complicatedArgs { + nonNullIntArgField(nonNullIntArg: $intVar) + } + }' + ); + } + + /** + * @see it('Int => Int! when optional argument provides default value') + */ + public function testIntXIntNonNullWhenOptionalArgumentProvidesDefaultValue() + { + $this->expectPassesRule( + new VariablesInAllowedPosition(), + ' + query Query($intVar: Int) { + complicatedArgs { + nonNullFieldWithDefault(nonNullIntArg: $intVar) + } + }' + ); + } + + /** + * @see it('Boolean => Boolean! in directive with default value with option') + */ + public function testBooleanXBooleanNonNullInDirectiveWithDefaultValueWithOption() + { + $this->expectPassesRule( + new VariablesInAllowedPosition(), + ' + query Query($boolVar: Boolean = false) { + dog @include(if: $boolVar) + }' + ); + } } From 84e5aaf24adb9095ff6e032bc41b0f27794fe157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=AE=B5=E5=B0=8F=E5=BC=BA?= Date: Sat, 17 Aug 2019 17:29:10 +0800 Subject: [PATCH 076/256] add resolver add resolver --- examples/01-blog/Blog/Type/StoryType.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/examples/01-blog/Blog/Type/StoryType.php b/examples/01-blog/Blog/Type/StoryType.php index 32cea4e70..a57c0b1d3 100644 --- a/examples/01-blog/Blog/Type/StoryType.php +++ b/examples/01-blog/Blog/Type/StoryType.php @@ -124,4 +124,16 @@ public function resolveComments(Story $story, $args) $args += ['after' => null]; return DataSource::findComments($story->id, $args['limit'], $args['after']); } + + public function resolveMentions(Story $story, $args, AppContext $context){ + return DataSource::findStoryMentions($story->id); + } + + public function resolveLikedBy(Story $story, $args, AppContext $context){ + return DataSource::findLikes($story->id,10); + } + + public function resolveLikes(Story $story, $args, AppContext $context){ + return DataSource::findLikes($story->id,10); + } } From 8ef4ab3ed804aaefaf0df0aecc58d6adba59ce66 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Sat, 24 Aug 2019 15:56:33 +0700 Subject: [PATCH 077/256] Execute: simplify + speedup --- src/Executor/ReferenceExecutor.php | 136 +++++++++-------------------- 1 file changed, 41 insertions(+), 95 deletions(-) diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index f51219aa8..043db4d4a 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -652,97 +652,49 @@ private function completeValueCatchingError( $path, $result ) { - $exeContext = $this->exeContext; - // If the field type is non-nullable, then it is resolved without any - // protection from errors. - if ($returnType instanceof NonNull) { - return $this->completeValueWithLocatedError( - $returnType, - $fieldNodes, - $info, - $path, - $result - ); - } // Otherwise, error protection is applied, logging the error and resolving // a null value for this field if one is encountered. try { - $completed = $this->completeValueWithLocatedError( - $returnType, - $fieldNodes, - $info, - $path, - $result - ); - $promise = $this->getPromise($completed); + $promise = $this->getPromise($result); if ($promise !== null) { - return $promise->then( - null, - function ($error) use ($exeContext) { - $exeContext->addError($error); + $completed = $promise->then(function (&$resolved) use ($returnType, $fieldNodes, $info, $path) { + return $this->completeValue($returnType, $fieldNodes, $info, $path, $resolved); + }); + } else { + $completed = $this->completeValue($returnType, $fieldNodes, $info, $path, $result); + } - return $this->exeContext->promiseAdapter->createFulfilled(null); - } - ); + $promise = $this->getPromise($completed); + if ($promise !== null) { + return $promise->then(null, function ($error) use ($fieldNodes, $path, $returnType) { + return $this->handleFieldError($error, $fieldNodes, $path, $returnType); + }); } return $completed; - } catch (Error $err) { - // If `completeValueWithLocatedError` returned abruptly (threw an error), log the error - // and return null. - $exeContext->addError($err); - - return null; + } catch (Throwable $err) { + return $this->handleFieldError($err, $fieldNodes, $path, $returnType); } } - /** - * This is a small wrapper around completeValue which annotates errors with - * location information. - * - * @param FieldNode[] $fieldNodes - * @param string[] $path - * @param mixed $result - * - * @return mixed[]|mixed|Promise|null - * - * @throws Error - */ - public function completeValueWithLocatedError( - Type $returnType, - $fieldNodes, - ResolveInfo $info, - $path, - $result - ) { - try { - $completed = $this->completeValue( - $returnType, - $fieldNodes, - $info, - $path, - $result - ); - $promise = $this->getPromise($completed); - if ($promise !== null) { - return $promise->then( - null, - function ($error) use ($fieldNodes, $path) { - return $this->exeContext->promiseAdapter->createRejected(Error::createLocatedError( - $error, - $fieldNodes, - $path - )); - } - ); - } + private function handleFieldError($rawError, $fieldNodes, $path, $returnType) + { + $error = Error::createLocatedError( + $rawError, + $fieldNodes, + $path + ); - return $completed; - } catch (Exception $error) { - throw Error::createLocatedError($error, $fieldNodes, $path); - } catch (Throwable $error) { - throw Error::createLocatedError($error, $fieldNodes, $path); + // If the field type is non-nullable, then it is resolved without any + // protection from errors, however it still properly locates the error. + if ($returnType instanceof NonNull) { + throw $error; } + // Otherwise, error protection is applied, logging the error and resolving + // a null value for this field if one is encountered. + $this->exeContext->addError($error); + + return null; } /** @@ -782,16 +734,11 @@ private function completeValue( $path, &$result ) { - $promise = $this->getPromise($result); - // If result is a Promise, apply-lift over completeValue. - if ($promise !== null) { - return $promise->then(function (&$resolved) use ($returnType, $fieldNodes, $info, $path) { - return $this->completeValue($returnType, $fieldNodes, $info, $path, $resolved); - }); - } - if ($result instanceof Exception || $result instanceof Throwable) { + // If result is an Error, throw a located error. + if ($result instanceof Throwable) { throw $result; } + // If field type is NonNull, complete for inner type, and throw field error // if result is null. if ($returnType instanceof NonNull) { @@ -1242,7 +1189,7 @@ private function collectSubFields(ObjectType $returnType, $fieldNodes) : ArrayOb private function executeFields(ObjectType $parentType, $rootValue, $path, $fields) { $containsPromise = false; - $finalResults = []; + $results = []; foreach ($fields as $responseName => $fieldNodes) { $fieldPath = $path; $fieldPath[] = $responseName; @@ -1250,21 +1197,20 @@ private function executeFields(ObjectType $parentType, $rootValue, $path, $field if ($result === self::$UNDEFINED) { continue; } - if (! $containsPromise && $this->getPromise($result) !== null) { + if (! $containsPromise && $this->isPromise($result)) { $containsPromise = true; } - $finalResults[$responseName] = $result; + $results[$responseName] = $result; } // If there are no promises, we can just return the object if (! $containsPromise) { - return self::fixResultsIfEmptyArray($finalResults); + return self::fixResultsIfEmptyArray($results); } - // Otherwise, results is a map from field name to the result - // of resolving that field, which is possibly a promise. Return - // a promise that will return this same map, but with any - // promises replaced with the values they resolved to. - return $this->promiseForAssocArray($finalResults); + // Otherwise, results is a map from field name to the result of resolving that + // field, which is possibly a promise. Return a promise that will return this + // same map, but with any promises replaced with the values they resolved to. + return $this->promiseForAssocArray($results); } /** From 0a748bb952ea0b7fcaf0004a1f3c62fd79b6ef4b Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Sat, 24 Aug 2019 16:15:10 +0700 Subject: [PATCH 078/256] Executor: Unify contextValue --- src/Executor/Executor.php | 6 +++--- src/Executor/ReferenceExecutor.php | 24 +++++++++++++++--------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/Executor/Executor.php b/src/Executor/Executor.php index 764fa1c59..ee9618748 100644 --- a/src/Executor/Executor.php +++ b/src/Executor/Executor.php @@ -163,11 +163,11 @@ public static function promiseToExecute( * * @param mixed $objectValue * @param mixed[] $args - * @param mixed|null $context + * @param mixed|null $contextValue * * @return mixed|null */ - public static function defaultFieldResolver($objectValue, $args, $context, ResolveInfo $info) + public static function defaultFieldResolver($objectValue, $args, $contextValue, ResolveInfo $info) { $fieldName = $info->fieldName; $property = null; @@ -183,7 +183,7 @@ public static function defaultFieldResolver($objectValue, $args, $context, Resol } return $property instanceof Closure - ? $property($objectValue, $args, $context, $info) + ? $property($objectValue, $args, $contextValue, $info) : $property; } } diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 043db4d4a..5bf1a4e0c 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -552,12 +552,11 @@ private function resolveField(ObjectType $parentType, $rootValue, $fieldNodes, $ } // Get the resolve function, regardless of if its result is normal // or abrupt (error). - $result = $this->resolveOrError( + $result = $this->resolveFieldValueOrError( $fieldDef, $fieldNode, $resolveFn, $rootValue, - $context, $info ); $result = $this->completeValueCatchingError( @@ -611,23 +610,23 @@ private function getFieldDef(Schema $schema, ObjectType $parentType, string $fie * @param FieldNode $fieldNode * @param callable $resolveFn * @param mixed $rootValue - * @param mixed $context * @param ResolveInfo $info * * @return Throwable|Promise|mixed */ - private function resolveOrError($fieldDef, $fieldNode, $resolveFn, $rootValue, $context, $info) + private function resolveFieldValueOrError($fieldDef, $fieldNode, $resolveFn, $rootValue, $info) { try { // Build a map of arguments from the field.arguments AST, using the // variables scope to fulfill any variable references. - $args = Values::getArgumentValues( + $args = Values::getArgumentValues( $fieldDef, $fieldNode, $this->exeContext->variableValues ); + $contextValue = $this->exeContext->contextValue; - return $resolveFn($rootValue, $args, $context, $info); + return $resolveFn($rootValue, $args, $contextValue, $info); } catch (Exception $error) { return $error; } catch (Throwable $error) { @@ -1002,12 +1001,12 @@ private function completeAbstractValue(AbstractType $returnType, $fieldNodes, Re * isTypeOf for the object being coerced, returning the first type that matches. * * @param mixed|null $value - * @param mixed|null $context + * @param mixed|null $contextValue * @param InterfaceType|UnionType $abstractType * * @return ObjectType|Promise|null */ - private function defaultTypeResolver($value, $context, ResolveInfo $info, AbstractType $abstractType) + private function defaultTypeResolver($value, $contextValue, ResolveInfo $info, AbstractType $abstractType) { // First, look for `__typename`. if ($value !== null && @@ -1035,7 +1034,7 @@ private function defaultTypeResolver($value, $context, ResolveInfo $info, Abstra $possibleTypes = $info->schema->getPossibleTypes($abstractType); $promisedIsTypeOfResults = []; foreach ($possibleTypes as $index => $type) { - $isTypeOfResult = $type->isTypeOf($value, $context, $info); + $isTypeOfResult = $type->isTypeOf($value, $contextValue, $info); if ($isTypeOfResult === null) { continue; } @@ -1150,6 +1149,13 @@ private function collectAndExecuteSubfields( return $this->executeFields($returnType, $result, $path, $subFieldNodes); } + /** + * A memoized collection of relevant subfields with regard to the return + * type. Memoizing ensures the subfields are not repeatedly calculated, which + * saves overhead when resolving lists of values. + * + * @param object $fieldNodes + */ private function collectSubFields(ObjectType $returnType, $fieldNodes) : ArrayObject { if (! isset($this->subFieldCache[$returnType])) { From cca8fd658767313c63d151b8dec2193e1a9e3c19 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Sat, 24 Aug 2019 16:54:06 +0700 Subject: [PATCH 079/256] Schema Validation: additional validation of extended schemas --- src/Type/SchemaValidationContext.php | 8 ++-- tests/Type/ValidationTest.php | 58 ++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/src/Type/SchemaValidationContext.php b/src/Type/SchemaValidationContext.php index e60ea2f3c..95e5a1cea 100644 --- a/src/Type/SchemaValidationContext.php +++ b/src/Type/SchemaValidationContext.php @@ -300,7 +300,7 @@ private function validateFields($type) if (! $fieldMap) { $this->reportError( sprintf('Type %s must define one or more fields.', $type->name), - $this->getAllObjectOrInterfaceNodes($type) + $this->getAllNodes($type) ); } @@ -376,7 +376,7 @@ private function validateFields($type) * * @return ObjectTypeDefinitionNode[]|ObjectTypeExtensionNode[]|InterfaceTypeDefinitionNode[]|InterfaceTypeExtensionNode[] */ - private function getAllObjectOrInterfaceNodes($type) + private function getAllNodes($type) { return $type->astNode ? ($type->extensionASTNodes @@ -394,7 +394,7 @@ private function getAllObjectOrInterfaceNodes($type) private function getAllFieldNodes($type, $fieldName) { $fieldNodes = []; - $astNodes = $this->getAllObjectOrInterfaceNodes($type); + $astNodes = $this->getAllNodes($type); foreach ($astNodes as $astNode) { if (! $astNode || ! $astNode->fields) { continue; @@ -554,7 +554,7 @@ private function getImplementsInterfaceNode(ObjectType $type, $iface) private function getAllImplementsInterfaceNodes(ObjectType $type, $iface) { $implementsNodes = []; - $astNodes = $this->getAllObjectOrInterfaceNodes($type); + $astNodes = $this->getAllNodes($type); foreach ($astNodes as $astNode) { if (! $astNode || ! $astNode->interfaces) { diff --git a/tests/Type/ValidationTest.php b/tests/Type/ValidationTest.php index c83bd806f..6faebd832 100644 --- a/tests/Type/ValidationTest.php +++ b/tests/Type/ValidationTest.php @@ -7,6 +7,7 @@ use GraphQL\Error\Error; use GraphQL\Error\InvariantViolation; use GraphQL\Error\Warning; +use GraphQL\Language\Parser; use GraphQL\Language\SourceLocation; use GraphQL\Type\Definition\CustomScalarType; use GraphQL\Type\Definition\EnumType; @@ -18,6 +19,7 @@ use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Schema; use GraphQL\Utils\BuildSchema; +use GraphQL\Utils\SchemaExtender; use GraphQL\Utils\Utils; use PHPUnit\Framework\TestCase; use function array_map; @@ -516,6 +518,62 @@ public function testRejectsASchemaWhoseSubscriptionTypeIsAnInputType() : void ); } + /** + * @see it('rejects a schema extended with invalid root types') + */ + public function testRejectsASchemaExtendedWithInvalidRootTypes() + { + $schema = BuildSchema::build(' + input SomeInputObject { + test: String + } + '); + + $schema = SchemaExtender::extend( + $schema, + Parser::parse(' + extend schema { + query: SomeInputObject + } + ') + ); + + $schema = SchemaExtender::extend( + $schema, + Parser::parse(' + extend schema { + mutation: SomeInputObject + } + ') + ); + + $schema = SchemaExtender::extend( + $schema, + Parser::parse(' + extend schema { + subscription: SomeInputObject + } + ') + ); + + $expected = [ + [ + 'message' => 'Query root type must be Object type, it cannot be SomeInputObject.', + 'locations' => [[ 'line' => 2, 'column' => 13 ]], + ], + [ + 'message' => 'Mutation root type must be Object type if provided, it cannot be SomeInputObject.', + 'locations' => [[ 'line' => 2, 'column' => 13 ]], + ], + [ + 'message' => 'Subscription root type must be Object type if provided, it cannot be SomeInputObject.', + 'locations' => [[ 'line' => 2, 'column' => 13 ]], + ], + ]; + + $this->assertMatchesValidationMessage($schema->validate(), $expected); + } + /** * @see it('rejects a Schema whose directives are incorrectly typed') */ From 89a7a7e362535dffd020fcab1736912f68b3c7d8 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Sat, 24 Aug 2019 17:15:07 +0700 Subject: [PATCH 080/256] Don't call global fieldResolver on introspection fields (#481) --- src/Type/Introspection.php | 83 +++++++++++++++++++++++++++----- tests/Type/IntrospectionTest.php | 27 +++++++++++ 2 files changed, 99 insertions(+), 11 deletions(-) diff --git a/src/Type/Introspection.php b/src/Type/Introspection.php index 5fe903cf2..9fa2084b8 100644 --- a/src/Type/Introspection.php +++ b/src/Type/Introspection.php @@ -284,8 +284,18 @@ public static function _type() } }, ], - 'name' => ['type' => Type::string()], - 'description' => ['type' => Type::string()], + 'name' => [ + 'type' => Type::string(), + 'resolve' => static function ($obj) { + return $obj->name; + }, + ], + 'description' => [ + 'type' => Type::string(), + 'resolve' => static function ($obj) { + return $obj->description; + }, + ], 'fields' => [ 'type' => Type::listOf(Type::nonNull(self::_field())), 'args' => [ @@ -440,8 +450,18 @@ public static function _field() 'which has a name, potentially a list of arguments, and a return type.', 'fields' => static function () { return [ - 'name' => ['type' => Type::nonNull(Type::string())], - 'description' => ['type' => Type::string()], + 'name' => [ + 'type' => Type::nonNull(Type::string()), + 'resolve' => static function (FieldDefinition $field) { + return $field->name; + }, + ], + 'description' => [ + 'type' => Type::string(), + 'resolve' => static function (FieldDefinition $field) { + return $field->description; + }, + ], 'args' => [ 'type' => Type::nonNull(Type::listOf(Type::nonNull(self::_inputValue()))), 'resolve' => static function (FieldDefinition $field) { @@ -461,7 +481,10 @@ public static function _field() }, ], 'deprecationReason' => [ - 'type' => Type::string(), + 'type' => Type::string(), + 'resolve' => static function (FieldDefinition $field) { + return $field->deprecationReason; + }, ], ]; }, @@ -483,8 +506,20 @@ public static function _inputValue() 'and optionally a default value.', 'fields' => static function () { return [ - 'name' => ['type' => Type::nonNull(Type::string())], - 'description' => ['type' => Type::string()], + 'name' => [ + 'type' => Type::nonNull(Type::string()), + 'resolve' => static function ($inputValue) { + /** @var FieldArgument|InputObjectField $inputValue */ + return $inputValue->name; + }, + ], + 'description' => [ + 'type' => Type::string(), + 'resolve' => static function ($inputValue) { + /** @var FieldArgument|InputObjectField $inputValue */ + return $inputValue->description; + }, + ], 'type' => [ 'type' => Type::nonNull(self::_type()), 'resolve' => static function ($value) { @@ -526,8 +561,18 @@ public static function _enumValue() 'a placeholder for a string or numeric value. However an Enum value is ' . 'returned in a JSON response as a string.', 'fields' => [ - 'name' => ['type' => Type::nonNull(Type::string())], - 'description' => ['type' => Type::string()], + 'name' => [ + 'type' => Type::nonNull(Type::string()), + 'resolve' => static function ($enumValue) { + return $enumValue->name; + }, + ], + 'description' => [ + 'type' => Type::string(), + 'resolve' => static function ($enumValue) { + return $enumValue->description; + }, + ], 'isDeprecated' => [ 'type' => Type::nonNull(Type::boolean()), 'resolve' => static function ($enumValue) { @@ -536,6 +581,9 @@ public static function _enumValue() ], 'deprecationReason' => [ 'type' => Type::string(), + 'resolve' => static function ($enumValue) { + return $enumValue->deprecationReason; + }, ], ], ]); @@ -557,12 +605,25 @@ public static function _directive() 'conditionally including or skipping a field. Directives provide this by ' . 'describing additional information to the executor.', 'fields' => [ - 'name' => ['type' => Type::nonNull(Type::string())], - 'description' => ['type' => Type::string()], + 'name' => [ + 'type' => Type::nonNull(Type::string()), + 'resolve' => static function ($obj) { + return $obj->name; + }, + ], + 'description' => [ + 'type' => Type::string(), + 'resolve' => static function ($obj) { + return $obj->description; + }, + ], 'locations' => [ 'type' => Type::nonNull(Type::listOf(Type::nonNull( self::_directiveLocation() ))), + 'resolve' => static function ($obj) { + return $obj->locations; + }, ], 'args' => [ 'type' => Type::nonNull(Type::listOf(Type::nonNull(self::_inputValue()))), diff --git a/tests/Type/IntrospectionTest.php b/tests/Type/IntrospectionTest.php index 781a64eda..2f983c478 100644 --- a/tests/Type/IntrospectionTest.php +++ b/tests/Type/IntrospectionTest.php @@ -1561,4 +1561,31 @@ enumValues { self::assertEquals($expected, GraphQL::executeQuery($schema, $request)->toArray()); } + + /** + * @see it('executes an introspection query without calling global fieldResolver') + */ + public function testExecutesAnIntrospectionQueryWithoutCallingGlobalFieldResolver() + { + $QueryRoot = new ObjectType([ + 'name' => 'QueryRoot', + 'fields' => [ + 'onlyField' => [ 'type' => Type::string() ], + ], + ]); + + $schema = new Schema([ 'query' => $QueryRoot ]); + $source = Introspection::getIntrospectionQuery(); + + $calledForFields = []; + /* istanbul ignore next */ + $fieldResolver = static function ($value, $_1, $_2, $info) use (&$calledForFields) { + $calledForFields[sprintf('%s::%s', $info->parentType->name, $info->fieldName)] = true; + + return $value; + }; + + GraphQL::executeQuery($schema, $source, null, null, null, null, $fieldResolver); + $this->assertEmpty($calledForFields); + } } From acdbd501fdeb8d27cde7dec451ee0b3b0a9af6b2 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Sat, 24 Aug 2019 17:24:54 +0700 Subject: [PATCH 081/256] Expose getOperationType(operation) on Schema --- src/Type/Schema.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/Type/Schema.php b/src/Type/Schema.php index 4e59a3335..b05c63212 100644 --- a/src/Type/Schema.php +++ b/src/Type/Schema.php @@ -241,6 +241,25 @@ public function getDirectives() return $this->config->directives ?: GraphQL::getStandardDirectives(); } + /** + * @param string $operation + * + * @return ObjectType|null + */ + public function getOperationType($operation) + { + switch ($operation) { + case 'query': + return $this->getQueryType(); + case 'mutation': + return $this->getMutationType(); + case 'subscription': + return $this->getSubscriptionType(); + default: + return null; + } + } + /** * Returns schema query type * From 5fa2dffa1b3d0b42d535f18db1f88a821d7c7811 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Sat, 24 Aug 2019 17:26:58 +0700 Subject: [PATCH 082/256] Code style fix --- tests/Type/IntrospectionTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Type/IntrospectionTest.php b/tests/Type/IntrospectionTest.php index 2f983c478..80a2363e3 100644 --- a/tests/Type/IntrospectionTest.php +++ b/tests/Type/IntrospectionTest.php @@ -16,6 +16,7 @@ use GraphQL\Validator\Rules\ProvidedRequiredArguments; use PHPUnit\Framework\TestCase; use function json_encode; +use function sprintf; class IntrospectionTest extends TestCase { @@ -1586,6 +1587,6 @@ public function testExecutesAnIntrospectionQueryWithoutCallingGlobalFieldResolve }; GraphQL::executeQuery($schema, $source, null, null, null, null, $fieldResolver); - $this->assertEmpty($calledForFields); + self::assertEmpty($calledForFields); } } From c705280e7d8a4d70ba4b0d5c0da4a5f2c69aa547 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Sat, 24 Aug 2019 17:40:00 +0700 Subject: [PATCH 083/256] Visitor: more tests --- tests/Language/VisitorTest.php | 37 ++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/Language/VisitorTest.php b/tests/Language/VisitorTest.php index bdc135241..72bf422c6 100644 --- a/tests/Language/VisitorTest.php +++ b/tests/Language/VisitorTest.php @@ -24,16 +24,21 @@ use GraphQL\Type\Definition\Type; use GraphQL\Utils\TypeInfo; use function array_keys; +use function array_pop; use function array_slice; use function count; use function file_get_contents; use function func_get_args; use function gettype; use function is_array; +use function is_numeric; use function iterator_to_array; class VisitorTest extends ValidatorTestCase { + /** + * @see it('validates path argument') + */ public function testValidatesPathArgument() : void { $visited = []; @@ -70,6 +75,38 @@ public function testValidatesPathArgument() : void self::assertEquals($expected, $visited); } + /** + * @see it('validates ancestors argument') + */ + public function testValidatesAncestorsArgument() + { + $ast = Parser::parse('{ a }', ['noLocation' => true]); + $visitedNodes = []; + + Visitor::visit($ast, [ + 'enter' => static function ($node, $key, $parent, $path, $ancestors) use (&$visitedNodes) { + $inArray = is_numeric($key); + if ($inArray) { + $visitedNodes[] = $parent; + } + $visitedNodes[] = $node; + + $expectedAncestors = array_slice($visitedNodes, 0, -2); + self::assertEquals($expectedAncestors, $ancestors); + }, + 'leave' => static function ($node, $key, $parent, $path, $ancestors) use (&$visitedNodes) { + $expectedAncestors = array_slice($visitedNodes, 0, -2); + self::assertEquals($expectedAncestors, $ancestors); + + $inArray = is_numeric($key); + if ($inArray) { + array_pop($visitedNodes); + } + array_pop($visitedNodes); + }, + ]); + } + private function checkVisitorFnArgs($ast, $args, $isEdited = false) { /** @var Node $node */ From 71f4d17b3c4c6fd7f81dcfac27092bebc8e3a54b Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Thu, 29 Aug 2019 10:54:47 +0700 Subject: [PATCH 084/256] Scalars: reject array serialization/coercion --- src/Type/Definition/BooleanType.php | 9 +++ src/Type/Definition/FloatType.php | 8 +++ src/Type/Definition/IDType.php | 13 +++- src/Type/Definition/IntType.php | 8 +++ src/Type/Definition/StringType.php | 22 +++--- tests/Executor/VariablesTest.php | 10 --- tests/Type/ScalarSerializationTest.php | 98 ++++++++++++++++++-------- tests/Utils/CoerceValueTest.php | 60 ++++++++++------ 8 files changed, 153 insertions(+), 75 deletions(-) diff --git a/src/Type/Definition/BooleanType.php b/src/Type/Definition/BooleanType.php index f9c91bb00..478159cba 100644 --- a/src/Type/Definition/BooleanType.php +++ b/src/Type/Definition/BooleanType.php @@ -9,6 +9,7 @@ use GraphQL\Language\AST\BooleanValueNode; use GraphQL\Language\AST\Node; use GraphQL\Utils\Utils; +use function is_array; use function is_bool; class BooleanType extends ScalarType @@ -26,9 +27,17 @@ class BooleanType extends ScalarType * PHP does natively to make this intuitive for developers. * * @param mixed $value + * + * @throws Error */ public function serialize($value) : bool { + if (is_array($value)) { + throw new Error( + 'Boolean cannot represent an array value: ' . Utils::printSafe($value) + ); + } + return (bool) $value; } diff --git a/src/Type/Definition/FloatType.php b/src/Type/Definition/FloatType.php index e8923ca6f..18e1835db 100644 --- a/src/Type/Definition/FloatType.php +++ b/src/Type/Definition/FloatType.php @@ -10,7 +10,9 @@ use GraphQL\Language\AST\IntValueNode; use GraphQL\Language\AST\Node; use GraphQL\Utils\Utils; +use function is_array; use function is_numeric; +use function sprintf; class FloatType extends ScalarType { @@ -37,6 +39,12 @@ public function serialize($value) private function coerceFloat($value) { + if (is_array($value)) { + throw new Error( + sprintf('Float cannot represent an array value: %s', Utils::printSafe($value)) + ); + } + if ($value === '') { throw new Error( 'Float cannot represent non numeric value: (empty string)' diff --git a/src/Type/Definition/IDType.php b/src/Type/Definition/IDType.php index 807739367..f4db03d31 100644 --- a/src/Type/Definition/IDType.php +++ b/src/Type/Definition/IDType.php @@ -10,6 +10,7 @@ use GraphQL\Language\AST\Node; use GraphQL\Language\AST\StringValueNode; use GraphQL\Utils\Utils; +use function is_array; use function is_int; use function is_object; use function is_scalar; @@ -47,8 +48,13 @@ public function serialize($value) if ($value === null) { return 'null'; } + if (is_array($value)) { + throw new Error( + 'ID cannot represent an array value: ' . Utils::printSafe($value) + ); + } if (! is_scalar($value) && (! is_object($value) || ! method_exists($value, '__toString'))) { - throw new Error('ID type cannot represent non scalar value: ' . Utils::printSafe($value)); + throw new Error('ID cannot represent non scalar value: ' . Utils::printSafe($value)); } return (string) $value; @@ -66,6 +72,11 @@ public function parseValue($value) if (is_string($value) || is_int($value)) { return (string) $value; } + if (is_array($value)) { + throw new Error( + 'ID cannot represent an array value: ' . Utils::printSafe($value) + ); + } throw new Error('Cannot represent value as ID: ' . Utils::printSafe($value)); } diff --git a/src/Type/Definition/IntType.php b/src/Type/Definition/IntType.php index e6176c33b..e67d1a208 100644 --- a/src/Type/Definition/IntType.php +++ b/src/Type/Definition/IntType.php @@ -11,8 +11,10 @@ use GraphQL\Utils\Utils; use function floatval; use function intval; +use function is_array; use function is_bool; use function is_numeric; +use function sprintf; class IntType extends ScalarType { @@ -51,6 +53,12 @@ public function serialize($value) */ private function coerceInt($value) { + if (is_array($value)) { + throw new Error( + sprintf('Int cannot represent an array value: %s', Utils::printSafe($value)) + ); + } + if ($value === '') { throw new Error( 'Int cannot represent non 32-bit signed integer value: (empty string)' diff --git a/src/Type/Definition/StringType.php b/src/Type/Definition/StringType.php index 533298fc2..79f67e113 100644 --- a/src/Type/Definition/StringType.php +++ b/src/Type/Definition/StringType.php @@ -33,6 +33,11 @@ class StringType extends ScalarType * @throws Error */ public function serialize($value) + { + return $this->coerceString($value); + } + + private function coerceString($value) { if ($value === true) { return 'true'; @@ -43,22 +48,17 @@ public function serialize($value) if ($value === null) { return 'null'; } + if (is_array($value)) { + throw new Error( + 'String cannot represent an array value: ' . Utils::printSafe($value) + ); + } if (is_object($value) && method_exists($value, '__toString')) { return (string) $value; } if (! is_scalar($value)) { - throw new Error('String cannot represent non scalar value: ' . Utils::printSafe($value)); - } - - return $this->coerceString($value); - } - - private function coerceString($value) - { - if (is_array($value)) { throw new Error( - 'String cannot represent an array value: ' . - Utils::printSafe($value) + 'String cannot represent non scalar value: ' . Utils::printSafe($value) ); } diff --git a/tests/Executor/VariablesTest.php b/tests/Executor/VariablesTest.php index be5f2d781..f6883aeb2 100644 --- a/tests/Executor/VariablesTest.php +++ b/tests/Executor/VariablesTest.php @@ -651,16 +651,6 @@ public function testReportsErrorForArrayPassedIntoStringInput() : void self::assertEquals($expected, $result->toArray()); } - /** - * @see it('serializing an array via GraphQLString throws TypeError') - */ - public function testSerializingAnArrayViaGraphQLStringThrowsTypeError() : void - { - $this->expectException(Error::class); - $this->expectExceptionMessage('String cannot represent non scalar value: [1,2,3]'); - Type::string()->serialize([1, 2, 3]); - } - /** * @see it('reports error for non-provided variables for non-nullable inputs') */ diff --git a/tests/Type/ScalarSerializationTest.php b/tests/Type/ScalarSerializationTest.php index f7b3c6d59..34737c8c8 100644 --- a/tests/Type/ScalarSerializationTest.php +++ b/tests/Type/ScalarSerializationTest.php @@ -5,9 +5,12 @@ namespace GraphQL\Tests\Type; use GraphQL\Error\Error; +use GraphQL\Type\Definition\IDType; +use GraphQL\Type\Definition\StringType; use GraphQL\Type\Definition\Type; use PHPUnit\Framework\TestCase; use stdClass; +use function sprintf; class ScalarSerializationTest extends TestCase { @@ -113,6 +116,14 @@ public function testSerializesOutputIntCannotRepresentEmptyString() : void $intType->serialize(''); } + public function testSerializesOutputIntCannotRepresentArray() : void + { + $intType = Type::int(); + $this->expectException(Error::class); + $this->expectExceptionMessage('Int cannot represent an array value: [5]'); + $intType->serialize([5]); + } + /** * @see it('serializes output as Float') */ @@ -148,13 +159,31 @@ public function testSerializesOutputFloatCannotRepresentEmptyString() : void $floatType->serialize(''); } + public function testSerializesOutputFloatCannotRepresentArray() : void + { + $floatType = Type::float(); + $this->expectException(Error::class); + $this->expectExceptionMessage('Float cannot represent an array value: [5]'); + $floatType->serialize([5]); + } + + public function stringLikeTypes() + { + return [ + [ Type::string() ], + [ Type::id() ], + ]; + } + /** * @see it('serializes output as String') + * + * @param StringType|IDType $stringType + * + * @dataProvider stringLikeTypes */ - public function testSerializesOutputAsString() : void + public function testSerializesOutputAsString($stringType) : void { - $stringType = Type::string(); - self::assertSame('string', $stringType->serialize('string')); self::assertSame('1', $stringType->serialize(1)); self::assertSame('-1.1', $stringType->serialize(-1.1)); @@ -164,22 +193,46 @@ public function testSerializesOutputAsString() : void self::assertSame('2', $stringType->serialize(new ObjectIdStub(2))); } - public function testSerializesOutputStringsCannotRepresentArray() : void + /** + * @param StringType|IDType $stringType + * + * @throws Error + * + * @dataProvider stringLikeTypes + */ + public function testSerializesOutputStringsCannotRepresentArray($stringType) : void { - $stringType = Type::string(); $this->expectException(Error::class); - $this->expectExceptionMessage('String cannot represent non scalar value: []'); - $stringType->serialize([]); + $this->expectExceptionMessage(sprintf('%s cannot represent an array value: [1]', $stringType->name)); + $stringType->serialize([1]); } - public function testSerializesOutputStringsCannotRepresentObject() : void + /** + * @param StringType|IDType $stringType + * + * @dataProvider stringLikeTypes + */ + public function testSerializesOutputStringsCannotRepresentObject($stringType) : void { - $stringType = Type::string(); $this->expectException(Error::class); - $this->expectExceptionMessage('String cannot represent non scalar value: instance of stdClass'); + $this->expectExceptionMessage(sprintf('%s cannot represent non scalar value: instance of stdClass', $stringType->name)); $stringType->serialize(new stdClass()); } + /** + * @param StringType|IDType $stringType + * + * @throws Error + * + * @dataProvider stringLikeTypes + */ + public function testSerializesOutputStringCannotRepresentArray($stringType) : void + { + $this->expectException(Error::class); + $this->expectExceptionMessage(sprintf('%s cannot represent an array value: [5]', $stringType->name)); + $stringType->serialize([5]); + } + /** * @see it('serializes output as Boolean') */ @@ -198,28 +251,11 @@ public function testSerializesOutputAsBoolean() : void self::assertFalse($boolType->serialize('')); } - /** - * @see it('serializes output as ID') - */ - public function testSerializesOutputAsID() : void + public function testSerializesOutputBooleanCannotRepresentArray() : void { - $idType = Type::id(); - - self::assertSame('string', $idType->serialize('string')); - self::assertSame('', $idType->serialize('')); - self::assertSame('1', $idType->serialize('1')); - self::assertSame('1', $idType->serialize(1)); - self::assertSame('0', $idType->serialize(0)); - self::assertSame('true', $idType->serialize(true)); - self::assertSame('false', $idType->serialize(false)); - self::assertSame('2', $idType->serialize(new ObjectIdStub(2))); - } - - public function testSerializesOutputIDCannotRepresentObject() : void - { - $idType = Type::id(); + $boolType = Type::boolean(); $this->expectException(Error::class); - $this->expectExceptionMessage('ID type cannot represent non scalar value: instance of stdClass'); - $idType->serialize(new stdClass()); + $this->expectExceptionMessage('Boolean cannot represent an array value: [5]'); + $boolType->serialize([5]); } } diff --git a/tests/Utils/CoerceValueTest.php b/tests/Utils/CoerceValueTest.php index 3d30fc87b..4d9fe554b 100644 --- a/tests/Utils/CoerceValueTest.php +++ b/tests/Utils/CoerceValueTest.php @@ -5,11 +5,14 @@ namespace GraphQL\Tests\Utils; use GraphQL\Type\Definition\EnumType; +use GraphQL\Type\Definition\IDType; use GraphQL\Type\Definition\InputObjectType; +use GraphQL\Type\Definition\StringType; use GraphQL\Type\Definition\Type; use GraphQL\Utils\Utils; use GraphQL\Utils\Value; use PHPUnit\Framework\TestCase; +use function sprintf; class CoerceValueTest extends TestCase { @@ -38,21 +41,37 @@ public function setUp() ]); } + public function stringLikeTypes() + { + return [ + [Type::string()], + [Type::id()], + ]; + } + /** * Describe: coerceValue * - * @see it('coercing an array to GraphQLString produces an error') + * @see it('returns error for array input as string') + * + * @param StringType|IDType $type + * + * @dataProvider stringLikeTypes */ - public function testCoercingAnArrayToGraphQLStringProducesAnError() : void + public function testCoercingAnArrayToGraphQLStringProducesAnError($type) : void { - $result = Value::coerceValue([1, 2, 3], Type::string()); + $result = Value::coerceValue([1, 2, 3], $type); $this->expectError( $result, - 'Expected type String; String cannot represent an array value: [1,2,3]' + sprintf( + 'Expected type %s; %s cannot represent an array value: [1,2,3]', + $type->name, + $type->name + ) ); self::assertEquals( - 'String cannot represent an array value: [1,2,3]', + sprintf('%s cannot represent an array value: [1,2,3]', $type->name), $result['errors'][0]->getPrevious()->getMessage() ); } @@ -75,14 +94,15 @@ private function expectError($result, $expected) public function testIntReturnsNoErrorForIntInput() : void { $result = Value::coerceValue('1', Type::int()); - $this->expectNoErrors($result); + $this->expectValue($result, 1); } - private function expectNoErrors($result) + private function expectValue($result, $expected) { self::assertInternalType('array', $result); self::assertNull($result['errors']); self::assertNotEquals(Utils::undefined(), $result['value']); + self::assertEquals($expected, $result['value']); } /** @@ -91,7 +111,7 @@ private function expectNoErrors($result) public function testIntReturnsNoErrorForNegativeIntInput() : void { $result = Value::coerceValue('-1', Type::int()); - $this->expectNoErrors($result); + $this->expectValue($result, -1); } /** @@ -100,7 +120,7 @@ public function testIntReturnsNoErrorForNegativeIntInput() : void public function testIntReturnsNoErrorForExponentInput() : void { $result = Value::coerceValue('1e3', Type::int()); - $this->expectNoErrors($result); + $this->expectValue($result, 1000); } /** @@ -109,7 +129,7 @@ public function testIntReturnsNoErrorForExponentInput() : void public function testIntReturnsASingleErrorNull() : void { $result = Value::coerceValue(null, Type::int()); - $this->expectNoErrors($result); + $this->expectValue($result, null); } /** @@ -168,7 +188,7 @@ public function testIntReturnsASingleErrorForMultiCharInput() : void public function testFloatReturnsNoErrorForIntInput() : void { $result = Value::coerceValue('1', Type::float()); - $this->expectNoErrors($result); + $this->expectValue($result, 1); } /** @@ -177,7 +197,7 @@ public function testFloatReturnsNoErrorForIntInput() : void public function testFloatReturnsNoErrorForExponentInput() : void { $result = Value::coerceValue('1e3', Type::float()); - $this->expectNoErrors($result); + $this->expectValue($result, 1000); } /** @@ -186,7 +206,7 @@ public function testFloatReturnsNoErrorForExponentInput() : void public function testFloatReturnsNoErrorForFloatInput() : void { $result = Value::coerceValue('1.5', Type::float()); - $this->expectNoErrors($result); + $this->expectValue($result, 1.5); } /** @@ -195,7 +215,7 @@ public function testFloatReturnsNoErrorForFloatInput() : void public function testFloatReturnsASingleErrorNull() : void { $result = Value::coerceValue(null, Type::float()); - $this->expectNoErrors($result); + $this->expectValue($result, null); } /** @@ -242,12 +262,10 @@ public function testFloatReturnsASingleErrorForMultiCharInput() : void public function testReturnsNoErrorForAKnownEnumName() : void { $fooResult = Value::coerceValue('FOO', $this->testEnum); - $this->expectNoErrors($fooResult); - self::assertEquals('InternalFoo', $fooResult['value']); + $this->expectValue($fooResult, 'InternalFoo'); $barResult = Value::coerceValue('BAR', $this->testEnum); - $this->expectNoErrors($barResult); - self::assertEquals(123456789, $barResult['value']); + $this->expectValue($barResult, 123456789); } // DESCRIBE: for GraphQLInputObject @@ -279,8 +297,7 @@ public function testReturnsErrorForIncorrectValueType() : void public function testReturnsNoErrorForValidInput() : void { $result = Value::coerceValue(['foo' => 123], $this->testInputObject); - $this->expectNoErrors($result); - self::assertEquals(['foo' => 123], $result['value']); + $this->expectValue($result, ['foo' => 123]); } /** @@ -295,8 +312,7 @@ public function testReturnsErrorForNonObjectType() : void public function testReturnsNoErrorForStdClassInput() : void { $result = Value::coerceValue((object) ['foo' => 123], $this->testInputObject); - $this->expectNoErrors($result); - self::assertEquals(['foo' => 123], $result['value']); + $this->expectValue($result, ['foo' => 123]); } /** From 9feb582da881b07bbe308fce2596a9ce610662bb Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Thu, 29 Aug 2019 12:01:02 +0700 Subject: [PATCH 085/256] Reject Infinity or NaN supplied as Int or Float value --- src/Type/Definition/FloatType.php | 9 ++- src/Type/Definition/IntType.php | 11 +++- tests/Type/ScalarSerializationTest.php | 46 +++++++++++++-- tests/Utils/CoerceValueTest.php | 77 +++++++++++++++++++++++--- 4 files changed, 127 insertions(+), 16 deletions(-) diff --git a/src/Type/Definition/FloatType.php b/src/Type/Definition/FloatType.php index 18e1835db..78b3fb858 100644 --- a/src/Type/Definition/FloatType.php +++ b/src/Type/Definition/FloatType.php @@ -11,6 +11,9 @@ use GraphQL\Language\AST\Node; use GraphQL\Utils\Utils; use function is_array; +use function is_bool; +use function is_finite; +use function is_nan; use function is_numeric; use function sprintf; @@ -51,14 +54,16 @@ private function coerceFloat($value) ); } - if (! is_numeric($value) && $value !== true && $value !== false) { + $float = is_numeric($value) || is_bool($value) ? (float) $value : null; + + if ($float === null || ! is_finite($float) || is_nan($float)) { throw new Error( 'Float cannot represent non numeric value: ' . Utils::printSafe($value) ); } - return (float) $value; + return $float; } /** diff --git a/src/Type/Definition/IntType.php b/src/Type/Definition/IntType.php index e67d1a208..df8e210c1 100644 --- a/src/Type/Definition/IntType.php +++ b/src/Type/Definition/IntType.php @@ -61,12 +61,19 @@ private function coerceInt($value) if ($value === '') { throw new Error( - 'Int cannot represent non 32-bit signed integer value: (empty string)' + 'Int cannot represent non-integer value: (empty string)' + ); + } + + if (! is_numeric($value) && ! is_bool($value)) { + throw new Error( + 'Int cannot represent non-integer value: ' . + Utils::printSafe($value) ); } $num = floatval($value); - if ((! is_numeric($value) && ! is_bool($value)) || $num > self::MAX_INT || $num < self::MIN_INT) { + if ($num > self::MAX_INT || $num < self::MIN_INT) { throw new Error( 'Int cannot represent non 32-bit signed integer value: ' . Utils::printSafe($value) diff --git a/tests/Type/ScalarSerializationTest.php b/tests/Type/ScalarSerializationTest.php index 34737c8c8..5310afdd9 100644 --- a/tests/Type/ScalarSerializationTest.php +++ b/tests/Type/ScalarSerializationTest.php @@ -10,6 +10,8 @@ use GraphQL\Type\Definition\Type; use PHPUnit\Framework\TestCase; use stdClass; +use function acos; +use function log; use function sprintf; class ScalarSerializationTest extends TestCase @@ -62,8 +64,8 @@ public function testSerializesOutputIntCannotRepresentNumericString() : void { $intType = Type::int(); $this->expectException(Error::class); - $this->expectExceptionMessage('Int cannot represent non 32-bit signed integer value: Int cannot represent non-integer value: "-1.1"'); - $intType->serialize('Int cannot represent non-integer value: "-1.1"'); + $this->expectExceptionMessage('Int cannot represent non-integer value: -1.1'); + $intType->serialize('-1.1'); } public function testSerializesOutputIntCannotRepresentBiggerThan32Bits() : void @@ -100,11 +102,29 @@ public function testSerializesOutputIntCannotRepresentLowerThanSigned32Bits() : $intType->serialize(-1e100); } + public function testSerializesOutputIntCannotRepresentInfinity() : void + { + $intType = Type::int(); + $infinity = log(0); + $this->expectException(Error::class); + $this->expectExceptionMessage('Int cannot represent non 32-bit signed integer value: -INF'); + $intType->serialize($infinity); + } + + public function testSerializesOutputIntCannotRepresentNaN() : void + { + $intType = Type::int(); + $nan = acos(8); + $this->expectException(Error::class); + $this->expectExceptionMessage('Int cannot represent non-integer value: NAN'); + $intType->serialize($nan); + } + public function testSerializesOutputIntCannotRepresentString() : void { $intType = Type::int(); $this->expectException(Error::class); - $this->expectExceptionMessage('Int cannot represent non 32-bit signed integer value: one'); + $this->expectExceptionMessage('Int cannot represent non-integer value: one'); $intType->serialize('one'); } @@ -112,7 +132,7 @@ public function testSerializesOutputIntCannotRepresentEmptyString() : void { $intType = Type::int(); $this->expectException(Error::class); - $this->expectExceptionMessage('Int cannot represent non 32-bit signed integer value: (empty string)'); + $this->expectExceptionMessage('Int cannot represent non-integer value: (empty string)'); $intType->serialize(''); } @@ -159,6 +179,24 @@ public function testSerializesOutputFloatCannotRepresentEmptyString() : void $floatType->serialize(''); } + public function testSerializesOutputFloatCannotRepresentInfinity() : void + { + $floatType = Type::float(); + $infinity = log(0); + $this->expectException(Error::class); + $this->expectExceptionMessage('Float cannot represent non numeric value: -INF'); + $floatType->serialize($infinity); + } + + public function testSerializesOutputFloatCannotRepresentNaN() : void + { + $floatType = Type::float(); + $nan = acos(8); + $this->expectException(Error::class); + $this->expectExceptionMessage('Float cannot represent non numeric value: NAN'); + $floatType->serialize($nan); + } + public function testSerializesOutputFloatCannotRepresentArray() : void { $floatType = Type::float(); diff --git a/tests/Utils/CoerceValueTest.php b/tests/Utils/CoerceValueTest.php index 4d9fe554b..6b04eefc9 100644 --- a/tests/Utils/CoerceValueTest.php +++ b/tests/Utils/CoerceValueTest.php @@ -12,6 +12,9 @@ use GraphQL\Utils\Utils; use GraphQL\Utils\Value; use PHPUnit\Framework\TestCase; +use function acos; +use function log; +use function pow; use function sprintf; class CoerceValueTest extends TestCase @@ -133,14 +136,26 @@ public function testIntReturnsASingleErrorNull() : void } /** - * @see it('returns a single error for empty value') + * @see it('returns a single error for empty string as value') */ public function testIntReturnsASingleErrorForEmptyValue() : void { $result = Value::coerceValue('', Type::int()); $this->expectError( $result, - 'Expected type Int; Int cannot represent non 32-bit signed integer value: (empty string)' + 'Expected type Int; Int cannot represent non-integer value: (empty string)' + ); + } + + /** + * @see it('returns a single error for 2^32 input as int') + */ + public function testReturnsASingleErrorFor2x32InputAsInt() + { + $result = Value::coerceValue(pow(2, 32), Type::int()); + $this->expectError( + $result, + 'Expected type Int; Int cannot represent non 32-bit signed integer value: 4294967296' ); } @@ -156,6 +171,29 @@ public function testIntReturnsErrorForFloatInputAsInt() : void ); } + /** + * @see it('returns a single error for Infinity input as int') + */ + public function testReturnsASingleErrorForInfinityInputAsInt() + { + $inf = log(0); + $result = Value::coerceValue($inf, Type::int()); + $this->expectError( + $result, + 'Expected type Int; Int cannot represent non 32-bit signed integer value: -INF' + ); + } + + public function testReturnsASingleErrorForNaNInputAsInt() + { + $nan = acos(8); + $result = Value::coerceValue($nan, Type::int()); + $this->expectError( + $result, + 'Expected type Int; Int cannot represent non-integer value: NAN' + ); + } + // Describe: for GraphQLFloat /** @@ -166,7 +204,7 @@ public function testIntReturnsASingleErrorForCharInput() : void $result = Value::coerceValue('a', Type::int()); $this->expectError( $result, - 'Expected type Int; Int cannot represent non 32-bit signed integer value: a' + 'Expected type Int; Int cannot represent non-integer value: a' ); } @@ -178,7 +216,7 @@ public function testIntReturnsASingleErrorForMultiCharInput() : void $result = Value::coerceValue('meow', Type::int()); $this->expectError( $result, - 'Expected type Int; Int cannot represent non 32-bit signed integer value: meow' + 'Expected type Int; Int cannot represent non-integer value: meow' ); } @@ -219,7 +257,7 @@ public function testFloatReturnsASingleErrorNull() : void } /** - * @see it('returns a single error for empty value') + * @see it('returns a single error for empty string input') */ public function testFloatReturnsASingleErrorForEmptyValue() : void { @@ -230,6 +268,29 @@ public function testFloatReturnsASingleErrorForEmptyValue() : void ); } + /** + * @see it('returns a single error for Infinity input') + */ + public function testFloatReturnsASingleErrorForInfinityInput() : void + { + $inf = log(0); + $result = Value::coerceValue($inf, Type::float()); + $this->expectError( + $result, + 'Expected type Float; Float cannot represent non numeric value: -INF' + ); + } + + public function testFloatReturnsASingleErrorForNaNInput() : void + { + $nan = acos(8); + $result = Value::coerceValue($nan, Type::float()); + $this->expectError( + $result, + 'Expected type Float; Float cannot represent non numeric value: NAN' + ); + } + // DESCRIBE: for GraphQLEnum /** @@ -323,7 +384,7 @@ public function testReturnErrorForAnInvalidField() : void $result = Value::coerceValue(['foo' => 'abc'], $this->testInputObject); $this->expectError( $result, - 'Expected type Int at value.foo; Int cannot represent non 32-bit signed integer value: abc' + 'Expected type Int at value.foo; Int cannot represent non-integer value: abc' ); } @@ -335,8 +396,8 @@ public function testReturnsMultipleErrorsForMultipleInvalidFields() : void $result = Value::coerceValue(['foo' => 'abc', 'bar' => 'def'], $this->testInputObject); self::assertEquals( [ - 'Expected type Int at value.foo; Int cannot represent non 32-bit signed integer value: abc', - 'Expected type Int at value.bar; Int cannot represent non 32-bit signed integer value: def', + 'Expected type Int at value.foo; Int cannot represent non-integer value: abc', + 'Expected type Int at value.bar; Int cannot represent non-integer value: def', ], $result['errors'] ); From fef556adc8c8b604a67f107b7c691100e1c0cf6d Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Thu, 29 Aug 2019 12:24:19 +0700 Subject: [PATCH 086/256] Add test for enum custom values as input args --- tests/Executor/VariablesTest.php | 73 ++++++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/tests/Executor/VariablesTest.php b/tests/Executor/VariablesTest.php index f6883aeb2..8927a76d1 100644 --- a/tests/Executor/VariablesTest.php +++ b/tests/Executor/VariablesTest.php @@ -8,11 +8,14 @@ use GraphQL\Executor\Executor; use GraphQL\Language\Parser; use GraphQL\Tests\Executor\TestClasses\ComplexScalar; +use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Schema; +use GraphQL\Utils\Utils; use PHPUnit\Framework\TestCase; +use function acos; use function array_key_exists; use function json_encode; @@ -115,9 +118,6 @@ private function executeQuery($query, $variableValues = null) return Executor::execute($this->schema(), $document, null, null, $variableValues); } - /** - * Describe: Handles nullable scalars - */ public function schema() : Schema { $ComplexScalarType = ComplexScalar::create(); @@ -140,9 +140,22 @@ public function schema() : Schema ], ]); + $TestEnum = new EnumType([ + 'name' => 'TestEnum', + 'values' => [ + 'NULL' => [ 'value' => null ], + 'NAN' => [ 'value' => acos(8) ], + 'FALSE' => [ 'value' => false ], + 'CUSTOM' => [ 'value' => 'custom value' ], + 'DEFAULT_VALUE' => [], + ], + ]); + $TestType = new ObjectType([ 'name' => 'TestType', 'fields' => [ + 'fieldWithEnumInput' => $this->fieldWithInputArg(['type' => $TestEnum]), + 'fieldWithNonNullableEnumInput' => $this->fieldWithInputArg(['type' => Type::nonNull($TestEnum)]), 'fieldWithObjectInput' => $this->fieldWithInputArg(['type' => $TestInputObject]), 'fieldWithNullableStringInput' => $this->fieldWithInputArg(['type' => Type::string()]), 'fieldWithNonNullableStringInput' => $this->fieldWithInputArg(['type' => Type::nonNull(Type::string())]), @@ -175,7 +188,7 @@ private function fieldWithInputArg($inputArg) 'args' => ['input' => $inputArg], 'resolve' => static function ($_, $args) { if (isset($args['input'])) { - return json_encode($args['input']); + return Utils::printSafeJson($args['input']); } if (array_key_exists('input', $args) && $args['input'] === null) { return 'null'; @@ -415,6 +428,58 @@ public function testUsingStdClassVariables() : void ); } + /** + * Describe: Handles custom enum values + */ + + /** + * @see it('allows custom enum values as inputs') + */ + public function testAllowsCustomEnumValuesAsInputs() + { + $result = $this->executeQuery(' + { + null: fieldWithEnumInput(input: NULL) + NaN: fieldWithEnumInput(input: NAN) + false: fieldWithEnumInput(input: FALSE) + customValue: fieldWithEnumInput(input: CUSTOM) + defaultValue: fieldWithEnumInput(input: DEFAULT_VALUE) + } + '); + + $expected = [ + 'data' => [ + 'null' => 'null', + 'NaN' => 'NAN', + 'false' => 'false', + 'customValue' => '"custom value"', + 'defaultValue' => '"DEFAULT_VALUE"', + ], + ]; + self::assertEquals($expected, $result->toArray()); + } + + /** + * @see it('allows non-nullable inputs to have null as enum custom value') + */ + public function testAllowsNonNullableInputsToHaveNullAsEnumCustomValue() + { + $result = $this->executeQuery(' + { + fieldWithNonNullableEnumInput(input: NULL) + } + '); + + self::assertEquals( + ['data' => ['fieldWithNonNullableEnumInput' => 'null']], + $result->toArray() + ); + } + + /** + * Describe: Handles nullable scalars + */ + /** * @see it('allows nullable inputs to be omitted') */ From 175ace7231d013339a06b54d6e1ee7ec902f6a74 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Thu, 29 Aug 2019 12:32:45 +0700 Subject: [PATCH 087/256] Spec compliance: do not require interfaces to have at least one implementor https://github.com/graphql/graphql-spec/pull/459 --- src/Type/SchemaValidationContext.php | 20 -------------------- tests/Type/ValidationTest.php | 8 ++------ 2 files changed, 2 insertions(+), 26 deletions(-) diff --git a/src/Type/SchemaValidationContext.php b/src/Type/SchemaValidationContext.php index 95e5a1cea..7056d12ca 100644 --- a/src/Type/SchemaValidationContext.php +++ b/src/Type/SchemaValidationContext.php @@ -270,9 +270,6 @@ public function validateTypes() } elseif ($type instanceof InterfaceType) { // Ensure fields are valid. $this->validateFields($type); - - // Ensure Interfaces include at least 1 Object type. - $this->validateInterfaces($type); } elseif ($type instanceof UnionType) { // Ensure Unions include valid member types. $this->validateUnionMembers($type); @@ -517,23 +514,6 @@ private function validateObjectInterfaces(ObjectType $object) } } - private function validateInterfaces(InterfaceType $iface) - { - $possibleTypes = $this->schema->getPossibleTypes($iface); - - if (count($possibleTypes) !== 0) { - return; - } - - $this->reportError( - sprintf( - 'Interface %s must be implemented by at least one Object type.', - $iface->name - ), - $iface->astNode - ); - } - /** * @param InterfaceType $iface * diff --git a/tests/Type/ValidationTest.php b/tests/Type/ValidationTest.php index 6faebd832..1c8e493ee 100644 --- a/tests/Type/ValidationTest.php +++ b/tests/Type/ValidationTest.php @@ -1562,7 +1562,7 @@ interface SomeInterface { } /** - * @see it('rejects an interface not implemented by at least one object') + * @see it('accepts an interface not implemented by at least one object') */ public function testRejectsAnInterfaceNotImplementedByAtLeastOneObject() { @@ -1577,11 +1577,7 @@ interface SomeInterface { '); $this->assertMatchesValidationMessage( $schema->validate(), - [[ - 'message' => 'Interface SomeInterface must be implemented by at least one Object type.', - 'locations' => [[ 'line' => 6, 'column' => 7 ]], - ], - ] + [] ); } From 1933a993be630f4e6009cff86e85d4da5deff474 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Thu, 29 Aug 2019 12:46:45 +0700 Subject: [PATCH 088/256] Refactor NoFragmentCycles rule --- src/Validator/Rules/NoFragmentCycles.php | 44 +++++++++--------------- 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/src/Validator/Rules/NoFragmentCycles.php b/src/Validator/Rules/NoFragmentCycles.php index 1180a4ee9..5b1c1c520 100644 --- a/src/Validator/Rules/NoFragmentCycles.php +++ b/src/Validator/Rules/NoFragmentCycles.php @@ -47,9 +47,7 @@ public function getVisitor(ValidationContext $context) return Visitor::skipNode(); }, NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) use ($context) { - if (! isset($this->visitedFrags[$node->name->value])) { - $this->detectCycleRecursive($node, $context); - } + $this->detectCycleRecursive($node, $context); return Visitor::skipNode(); }, @@ -58,6 +56,10 @@ public function getVisitor(ValidationContext $context) private function detectCycleRecursive(FragmentDefinitionNode $fragment, ValidationContext $context) { + if (! empty($this->visitedFrags[$fragment->name->value])) { + return; + } + $fragmentName = $fragment->name->value; $this->visitedFrags[$fragmentName] = true; @@ -74,38 +76,24 @@ private function detectCycleRecursive(FragmentDefinitionNode $fragment, Validati $spreadName = $spreadNode->name->value; $cycleIndex = $this->spreadPathIndexByName[$spreadName] ?? null; + $this->spreadPath[] = $spreadNode; if ($cycleIndex === null) { - $this->spreadPath[] = $spreadNode; - if (empty($this->visitedFrags[$spreadName])) { - $spreadFragment = $context->getFragment($spreadName); - if ($spreadFragment) { - $this->detectCycleRecursive($spreadFragment, $context); - } + $spreadFragment = $context->getFragment($spreadName); + if ($spreadFragment) { + $this->detectCycleRecursive($spreadFragment, $context); } - array_pop($this->spreadPath); } else { - $cyclePath = array_slice($this->spreadPath, $cycleIndex); - $nodes = $cyclePath; - - if (is_array($spreadNode)) { - $nodes = array_merge($nodes, $spreadNode); - } else { - $nodes[] = $spreadNode; - } + $cyclePath = array_slice($this->spreadPath, $cycleIndex); + $fragmentNames = Utils::map(array_slice($cyclePath, 0, -1), static function ($s) { + return $s->name->value; + }); $context->reportError(new Error( - self::cycleErrorMessage( - $spreadName, - Utils::map( - $cyclePath, - static function ($s) { - return $s->name->value; - } - ) - ), - $nodes + self::cycleErrorMessage($spreadName, $fragmentNames), + $cyclePath )); } + array_pop($this->spreadPath); } $this->spreadPathIndexByName[$fragmentName] = null; From ef8e3b374f0b65373fb5ff02ca6e3a0319fa4db7 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Thu, 29 Aug 2019 12:54:12 +0700 Subject: [PATCH 089/256] BREAKING: Removed deprecated directive introspection fields (onOperation, onFragment, onField) --- CHANGELOG.md | 1 + src/Type/Introspection.php | 28 ---------------- tests/Type/IntrospectionTest.php | 55 ------------------------------- tests/Utils/SchemaPrinterTest.php | 6 ---- 4 files changed, 1 insertion(+), 89 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49b8fc8fc..034b6eb30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## Unreleased +- **BREAKING:** Removed deprecated directive introspection fields (onOperation, onFragment, onField) - **BREAKING:** Removal of `VariablesDefaultValueAllowed` validation rule. All variables may now specify a default value. - **BREAKING:** renamed `ProvidedNonNullArguments` to `ProvidedRequiredArguments` (no longer require values to be provided to non-null arguments which provide a default value). - Add schema validation: Input Objects must not contain non-nullable circular references (#492) diff --git a/src/Type/Introspection.php b/src/Type/Introspection.php index 9fa2084b8..1f4b11b0c 100644 --- a/src/Type/Introspection.php +++ b/src/Type/Introspection.php @@ -631,34 +631,6 @@ public static function _directive() return $directive->args ?: []; }, ], - - // NOTE: the following three fields are deprecated and are no longer part - // of the GraphQL specification. - 'onOperation' => [ - 'deprecationReason' => 'Use `locations`.', - 'type' => Type::nonNull(Type::boolean()), - 'resolve' => static function ($d) { - return in_array(DirectiveLocation::QUERY, $d->locations, true) || - in_array(DirectiveLocation::MUTATION, $d->locations, true) || - in_array(DirectiveLocation::SUBSCRIPTION, $d->locations, true); - }, - ], - 'onFragment' => [ - 'deprecationReason' => 'Use `locations`.', - 'type' => Type::nonNull(Type::boolean()), - 'resolve' => static function ($d) { - return in_array(DirectiveLocation::FRAGMENT_SPREAD, $d->locations, true) || - in_array(DirectiveLocation::INLINE_FRAGMENT, $d->locations, true) || - in_array(DirectiveLocation::FRAGMENT_DEFINITION, $d->locations, true); - }, - ], - 'onField' => [ - 'deprecationReason' => 'Use `locations`.', - 'type' => Type::nonNull(Type::boolean()), - 'resolve' => static function ($d) { - return in_array(DirectiveLocation::FIELD, $d->locations, true); - }, - ], ], ]); } diff --git a/tests/Type/IntrospectionTest.php b/tests/Type/IntrospectionTest.php index 80a2363e3..742fe6c8d 100644 --- a/tests/Type/IntrospectionTest.php +++ b/tests/Type/IntrospectionTest.php @@ -845,61 +845,6 @@ public function testExecutesAnIntrospectionQuery() : void 'isDeprecated' => false, 'deprecationReason' => null, ], - 4 => - [ - 'name' => 'onOperation', - 'args' => - [], - 'type' => - [ - 'kind' => 'NON_NULL', - 'name' => null, - 'ofType' => - [ - 'kind' => 'SCALAR', - 'name' => 'Boolean', - ], - ], - 'isDeprecated' => true, - 'deprecationReason' => 'Use `locations`.', - ], - 5 => - [ - 'name' => 'onFragment', - 'args' => - [], - 'type' => - [ - 'kind' => 'NON_NULL', - 'name' => null, - 'ofType' => - [ - 'kind' => 'SCALAR', - 'name' => 'Boolean', - ], - ], - 'isDeprecated' => true, - 'deprecationReason' => 'Use `locations`.', - ], - 6 => - [ - 'name' => 'onField', - 'args' => - [], - 'type' => - [ - 'kind' => 'NON_NULL', - 'name' => null, - 'ofType' => - [ - 'kind' => 'SCALAR', - 'name' => 'Boolean', - - ], - ], - 'isDeprecated' => true, - 'deprecationReason' => 'Use `locations`.', - ], ], 'inputFields' => null, 'interfaces' => diff --git a/tests/Utils/SchemaPrinterTest.php b/tests/Utils/SchemaPrinterTest.php index fc2049f1f..d24ac43c2 100644 --- a/tests/Utils/SchemaPrinterTest.php +++ b/tests/Utils/SchemaPrinterTest.php @@ -830,9 +830,6 @@ public function testPrintIntrospectionSchema() : void description: String locations: [__DirectiveLocation!]! args: [__InputValue!]! - onOperation: Boolean! @deprecated(reason: "Use `locations`.") - onFragment: Boolean! @deprecated(reason: "Use `locations`.") - onField: Boolean! @deprecated(reason: "Use `locations`.") } """ @@ -1070,9 +1067,6 @@ public function testPrintIntrospectionSchemaWithCommentDescriptions() : void description: String locations: [__DirectiveLocation!]! args: [__InputValue!]! - onOperation: Boolean! @deprecated(reason: "Use `locations`.") - onFragment: Boolean! @deprecated(reason: "Use `locations`.") - onField: Boolean! @deprecated(reason: "Use `locations`.") } # A Directive can be adjacent to many parts of the GraphQL language, a From 975c9fe3993bcc34066d40d87c1667090c0e6ac4 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Thu, 29 Aug 2019 16:19:56 +0700 Subject: [PATCH 090/256] BREAKING/BUGFIX: Strict coercion of scalar types (#278) --- CHANGELOG.md | 1 + src/Type/Definition/BooleanType.php | 12 +- src/Type/Definition/FloatType.php | 35 +-- src/Type/Definition/IDType.php | 30 +- src/Type/Definition/IntType.php | 60 ++-- src/Type/Definition/StringType.php | 37 +-- tests/Executor/ExecutorSchemaTest.php | 2 +- tests/Executor/VariablesTest.php | 2 +- tests/Type/ScalarSerializationTest.php | 284 ++++++------------ tests/Type/TestClasses/CanCastToString.php | 21 ++ tests/Type/{ => TestClasses}/ObjectIdStub.php | 2 +- tests/Utils/AstFromValueTest.php | 3 +- tests/Utils/CoerceValueTest.php | 106 ++++--- 13 files changed, 252 insertions(+), 343 deletions(-) create mode 100644 tests/Type/TestClasses/CanCastToString.php rename tests/Type/{ => TestClasses}/ObjectIdStub.php (87%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 034b6eb30..c3f3a3310 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## Unreleased +- **BREAKING/BUGFIX:** Strict coercion of scalar types (#278) - **BREAKING:** Removed deprecated directive introspection fields (onOperation, onFragment, onField) - **BREAKING:** Removal of `VariablesDefaultValueAllowed` validation rule. All variables may now specify a default value. - **BREAKING:** renamed `ProvidedNonNullArguments` to `ProvidedRequiredArguments` (no longer require values to be provided to non-null arguments which provide a default value). diff --git a/src/Type/Definition/BooleanType.php b/src/Type/Definition/BooleanType.php index 478159cba..3ef882db6 100644 --- a/src/Type/Definition/BooleanType.php +++ b/src/Type/Definition/BooleanType.php @@ -21,23 +21,15 @@ class BooleanType extends ScalarType public $description = 'The `Boolean` scalar type represents `true` or `false`.'; /** - * Coerce the given value to a boolean. + * Serialize the given value to a boolean. * * The GraphQL spec leaves this up to the implementations, so we just do what * PHP does natively to make this intuitive for developers. * * @param mixed $value - * - * @throws Error */ public function serialize($value) : bool { - if (is_array($value)) { - throw new Error( - 'Boolean cannot represent an array value: ' . Utils::printSafe($value) - ); - } - return (bool) $value; } @@ -54,7 +46,7 @@ public function parseValue($value) return $value; } - throw new Error('Cannot represent value as boolean: ' . Utils::printSafe($value)); + throw new Error('Boolean cannot represent a non boolean value: ' . Utils::printSafe($value)); } /** diff --git a/src/Type/Definition/FloatType.php b/src/Type/Definition/FloatType.php index 78b3fb858..2335bcd50 100644 --- a/src/Type/Definition/FloatType.php +++ b/src/Type/Definition/FloatType.php @@ -10,9 +10,12 @@ use GraphQL\Language\AST\IntValueNode; use GraphQL\Language\AST\Node; use GraphQL\Utils\Utils; +use function floatval; use function is_array; use function is_bool; use function is_finite; +use function is_float; +use function is_int; use function is_nan; use function is_numeric; use function sprintf; @@ -37,26 +40,9 @@ class FloatType extends ScalarType */ public function serialize($value) { - return $this->coerceFloat($value); - } - - private function coerceFloat($value) - { - if (is_array($value)) { - throw new Error( - sprintf('Float cannot represent an array value: %s', Utils::printSafe($value)) - ); - } + $float = is_numeric($value) || is_bool($value) ? floatval($value) : null; - if ($value === '') { - throw new Error( - 'Float cannot represent non numeric value: (empty string)' - ); - } - - $float = is_numeric($value) || is_bool($value) ? (float) $value : null; - - if ($float === null || ! is_finite($float) || is_nan($float)) { + if ($float === null || ! is_finite($float)) { throw new Error( 'Float cannot represent non numeric value: ' . Utils::printSafe($value) @@ -75,7 +61,16 @@ private function coerceFloat($value) */ public function parseValue($value) { - return $this->coerceFloat($value); + $float = is_float($value) || is_int($value) ? floatval($value) : null; + + if ($float === null || ! is_finite($float)) { + throw new Error( + 'Float cannot represent non numeric value: ' . + Utils::printSafe($value) + ); + } + + return $float; } /** diff --git a/src/Type/Definition/IDType.php b/src/Type/Definition/IDType.php index f4db03d31..9f7610811 100644 --- a/src/Type/Definition/IDType.php +++ b/src/Type/Definition/IDType.php @@ -39,22 +39,12 @@ class IDType extends ScalarType */ public function serialize($value) { - if ($value === true) { - return 'true'; - } - if ($value === false) { - return 'false'; - } - if ($value === null) { - return 'null'; - } - if (is_array($value)) { - throw new Error( - 'ID cannot represent an array value: ' . Utils::printSafe($value) - ); - } - if (! is_scalar($value) && (! is_object($value) || ! method_exists($value, '__toString'))) { - throw new Error('ID cannot represent non scalar value: ' . Utils::printSafe($value)); + $canCast = is_string($value) + || is_int($value) + || (is_object($value) && method_exists($value, '__toString')); + + if (! $canCast) { + throw new Error('ID cannot represent value: ' . Utils::printSafe($value)); } return (string) $value; @@ -72,13 +62,7 @@ public function parseValue($value) if (is_string($value) || is_int($value)) { return (string) $value; } - if (is_array($value)) { - throw new Error( - 'ID cannot represent an array value: ' . Utils::printSafe($value) - ); - } - - throw new Error('Cannot represent value as ID: ' . Utils::printSafe($value)); + throw new Error('ID cannot represent value: ' . Utils::printSafe($value)); } /** diff --git a/src/Type/Definition/IntType.php b/src/Type/Definition/IntType.php index df8e210c1..1fb96a468 100644 --- a/src/Type/Definition/IntType.php +++ b/src/Type/Definition/IntType.php @@ -10,9 +10,12 @@ use GraphQL\Language\AST\Node; use GraphQL\Utils\Utils; use function floatval; +use function floor; use function intval; use function is_array; use function is_bool; +use function is_float; +use function is_int; use function is_numeric; use function sprintf; @@ -43,53 +46,28 @@ class IntType extends ScalarType */ public function serialize($value) { - return $this->coerceInt($value); - } - - /** - * @param mixed $value - * - * @return int - */ - private function coerceInt($value) - { - if (is_array($value)) { - throw new Error( - sprintf('Int cannot represent an array value: %s', Utils::printSafe($value)) - ); + // Fast path for 90+% of cases: + if (is_int($value) && $value <= self::MAX_INT && $value >= self::MIN_INT) { + return $value; } - if ($value === '') { - throw new Error( - 'Int cannot represent non-integer value: (empty string)' - ); - } + $float = is_numeric($value) || is_bool($value) ? floatval($value) : null; - if (! is_numeric($value) && ! is_bool($value)) { + if ($float === null || floor($float) !== $float) { throw new Error( 'Int cannot represent non-integer value: ' . Utils::printSafe($value) ); } - $num = floatval($value); - if ($num > self::MAX_INT || $num < self::MIN_INT) { + if ($float > self::MAX_INT || $float < self::MIN_INT) { throw new Error( 'Int cannot represent non 32-bit signed integer value: ' . Utils::printSafe($value) ); } - $int = intval($num); - // int cast with == used for performance reasons - // phpcs:ignore - if ($int != $num) { - throw new Error( - 'Int cannot represent non-integer value: ' . - Utils::printSafe($value) - ); - } - return $int; + return intval($float); } /** @@ -101,7 +79,23 @@ private function coerceInt($value) */ public function parseValue($value) { - return $this->coerceInt($value); + $isInt = is_int($value) || (is_float($value) && floor($value) === $value); + + if (! $isInt) { + throw new Error( + 'Int cannot represent non-integer value: ' . + Utils::printSafe($value) + ); + } + + if ($value > self::MAX_INT || $value < self::MIN_INT) { + throw new Error( + 'Int cannot represent non 32-bit signed integer value: ' . + Utils::printSafe($value) + ); + } + + return intval($value); } /** diff --git a/src/Type/Definition/StringType.php b/src/Type/Definition/StringType.php index 79f67e113..7eafd0107 100644 --- a/src/Type/Definition/StringType.php +++ b/src/Type/Definition/StringType.php @@ -12,6 +12,7 @@ use function is_array; use function is_object; use function is_scalar; +use function is_string; use function method_exists; class StringType extends ScalarType @@ -34,31 +35,13 @@ class StringType extends ScalarType */ public function serialize($value) { - return $this->coerceString($value); - } + $canCast = is_scalar($value) + || (is_object($value) && method_exists($value, '__toString')) + || $value === null; - private function coerceString($value) - { - if ($value === true) { - return 'true'; - } - if ($value === false) { - return 'false'; - } - if ($value === null) { - return 'null'; - } - if (is_array($value)) { + if (! $canCast) { throw new Error( - 'String cannot represent an array value: ' . Utils::printSafe($value) - ); - } - if (is_object($value) && method_exists($value, '__toString')) { - return (string) $value; - } - if (! is_scalar($value)) { - throw new Error( - 'String cannot represent non scalar value: ' . Utils::printSafe($value) + 'String cannot represent value: ' . Utils::printSafe($value) ); } @@ -74,7 +57,13 @@ private function coerceString($value) */ public function parseValue($value) { - return $this->coerceString($value); + if (! is_string($value)) { + throw new Error( + 'String cannot represent a non string value: ' . Utils::printSafe($value) + ); + } + + return $value; } /** diff --git a/tests/Executor/ExecutorSchemaTest.php b/tests/Executor/ExecutorSchemaTest.php index ae5a3c180..42b5dc2e6 100644 --- a/tests/Executor/ExecutorSchemaTest.php +++ b/tests/Executor/ExecutorSchemaTest.php @@ -198,7 +198,7 @@ public function testExecutesUsingASchema() : void 'isPublished' => true, 'title' => 'My Article 1', 'body' => 'This is a post', - 'keywords' => ['foo', 'bar', '1', 'true', null], + 'keywords' => ['foo', 'bar', '1', '1', null], ], ], 'meta' => [ 'title' => 'My Article 1 | My Blog' ], diff --git a/tests/Executor/VariablesTest.php b/tests/Executor/VariablesTest.php index 8927a76d1..8c1750529 100644 --- a/tests/Executor/VariablesTest.php +++ b/tests/Executor/VariablesTest.php @@ -705,7 +705,7 @@ public function testReportsErrorForArrayPassedIntoStringInput() : void 'errors' => [[ 'message' => 'Variable "$value" got invalid value [1,2,3]; Expected type ' . - 'String; String cannot represent an array value: [1,2,3]', + 'String; String cannot represent a non string value: [1,2,3]', 'locations' => [ ['line' => 2, 'column' => 31], ], diff --git a/tests/Type/ScalarSerializationTest.php b/tests/Type/ScalarSerializationTest.php index 5310afdd9..f5d9ec546 100644 --- a/tests/Type/ScalarSerializationTest.php +++ b/tests/Type/ScalarSerializationTest.php @@ -5,14 +5,13 @@ namespace GraphQL\Tests\Type; use GraphQL\Error\Error; -use GraphQL\Type\Definition\IDType; -use GraphQL\Type\Definition\StringType; +use GraphQL\Tests\Type\TestClasses\CanCastToString; +use GraphQL\Tests\Type\TestClasses\ObjectIdStub; use GraphQL\Type\Definition\Type; use PHPUnit\Framework\TestCase; use stdClass; use function acos; use function log; -use function sprintf; class ScalarSerializationTest extends TestCase { @@ -34,114 +33,38 @@ public function testSerializesOutputAsInt() : void self::assertSame(1, $intType->serialize(true)); } - public function testSerializesOutputIntCannotRepresentFloat1() : void + public function badIntValues() { - // The GraphQL specification does not allow serializing non-integer values - // as Int to avoid accidental data loss. - $intType = Type::int(); - $this->expectException(Error::class); - $this->expectExceptionMessage('Int cannot represent non-integer value: 0.1'); - $intType->serialize(0.1); - } - - public function testSerializesOutputIntCannotRepresentFloat2() : void - { - $intType = Type::int(); - $this->expectException(Error::class); - $this->expectExceptionMessage('Int cannot represent non-integer value: 1.1'); - $intType->serialize(1.1); - } - - public function testSerializesOutputIntCannotRepresentNegativeFloat() : void - { - $intType = Type::int(); - $this->expectException(Error::class); - $this->expectExceptionMessage('Int cannot represent non-integer value: -1.1'); - $intType->serialize(-1.1); - } - - public function testSerializesOutputIntCannotRepresentNumericString() : void - { - $intType = Type::int(); - $this->expectException(Error::class); - $this->expectExceptionMessage('Int cannot represent non-integer value: -1.1'); - $intType->serialize('-1.1'); - } - - public function testSerializesOutputIntCannotRepresentBiggerThan32Bits() : void - { - // Maybe a safe PHP int, but bigger than 2^32, so not - // representable as a GraphQL Int - $intType = Type::int(); - $this->expectException(Error::class); - $this->expectExceptionMessage('Int cannot represent non 32-bit signed integer value: 9876504321'); - $intType->serialize(9876504321); - } - - public function testSerializesOutputIntCannotRepresentLowerThan32Bits() : void - { - $intType = Type::int(); - $this->expectException(Error::class); - $this->expectExceptionMessage('Int cannot represent non 32-bit signed integer value: -9876504321'); - $intType->serialize(-9876504321); - } - - public function testSerializesOutputIntCannotRepresentBiggerThanSigned32Bits() : void - { - $intType = Type::int(); - $this->expectException(Error::class); - $this->expectExceptionMessage('Int cannot represent non 32-bit signed integer value: 1.0E+100'); - $intType->serialize(1e100); - } - - public function testSerializesOutputIntCannotRepresentLowerThanSigned32Bits() : void - { - $intType = Type::int(); - $this->expectException(Error::class); - $this->expectExceptionMessage('Int cannot represent non 32-bit signed integer value: -1.0E+100'); - $intType->serialize(-1e100); - } - - public function testSerializesOutputIntCannotRepresentInfinity() : void - { - $intType = Type::int(); - $infinity = log(0); - $this->expectException(Error::class); - $this->expectExceptionMessage('Int cannot represent non 32-bit signed integer value: -INF'); - $intType->serialize($infinity); - } - - public function testSerializesOutputIntCannotRepresentNaN() : void - { - $intType = Type::int(); - $nan = acos(8); - $this->expectException(Error::class); - $this->expectExceptionMessage('Int cannot represent non-integer value: NAN'); - $intType->serialize($nan); - } - - public function testSerializesOutputIntCannotRepresentString() : void - { - $intType = Type::int(); - $this->expectException(Error::class); - $this->expectExceptionMessage('Int cannot represent non-integer value: one'); - $intType->serialize('one'); - } - - public function testSerializesOutputIntCannotRepresentEmptyString() : void - { - $intType = Type::int(); - $this->expectException(Error::class); - $this->expectExceptionMessage('Int cannot represent non-integer value: (empty string)'); - $intType->serialize(''); + return [ + [0.1, 'Int cannot represent non-integer value: 0.1'], + [1.1, 'Int cannot represent non-integer value: 1.1'], + [-1.1, 'Int cannot represent non-integer value: -1.1'], + ['-1.1', 'Int cannot represent non-integer value: -1.1'], + [9876504321, 'Int cannot represent non 32-bit signed integer value: 9876504321'], + [-9876504321, 'Int cannot represent non 32-bit signed integer value: -9876504321'], + [1e100, 'Int cannot represent non 32-bit signed integer value: 1.0E+100'], + [-1e100, 'Int cannot represent non 32-bit signed integer value: -1.0E+100'], + [log(0), 'Int cannot represent non 32-bit signed integer value: -INF'], + [acos(8), 'Int cannot represent non-integer value: NAN'], + ['one', 'Int cannot represent non-integer value: one'], + ['', 'Int cannot represent non-integer value: (empty string)'], + [[5], 'Int cannot represent non-integer value: [5]'], + ]; } - public function testSerializesOutputIntCannotRepresentArray() : void + /** + * @throws Error + * + * @dataProvider badIntValues + */ + public function testSerializesOutputAsIntErrors($value, $expectedError) : void { + // The GraphQL specification does not allow serializing non-integer values + // as Int to avoid accidental data loss. $intType = Type::int(); $this->expectException(Error::class); - $this->expectExceptionMessage('Int cannot represent an array value: [5]'); - $intType->serialize([5]); + $this->expectExceptionMessage($expectedError); + $intType->serialize($value); } /** @@ -163,112 +86,64 @@ public function testSerializesOutputAsFloat() : void self::assertSame(1.0, $floatType->serialize(true)); } - public function testSerializesOutputFloatCannotRepresentString() : void - { - $floatType = Type::float(); - $this->expectException(Error::class); - $this->expectExceptionMessage('Float cannot represent non numeric value: one'); - $floatType->serialize('one'); - } - - public function testSerializesOutputFloatCannotRepresentEmptyString() : void - { - $floatType = Type::float(); - $this->expectException(Error::class); - $this->expectExceptionMessage('Float cannot represent non numeric value: (empty string)'); - $floatType->serialize(''); - } - - public function testSerializesOutputFloatCannotRepresentInfinity() : void - { - $floatType = Type::float(); - $infinity = log(0); - $this->expectException(Error::class); - $this->expectExceptionMessage('Float cannot represent non numeric value: -INF'); - $floatType->serialize($infinity); - } - - public function testSerializesOutputFloatCannotRepresentNaN() : void + public function badFloatValues() { - $floatType = Type::float(); - $nan = acos(8); - $this->expectException(Error::class); - $this->expectExceptionMessage('Float cannot represent non numeric value: NAN'); - $floatType->serialize($nan); + return [ + ['one', 'Float cannot represent non numeric value: one'], + ['', 'Float cannot represent non numeric value: (empty string)'], + [log(0), 'Float cannot represent non numeric value: -INF'], + [acos(8), 'Float cannot represent non numeric value: NAN'], + [[5], 'Float cannot represent non numeric value: [5]'], + ]; } - public function testSerializesOutputFloatCannotRepresentArray() : void + /** + * @throws Error + * + * @dataProvider badFloatValues + */ + public function testSerializesOutputFloatErrors($value, $expectedError) : void { $floatType = Type::float(); $this->expectException(Error::class); - $this->expectExceptionMessage('Float cannot represent an array value: [5]'); - $floatType->serialize([5]); - } - - public function stringLikeTypes() - { - return [ - [ Type::string() ], - [ Type::id() ], - ]; + $this->expectExceptionMessage($expectedError); + $floatType->serialize($value); } /** * @see it('serializes output as String') - * - * @param StringType|IDType $stringType - * - * @dataProvider stringLikeTypes */ - public function testSerializesOutputAsString($stringType) : void + public function testSerializesOutputAsString() : void { + $stringType = Type::string(); self::assertSame('string', $stringType->serialize('string')); self::assertSame('1', $stringType->serialize(1)); self::assertSame('-1.1', $stringType->serialize(-1.1)); - self::assertSame('true', $stringType->serialize(true)); - self::assertSame('false', $stringType->serialize(false)); - self::assertSame('null', $stringType->serialize(null)); - self::assertSame('2', $stringType->serialize(new ObjectIdStub(2))); - } - - /** - * @param StringType|IDType $stringType - * - * @throws Error - * - * @dataProvider stringLikeTypes - */ - public function testSerializesOutputStringsCannotRepresentArray($stringType) : void - { - $this->expectException(Error::class); - $this->expectExceptionMessage(sprintf('%s cannot represent an array value: [1]', $stringType->name)); - $stringType->serialize([1]); + self::assertSame('1', $stringType->serialize(true)); + self::assertSame('', $stringType->serialize(false)); + self::assertSame('', $stringType->serialize(null)); + self::assertSame('foo', $stringType->serialize(new CanCastToString('foo'))); } - /** - * @param StringType|IDType $stringType - * - * @dataProvider stringLikeTypes - */ - public function testSerializesOutputStringsCannotRepresentObject($stringType) : void + public function badStringValues() { - $this->expectException(Error::class); - $this->expectExceptionMessage(sprintf('%s cannot represent non scalar value: instance of stdClass', $stringType->name)); - $stringType->serialize(new stdClass()); + return [ + [[1], 'String cannot represent value: [1]'], + [new stdClass(), 'String cannot represent value: instance of stdClass'], + ]; } /** - * @param StringType|IDType $stringType - * * @throws Error * - * @dataProvider stringLikeTypes + * @dataProvider badStringValues */ - public function testSerializesOutputStringCannotRepresentArray($stringType) : void + public function testSerializesOutputStringErrors($value, $expectedError) : void { + $stringType = Type::string(); $this->expectException(Error::class); - $this->expectExceptionMessage(sprintf('%s cannot represent an array value: [5]', $stringType->name)); - $stringType->serialize([5]); + $this->expectExceptionMessage($expectedError); + $stringType->serialize($value); } /** @@ -289,11 +164,42 @@ public function testSerializesOutputAsBoolean() : void self::assertFalse($boolType->serialize('')); } - public function testSerializesOutputBooleanCannotRepresentArray() : void + /** + * @see it('serializes output as ID') + */ + public function testSerializesOutputAsID() : void { - $boolType = Type::boolean(); + $idType = Type::id(); + + self::assertSame('string', $idType->serialize('string')); + self::assertSame('false', $idType->serialize('false')); + self::assertSame('', $idType->serialize('')); + self::assertSame('1', $idType->serialize('1')); + self::assertSame('0', $idType->serialize('0')); + self::assertSame('1', $idType->serialize(1)); + self::assertSame('0', $idType->serialize(0)); + self::assertSame('2', $idType->serialize(new ObjectIdStub(2))); + } + + public function badIDValues() + { + return [ + [new stdClass(), 'ID cannot represent value: instance of stdClass'], + [true, 'ID cannot represent value: true'], + [false, 'ID cannot represent value: false'], + [-1.1, 'ID cannot represent value: -1.1'], + [['abc'], 'ID cannot represent value: ["abc"]'], + ]; + } + + /** + * @dataProvider badIDValues + */ + public function testSerializesOutputAsIDError($value, $expectedError) + { + $idType = Type::id(); $this->expectException(Error::class); - $this->expectExceptionMessage('Boolean cannot represent an array value: [5]'); - $boolType->serialize([5]); + $this->expectExceptionMessage($expectedError); + $idType->serialize($value); } } diff --git a/tests/Type/TestClasses/CanCastToString.php b/tests/Type/TestClasses/CanCastToString.php new file mode 100644 index 000000000..017b485f6 --- /dev/null +++ b/tests/Type/TestClasses/CanCastToString.php @@ -0,0 +1,21 @@ +str = $str; + } + + public function __toString() + { + return $this->str; + } +} diff --git a/tests/Type/ObjectIdStub.php b/tests/Type/TestClasses/ObjectIdStub.php similarity index 87% rename from tests/Type/ObjectIdStub.php rename to tests/Type/TestClasses/ObjectIdStub.php index 3049569f4..a6c2b64e7 100644 --- a/tests/Type/ObjectIdStub.php +++ b/tests/Type/TestClasses/ObjectIdStub.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace GraphQL\Tests\Type; +namespace GraphQL\Tests\Type\TestClasses; class ObjectIdStub { diff --git a/tests/Utils/AstFromValueTest.php b/tests/Utils/AstFromValueTest.php index 02e758289..86b62e00e 100644 --- a/tests/Utils/AstFromValueTest.php +++ b/tests/Utils/AstFromValueTest.php @@ -101,7 +101,7 @@ public function testConvertsStringValuesToASTs() : void self::assertEquals(new StringValueNode(['value' => 'VALUE']), AST::astFromValue('VALUE', Type::string())); self::assertEquals(new StringValueNode(['value' => "VA\nLUE"]), AST::astFromValue("VA\nLUE", Type::string())); self::assertEquals(new StringValueNode(['value' => '123']), AST::astFromValue(123, Type::string())); - self::assertEquals(new StringValueNode(['value' => 'false']), AST::astFromValue(false, Type::string())); + self::assertEquals(new StringValueNode(['value' => '']), AST::astFromValue(false, Type::string())); self::assertEquals(new NullValueNode([]), AST::astFromValue(null, Type::string())); self::assertEquals(null, AST::astFromValue(null, Type::nonNull(Type::string()))); } @@ -118,7 +118,6 @@ public function testConvertIdValuesToIntOrStringASTs() : void self::assertEquals(new IntValueNode(['value' => '123']), AST::astFromValue(123, Type::id())); self::assertEquals(new IntValueNode(['value' => '123']), AST::astFromValue('123', Type::id())); self::assertEquals(new StringValueNode(['value' => '01']), AST::astFromValue('01', Type::id())); - self::assertEquals(new StringValueNode(['value' => 'false']), AST::astFromValue(false, Type::id())); self::assertEquals(new NullValueNode([]), AST::astFromValue(null, Type::id())); self::assertEquals(null, AST::astFromValue(null, Type::nonNull(Type::id()))); } diff --git a/tests/Utils/CoerceValueTest.php b/tests/Utils/CoerceValueTest.php index 6b04eefc9..0cc92241e 100644 --- a/tests/Utils/CoerceValueTest.php +++ b/tests/Utils/CoerceValueTest.php @@ -44,37 +44,44 @@ public function setUp() ]); } - public function stringLikeTypes() + /** + * Describe: coerceValue + */ + + /** + * Describe: for GraphQLString + * + * @see it('returns error for array input as string') + */ + public function testCoercingAnArrayToGraphQLStringProducesAnError() : void { - return [ - [Type::string()], - [Type::id()], - ]; + $result = Value::coerceValue([1, 2, 3], Type::string()); + $this->expectError( + $result, + 'Expected type String; String cannot represent a non string value: [1,2,3]' + ); + + self::assertEquals( + 'String cannot represent a non string value: [1,2,3]', + $result['errors'][0]->getPrevious()->getMessage() + ); } /** - * Describe: coerceValue + * Describe: for GraphQLID * * @see it('returns error for array input as string') - * - * @param StringType|IDType $type - * - * @dataProvider stringLikeTypes */ - public function testCoercingAnArrayToGraphQLStringProducesAnError($type) : void + public function testCoercingAnArrayToGraphQLIDProducesAnError() : void { - $result = Value::coerceValue([1, 2, 3], $type); + $result = Value::coerceValue([1, 2, 3], Type::id()); $this->expectError( $result, - sprintf( - 'Expected type %s; %s cannot represent an array value: [1,2,3]', - $type->name, - $type->name - ) + 'Expected type ID; ID cannot represent value: [1,2,3]' ); self::assertEquals( - sprintf('%s cannot represent an array value: [1,2,3]', $type->name), + 'ID cannot represent value: [1,2,3]', $result['errors'][0]->getPrevious()->getMessage() ); } @@ -92,42 +99,51 @@ private function expectError($result, $expected) } /** - * @see it('returns no error for int input') + * @see it('returns value for integer') */ public function testIntReturnsNoErrorForIntInput() : void { - $result = Value::coerceValue('1', Type::int()); + $result = Value::coerceValue(1, Type::int()); $this->expectValue($result, 1); } + /** + * @see it('returns error for numeric looking string') + */ + public function testReturnsErrorForNumericLookingString() + { + $result = Value::coerceValue('1', Type::int()); + $this->expectError($result, 'Expected type Int; Int cannot represent non-integer value: 1'); + } + private function expectValue($result, $expected) { self::assertInternalType('array', $result); - self::assertNull($result['errors']); + self::assertEquals(null, $result['errors']); self::assertNotEquals(Utils::undefined(), $result['value']); self::assertEquals($expected, $result['value']); } /** - * @see it('returns no error for negative int input') + * @see it('returns value for negative int input') */ public function testIntReturnsNoErrorForNegativeIntInput() : void { - $result = Value::coerceValue('-1', Type::int()); + $result = Value::coerceValue(-1, Type::int()); $this->expectValue($result, -1); } /** - * @see it('returns no error for exponent input') + * @see it('returns value for exponent input') */ public function testIntReturnsNoErrorForExponentInput() : void { - $result = Value::coerceValue('1e3', Type::int()); + $result = Value::coerceValue(1e3, Type::int()); $this->expectValue($result, 1000); } /** - * @see it('returns no error for null') + * @see it('returns null for null value') */ public function testIntReturnsASingleErrorNull() : void { @@ -164,7 +180,7 @@ public function testReturnsASingleErrorFor2x32InputAsInt() */ public function testIntReturnsErrorForFloatInputAsInt() : void { - $result = Value::coerceValue('1.5', Type::int()); + $result = Value::coerceValue(1.5, Type::int()); $this->expectError( $result, 'Expected type Int; Int cannot represent non-integer value: 1.5' @@ -194,10 +210,8 @@ public function testReturnsASingleErrorForNaNInputAsInt() ); } - // Describe: for GraphQLFloat - /** - * @see it('returns a single error for char input') + * @see it('returns a single error for string input') */ public function testIntReturnsASingleErrorForCharInput() : void { @@ -220,35 +234,49 @@ public function testIntReturnsASingleErrorForMultiCharInput() : void ); } + // Describe: for GraphQLFloat + /** - * @see it('returns no error for int input') + * @see it('returns value for integer') */ public function testFloatReturnsNoErrorForIntInput() : void { - $result = Value::coerceValue('1', Type::float()); + $result = Value::coerceValue(1, Type::float()); $this->expectValue($result, 1); } /** - * @see it('returns no error for exponent input') + * @see it('returns value for decimal') + */ + public function testReturnsValueForDecimal() + { + $result = Value::coerceValue(1.1, Type::float()); + $this->expectValue($result, 1.1); + } + + /** + * @see it('returns value for exponent input') */ public function testFloatReturnsNoErrorForExponentInput() : void { - $result = Value::coerceValue('1e3', Type::float()); + $result = Value::coerceValue(1e3, Type::float()); $this->expectValue($result, 1000); } /** - * @see it('returns no error for float input') + * @see it('returns error for numeric looking string') */ - public function testFloatReturnsNoErrorForFloatInput() : void + public function testFloatReturnsErrorForNumericLookingString() { - $result = Value::coerceValue('1.5', Type::float()); - $this->expectValue($result, 1.5); + $result = Value::coerceValue('1', Type::float()); + $this->expectError( + $result, + 'Expected type Float; Float cannot represent non numeric value: 1' + ); } /** - * @see it('returns no error for null') + * @see it('returns null for null value') */ public function testFloatReturnsASingleErrorNull() : void { From 40d80d9d3d7fcaae1a3f0838db10c77efa3fb8e5 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Thu, 29 Aug 2019 16:24:04 +0700 Subject: [PATCH 091/256] Fixed test reference --- tests/Utils/CoerceValueTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Utils/CoerceValueTest.php b/tests/Utils/CoerceValueTest.php index 0cc92241e..62ccbc647 100644 --- a/tests/Utils/CoerceValueTest.php +++ b/tests/Utils/CoerceValueTest.php @@ -70,7 +70,7 @@ public function testCoercingAnArrayToGraphQLStringProducesAnError() : void /** * Describe: for GraphQLID * - * @see it('returns error for array input as string') + * @see it('returns error for array input as ID') */ public function testCoercingAnArrayToGraphQLIDProducesAnError() : void { From c5d913fbeffcbe530cbe740e9260b051d6d28d84 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Thu, 29 Aug 2019 16:29:10 +0700 Subject: [PATCH 092/256] Updated comments about contextValue argument --- src/GraphQL.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/GraphQL.php b/src/GraphQL.php index a9819be80..c11de3bf0 100644 --- a/src/GraphQL.php +++ b/src/GraphQL.php @@ -47,9 +47,11 @@ class GraphQL * rootValue: * The value provided as the first argument to resolver functions on the top * level type (e.g. the query object type). - * context: - * The value provided as the third argument to all resolvers. - * Use this to pass current session, user data, etc + * contextValue: + * The context value is provided as an argument to resolver functions after + * field arguments. It is used to pass shared information useful at any point + * during executing this query, for example the currently logged in user and + * connections to databases or other services. * variableValues: * A mapping of variable name to runtime value to use for all variables * defined in the requestString. @@ -68,7 +70,7 @@ class GraphQL * * @param string|DocumentNode $source * @param mixed $rootValue - * @param mixed $context + * @param mixed $contextValue * @param mixed[]|null $variableValues * @param ValidationRule[] $validationRules * @@ -78,7 +80,7 @@ public static function executeQuery( SchemaType $schema, $source, $rootValue = null, - $context = null, + $contextValue = null, $variableValues = null, ?string $operationName = null, ?callable $fieldResolver = null, @@ -91,7 +93,7 @@ public static function executeQuery( $schema, $source, $rootValue, - $context, + $contextValue, $variableValues, $operationName, $fieldResolver, From 44fb03a0056ce9fa82339d6dc7247b6815a1716f Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Thu, 29 Aug 2019 18:03:11 +0700 Subject: [PATCH 093/256] Validate schema use new extension ast nodes --- src/Type/SchemaValidationContext.php | 137 +++++++++++++-------------- tests/Type/ValidationTest.php | 83 +++++++++++++--- 2 files changed, 137 insertions(+), 83 deletions(-) diff --git a/src/Type/SchemaValidationContext.php b/src/Type/SchemaValidationContext.php index 7056d12ca..ecdcd9b5f 100644 --- a/src/Type/SchemaValidationContext.php +++ b/src/Type/SchemaValidationContext.php @@ -13,6 +13,7 @@ use GraphQL\Language\AST\ListTypeNode; use GraphQL\Language\AST\NamedTypeNode; use GraphQL\Language\AST\Node; +use GraphQL\Language\AST\NodeList; use GraphQL\Language\AST\NonNullTypeNode; use GraphQL\Language\AST\ObjectTypeDefinitionNode; use GraphQL\Language\AST\ObjectTypeExtensionNode; @@ -219,19 +220,19 @@ private function validateName($node) */ private function getAllDirectiveArgNodes(Directive $directive, $argName) { - $argNodes = []; - $directiveNode = $directive->astNode; - if ($directiveNode && $directiveNode->arguments) { - foreach ($directiveNode->arguments as $node) { - if ($node->name->value !== $argName) { - continue; - } - - $argNodes[] = $node; + $subNodes = $this->getAllSubNodes( + $directive, + static function ($directiveNode) { + return $directiveNode->arguments; } - } + ); - return $argNodes; + return Utils::filter( + $subNodes, + static function ($argNode) use ($argName) { + return $argNode->name->value === $argName; + } + ); } /** @@ -369,7 +370,7 @@ private function validateFields($type) } /** - * @param ObjectType|InterfaceType $type + * @param ObjectType|InterfaceType|UnionType|EnumType|InputObjectType|Directive $type * * @return ObjectTypeDefinitionNode[]|ObjectTypeExtensionNode[]|InterfaceTypeDefinitionNode[]|InterfaceTypeExtensionNode[] */ @@ -383,30 +384,44 @@ private function getAllNodes($type) } /** - * @param ObjectType|InterfaceType $type - * @param string $fieldName + * @param ObjectType|InterfaceType|UnionType|EnumType|Directive $obj * - * @return FieldDefinitionNode[] + * @return NodeList */ - private function getAllFieldNodes($type, $fieldName) + private function getAllSubNodes($obj, callable $getter) { - $fieldNodes = []; - $astNodes = $this->getAllNodes($type); - foreach ($astNodes as $astNode) { - if (! $astNode || ! $astNode->fields) { + $result = new NodeList([]); + foreach ($this->getAllNodes($obj) as $astNode) { + if (! $astNode) { continue; } - foreach ($astNode->fields as $node) { - if ($node->name->value !== $fieldName) { - continue; - } - - $fieldNodes[] = $node; + $subNodes = $getter($astNode); + if (! $subNodes) { + continue; } + + $result = $result->merge($subNodes); } - return $fieldNodes; + return $result; + } + + /** + * @param ObjectType|InterfaceType $type + * @param string $fieldName + * + * @return FieldDefinitionNode[] + */ + private function getAllFieldNodes($type, $fieldName) + { + $subNodes = $this->getAllSubNodes($type, static function ($typeNode) { + return $typeNode->fields; + }); + + return Utils::filter($subNodes, static function ($fieldNode) use ($fieldName) { + return $fieldNode->name->value === $fieldName; + }); } /** @@ -533,24 +548,13 @@ private function getImplementsInterfaceNode(ObjectType $type, $iface) */ private function getAllImplementsInterfaceNodes(ObjectType $type, $iface) { - $implementsNodes = []; - $astNodes = $this->getAllNodes($type); - - foreach ($astNodes as $astNode) { - if (! $astNode || ! $astNode->interfaces) { - continue; - } + $subNodes = $this->getAllSubNodes($type, static function ($typeNode) { + return $typeNode->interfaces; + }); - foreach ($astNode->interfaces as $node) { - if ($node->name->value !== $iface->name) { - continue; - } - - $implementsNodes[] = $node; - } - } - - return $implementsNodes; + return Utils::filter($subNodes, static function ($ifaceNode) use ($iface) { + return $ifaceNode->name->value === $iface->name; + }); } /** @@ -576,7 +580,10 @@ private function validateObjectImplementsInterface(ObjectType $object, $iface) $fieldName, $object->name ), - [$this->getFieldNode($iface, $fieldName), $object->astNode] + array_merge( + [$this->getFieldNode($iface, $fieldName)], + $this->getAllNodes($object) + ) ); continue; } @@ -704,7 +711,7 @@ private function validateUnionMembers(UnionType $union) if (! $memberTypes) { $this->reportError( sprintf('Union type %s must define one or more member types.', $union->name), - $union->astNode + $this->getAllNodes($union) ); } @@ -741,18 +748,13 @@ private function validateUnionMembers(UnionType $union) */ private function getUnionMemberTypeNodes(UnionType $union, $typeName) { - if ($union->astNode && $union->astNode->types) { - return array_filter( - $union->astNode->types, - static function (NamedTypeNode $value) use ($typeName) { - return $value->name->value === $typeName; - } - ); - } + $subNodes = $this->getAllSubNodes($union, static function ($unionNode) { + return $unionNode->types; + }); - return $union->astNode - ? $union->astNode->types - : null; + return Utils::filter($subNodes, static function ($typeNode) use ($typeName) { + return $typeNode->name->value === $typeName; + }); } private function validateEnumValues(EnumType $enumType) @@ -762,7 +764,7 @@ private function validateEnumValues(EnumType $enumType) if (! $enumValues) { $this->reportError( sprintf('Enum type %s must define one or more values.', $enumType->name), - $enumType->astNode + $this->getAllNodes($enumType) ); } @@ -798,18 +800,13 @@ private function validateEnumValues(EnumType $enumType) */ private function getEnumValueNodes(EnumType $enum, $valueName) { - if ($enum->astNode && $enum->astNode->values) { - return array_filter( - iterator_to_array($enum->astNode->values), - static function (EnumValueDefinitionNode $value) use ($valueName) { - return $value->name->value === $valueName; - } - ); - } + $subNodes = $this->getAllSubNodes($enum, static function ($enumNode) { + return $enumNode->values; + }); - return $enum->astNode - ? $enum->astNode->values - : null; + return Utils::filter($subNodes, static function ($valueNode) use ($valueName) { + return $valueNode->name->value === $valueName; + }); } private function validateInputFields(InputObjectType $inputObj) @@ -819,7 +816,7 @@ private function validateInputFields(InputObjectType $inputObj) if (! $fieldMap) { $this->reportError( sprintf('Input Object type %s must define one or more fields.', $inputObj->name), - $inputObj->astNode + $this->getAllNodes($inputObj) ); } diff --git a/tests/Type/ValidationTest.php b/tests/Type/ValidationTest.php index 1c8e493ee..a7e40b8cb 100644 --- a/tests/Type/ValidationTest.php +++ b/tests/Type/ValidationTest.php @@ -789,17 +789,27 @@ public function testAcceptsAUnionTypeWithArrayTypes() : void public function testRejectsAUnionTypeWithEmptyTypes() : void { $schema = BuildSchema::build(' - type Query { - test: BadUnion - } - - union BadUnion + type Query { + test: BadUnion + } + + union BadUnion '); + + $schema = SchemaExtender::extend( + $schema, + Parser::parse(' + directive @test on UNION + + extend union BadUnion @test + ') + ); + $this->assertMatchesValidationMessage( $schema->validate(), [[ 'message' => 'Union type BadUnion must define one or more member types.', - 'locations' => [['line' => 6, 'column' => 7]], + 'locations' => [['line' => 6, 'column' => 13], ['line' => 4, 'column' => 11]], ], ] ); @@ -836,6 +846,25 @@ public function testRejectsAUnionTypeWithDuplicatedMemberType() : void ], ] ); + + $extendedSchema = SchemaExtender::extend( + $schema, + Parser::parse('extend union BadUnion = TypeB') + ); + + $this->assertMatchesValidationMessage( + $extendedSchema->validate(), + [ + [ + 'message' => 'Union type BadUnion can only include type TypeA once.', + 'locations' => [['line' => 15, 'column' => 11], ['line' => 17, 'column' => 11]], + ], + [ + 'message' => 'Union type BadUnion can only include type TypeB once.', + 'locations' => [[ 'line' => 16, 'column' => 11 ], [ 'line' => 3, 'column' => 5 ]], + ], + ] + ); } /** @@ -861,13 +890,20 @@ public function testRejectsAUnionTypeWithNonObjectMembersType() : void | String | TypeB '); + + $schema = SchemaExtender::extend($schema, Parser::parse('extend union BadUnion = Int')); + $this->assertMatchesValidationMessage( $schema->validate(), - [[ - 'message' => 'Union type BadUnion can only include Object types, ' . - 'it cannot include String.', - 'locations' => [['line' => 16, 'column' => 11]], - ], + [ + [ + 'message' => 'Union type BadUnion can only include Object types, it cannot include String.', + 'locations' => [['line' => 16, 'column' => 11]], + ], + [ + 'message' => 'Union type BadUnion can only include Object types, it cannot include Int.', + 'locations' => [[ 'line' => 1, 'column' => 25 ]], + ], ] ); @@ -927,11 +963,22 @@ public function testRejectsAnInputObjectTypeWithMissingFields() : void input SomeInputObject '); + + $schema = SchemaExtender::extend( + $schema, + Parser::parse(' + directive @test on ENUM + + extend input SomeInputObject @test + '), + ['assumeValid' => true] + ); + $this->assertMatchesValidationMessage( $schema->validate(), [[ 'message' => 'Input Object type SomeInputObject must define one or more fields.', - 'locations' => [['line' => 6, 'column' => 7]], + 'locations' => [['line' => 6, 'column' => 7], ['line' => 3, 'column' => 23]], ], ] ); @@ -1124,11 +1171,21 @@ public function testRejectsAnEnumTypeWithoutValues() : void enum SomeEnum '); + + $schema = SchemaExtender::extend( + $schema, + Parser::parse(' + directive @test on ENUM + + extend enum SomeEnum @test + ') + ); + $this->assertMatchesValidationMessage( $schema->validate(), [[ 'message' => 'Enum type SomeEnum must define one or more values.', - 'locations' => [['line' => 6, 'column' => 7]], + 'locations' => [['line' => 6, 'column' => 7], ['line' => 3, 'column' => 23]], ], ] ); From a4f39bb1c30a7488e94dbb6f27414fea03a0726b Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Thu, 29 Aug 2019 18:03:28 +0700 Subject: [PATCH 094/256] Validate schema: added several missing tests --- tests/Type/ValidationTest.php | 139 ++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) diff --git a/tests/Type/ValidationTest.php b/tests/Type/ValidationTest.php index a7e40b8cb..0260543f7 100644 --- a/tests/Type/ValidationTest.php +++ b/tests/Type/ValidationTest.php @@ -1489,6 +1489,145 @@ interface AnotherInterface { ); } + // DESCRIBE: Type System: Interface extensions should be valid + + /** + * @see it('rejects an Object implementing the extended interface due to missing field') + */ + public function testRejectsAnObjectImplementingTheExtendedInterfaceDueToMissingField() + { + $schema = BuildSchema::build(' + type Query { + test: AnotherObject + } + + interface AnotherInterface { + field: String + } + + type AnotherObject implements AnotherInterface { + field: String + }'); + $extendedSchema = SchemaExtender::extend( + $schema, + Parser::parse(' + extend interface AnotherInterface { + newField: String + } + + extend type AnotherObject { + differentNewField: String + } + ') + ); + $this->assertMatchesValidationMessage( + $extendedSchema->validate(), + [[ + 'message' => 'Interface field AnotherInterface.newField expected but AnotherObject does not provide it.', + 'locations' => [ + ['line' => 3, 'column' => 19], + ['line' => 7, 'column' => 7], + ['line' => 6, 'column' => 17], + ], + ], + ] + ); + } + + /** + * @see it('rejects an Object implementing the extended interface due to missing field args') + */ + public function testRejectsAnObjectImplementingTheExtendedInterfaceDueToMissingFieldArgs() + { + $schema = BuildSchema::build(' + type Query { + test: AnotherObject + } + + interface AnotherInterface { + field: String + } + + type AnotherObject implements AnotherInterface { + field: String + }'); + $extendedSchema = SchemaExtender::extend( + $schema, + Parser::parse(' + extend interface AnotherInterface { + newField(test: Boolean): String + } + + extend type AnotherObject { + newField: String + } + ') + ); + $this->assertMatchesValidationMessage( + $extendedSchema->validate(), + [[ + 'message' => 'Interface field argument AnotherInterface.newField(test:) expected but AnotherObject.newField does not provide it.', + 'locations' => [ + ['line' => 3, 'column' => 28], + ['line' => 7, 'column' => 19], + ], + ], + ] + ); + } + + /** + * @see it('rejects Objects implementing the extended interface due to mismatching interface type') + */ + public function testRejectsObjectsImplementingTheExtendedInterfaceDueToMismatchingInterfaceType() + { + $schema = BuildSchema::build(' + type Query { + test: AnotherObject + } + + interface AnotherInterface { + field: String + } + + type AnotherObject implements AnotherInterface { + field: String + }'); + $extendedSchema = SchemaExtender::extend( + $schema, + Parser::parse(' + extend interface AnotherInterface { + newInterfaceField: NewInterface + } + + interface NewInterface { + newField: String + } + + interface MismatchingInterface { + newField: String + } + + extend type AnotherObject { + newInterfaceField: MismatchingInterface + } + + # Required to prevent unused interface errors + type DummyObject implements NewInterface & MismatchingInterface { + newField: String + } + ') + ); + $this->assertMatchesValidationMessage( + $extendedSchema->validate(), + [[ + 'message' => 'Interface field AnotherInterface.newInterfaceField expects type NewInterface but AnotherObject.newInterfaceField is type MismatchingInterface.', + 'locations' => [['line' => 3, 'column' => 38], ['line' => 15, 'column' => 38]], + ], + ] + ); + } + // DESCRIBE: Type System: Field arguments must have input types /** From 8df14ea3ef53be612f01b07e143aa477db9983c6 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Sat, 7 Sep 2019 10:37:54 +0700 Subject: [PATCH 095/256] Prevent infinite recursion on invalid unions --- src/Utils/ASTDefinitionBuilder.php | 64 +++++++++--------------------- src/Utils/BuildSchema.php | 31 ++++----------- src/Utils/SchemaExtender.php | 11 +++-- tests/Utils/BuildSchemaTest.php | 16 ++++++++ 4 files changed, 49 insertions(+), 73 deletions(-) diff --git a/src/Utils/ASTDefinitionBuilder.php b/src/Utils/ASTDefinitionBuilder.php index 0ffac1923..567d3439f 100644 --- a/src/Utils/ASTDefinitionBuilder.php +++ b/src/Utils/ASTDefinitionBuilder.php @@ -30,7 +30,6 @@ use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\InputType; use GraphQL\Type\Definition\InterfaceType; -use GraphQL\Type\Definition\NonNull; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\UnionType; @@ -143,7 +142,7 @@ function ($value) { // Note: While this could make assertions to get the correctly typed // value, that would throw immediately while type system validation // with validateSchema() will produce more actionable results. - $type = $this->internalBuildWrappedType($value->type); + $type = $this->buildWrappedType($value->type); $config = [ 'name' => $value->name->value, @@ -165,11 +164,16 @@ function ($value) { * * @throws Error */ - private function internalBuildWrappedType(TypeNode $typeNode) + private function buildWrappedType(TypeNode $typeNode) { - $typeDef = $this->buildType($this->getNamedTypeNode($typeNode)); + if ($typeNode instanceof ListTypeNode) { + return Type::listOf($this->buildWrappedType($typeNode->type)); + } + if ($typeNode instanceof NonNullTypeNode) { + return Type::nonNull($this->buildWrappedType($typeNode->type)); + } - return $this->buildWrappedType($typeDef, $typeNode); + return $this->buildType($typeNode); } /** @@ -302,7 +306,7 @@ public function buildField(FieldDefinitionNode $field) // Note: While this could make assertions to get the correctly typed // value, that would throw immediately while type system validation // with validateSchema() will produce more actionable results. - 'type' => $this->internalBuildWrappedType($field->type), + 'type' => $this->buildWrappedType($field->type), 'description' => $this->getDescription($field), 'args' => $field->arguments ? $this->makeInputValues($field->arguments) : null, 'deprecationReason' => $this->getDeprecationReason($field), @@ -389,12 +393,14 @@ private function makeUnionDef(UnionTypeDefinitionNode $def) // values below, that would throw immediately while type system // validation with validateSchema() will produce more actionable results. 'types' => $def->types - ? Utils::map( - $def->types, - function ($typeNode) { - return $this->buildType($typeNode); - } - ) + ? function () use ($def) { + return Utils::map( + $def->types, + function ($typeNode) { + return $this->buildType($typeNode); + } + ); + } : [], 'astNode' => $def, ]); @@ -453,44 +459,12 @@ private function makeSchemaDefFromConfig(Node $def, array $config) } } - /** - * @param NamedTypeNode|ListTypeNode|NonNullTypeNode $typeNode - * - * @return NamedTypeNode|ListTypeNode|NonNullTypeNode - */ - private function getNamedTypeNode(TypeNode $typeNode) : TypeNode - { - $namedType = $typeNode; - while ($namedType instanceof ListTypeNode || $namedType instanceof NonNullTypeNode) { - $namedType = $namedType->type; - } - - return $namedType; - } - - /** - * @param NamedTypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode - */ - private function buildWrappedType(Type $innerType, TypeNode $inputTypeNode) : Type - { - if ($inputTypeNode instanceof ListTypeNode) { - return Type::listOf($this->buildWrappedType($innerType, $inputTypeNode->type)); - } - if ($inputTypeNode instanceof NonNullTypeNode) { - $wrappedType = $this->buildWrappedType($innerType, $inputTypeNode->type); - - return Type::nonNull(NonNull::assertNullableType($wrappedType)); - } - - return $innerType; - } - /** * @return mixed[] */ public function buildInputField(InputValueDefinitionNode $value) : array { - $type = $this->internalBuildWrappedType($value->type); + $type = $this->buildWrappedType($value->type); $config = [ 'name' => $value->name->value, diff --git a/src/Utils/BuildSchema.php b/src/Utils/BuildSchema.php index 62f610164..2f49c20bb 100644 --- a/src/Utils/BuildSchema.php +++ b/src/Utils/BuildSchema.php @@ -157,36 +157,19 @@ static function ($def) use ($DefinitionBuilder) { ); // If specified directives were not explicitly declared, add them. - $skip = array_reduce( + $directivesByName = Utils::groupBy( $directives, - static function (bool $hasSkip, Directive $directive) : bool { - return $hasSkip || $directive->name === 'skip'; - }, - false + static function (Directive $directive) : string { + return $directive->name; + } ); - if (! $skip) { + if (! isset($directivesByName['skip'])) { $directives[] = Directive::skipDirective(); } - - $include = array_reduce( - $directives, - static function (bool $hasInclude, Directive $directive) : bool { - return $hasInclude || $directive->name === 'include'; - }, - false - ); - if (! $include) { + if (! isset($directivesByName['include'])) { $directives[] = Directive::includeDirective(); } - - $deprecated = array_reduce( - $directives, - static function (bool $hasDeprecated, Directive $directive) : bool { - return $hasDeprecated || $directive->name === 'deprecated'; - }, - false - ); - if (! $deprecated) { + if (! isset($directivesByName['deprecated'])) { $directives[] = Directive::deprecatedDirective(); } diff --git a/src/Utils/SchemaExtender.php b/src/Utils/SchemaExtender.php index d20df4df7..997e113d5 100644 --- a/src/Utils/SchemaExtender.php +++ b/src/Utils/SchemaExtender.php @@ -123,7 +123,7 @@ protected static function checkExtensionNode(Type $type, Node $node) : void } } - protected static function extendCustomScalarType(CustomScalarType $type) : CustomScalarType + protected static function extendScalarType(ScalarType $type) : CustomScalarType { return new CustomScalarType([ 'name' => $type->name, @@ -428,8 +428,8 @@ protected static function extendNamedType(Type $type) $name = $type->name; if (! isset(static::$extendTypeCache[$name])) { - if ($type instanceof CustomScalarType) { - static::$extendTypeCache[$name] = static::extendCustomScalarType($type); + if ($type instanceof ScalarType) { + static::$extendTypeCache[$name] = static::extendScalarType($type); } elseif ($type instanceof ObjectType) { static::$extendTypeCache[$name] = static::extendObjectType($type); } elseif ($type instanceof InterfaceType) { @@ -616,9 +616,12 @@ static function (string $typeName) use ($schema) { : $schema->extensionASTNodes; $types = array_merge( + // Iterate through all types, getting the type definition for each, ensuring + // that any type not directly referenced by a field will get created. array_map(static function ($type) { - return static::extendType($type); + return static::extendNamedType($type); }, array_values($schema->getTypeMap())), + // Do the same with new types. array_map(static function ($type) { return static::$astBuilder->buildType($type); }, array_values($typeDefinitionMap)) diff --git a/tests/Utils/BuildSchemaTest.php b/tests/Utils/BuildSchemaTest.php index 951090bbf..07e47b9e6 100644 --- a/tests/Utils/BuildSchemaTest.php +++ b/tests/Utils/BuildSchemaTest.php @@ -436,6 +436,22 @@ public function testMultipleUnion() : void self::assertEquals($output, $body); } + /** + * @see it('Can build recursive Union') + */ + public function testCanBuildRecursiveUnion() + { + $schema = BuildSchema::build(' + union Hello = Hello + + type Query { + hello: Hello + } + '); + $errors = $schema->validate(); + self::assertNotEmpty($errors); + } + /** * @see it('Specifying Union type using __typename') */ From c975934ad0532b50f81c1cdfdc1f85dc4afe0c62 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Sat, 7 Sep 2019 13:44:17 +0700 Subject: [PATCH 096/256] Schema Validation: Added validations for directive location and duplicates --- src/Type/SchemaValidationContext.php | 241 +++++++++++++++++++--- src/Utils/Utils.php | 16 ++ tests/Type/ValidationTest.php | 297 ++++++++++++++++++++++++++- 3 files changed, 517 insertions(+), 37 deletions(-) diff --git a/src/Type/SchemaValidationContext.php b/src/Type/SchemaValidationContext.php index ecdcd9b5f..c7fbb27d3 100644 --- a/src/Type/SchemaValidationContext.php +++ b/src/Type/SchemaValidationContext.php @@ -5,6 +5,7 @@ namespace GraphQL\Type; use GraphQL\Error\Error; +use GraphQL\Language\AST\DirectiveNode; use GraphQL\Language\AST\EnumValueDefinitionNode; use GraphQL\Language\AST\FieldDefinitionNode; use GraphQL\Language\AST\InputValueDefinitionNode; @@ -20,6 +21,7 @@ use GraphQL\Language\AST\SchemaDefinitionNode; use GraphQL\Language\AST\TypeDefinitionNode; use GraphQL\Language\AST\TypeNode; +use GraphQL\Language\DirectiveLocation; use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\EnumValueDefinition; @@ -30,6 +32,7 @@ use GraphQL\Type\Definition\NamedType; use GraphQL\Type\Definition\NonNull; use GraphQL\Type\Definition\ObjectType; +use GraphQL\Type\Definition\ScalarType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Validation\InputObjectCircularRefs; @@ -41,7 +44,6 @@ use function count; use function is_array; use function is_object; -use function iterator_to_array; use function sprintf; class SchemaValidationContext @@ -148,6 +150,19 @@ private function getOperationTypeNode($type, $operation) public function validateDirectives() { + $this->validateDirectiveDefinitions(); + + // Validate directives that are used on the schema + $this->validateDirectivesAtLocation( + $this->getDirectives($this->schema), + DirectiveLocation::SCHEMA + ); + } + + public function validateDirectiveDefinitions() + { + $directiveDefinitions = []; + $directives = $this->schema->getDirectives(); foreach ($directives as $directive) { // Ensure all directives are in fact GraphQL directives. @@ -158,6 +173,9 @@ public function validateDirectives() ); continue; } + $existingDefinitions = $directiveDefinitions[$directive->name] ?? []; + $existingDefinitions[] = $directive; + $directiveDefinitions[$directive->name] = $existingDefinitions; // Ensure they are named correctly. $this->validateName($directive); @@ -197,6 +215,22 @@ public function validateDirectives() ); } } + foreach ($directiveDefinitions as $directiveName => $directiveList) { + if (count($directiveList) <= 1) { + continue; + } + + $nodes = Utils::map( + $directiveList, + static function (Directive $directive) { + return $directive->astNode; + } + ); + $this->reportError( + sprintf('Directive @%s defined multiple times.', $directiveName), + array_filter($nodes) + ); + } } /** @@ -247,7 +281,7 @@ private function getDirectiveArgTypeNode(Directive $directive, $argName) : ?Type return $argNode ? $argNode->type : null; } - public function validateTypes() + public function validateTypes() : void { $typeMap = $this->schema->getTypeMap(); foreach ($typeMap as $typeName => $type) { @@ -268,22 +302,109 @@ public function validateTypes() // Ensure objects implement the interfaces they claim to. $this->validateObjectInterfaces($type); + + // Ensure directives are valid + $this->validateDirectivesAtLocation( + $this->getDirectives($type), + DirectiveLocation::OBJECT + ); } elseif ($type instanceof InterfaceType) { // Ensure fields are valid. $this->validateFields($type); + + // Ensure directives are valid + $this->validateDirectivesAtLocation( + $this->getDirectives($type), + DirectiveLocation::IFACE + ); } elseif ($type instanceof UnionType) { // Ensure Unions include valid member types. $this->validateUnionMembers($type); + + // Ensure directives are valid + $this->validateDirectivesAtLocation( + $this->getDirectives($type), + DirectiveLocation::UNION + ); } elseif ($type instanceof EnumType) { // Ensure Enums have valid values. $this->validateEnumValues($type); + + // Ensure directives are valid + $this->validateDirectivesAtLocation( + $this->getDirectives($type), + DirectiveLocation::ENUM + ); } elseif ($type instanceof InputObjectType) { // Ensure Input Object fields are valid. $this->validateInputFields($type); + // Ensure directives are valid + $this->validateDirectivesAtLocation( + $this->getDirectives($type), + DirectiveLocation::INPUT_OBJECT + ); + // Ensure Input Objects do not contain non-nullable circular references $this->inputObjectCircularRefs->validate($type); + } elseif ($type instanceof ScalarType) { + // Ensure directives are valid + $this->validateDirectivesAtLocation( + $this->getDirectives($type), + DirectiveLocation::SCALAR + ); + } + } + } + + /** + * @param NodeList $directives + */ + private function validateDirectivesAtLocation($directives, string $location) + { + $directivesNamed = []; + $schema = $this->schema; + foreach ($directives as $directive) { + $directiveName = $directive->name->value; + + // Ensure directive used is also defined + $schemaDirective = $schema->getDirective($directiveName); + if ($schemaDirective === null) { + $this->reportError( + sprintf('No directive @%s defined.', $directiveName), + $directive + ); + continue; + } + $includes = Utils::some( + $schemaDirective->locations, + static function ($schemaLocation) use ($location) { + return $schemaLocation === $location; + } + ); + if (! $includes) { + $errorNodes = $schemaDirective->astNode + ? [$directive, $schemaDirective->astNode] + : [$directive]; + $this->reportError( + sprintf('Directive @%s not allowed at %s location.', $directiveName, $location), + $errorNodes + ); } + + $existingNodes = $directivesNamed[$directiveName] ?? []; + $existingNodes[] = $directive; + $directivesNamed[$directiveName] = $existingNodes; + } + foreach ($directivesNamed as $directiveName => $directiveList) { + if (count($directiveList) <= 1) { + continue; + } + + $this->reportError( + sprintf('Directive @%s used twice at the same location.', $directiveName), + $directiveList + ); } } @@ -351,40 +472,66 @@ private function validateFields($type) $argNames[$argName] = true; // Ensure the type is an input type - if (Type::isInputType($arg->getType())) { + if (! Type::isInputType($arg->getType())) { + $this->reportError( + sprintf( + 'The type of %s.%s(%s:) must be Input Type but got: %s.', + $type->name, + $fieldName, + $argName, + Utils::printSafe($arg->getType()) + ), + $this->getFieldArgTypeNode($type, $fieldName, $argName) + ); + } + + // Ensure argument definition directives are valid + if (! isset($arg->astNode, $arg->astNode->directives)) { continue; } - $this->reportError( - sprintf( - 'The type of %s.%s(%s:) must be Input Type but got: %s.', - $type->name, - $fieldName, - $argName, - Utils::printSafe($arg->getType()) - ), - $this->getFieldArgTypeNode($type, $fieldName, $argName) + $this->validateDirectivesAtLocation( + $arg->astNode->directives, + DirectiveLocation::ARGUMENT_DEFINITION ); } + + // Ensure any directives are valid + if (! isset($field->astNode, $field->astNode->directives)) { + continue; + } + + $this->validateDirectivesAtLocation( + $field->astNode->directives, + DirectiveLocation::FIELD_DEFINITION + ); } } /** - * @param ObjectType|InterfaceType|UnionType|EnumType|InputObjectType|Directive $type + * @param Schema|ObjectType|InterfaceType|UnionType|EnumType|InputObjectType|Directive $obj * * @return ObjectTypeDefinitionNode[]|ObjectTypeExtensionNode[]|InterfaceTypeDefinitionNode[]|InterfaceTypeExtensionNode[] */ - private function getAllNodes($type) + private function getAllNodes($obj) { - return $type->astNode - ? ($type->extensionASTNodes - ? array_merge([$type->astNode], $type->extensionASTNodes) - : [$type->astNode]) - : ($type->extensionASTNodes ?: []); + if ($obj instanceof Schema) { + $astNode = $obj->getAstNode(); + $extensionNodes = $obj->extensionASTNodes; + } else { + $astNode = $obj->astNode; + $extensionNodes = $obj->extensionASTNodes; + } + + return $astNode + ? ($extensionNodes + ? array_merge([$astNode], $extensionNodes) + : [$astNode]) + : ($extensionNodes ?: []); } /** - * @param ObjectType|InterfaceType|UnionType|EnumType|Directive $obj + * @param Schema|ObjectType|InterfaceType|UnionType|EnumType|Directive $obj * * @return NodeList */ @@ -529,6 +676,18 @@ private function validateObjectInterfaces(ObjectType $object) } } + /** + * @param Schema|Type $object + * + * @return NodeList + */ + private function getDirectives($object) + { + return $this->getAllSubNodes($object, static function ($node) { + return $node->directives; + }); + } + /** * @param InterfaceType $iface * @@ -782,13 +941,21 @@ private function validateEnumValues(EnumType $enumType) // Ensure valid name. $this->validateName($enumValue); - if ($valueName !== 'true' && $valueName !== 'false' && $valueName !== 'null') { + if ($valueName === 'true' || $valueName === 'false' || $valueName === 'null') { + $this->reportError( + sprintf('Enum type %s cannot include value: %s.', $enumType->name, $valueName), + $enumValue->astNode + ); + } + + // Ensure valid directives + if (! isset($enumValue->astNode, $enumValue->astNode->directives)) { continue; } - $this->reportError( - sprintf('Enum type %s cannot include value: %s.', $enumType->name, $valueName), - $enumValue->astNode + $this->validateDirectivesAtLocation( + $enumValue->astNode->directives, + DirectiveLocation::ENUM_VALUE ); } } @@ -828,18 +995,26 @@ private function validateInputFields(InputObjectType $inputObj) // TODO: Ensure they are unique per field. // Ensure the type is an input type - if (Type::isInputType($field->getType())) { + if (! Type::isInputType($field->getType())) { + $this->reportError( + sprintf( + 'The type of %s.%s must be Input Type but got: %s.', + $inputObj->name, + $fieldName, + Utils::printSafe($field->getType()) + ), + $field->astNode ? $field->astNode->type : null + ); + } + + // Ensure valid directives + if (! isset($field->astNode, $field->astNode->directives)) { continue; } - $this->reportError( - sprintf( - 'The type of %s.%s must be Input Type but got: %s.', - $inputObj->name, - $fieldName, - Utils::printSafe($field->getType()) - ), - $field->astNode ? $field->astNode->type : null + $this->validateDirectivesAtLocation( + $field->astNode->directives, + DirectiveLocation::FIELD_DEFINITION ); } } diff --git a/src/Utils/Utils.php b/src/Utils/Utils.php index 8dddd6563..deec77fee 100644 --- a/src/Utils/Utils.php +++ b/src/Utils/Utils.php @@ -303,6 +303,22 @@ public static function every($traversable, callable $predicate) return true; } + /** + * @param mixed[] $traversable + * + * @return bool + */ + public static function some($traversable, callable $predicate) + { + foreach ($traversable as $key => $value) { + if ($predicate($value, $key)) { + return true; + } + } + + return false; + } + /** * @param bool $test * @param string $message diff --git a/tests/Type/ValidationTest.php b/tests/Type/ValidationTest.php index 0260543f7..1c3b85e57 100644 --- a/tests/Type/ValidationTest.php +++ b/tests/Type/ValidationTest.php @@ -976,10 +976,14 @@ public function testRejectsAnInputObjectTypeWithMissingFields() : void $this->assertMatchesValidationMessage( $schema->validate(), - [[ - 'message' => 'Input Object type SomeInputObject must define one or more fields.', - 'locations' => [['line' => 6, 'column' => 7], ['line' => 3, 'column' => 23]], - ], + [ + [ + 'message' => 'Input Object type SomeInputObject must define one or more fields.', + 'locations' => [['line' => 6, 'column' => 7], ['line' => 3, 'column' => 23]], + ],[ + 'message' => 'Directive @test not allowed at INPUT_OBJECT location.', + 'locations' => [['line' => 4, 'column' => 38], ['line' => 2, 'column' => 9]], + ], ] ); } @@ -2500,4 +2504,289 @@ public function testRejectsDifferentInstancesOfTheSameType() : void ); $schema->assertValid(); } + + // DESCRIBE: Type System: Schema directives must validate + + /** + * @see it('accepts a Schema with valid directives') + */ + public function testAcceptsASchemaWithValidDirectives() + { + $schema = BuildSchema::build(' + schema @testA @testB { + query: Query + } + + type Query @testA @testB { + test: AnInterface @testC + } + + directive @testA on SCHEMA | OBJECT | INTERFACE | UNION | SCALAR | INPUT_OBJECT | ENUM + directive @testB on SCHEMA | OBJECT | INTERFACE | UNION | SCALAR | INPUT_OBJECT | ENUM + directive @testC on FIELD_DEFINITION | ARGUMENT_DEFINITION | ENUM_VALUE | INPUT_FIELD_DEFINITION + directive @testD on FIELD_DEFINITION | ARGUMENT_DEFINITION | ENUM_VALUE | INPUT_FIELD_DEFINITION + + interface AnInterface @testA { + field: String! @testC + } + + type TypeA implements AnInterface @testA { + field(arg: SomeInput @testC): String! @testC @testD + } + + type TypeB @testB @testA { + scalar_field: SomeScalar @testC + enum_field: SomeEnum @testC @testD + } + + union SomeUnion @testA = TypeA | TypeB + + scalar SomeScalar @testA @testB + + enum SomeEnum @testA @testB { + SOME_VALUE @testC + } + + input SomeInput @testA @testB { + some_input_field: String @testC + } + '); + + self::assertEquals([], $schema->validate()); + } + + /** + * @see it('rejects a Schema with directive defined multiple times') + */ + public function testRejectsASchemaWithDirectiveDefinedMultipleTimes() + { + $schema = BuildSchema::build(' + type Query { + test: String + } + + directive @testA on SCHEMA + directive @testA on SCHEMA + '); + $this->assertMatchesValidationMessage( + $schema->validate(), + [[ + 'message' => 'Directive @testA defined multiple times.', + 'locations' => [[ 'line' => 6, 'column' => 11 ], [ 'line' => 7, 'column' => 11 ]], + ], + ] + ); + } + + /** + * @see it('rejects a Schema with same directive used twice per location') + */ + public function testRejectsASchemaWithSameSchemaDirectiveUsedTwice() + { + $schema = BuildSchema::build(' + directive @schema on SCHEMA + directive @object on OBJECT + directive @interface on INTERFACE + directive @union on UNION + directive @scalar on SCALAR + directive @input_object on INPUT_OBJECT + directive @enum on ENUM + directive @field_definition on FIELD_DEFINITION + directive @enum_value on ENUM_VALUE + directive @input_field_definition on INPUT_FIELD_DEFINITION + directive @argument_definition on ARGUMENT_DEFINITION + + schema @schema @schema { + query: Query + } + + type Query implements SomeInterface @object @object { + test(arg: SomeInput @argument_definition @argument_definition): String + } + + interface SomeInterface @interface @interface { + test: String @field_definition @field_definition + } + + union SomeUnion @union @union = Query + + scalar SomeScalar @scalar @scalar + + enum SomeEnum @enum @enum { + SOME_VALUE @enum_value @enum_value + } + + input SomeInput @input_object @input_object { + some_input_field: String @input_field_definition @input_field_definition + } + '); + $this->assertMatchesValidationMessage( + $schema->validate(), + [ + [ + 'message' => 'Directive @schema used twice at the same location.', + 'locations' => [[ 'line' => 14, 'column' => 18 ], [ 'line' => 14, 'column' => 26 ]], + ],[ + 'message' => 'Directive @argument_definition used twice at the same location.', + 'locations' => [[ 'line' => 19, 'column' => 33 ], [ 'line' => 19, 'column' => 54 ]], + ],[ + 'message' => 'Directive @object used twice at the same location.', + 'locations' => [[ 'line' => 18, 'column' => 47 ], [ 'line' => 18, 'column' => 55 ]], + ],[ + 'message' => 'Directive @field_definition used twice at the same location.', + 'locations' => [[ 'line' => 23, 'column' => 26 ], [ 'line' => 23, 'column' => 44 ]], + ],[ + 'message' => 'Directive @interface used twice at the same location.', + 'locations' => [[ 'line' => 22, 'column' => 35 ], [ 'line' => 22, 'column' => 46 ]], + ],[ + 'message' => 'Directive @input_field_definition not allowed at FIELD_DEFINITION location.', + 'locations' => [[ 'line' => 35, 'column' => 38 ], [ 'line' => 11, 'column' => 11 ]], + ],[ + 'message' => 'Directive @input_field_definition not allowed at FIELD_DEFINITION location.', + 'locations' => [[ 'line' => 35, 'column' => 62 ], [ 'line' => 11, 'column' => 11 ]], + ],[ + 'message' => 'Directive @input_field_definition used twice at the same location.', + 'locations' => [[ 'line' => 35, 'column' => 38 ], [ 'line' => 35, 'column' => 62 ]], + ],[ + 'message' => 'Directive @input_object used twice at the same location.', + 'locations' => [[ 'line' => 34, 'column' => 27 ], [ 'line' => 34, 'column' => 41 ]], + ],[ + 'message' => 'Directive @union used twice at the same location.', + 'locations' => [[ 'line' => 26, 'column' => 27 ], [ 'line' => 26, 'column' => 34 ]], + ],[ + 'message' => 'Directive @scalar used twice at the same location.', + 'locations' => [[ 'line' => 28, 'column' => 29 ], [ 'line' => 28, 'column' => 37 ]], + ],[ + 'message' => 'Directive @enum_value used twice at the same location.', + 'locations' => [[ 'line' => 31, 'column' => 24 ], [ 'line' => 31, 'column' => 36 ]], + ],[ + 'message' => 'Directive @enum used twice at the same location.', + 'locations' => [[ 'line' => 30, 'column' => 25 ], [ 'line' => 30, 'column' => 31 ]], + ], + ] + ); + } + + /** + * @see it('rejects a Schema with directive used again in extension') + */ + public function testRejectsASchemaWithSameDefinitionDirectiveUsedTwice() + { + $schema = BuildSchema::build(' + directive @testA on OBJECT + + type Query @testA { + test: String + } + '); + + $extensions = Parser::parse(' + extend type Query @testA + '); + + $extendedSchema = SchemaExtender::extend($schema, $extensions); + + $this->assertMatchesValidationMessage( + $extendedSchema->validate(), + [[ + 'message' => 'Directive @testA used twice at the same location.', + 'locations' => [[ 'line' => 4, 'column' => 22 ], [ 'line' => 2, 'column' => 29 ]], + ], + ] + ); + } + + /** + * @see it('rejects a Schema with directives used in wrong location') + */ + public function testRejectsASchemaWithDirectivesUsedInWrongLocation() + { + $schema = BuildSchema::build(' + directive @schema on SCHEMA + directive @object on OBJECT + directive @interface on INTERFACE + directive @union on UNION + directive @scalar on SCALAR + directive @input_object on INPUT_OBJECT + directive @enum on ENUM + directive @field_definition on FIELD_DEFINITION + directive @enum_value on ENUM_VALUE + directive @input_field_definition on INPUT_FIELD_DEFINITION + directive @argument_definition on ARGUMENT_DEFINITION + + schema @object { + query: Query + } + + type Query implements SomeInterface @schema { + test(arg: SomeInput @field_definition): String + } + + interface SomeInterface @interface { + test: String @argument_definition + } + + union SomeUnion @interface = Query + + scalar SomeScalar @enum_value + + enum SomeEnum @input_object { + SOME_VALUE @enum + } + + input SomeInput @object { + some_input_field: String @union + } + '); + + $extensions = Parser::parse(' + extend type Query @testA + '); + + $extendedSchema = SchemaExtender::extend( + $schema, + $extensions, + ['assumeValid' => true] // TODO: remove this line + ); + + $this->assertMatchesValidationMessage( + $extendedSchema->validate(), + [ + [ + 'message' => 'Directive @object not allowed at SCHEMA location.', + 'locations' => [[ 'line' => 14, 'column' => 18 ], [ 'line' => 3, 'column' => 11 ]], + ], [ + 'message' => 'Directive @field_definition not allowed at ARGUMENT_DEFINITION location.', + 'locations' => [[ 'line' => 19, 'column' => 33 ], [ 'line' => 9, 'column' => 11 ]], + ], [ + 'message' => 'Directive @schema not allowed at OBJECT location.', + 'locations' => [[ 'line' => 18, 'column' => 47 ], [ 'line' => 2, 'column' => 11 ]], + ], [ + 'message' => 'No directive @testA defined.', + 'locations' => [[ 'line' => 2, 'column' => 29 ]], + ], [ + 'message' => 'Directive @argument_definition not allowed at FIELD_DEFINITION location.', + 'locations' => [[ 'line' => 23, 'column' => 26 ], [ 'line' => 12, 'column' => 11 ]], + ], [ + 'message' => 'Directive @union not allowed at FIELD_DEFINITION location.', + 'locations' => [[ 'line' => 35, 'column' => 38 ], [ 'line' => 5, 'column' => 11 ]], + ], [ + 'message' => 'Directive @object not allowed at INPUT_OBJECT location.', + 'locations' => [[ 'line' => 34, 'column' => 27 ], [ 'line' => 3, 'column' => 11 ]], + ], [ + 'message' => 'Directive @interface not allowed at UNION location.', + 'locations' => [[ 'line' => 26, 'column' => 27 ], [ 'line' => 4, 'column' => 11 ]], + ], [ + 'message' => 'Directive @enum_value not allowed at SCALAR location.', + 'locations' => [[ 'line' => 28, 'column' => 29 ], [ 'line' => 10, 'column' => 11 ]], + ], [ + 'message' => 'Directive @enum not allowed at ENUM_VALUE location.', + 'locations' => [[ 'line' => 31, 'column' => 24 ], [ 'line' => 8, 'column' => 11 ]], + ], [ + 'message' => 'Directive @input_object not allowed at ENUM location.', + 'locations' => [[ 'line' => 30, 'column' => 25 ], [ 'line' => 7, 'column' => 11 ]], + ], + ] + ); + } } From b8e08968527a0796f4b3d9bd3fac77c23d6e3c62 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Sat, 7 Sep 2019 13:48:02 +0700 Subject: [PATCH 097/256] Test absence of name clash between type names and directives --- tests/Utils/BuildSchemaTest.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/Utils/BuildSchemaTest.php b/tests/Utils/BuildSchemaTest.php index 07e47b9e6..3b34f529d 100644 --- a/tests/Utils/BuildSchemaTest.php +++ b/tests/Utils/BuildSchemaTest.php @@ -1136,6 +1136,23 @@ public function testUnknownSubscriptionType() : void BuildSchema::buildAST($doc); } + /** + * @see it('Does not consider directive names') + */ + public function testDoesNotConsiderDirectiveNames() + { + $body = ' + schema { + query: Foo + } + + directive @Foo on QUERY + '; + $doc = Parser::parse($body); + $this->expectExceptionMessage('Specified query type "Foo" not found in document.'); + BuildSchema::build($doc); + } + /** * @see it('Does not consider operation names') */ From 843dcabea04e5ec49f8237bf689b82b7881b1d78 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Sat, 7 Sep 2019 16:03:03 +0700 Subject: [PATCH 098/256] Breaking: Validation Rework - Support SDL validation - Validate SDL during BuildSchema::build - Additional tests --- src/Utils/BuildSchema.php | 9 +- src/Validator/ASTValidationContext.php | 54 +++++ src/Validator/DocumentValidator.php | 55 ++++- .../Rules/KnownArgumentNamesOnDirectives.php | 4 +- src/Validator/Rules/KnownDirectives.php | 17 +- src/Validator/Rules/LoneSchemaDefinition.php | 18 +- .../ProvidedRequiredArgumentsOnDirectives.php | 9 +- src/Validator/Rules/UniqueArgumentNames.php | 12 ++ .../Rules/UniqueDirectivesPerLocation.php | 12 ++ src/Validator/Rules/UniqueInputFieldNames.php | 12 ++ src/Validator/Rules/ValidationRule.php | 18 +- src/Validator/SDLValidationContext.php | 12 ++ src/Validator/ValidationContext.php | 44 +--- tests/Type/ValidationTest.php | 14 +- tests/Utils/BuildSchemaTest.php | 51 +++-- tests/Utils/SchemaExtenderTest.php | 10 +- tests/Validator/KnownDirectivesTest.php | 147 ++++++++++++- tests/Validator/LoneSchemaDefinitionTest.php | 203 ++++++++++++++++++ .../UniqueDirectivesPerLocationTest.php | 48 +++++ tests/Validator/ValidatorTestCase.php | 53 +---- 20 files changed, 652 insertions(+), 150 deletions(-) create mode 100644 src/Validator/ASTValidationContext.php create mode 100644 src/Validator/SDLValidationContext.php create mode 100644 tests/Validator/LoneSchemaDefinitionTest.php diff --git a/src/Utils/BuildSchema.php b/src/Utils/BuildSchema.php index 2f49c20bb..14f3e4a93 100644 --- a/src/Utils/BuildSchema.php +++ b/src/Utils/BuildSchema.php @@ -19,6 +19,7 @@ use GraphQL\Language\Source; use GraphQL\Type\Definition\Directive; use GraphQL\Type\Schema; +use GraphQL\Validator\DocumentValidator; use function array_map; use function array_reduce; use function sprintf; @@ -101,6 +102,11 @@ public static function buildAST(DocumentNode $ast, ?callable $typeConfigDecorato public function buildSchema() { + $options = $this->options; + if (empty($options['assumeValid']) && empty($options['assumeValidSDL'])) { + DocumentValidator::assertValidSDL($this->ast); + } + $schemaDef = null; $typeDefs = []; $this->nodeMap = []; @@ -108,9 +114,6 @@ public function buildSchema() foreach ($this->ast->definitions as $definition) { switch (true) { case $definition instanceof SchemaDefinitionNode: - if ($schemaDef !== null) { - throw new Error('Must provide only one schema definition.'); - } $schemaDef = $definition; break; case $definition instanceof ScalarTypeDefinitionNode: diff --git a/src/Validator/ASTValidationContext.php b/src/Validator/ASTValidationContext.php new file mode 100644 index 000000000..0b1f1bc78 --- /dev/null +++ b/src/Validator/ASTValidationContext.php @@ -0,0 +1,54 @@ +ast = $ast; + $this->schema = $schema; + $this->errors = []; + } + + public function reportError(Error $error) + { + $this->errors[] = $error; + } + + /** + * @return Error[] + */ + public function getErrors() + { + return $this->errors; + } + + /** + * @return DocumentNode + */ + public function getDocument() + { + return $this->ast; + } + + public function getSchema() : ?Schema + { + return $this->schema; + } +} diff --git a/src/Validator/DocumentValidator.php b/src/Validator/DocumentValidator.php index 65daa763e..bd06b1518 100644 --- a/src/Validator/DocumentValidator.php +++ b/src/Validator/DocumentValidator.php @@ -307,18 +307,55 @@ public static function isValidLiteralValue(Type $type, $valueNode) return $context->getErrors(); } + /** + * @param ValidationRule[]|null $rules + * + * @return Error[] + * + * @throws Exception + */ + public static function validateSDL( + DocumentNode $documentAST, + ?Schema $schemaToExtend = null, + ?array $rules = null + ) { + $usedRules = $rules ?? self::sdlRules(); + $context = new SDLValidationContext($documentAST, $schemaToExtend); + $visitors = []; + foreach ($usedRules as $rule) { + $visitors[] = $rule->getSDLVisitor($context); + } + Visitor::visit($documentAST, Visitor::visitInParallel($visitors)); + + return $context->getErrors(); + } + + public static function assertValidSDL(DocumentNode $documentAST) + { + $errors = self::validateSDL($documentAST); + if (count($errors) !== 0) { + throw new Error(self::combineErrorMessages($errors)); + } + } + public static function assertValidSDLExtension(DocumentNode $documentAST, Schema $schema) { - $errors = self::visitUsingRules($schema, new TypeInfo($schema), $documentAST, self::sdlRules()); + $errors = self::validateSDL($documentAST, $schema); if (count($errors) !== 0) { - throw new Error( - implode( - "\n\n", - array_map(static function (Error $error) : string { - return $error->message; - }, $errors) - ) - ); + throw new Error(self::combineErrorMessages($errors)); + } + } + + /** + * @param Error[] $errors + */ + private static function combineErrorMessages(array $errors) : string + { + $str = ''; + foreach ($errors as $error) { + $str .= ($error->getMessage() . "\n\n"); } + + return $str; } } diff --git a/src/Validator/Rules/KnownArgumentNamesOnDirectives.php b/src/Validator/Rules/KnownArgumentNamesOnDirectives.php index a013820b7..4087a321a 100644 --- a/src/Validator/Rules/KnownArgumentNamesOnDirectives.php +++ b/src/Validator/Rules/KnownArgumentNamesOnDirectives.php @@ -12,7 +12,7 @@ use GraphQL\Language\AST\NodeList; use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\FieldArgument; -use GraphQL\Validator\ValidationContext; +use GraphQL\Validator\SDLValidationContext; use function array_map; use function in_array; use function iterator_to_array; @@ -30,7 +30,7 @@ protected static function unknownDirectiveArgMessage(string $argName, string $di return 'Unknown argument "' . $argName . '" on directive "@' . $directionName . '".'; } - public function getVisitor(ValidationContext $context) + public function getSDLVisitor(SDLValidationContext $context) { $directiveArgs = []; $schema = $context->getSchema(); diff --git a/src/Validator/Rules/KnownDirectives.php b/src/Validator/Rules/KnownDirectives.php index e986c0945..8df9b2a52 100644 --- a/src/Validator/Rules/KnownDirectives.php +++ b/src/Validator/Rules/KnownDirectives.php @@ -33,6 +33,9 @@ use GraphQL\Language\AST\UnionTypeDefinitionNode; use GraphQL\Language\AST\UnionTypeExtensionNode; use GraphQL\Language\DirectiveLocation; +use GraphQL\Type\Definition\Directive; +use GraphQL\Validator\ASTValidationContext; +use GraphQL\Validator\SDLValidationContext; use GraphQL\Validator\ValidationContext; use function array_map; use function count; @@ -42,10 +45,22 @@ class KnownDirectives extends ValidationRule { public function getVisitor(ValidationContext $context) + { + return $this->getASTVisitor($context); + } + + public function getSDLVisitor(SDLValidationContext $context) + { + return $this->getASTVisitor($context); + } + + public function getASTVisitor(ASTValidationContext $context) { $locationsMap = []; $schema = $context->getSchema(); - $definedDirectives = $schema->getDirectives(); + $definedDirectives = $schema + ? $schema->getDirectives() + : Directive::getInternalDirectives(); foreach ($definedDirectives as $directive) { $locationsMap[$directive->name] = $directive->locations; diff --git a/src/Validator/Rules/LoneSchemaDefinition.php b/src/Validator/Rules/LoneSchemaDefinition.php index 72e5832df..547b0fdd8 100644 --- a/src/Validator/Rules/LoneSchemaDefinition.php +++ b/src/Validator/Rules/LoneSchemaDefinition.php @@ -7,7 +7,7 @@ use GraphQL\Error\Error; use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\SchemaDefinitionNode; -use GraphQL\Validator\ValidationContext; +use GraphQL\Validator\SDLValidationContext; /** * Lone Schema definition @@ -16,7 +16,17 @@ */ class LoneSchemaDefinition extends ValidationRule { - public function getVisitor(ValidationContext $context) + public static function schemaDefinitionNotAloneMessage() + { + return 'Must provide only one schema definition.'; + } + + public static function canNotDefineSchemaWithinExtensionMessage() + { + return 'Cannot define a new schema within a schema extension.'; + } + + public function getSDLVisitor(SDLValidationContext $context) { $oldSchema = $context->getSchema(); $alreadyDefined = $oldSchema !== null @@ -33,13 +43,13 @@ public function getVisitor(ValidationContext $context) return [ NodeKind::SCHEMA_DEFINITION => static function (SchemaDefinitionNode $node) use ($alreadyDefined, $context, &$schemaDefinitionsCount) { if ($alreadyDefined !== false) { - $context->reportError(new Error('Cannot define a new schema within a schema extension.', $node)); + $context->reportError(new Error(self::canNotDefineSchemaWithinExtensionMessage(), $node)); return; } if ($schemaDefinitionsCount > 0) { - $context->reportError(new Error('Must provide only one schema definition.', $node)); + $context->reportError(new Error(self::schemaDefinitionNotAloneMessage(), $node)); } ++$schemaDefinitionsCount; diff --git a/src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php b/src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php index 080d53e60..262fbad6a 100644 --- a/src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php +++ b/src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php @@ -13,10 +13,11 @@ use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\NodeList; use GraphQL\Language\AST\NonNullTypeNode; +use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\FieldArgument; use GraphQL\Type\Definition\NonNull; use GraphQL\Utils\Utils; -use GraphQL\Validator\ValidationContext; +use GraphQL\Validator\SDLValidationContext; use function array_filter; use function is_array; use function iterator_to_array; @@ -34,11 +35,13 @@ protected static function missingDirectiveArgMessage(string $directiveName, stri return 'Directive "' . $directiveName . '" argument "' . $argName . '" is required but ont provided.'; } - public function getVisitor(ValidationContext $context) + public function getSDLVisitor(SDLValidationContext $context) { $requiredArgsMap = []; $schema = $context->getSchema(); - $definedDirectives = $schema->getDirectives(); + $definedDirectives = $schema + ? $schema->getDirectives() + : Directive::getInternalDirectives(); foreach ($definedDirectives as $directive) { $requiredArgsMap[$directive->name] = Utils::keyMap( diff --git a/src/Validator/Rules/UniqueArgumentNames.php b/src/Validator/Rules/UniqueArgumentNames.php index 2e83d4308..982fd6346 100644 --- a/src/Validator/Rules/UniqueArgumentNames.php +++ b/src/Validator/Rules/UniqueArgumentNames.php @@ -9,6 +9,8 @@ use GraphQL\Language\AST\NameNode; use GraphQL\Language\AST\NodeKind; use GraphQL\Language\Visitor; +use GraphQL\Validator\ASTValidationContext; +use GraphQL\Validator\SDLValidationContext; use GraphQL\Validator\ValidationContext; use function sprintf; @@ -17,7 +19,17 @@ class UniqueArgumentNames extends ValidationRule /** @var NameNode[] */ public $knownArgNames; + public function getSDLVisitor(SDLValidationContext $context) + { + return $this->getASTVisitor($context); + } + public function getVisitor(ValidationContext $context) + { + return $this->getASTVisitor($context); + } + + public function getASTVisitor(ASTValidationContext $context) { $this->knownArgNames = []; diff --git a/src/Validator/Rules/UniqueDirectivesPerLocation.php b/src/Validator/Rules/UniqueDirectivesPerLocation.php index f0e8c4591..28e8fbdca 100644 --- a/src/Validator/Rules/UniqueDirectivesPerLocation.php +++ b/src/Validator/Rules/UniqueDirectivesPerLocation.php @@ -7,12 +7,24 @@ use GraphQL\Error\Error; use GraphQL\Language\AST\DirectiveNode; use GraphQL\Language\AST\Node; +use GraphQL\Validator\ASTValidationContext; +use GraphQL\Validator\SDLValidationContext; use GraphQL\Validator\ValidationContext; use function sprintf; class UniqueDirectivesPerLocation extends ValidationRule { public function getVisitor(ValidationContext $context) + { + return $this->getASTVisitor($context); + } + + public function getSDLVisitor(SDLValidationContext $context) + { + return $this->getASTVisitor($context); + } + + public function getASTVisitor(ASTValidationContext $context) { return [ 'enter' => static function (Node $node) use ($context) { diff --git a/src/Validator/Rules/UniqueInputFieldNames.php b/src/Validator/Rules/UniqueInputFieldNames.php index 6426b437e..0ea374261 100644 --- a/src/Validator/Rules/UniqueInputFieldNames.php +++ b/src/Validator/Rules/UniqueInputFieldNames.php @@ -8,6 +8,8 @@ use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\ObjectFieldNode; use GraphQL\Language\Visitor; +use GraphQL\Validator\ASTValidationContext; +use GraphQL\Validator\SDLValidationContext; use GraphQL\Validator\ValidationContext; use function array_pop; use function sprintf; @@ -21,6 +23,16 @@ class UniqueInputFieldNames extends ValidationRule public $knownNameStack; public function getVisitor(ValidationContext $context) + { + return $this->getASTVisitor($context); + } + + public function getSDLVisitor(SDLValidationContext $context) + { + return $this->getASTVisitor($context); + } + + public function getASTVisitor(ASTValidationContext $context) { $this->knownNames = []; $this->knownNameStack = []; diff --git a/src/Validator/Rules/ValidationRule.php b/src/Validator/Rules/ValidationRule.php index fe8504d16..cf38cd138 100644 --- a/src/Validator/Rules/ValidationRule.php +++ b/src/Validator/Rules/ValidationRule.php @@ -4,6 +4,7 @@ namespace GraphQL\Validator\Rules; +use GraphQL\Validator\SDLValidationContext; use GraphQL\Validator\ValidationContext; use function class_alias; @@ -29,7 +30,22 @@ public function __invoke(ValidationContext $context) * * @return mixed[] */ - abstract public function getVisitor(ValidationContext $context); + public function getVisitor(ValidationContext $context) + { + return []; + } + + /** + * Returns structure suitable for GraphQL\Language\Visitor + * + * @see \GraphQL\Language\Visitor + * + * @return mixed[] + */ + public function getSDLVisitor(SDLValidationContext $context) + { + return []; + } } class_alias(ValidationRule::class, 'GraphQL\Validator\Rules\AbstractValidationRule'); diff --git a/src/Validator/SDLValidationContext.php b/src/Validator/SDLValidationContext.php new file mode 100644 index 000000000..dff94051e --- /dev/null +++ b/src/Validator/SDLValidationContext.php @@ -0,0 +1,12 @@ +schema = $schema; - $this->ast = $ast; + parent::__construct($ast, $schema); $this->typeInfo = $typeInfo; - $this->errors = []; $this->fragmentSpreads = new SplObjectStorage(); $this->recursivelyReferencedFragments = new SplObjectStorage(); $this->variableUsages = new SplObjectStorage(); $this->recursiveVariableUsages = new SplObjectStorage(); } - public function reportError(Error $error) - { - $this->errors[] = $error; - } - - /** - * @return Error[] - */ - public function getErrors() - { - return $this->errors; - } - - /** - * @return Schema - */ - public function getSchema() - { - return $this->schema; - } - /** * @return mixed[][] List of ['node' => VariableNode, 'type' => ?InputObjectType] */ @@ -251,14 +219,6 @@ public function getFragment($name) return $fragments[$name] ?? null; } - /** - * @return DocumentNode - */ - public function getDocument() - { - return $this->ast; - } - /** * Returns OutputType * diff --git a/tests/Type/ValidationTest.php b/tests/Type/ValidationTest.php index 1c3b85e57..07b4ab056 100644 --- a/tests/Type/ValidationTest.php +++ b/tests/Type/ValidationTest.php @@ -967,11 +967,10 @@ public function testRejectsAnInputObjectTypeWithMissingFields() : void $schema = SchemaExtender::extend( $schema, Parser::parse(' - directive @test on ENUM + directive @test on INPUT_OBJECT extend input SomeInputObject @test - '), - ['assumeValid' => true] + ') ); $this->assertMatchesValidationMessage( @@ -979,10 +978,7 @@ public function testRejectsAnInputObjectTypeWithMissingFields() : void [ [ 'message' => 'Input Object type SomeInputObject must define one or more fields.', - 'locations' => [['line' => 6, 'column' => 7], ['line' => 3, 'column' => 23]], - ],[ - 'message' => 'Directive @test not allowed at INPUT_OBJECT location.', - 'locations' => [['line' => 4, 'column' => 38], ['line' => 2, 'column' => 9]], + 'locations' => [['line' => 6, 'column' => 7], ['line' => 3, 'column' => 31]], ], ] ); @@ -2583,6 +2579,8 @@ public function testRejectsASchemaWithDirectiveDefinedMultipleTimes() */ public function testRejectsASchemaWithSameSchemaDirectiveUsedTwice() { + self::markTestSkipped(); + $schema = BuildSchema::build(' directive @schema on SCHEMA directive @object on OBJECT @@ -2701,6 +2699,8 @@ public function testRejectsASchemaWithSameDefinitionDirectiveUsedTwice() */ public function testRejectsASchemaWithDirectivesUsedInWrongLocation() { + self::markTestSkipped(); + $schema = BuildSchema::build(' directive @schema on SCHEMA directive @object on OBJECT diff --git a/tests/Utils/BuildSchemaTest.php b/tests/Utils/BuildSchemaTest.php index 3b34f529d..64b29ae15 100644 --- a/tests/Utils/BuildSchemaTest.php +++ b/tests/Utils/BuildSchemaTest.php @@ -912,32 +912,39 @@ public function testCanBuildInvalidSchema() : void self::assertGreaterThan(0, $errors); } - // Describe: Failures - /** - * @see it('Allows only a single schema definition') + * @see it('Rejects invalid SDL') */ - public function testAllowsOnlySingleSchemaDefinition() : void + public function testRejectsInvalidSDL() { + $doc = Parser::parse(' + type Query { + foo: String @unknown + } + '); $this->expectException(Error::class); - $this->expectExceptionMessage('Must provide only one schema definition.'); - $body = ' -schema { - query: Hello -} - -schema { - query: Hello -} + $this->expectExceptionMessage('Unknown directive "unknown".'); + BuildSchema::build($doc); + } -type Hello { - bar: Bar -} -'; - $doc = Parser::parse($body); - BuildSchema::buildAST($doc); + /** + * @see it('Allows to disable SDL validation') + */ + public function testAllowsToDisableSDLValidation() + { + $body = ' + type Query { + foo: String @unknown + } + '; + // Should not throw: + BuildSchema::build($body, null, ['assumeValid' => true]); + BuildSchema::build($body, null, ['assumeValidSDL' => true]); + self::assertTrue(true); } + // Describe: Failures + /** * @see it('Allows only a single query type') */ @@ -952,7 +959,7 @@ public function testAllowsOnlySingleQueryType() : void } type Hello { - bar: Bar + bar: String } type Yellow { @@ -978,7 +985,7 @@ public function testAllowsOnlySingleMutationType() : void } type Hello { - bar: Bar + bar: String } type Yellow { @@ -1004,7 +1011,7 @@ public function testAllowsOnlySingleSubscriptionType() : void } type Hello { - bar: Bar + bar: String } type Yellow { diff --git a/tests/Utils/SchemaExtenderTest.php b/tests/Utils/SchemaExtenderTest.php index 98675c7d2..c6079a966 100644 --- a/tests/Utils/SchemaExtenderTest.php +++ b/tests/Utils/SchemaExtenderTest.php @@ -1292,12 +1292,10 @@ public function testRejectsInvalidSDL() extend schema @unknown '; - try { - $this->extendTestSchema($sdl); - self::fail(); - } catch (Error $error) { - self::assertEquals('Unknown directive "unknown".', $error->getMessage()); - } + $this->expectException(Error::class); + $this->expectExceptionMessage('Unknown directive "unknown".'); + + $this->extendTestSchema($sdl); } /** diff --git a/tests/Validator/KnownDirectivesTest.php b/tests/Validator/KnownDirectivesTest.php index 014e892e0..efbc29a01 100644 --- a/tests/Validator/KnownDirectivesTest.php +++ b/tests/Validator/KnownDirectivesTest.php @@ -6,11 +6,39 @@ use GraphQL\Error\FormattedError; use GraphQL\Language\SourceLocation; +use GraphQL\Type\Schema; +use GraphQL\Utils\BuildSchema; use GraphQL\Validator\Rules\KnownDirectives; class KnownDirectivesTest extends ValidatorTestCase { + /** @var Schema */ + public $schemaWithSDLDirectives; + + public function setUp() + { + $this->schemaWithSDLDirectives = BuildSchema::build(' + directive @onSchema on SCHEMA + directive @onScalar on SCALAR + directive @onObject on OBJECT + directive @onFieldDefinition on FIELD_DEFINITION + directive @onArgumentDefinition on ARGUMENT_DEFINITION + directive @onInterface on INTERFACE + directive @onUnion on UNION + directive @onEnum on ENUM + directive @onEnumValue on ENUM_VALUE + directive @onInputObject on INPUT_OBJECT + directive @onInputFieldDefinition on INPUT_FIELD_DEFINITION + '); + } + + private function expectSDLErrors($sdlString, $schema = null, $errors = []) + { + return $this->expectSDLErrorsFromRule(new KnownDirectives(), $sdlString, $schema, $errors); + } + // Validate: Known directives + /** * @see it('with no directives') */ @@ -127,7 +155,113 @@ public function testWithWellPlacedDirectives() : void ); } - // within schema language + // DESCRIBE: within SDL + + /** + * @see it('with directive defined inside SDL') + */ + public function testWithDirectiveDefinedInsideSDL() + { + $this->expectSDLErrors(' + type Query { + foo: String @test + } + + directive @test on FIELD_DEFINITION + ', null, []); + } + + /** + * @see it('with standard directive') + */ + public function testWithStandardDirective() + { + $this->expectSDLErrors( + ' + type Query { + foo: String @deprecated + }', + null, + [] + ); + } + + /** + * @see it('with overrided standard directive') + */ + public function testWithOverridedStandardDirective() + { + $this->expectSDLErrors( + ' + schema @deprecated { + query: Query + } + directive @deprecated on SCHEMA', + null, + [] + ); + } + + /** + * @see it('with directive defined in schema extension') + */ + public function testWithDirectiveDefinedInSchemaExtension() + { + $schema = BuildSchema::build(' + type Query { + foo: String + } + '); + $this->expectSDLErrors( + ' + directive @test on OBJECT + + extend type Query @test + ', + $schema, + [] + ); + } + + /** + * @see it('with directive used in schema extension') + */ + public function testWithDirectiveUsedInSchemaExtension() + { + $schema = BuildSchema::build(' + directive @test on OBJECT + + type Query { + foo: String + } + '); + $this->expectSDLErrors( + ' + extend type Query @test + ', + $schema, + [] + ); + } + + /** + * @see it('with unknown directive in schema extension') + */ + public function testWithUnknownDirectiveInSchemaExtension() + { + $schema = BuildSchema::build(' + type Query { + foo: String + } + '); + $this->expectSDLErrors( + ' + extend type Query @unknown + ', + $schema, + [$this->unknownDirective('unknown', 2, 29)] + ); + } /** * @see it('with misplaced directives') @@ -168,8 +302,7 @@ private function misplacedDirective($directiveName, $placement, $line, $column) */ public function testWSLWithWellPlacedDirectives() : void { - $this->expectPassesRule( - new KnownDirectives(), + $this->expectSDLErrors( ' type MyObj implements MyInterface @onObject { myField(myArg: Int @onArgumentDefinition): String @onFieldDefinition @@ -206,7 +339,9 @@ enum MyEnum @onEnum { schema @onSchema { query: MyQuery } - ' + ', + $this->schemaWithSDLDirectives, + [] ); } @@ -215,8 +350,7 @@ enum MyEnum @onEnum { */ public function testWSLWithMisplacedDirectives() : void { - $this->expectFailsRule( - new KnownDirectives(), + $this->expectSDLErrors( ' type MyObj implements MyInterface @onInterface { myField(myArg: Int @onInputFieldDefinition): String @onInputFieldDefinition @@ -242,6 +376,7 @@ enum MyEnum @onScalar { query: MyQuery } ', + $this->schemaWithSDLDirectives, [ $this->misplacedDirective('onInterface', 'OBJECT', 2, 43), $this->misplacedDirective('onInputFieldDefinition', 'ARGUMENT_DEFINITION', 3, 30), diff --git a/tests/Validator/LoneSchemaDefinitionTest.php b/tests/Validator/LoneSchemaDefinitionTest.php new file mode 100644 index 000000000..528851f25 --- /dev/null +++ b/tests/Validator/LoneSchemaDefinitionTest.php @@ -0,0 +1,203 @@ +expectSDLErrorsFromRule(new LoneSchemaDefinition(), $sdlString, $schema, $errors); + } + + private function schemaDefinitionNotAlone($line, $column) + { + return FormattedError::create( + LoneSchemaDefinition::schemaDefinitionNotAloneMessage(), + [new SourceLocation($line, $column)] + ); + } + + private function canNotDefineSchemaWithinExtension($line, $column) + { + return FormattedError::create( + LoneSchemaDefinition::canNotDefineSchemaWithinExtensionMessage(), + [new SourceLocation($line, $column)] + ); + } + + // Validate: Schema definition should be alone + + /** + * @see it('no schema') + */ + public function testNoSchema() + { + $this->expectSDLErrors( + ' + type Query { + foo: String + } + ', + null, + [] + ); + } + + /** + * @see it('one schema definition') + */ + public function testOneSchemaDefinition() + { + $this->expectSDLErrors( + ' + schema { + query: Foo + } + + type Foo { + foo: String + } + ', + null, + [] + ); + } + + /** + * @see it('multiple schema definitions') + */ + public function testMultipleSchemaDefinitions() + { + $this->expectSDLErrors( + ' + schema { + query: Foo + } + + type Foo { + foo: String + } + + schema { + mutation: Foo + } + + schema { + subscription: Foo + } + ', + null, + [ + $this->schemaDefinitionNotAlone(10, 7), + $this->schemaDefinitionNotAlone(14, 7), + ] + ); + } + + /** + * @see it('define schema in schema extension') + */ + public function testDefineSchemaInSchemaExtension() + { + $schema = BuildSchema::build(' + type Foo { + foo: String + } + '); + + $this->expectSDLErrors( + ' + schema { + query: Foo + } + ', + $schema, + [] + ); + } + + /** + * @see it('redefine schema in schema extension') + */ + public function testRedefineSchemaInSchemaExtension() + { + $schema = BuildSchema::build(' + schema { + query: Foo + } + + type Foo { + foo: String + }'); + + $this->expectSDLErrors( + ' + schema { + mutation: Foo + } + ', + $schema, + [$this->canNotDefineSchemaWithinExtension(2, 17)] + ); + } + + /** + * @see it('redefine implicit schema in schema extension') + */ + public function testRedefineImplicitSchemaInSchemaExtension() + { + $schema = BuildSchema::build(' + type Query { + fooField: Foo + } + + type Foo { + foo: String + } + '); + + $this->expectSDLErrors( + ' + schema { + mutation: Foo + } + ', + $schema, + [$this->canNotDefineSchemaWithinExtension(2, 17)] + ); + } + + /** + * @see it('extend schema in schema extension') + */ + public function testExtendSchemaInSchemaExtension() + { + $schema = BuildSchema::build(' + type Query { + fooField: Foo + } + + type Foo { + foo: String + } + '); + + $this->expectSDLErrors( + ' + extend schema { + mutation: Foo + } + ', + $schema, + [] + ); + } +} diff --git a/tests/Validator/UniqueDirectivesPerLocationTest.php b/tests/Validator/UniqueDirectivesPerLocationTest.php index e1852201e..c71d02c65 100644 --- a/tests/Validator/UniqueDirectivesPerLocationTest.php +++ b/tests/Validator/UniqueDirectivesPerLocationTest.php @@ -8,6 +8,11 @@ class UniqueDirectivesPerLocationTest extends ValidatorTestCase { + private function expectSDLErrors($sdlString, $schema = null, $errors = []) + { + $this->expectSDLErrorsFromRule(new UniqueDirectivesPerLocation(), $sdlString, $schema, $errors); + } + /** * @see it('no directives') */ @@ -167,4 +172,47 @@ public function testDuplicateDirectivesInManyLocations() : void ] ); } + + /** + * @see it('duplicate directives on SDL definitions') + */ + public function testDuplicateDirectivesOnSDLDefinitions() + { + $this->expectSDLErrors( + ' + schema @directive @directive { query: Dummy } + extend schema @directive @directive + + scalar TestScalar @directive @directive + extend scalar TestScalar @directive @directive + + type TestObject @directive @directive + extend type TestObject @directive @directive + + interface TestInterface @directive @directive + extend interface TestInterface @directive @directive + + union TestUnion @directive @directive + extend union TestUnion @directive @directive + + input TestInput @directive @directive + extend input TestInput @directive @directive + ', + null, + [ + $this->duplicateDirective('directive', 2, 14, 2, 25), + $this->duplicateDirective('directive', 3, 21, 3, 32), + $this->duplicateDirective('directive', 5, 25, 5, 36), + $this->duplicateDirective('directive', 6, 32, 6, 43), + $this->duplicateDirective('directive', 8, 23, 8, 34), + $this->duplicateDirective('directive', 9, 30, 9, 41), + $this->duplicateDirective('directive', 11, 31, 11, 42), + $this->duplicateDirective('directive', 12, 38, 12, 49), + $this->duplicateDirective('directive', 14, 23, 14, 34), + $this->duplicateDirective('directive', 15, 30, 15, 41), + $this->duplicateDirective('directive', 17, 23, 17, 34), + $this->duplicateDirective('directive', 18, 30, 18, 41), + ] + ); + } } diff --git a/tests/Validator/ValidatorTestCase.php b/tests/Validator/ValidatorTestCase.php index e315d9190..644cf1842 100644 --- a/tests/Validator/ValidatorTestCase.php +++ b/tests/Validator/ValidatorTestCase.php @@ -382,50 +382,6 @@ public static function getTestSchema() 'name' => 'onInlineFragment', 'locations' => ['INLINE_FRAGMENT'], ]), - new Directive([ - 'name' => 'onSchema', - 'locations' => ['SCHEMA'], - ]), - new Directive([ - 'name' => 'onScalar', - 'locations' => ['SCALAR'], - ]), - new Directive([ - 'name' => 'onObject', - 'locations' => ['OBJECT'], - ]), - new Directive([ - 'name' => 'onFieldDefinition', - 'locations' => ['FIELD_DEFINITION'], - ]), - new Directive([ - 'name' => 'onArgumentDefinition', - 'locations' => ['ARGUMENT_DEFINITION'], - ]), - new Directive([ - 'name' => 'onInterface', - 'locations' => ['INTERFACE'], - ]), - new Directive([ - 'name' => 'onUnion', - 'locations' => ['UNION'], - ]), - new Directive([ - 'name' => 'onEnum', - 'locations' => ['ENUM'], - ]), - new Directive([ - 'name' => 'onEnumValue', - 'locations' => ['ENUM_VALUE'], - ]), - new Directive([ - 'name' => 'onInputObject', - 'locations' => ['INPUT_OBJECT'], - ]), - new Directive([ - 'name' => 'onInputFieldDefinition', - 'locations' => ['INPUT_FIELD_DEFINITION'], - ]), ], ]); } @@ -464,4 +420,13 @@ protected function expectFailsCompleteValidation($queryString, $errors) : void { $this->expectInvalid(self::getTestSchema(), DocumentValidator::allRules(), $queryString, $errors); } + + protected function expectSDLErrorsFromRule($rule, $sdlString, ?Schema $schema = null, $errors = []) + { + $actualErrors = DocumentValidator::validateSDL(Parser::parse($sdlString), $schema, [$rule]); + self::assertEquals( + $errors, + array_map(['GraphQL\Error\Error', 'formatError'], $actualErrors) + ); + } } From 75cb97309bc7cc98f3a2a0fbf5366fcfef16b9c5 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Sat, 7 Sep 2019 17:38:15 +0700 Subject: [PATCH 099/256] Allow directives on variable definitions --- src/Language/AST/VariableDefinitionNode.php | 3 + src/Language/DirectiveLocation.php | 1 + src/Language/Parser.php | 1 + src/Language/Printer.php | 6 +- src/Language/Visitor.php | 2 +- src/Type/Introspection.php | 4 + src/Validator/Rules/KnownDirectives.php | 3 + tests/Language/ParserTest.php | 9 +++ tests/Language/PrinterTest.php | 9 +++ tests/Language/kitchen-sink-noloc.ast | 9 ++- tests/Language/kitchen-sink.ast | 9 ++- tests/Type/IntrospectionTest.php | 84 +++++++++++++++++++++ tests/Utils/SchemaPrinterTest.php | 6 ++ tests/Validator/KnownDirectivesTest.php | 11 +-- tests/Validator/ValidatorTestCase.php | 4 + 15 files changed, 148 insertions(+), 13 deletions(-) diff --git a/src/Language/AST/VariableDefinitionNode.php b/src/Language/AST/VariableDefinitionNode.php index f46d5b667..26c067f31 100644 --- a/src/Language/AST/VariableDefinitionNode.php +++ b/src/Language/AST/VariableDefinitionNode.php @@ -17,4 +17,7 @@ class VariableDefinitionNode extends Node implements DefinitionNode /** @var VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode|null */ public $defaultValue; + + /** @var DirectiveNode[] */ + public $directives; } diff --git a/src/Language/DirectiveLocation.php b/src/Language/DirectiveLocation.php index 5f22537c7..f7ee72df5 100644 --- a/src/Language/DirectiveLocation.php +++ b/src/Language/DirectiveLocation.php @@ -17,6 +17,7 @@ class DirectiveLocation const FRAGMENT_DEFINITION = 'FRAGMENT_DEFINITION'; const FRAGMENT_SPREAD = 'FRAGMENT_SPREAD'; const INLINE_FRAGMENT = 'INLINE_FRAGMENT'; + const VARIABLE_DEFINITION = 'VARIABLE_DEFINITION'; // Type System Definitions const SCHEMA = 'SCHEMA'; diff --git a/src/Language/Parser.php b/src/Language/Parser.php index 8946d4817..858965205 100644 --- a/src/Language/Parser.php +++ b/src/Language/Parser.php @@ -618,6 +618,7 @@ private function parseVariableDefinition() 'type' => $type, 'defaultValue' => ($this->skip(Token::EQUALS) ? $this->parseValueLiteral(true) : null), + 'directives' => $this->parseDirectives(true), 'loc' => $this->loc($start), ]); } diff --git a/src/Language/Printer.php b/src/Language/Printer.php index 1a89400c9..e54eee601 100644 --- a/src/Language/Printer.php +++ b/src/Language/Printer.php @@ -126,7 +126,11 @@ public function printAST($ast) }, NodeKind::VARIABLE_DEFINITION => function (VariableDefinitionNode $node) { - return $node->variable . ': ' . $node->type . $this->wrap(' = ', $node->defaultValue); + return $node->variable + . ': ' + . $node->type + . $this->wrap(' = ', $node->defaultValue) + . $this->wrap(' ', $this->join($node->directives, ' ')); }, NodeKind::SELECTION_SET => function (SelectionSetNode $node) { diff --git a/src/Language/Visitor.php b/src/Language/Visitor.php index 43c59edb8..2faeb47d3 100644 --- a/src/Language/Visitor.php +++ b/src/Language/Visitor.php @@ -116,7 +116,7 @@ class Visitor NodeKind::NAME => [], NodeKind::DOCUMENT => ['definitions'], NodeKind::OPERATION_DEFINITION => ['name', 'variableDefinitions', 'directives', 'selectionSet'], - NodeKind::VARIABLE_DEFINITION => ['variable', 'type', 'defaultValue'], + NodeKind::VARIABLE_DEFINITION => ['variable', 'type', 'defaultValue', 'directives'], NodeKind::VARIABLE => ['name'], NodeKind::SELECTION_SET => ['selections'], NodeKind::FIELD => ['alias', 'name', 'arguments', 'directives', 'selectionSet'], diff --git a/src/Type/Introspection.php b/src/Type/Introspection.php index 1f4b11b0c..a15900609 100644 --- a/src/Type/Introspection.php +++ b/src/Type/Introspection.php @@ -676,6 +676,10 @@ public static function _directiveLocation() 'value' => DirectiveLocation::INLINE_FRAGMENT, 'description' => 'Location adjacent to an inline fragment.', ], + 'VARIABLE_DEFINITION' => [ + 'value' => DirectiveLocation::VARIABLE_DEFINITION, + 'description' => 'Location adjacent to a variable definition.', + ], 'SCHEMA' => [ 'value' => DirectiveLocation::SCHEMA, 'description' => 'Location adjacent to a schema definition.', diff --git a/src/Validator/Rules/KnownDirectives.php b/src/Validator/Rules/KnownDirectives.php index 8df9b2a52..43d14a668 100644 --- a/src/Validator/Rules/KnownDirectives.php +++ b/src/Validator/Rules/KnownDirectives.php @@ -32,6 +32,7 @@ use GraphQL\Language\AST\SchemaTypeExtensionNode; use GraphQL\Language\AST\UnionTypeDefinitionNode; use GraphQL\Language\AST\UnionTypeExtensionNode; +use GraphQL\Language\AST\VariableDefinitionNode; use GraphQL\Language\DirectiveLocation; use GraphQL\Type\Definition\Directive; use GraphQL\Validator\ASTValidationContext; @@ -151,6 +152,8 @@ private function getDirectiveLocationForASTPath(array $ancestors) return DirectiveLocation::INLINE_FRAGMENT; case $appliedTo instanceof FragmentDefinitionNode: return DirectiveLocation::FRAGMENT_DEFINITION; + case $appliedTo instanceof VariableDefinitionNode: + return DirectiveLocation::VARIABLE_DEFINITION; case $appliedTo instanceof SchemaDefinitionNode: case $appliedTo instanceof SchemaTypeExtensionNode: return DirectiveLocation::SCHEMA; diff --git a/tests/Language/ParserTest.php b/tests/Language/ParserTest.php index f465f6155..c71ceef69 100644 --- a/tests/Language/ParserTest.php +++ b/tests/Language/ParserTest.php @@ -140,6 +140,15 @@ public function testParsesConstantDefaultValues() : void ); } + /** + * @see it('parses variable definition directives') + */ + public function testParsesVariableDefinitionDirectives() + { + $this->expectNotToPerformAssertions(); + Parser::parse('query Foo($x: Boolean = false @bar) { field }'); + } + private function expectSyntaxError($text, $message, $location) { $this->expectException(SyntaxError::class); diff --git a/tests/Language/PrinterTest.php b/tests/Language/PrinterTest.php index 91e851423..d386af9f0 100644 --- a/tests/Language/PrinterTest.php +++ b/tests/Language/PrinterTest.php @@ -82,6 +82,15 @@ public function testCorrectlyPrintsOpsWithoutName() : void '; self::assertEquals($expected, Printer::doPrint($queryAstWithArtifacts)); + $queryAstWithVariableDirective = Parser::parse( + 'query ($foo: TestType = {a: 123} @testDirective(if: true) @test) { id }' + ); + $expected = 'query ($foo: TestType = {a: 123} @testDirective(if: true) @test) { + id +} +'; + self::assertEquals($expected, Printer::doPrint($queryAstWithVariableDirective)); + $mutationAstWithArtifacts = Parser::parse( 'mutation ($foo: TestType) @testDirective { id, name }' ); diff --git a/tests/Language/kitchen-sink-noloc.ast b/tests/Language/kitchen-sink-noloc.ast index d1427c12b..980a95d24 100644 --- a/tests/Language/kitchen-sink-noloc.ast +++ b/tests/Language/kitchen-sink-noloc.ast @@ -24,7 +24,8 @@ "kind": "Name", "value": "ComplexType" } - } + }, + "directives": [] }, { "kind": "VariableDefinition", @@ -45,7 +46,8 @@ "defaultValue": { "kind": "EnumValue", "value": "MOBILE" - } + }, + "directives": [] } ], "directives": [], @@ -392,7 +394,8 @@ "kind": "Name", "value": "StoryLikeSubscribeInput" } - } + }, + "directives": [] } ], "directives": [], diff --git a/tests/Language/kitchen-sink.ast b/tests/Language/kitchen-sink.ast index 606b03c25..ffc35480e 100644 --- a/tests/Language/kitchen-sink.ast +++ b/tests/Language/kitchen-sink.ast @@ -56,7 +56,8 @@ }, "value": "ComplexType" } - } + }, + "directives": [] }, { "kind": "VariableDefinition", @@ -101,7 +102,8 @@ "end": 343 }, "value": "MOBILE" - } + }, + "directives": [] } ], "directives": [], @@ -772,7 +774,8 @@ }, "value": "StoryLikeSubscribeInput" } - } + }, + "directives": [] } ], "directives": [], diff --git a/tests/Type/IntrospectionTest.php b/tests/Type/IntrospectionTest.php index 742fe6c8d..fa95feb66 100644 --- a/tests/Type/IntrospectionTest.php +++ b/tests/Type/IntrospectionTest.php @@ -902,6 +902,67 @@ public function testExecutesAnIntrospectionQuery() : void 'isDeprecated' => false, 'deprecationReason' => null, ], + 7 => + [ + 'name' => 'VARIABLE_DEFINITION', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + [ + 'name' => 'SCHEMA', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + [ + 'name' => 'SCALAR', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + [ + 'name' => 'OBJECT', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + [ + 'name' => 'FIELD_DEFINITION', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + [ + 'name' => 'ARGUMENT_DEFINITION', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + [ + 'name' => 'INTERFACE', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + [ + 'name' => 'UNION', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + [ + 'name' => 'ENUM', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + [ + 'name' => 'ENUM_VALUE', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + [ + 'name' => 'INPUT_OBJECT', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + [ + 'name' => 'INPUT_FIELD_DEFINITION', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], ], 'possibleTypes' => null, ], @@ -964,6 +1025,29 @@ public function testExecutesAnIntrospectionQuery() : void ], ], ], + 2 => + [ + 'name' => 'deprecated', + 'locations' => + [ + 0 => 'FIELD_DEFINITION', + 1 => 'ENUM_VALUE', + ], + 'args' => + [ + 0 => + [ + 'defaultValue' => '"No longer supported"', + 'name' => 'reason', + 'type' => + [ + 'kind' => 'SCALAR', + 'name' => 'String', + 'ofType' => null, + ], + ], + ], + ], ], ], ], diff --git a/tests/Utils/SchemaPrinterTest.php b/tests/Utils/SchemaPrinterTest.php index d24ac43c2..74072ff50 100644 --- a/tests/Utils/SchemaPrinterTest.php +++ b/tests/Utils/SchemaPrinterTest.php @@ -858,6 +858,9 @@ enum __DirectiveLocation { """Location adjacent to an inline fragment.""" INLINE_FRAGMENT + """Location adjacent to a variable definition.""" + VARIABLE_DEFINITION + """Location adjacent to a schema definition.""" SCHEMA @@ -1093,6 +1096,9 @@ enum __DirectiveLocation { # Location adjacent to an inline fragment. INLINE_FRAGMENT + # Location adjacent to a variable definition. + VARIABLE_DEFINITION + # Location adjacent to a schema definition. SCHEMA diff --git a/tests/Validator/KnownDirectivesTest.php b/tests/Validator/KnownDirectivesTest.php index efbc29a01..87c05d7b6 100644 --- a/tests/Validator/KnownDirectivesTest.php +++ b/tests/Validator/KnownDirectivesTest.php @@ -141,8 +141,8 @@ public function testWithWellPlacedDirectives() : void $this->expectPassesRule( new KnownDirectives(), ' - query Foo @onQuery { - name @include(if: true) + query Foo($var: Boolean @onVariableDefinition) @onQuery { + name @include(if: $var) ...Frag @include(if: true) skippedField @skip(if: true) ...SkippedFrag @skip(if: true) @@ -271,8 +271,8 @@ public function testWithMisplacedDirectives() : void $this->expectFailsRule( new KnownDirectives(), ' - query Foo @include(if: true) { - name @onQuery + query Foo($var: Boolean @onField) @include(if: true) { + name @onQuery @include(if: $var) ...Frag @onQuery } @@ -281,7 +281,8 @@ public function testWithMisplacedDirectives() : void } ', [ - $this->misplacedDirective('include', 'QUERY', 2, 17), + $this->misplacedDirective('onField', 'VARIABLE_DEFINITION', 2, 31), + $this->misplacedDirective('include', 'QUERY', 2, 41), $this->misplacedDirective('onQuery', 'FIELD', 3, 14), $this->misplacedDirective('onQuery', 'FRAGMENT_SPREAD', 4, 17), $this->misplacedDirective('onQuery', 'MUTATION', 7, 20), diff --git a/tests/Validator/ValidatorTestCase.php b/tests/Validator/ValidatorTestCase.php index 644cf1842..cb46822c7 100644 --- a/tests/Validator/ValidatorTestCase.php +++ b/tests/Validator/ValidatorTestCase.php @@ -382,6 +382,10 @@ public static function getTestSchema() 'name' => 'onInlineFragment', 'locations' => ['INLINE_FRAGMENT'], ]), + new Directive([ + 'name' => 'onVariableDefinition', + 'locations' => ['VARIABLE_DEFINITION'], + ]), ], ]); } From 7f8e9fe620da3ce51311d67792e1df201abb03d7 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Sat, 7 Sep 2019 17:41:09 +0700 Subject: [PATCH 100/256] Reuse 'many' for parsing document --- src/Language/Parser.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Language/Parser.php b/src/Language/Parser.php index 858965205..c37c4133d 100644 --- a/src/Language/Parser.php +++ b/src/Language/Parser.php @@ -452,15 +452,15 @@ private function parseName() private function parseDocument() { $start = $this->lexer->token; - $this->expect(Token::SOF); - - $definitions = []; - do { - $definitions[] = $this->parseDefinition(); - } while (! $this->skip(Token::EOF)); return new DocumentNode([ - 'definitions' => new NodeList($definitions), + 'definitions' => $this->many( + Token::SOF, + function () { + return $this->parseDefinition(); + }, + Token::EOF + ), 'loc' => $this->loc($start), ]); } From 12ee90d9c43f93bad0e395e85665700ff6e684ac Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Sat, 7 Sep 2019 18:00:24 +0700 Subject: [PATCH 101/256] Refactored tests for variable definition directives --- tests/Language/PrinterTest.php | 32 +++++++++++++++++---- tests/Validator/KnownDirectivesTest.php | 38 ++++++++++++++++++++++--- tests/Validator/ValidatorTestCase.php | 16 +++++------ 3 files changed, 69 insertions(+), 17 deletions(-) diff --git a/tests/Language/PrinterTest.php b/tests/Language/PrinterTest.php index d386af9f0..5f6f74a2a 100644 --- a/tests/Language/PrinterTest.php +++ b/tests/Language/PrinterTest.php @@ -82,6 +82,22 @@ public function testCorrectlyPrintsOpsWithoutName() : void '; self::assertEquals($expected, Printer::doPrint($queryAstWithArtifacts)); + $mutationAstWithArtifacts = Parser::parse( + 'mutation ($foo: TestType) @testDirective { id, name }' + ); + $expected = 'mutation ($foo: TestType) @testDirective { + id + name +} +'; + self::assertEquals($expected, Printer::doPrint($mutationAstWithArtifacts)); + } + + /** + * @see it('prints query with variable directives') + */ + public function testPrintsQueryWithVariableDirectives() + { $queryAstWithVariableDirective = Parser::parse( 'query ($foo: TestType = {a: 123} @testDirective(if: true) @test) { id }' ); @@ -90,16 +106,22 @@ public function testCorrectlyPrintsOpsWithoutName() : void } '; self::assertEquals($expected, Printer::doPrint($queryAstWithVariableDirective)); + } - $mutationAstWithArtifacts = Parser::parse( - 'mutation ($foo: TestType) @testDirective { id, name }' + /** + * @see it('prints fragment with variable directives') + */ + public function testPrintsFragmentWithVariableDirectives() + { + $queryAstWithVariableDirective = Parser::parse( + 'fragment Foo($foo: TestType @test) on TestType @testDirective { id }', + ['experimentalFragmentVariables' => true] ); - $expected = 'mutation ($foo: TestType) @testDirective { + $expected = 'fragment Foo($foo: TestType @test) on TestType @testDirective { id - name } '; - self::assertEquals($expected, Printer::doPrint($mutationAstWithArtifacts)); + self::assertEquals($expected, Printer::doPrint($queryAstWithVariableDirective)); } /** diff --git a/tests/Validator/KnownDirectivesTest.php b/tests/Validator/KnownDirectivesTest.php index 87c05d7b6..ffcec503b 100644 --- a/tests/Validator/KnownDirectivesTest.php +++ b/tests/Validator/KnownDirectivesTest.php @@ -141,7 +141,7 @@ public function testWithWellPlacedDirectives() : void $this->expectPassesRule( new KnownDirectives(), ' - query Foo($var: Boolean @onVariableDefinition) @onQuery { + query Foo($var: Boolean) @onQuery { name @include(if: $var) ...Frag @include(if: true) skippedField @skip(if: true) @@ -155,6 +155,21 @@ public function testWithWellPlacedDirectives() : void ); } + /** + * @see it('with well placed variable definition directive') + */ + public function testWithWellPlacedVariableDefinitionDirective() + { + $this->expectPassesRule( + new KnownDirectives(), + ' + query Foo($var: Boolean @onVariableDefinition) { + name + } + ' + ); + } + // DESCRIBE: within SDL /** @@ -271,7 +286,7 @@ public function testWithMisplacedDirectives() : void $this->expectFailsRule( new KnownDirectives(), ' - query Foo($var: Boolean @onField) @include(if: true) { + query Foo($var: Boolean) @include(if: true) { name @onQuery @include(if: $var) ...Frag @onQuery } @@ -281,8 +296,7 @@ public function testWithMisplacedDirectives() : void } ', [ - $this->misplacedDirective('onField', 'VARIABLE_DEFINITION', 2, 31), - $this->misplacedDirective('include', 'QUERY', 2, 41), + $this->misplacedDirective('include', 'QUERY', 2, 32), $this->misplacedDirective('onQuery', 'FIELD', 3, 14), $this->misplacedDirective('onQuery', 'FRAGMENT_SPREAD', 4, 17), $this->misplacedDirective('onQuery', 'MUTATION', 7, 20), @@ -290,6 +304,22 @@ public function testWithMisplacedDirectives() : void ); } + /** + * @see it('with misplaced variable definition directive') + */ + public function testWithMisplacedVariableDefinitionDirective() + { + $this->expectFailsRule( + new KnownDirectives(), + ' + query Foo($var: Boolean @onField) { + name + } + ', + [$this->misplacedDirective('onField', 'VARIABLE_DEFINITION', 2, 39)] + ); + } + private function misplacedDirective($directiveName, $placement, $line, $column) { return FormattedError::create( diff --git a/tests/Validator/ValidatorTestCase.php b/tests/Validator/ValidatorTestCase.php index cb46822c7..641039835 100644 --- a/tests/Validator/ValidatorTestCase.php +++ b/tests/Validator/ValidatorTestCase.php @@ -21,16 +21,16 @@ abstract class ValidatorTestCase extends TestCase { - protected function expectPassesRule($rule, $queryString) : void + protected function expectPassesRule($rule, $queryString, $options = []) : void { - $this->expectValid(self::getTestSchema(), [$rule], $queryString); + $this->expectValid(self::getTestSchema(), [$rule], $queryString, $options); } - protected function expectValid($schema, $rules, $queryString) : void + protected function expectValid($schema, $rules, $queryString, $options = []) : void { self::assertEquals( [], - DocumentValidator::validate($schema, Parser::parse($queryString), $rules), + DocumentValidator::validate($schema, Parser::parse($queryString, $options), $rules), 'Should validate' ); } @@ -390,14 +390,14 @@ public static function getTestSchema() ]); } - protected function expectFailsRule($rule, $queryString, $errors) + protected function expectFailsRule($rule, $queryString, $errors, $options = []) { - return $this->expectInvalid(self::getTestSchema(), [$rule], $queryString, $errors); + return $this->expectInvalid(self::getTestSchema(), [$rule], $queryString, $errors, $options); } - protected function expectInvalid($schema, $rules, $queryString, $expectedErrors) + protected function expectInvalid($schema, $rules, $queryString, $expectedErrors, $options = []) { - $errors = DocumentValidator::validate($schema, Parser::parse($queryString), $rules); + $errors = DocumentValidator::validate($schema, Parser::parse($queryString, $options), $rules); self::assertNotEmpty($errors, 'GraphQL should not validate'); self::assertEquals($expectedErrors, array_map(['GraphQL\Error\Error', 'formatError'], $errors)); From 785961ee1d4b80c5b3e71172f84ad7cdeed88700 Mon Sep 17 00:00:00 2001 From: spawnia Date: Wed, 11 Sep 2019 19:20:31 +0200 Subject: [PATCH 102/256] Ensure fieldDefinition is set --- tests/Type/ResolveInfoTest.php | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/Type/ResolveInfoTest.php b/tests/Type/ResolveInfoTest.php index d05a44c35..c7b802261 100644 --- a/tests/Type/ResolveInfoTest.php +++ b/tests/Type/ResolveInfoTest.php @@ -5,6 +5,7 @@ namespace GraphQL\Tests\Type; use GraphQL\GraphQL; +use GraphQL\Type\Definition\FieldDefinition; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ResolveInfo; use GraphQL\Type\Definition\Type; @@ -378,4 +379,32 @@ public function testMergedFragmentsFieldSelection() : void self::assertEquals(['data' => ['article' => null]], $result); self::assertEquals($expectedDeepSelection, $actualDeepSelection); } + + public function testFieldDefinition() : void + { + $query = ' + query Ping { + ping + } + '; + + $pingPongQuery = new ObjectType([ + 'name' => 'Query', + 'fields' => [ + 'ping' => [ + 'type' => Type::string(), + 'resolve' => static function ($value, $args, $context, ResolveInfo $info) : string { + self::assertInstanceOf(FieldDefinition::class, $info->fieldDefinition); + + return 'pong'; + }, + ], + ], + ]); + + $schema = new Schema(['query' => $pingPongQuery]); + $result = GraphQL::executeQuery($schema, $query)->toArray(); + + self::assertEquals(['data' => ['ping' => 'pong']], $result); + } } From 2f0c8f4f68cf2b1d6b55bff0a2d3ea5180b93305 Mon Sep 17 00:00:00 2001 From: spawnia Date: Wed, 11 Sep 2019 19:22:55 +0200 Subject: [PATCH 103/256] Move test --- tests/Executor/ExecutorTest.php | 2 ++ tests/Type/ResolveInfoTest.php | 28 ---------------------------- 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/tests/Executor/ExecutorTest.php b/tests/Executor/ExecutorTest.php index eb8e4a12e..77ef6e938 100644 --- a/tests/Executor/ExecutorTest.php +++ b/tests/Executor/ExecutorTest.php @@ -12,6 +12,7 @@ use GraphQL\Tests\Executor\TestClasses\NotSpecial; use GraphQL\Tests\Executor\TestClasses\Special; use GraphQL\Type\Definition\EnumType; +use GraphQL\Type\Definition\FieldDefinition; use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\ObjectType; @@ -295,6 +296,7 @@ public function testProvidesInfoAboutCurrentExecutionState() : void self::assertSame($rootValue, $info->rootValue); self::assertEquals($ast->definitions[0], $info->operation); self::assertEquals(['var' => '123'], $info->variableValues); + self::assertInstanceOf(FieldDefinition::class, $info->fieldDefinition); } /** diff --git a/tests/Type/ResolveInfoTest.php b/tests/Type/ResolveInfoTest.php index c7b802261..8b7e93065 100644 --- a/tests/Type/ResolveInfoTest.php +++ b/tests/Type/ResolveInfoTest.php @@ -379,32 +379,4 @@ public function testMergedFragmentsFieldSelection() : void self::assertEquals(['data' => ['article' => null]], $result); self::assertEquals($expectedDeepSelection, $actualDeepSelection); } - - public function testFieldDefinition() : void - { - $query = ' - query Ping { - ping - } - '; - - $pingPongQuery = new ObjectType([ - 'name' => 'Query', - 'fields' => [ - 'ping' => [ - 'type' => Type::string(), - 'resolve' => static function ($value, $args, $context, ResolveInfo $info) : string { - self::assertInstanceOf(FieldDefinition::class, $info->fieldDefinition); - - return 'pong'; - }, - ], - ], - ]); - - $schema = new Schema(['query' => $pingPongQuery]); - $result = GraphQL::executeQuery($schema, $query)->toArray(); - - self::assertEquals(['data' => ['ping' => 'pong']], $result); - } } From 16365d0997903a3f92872be6482fb82ed8858d3f Mon Sep 17 00:00:00 2001 From: Romain VALAT Date: Thu, 12 Sep 2019 08:23:04 +0200 Subject: [PATCH 104/256] fix coding standard violation --- src/Validator/Rules/QueryComplexity.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Validator/Rules/QueryComplexity.php b/src/Validator/Rules/QueryComplexity.php index 5b04d29a5..d269b7b35 100644 --- a/src/Validator/Rules/QueryComplexity.php +++ b/src/Validator/Rules/QueryComplexity.php @@ -206,7 +206,7 @@ static function ($error) { return ! $directiveArgsIf; } - if (Directive::SKIP_NAME === $directiveNode->name->value) { + if ($directiveNode->name->value === Directive::SKIP_NAME) { $directive = Directive::skipDirective(); /** @var bool $directiveArgsIf */ $directiveArgsIf = Values::getArgumentValues($directive, $directiveNode, $variableValues)['if']; From 1e4e593569fda86555620ad8cbd60696f1b78c46 Mon Sep 17 00:00:00 2001 From: Romain VALAT <37904498+sukano@users.noreply.github.com> Date: Sun, 15 Sep 2019 08:52:19 +0200 Subject: [PATCH 105/256] add return type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Šimon Podlipský --- tests/Validator/QuerySecuritySchema.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Validator/QuerySecuritySchema.php b/tests/Validator/QuerySecuritySchema.php index 898ae3e92..58ba2692e 100644 --- a/tests/Validator/QuerySecuritySchema.php +++ b/tests/Validator/QuerySecuritySchema.php @@ -120,7 +120,7 @@ public static function buildDogType() return self::$dogType; } - public static function buildFooDirective() + public static function buildFooDirective() : Directive { if (self::$fooDirective !== null) { return self::$fooDirective; From e0dc6c6acdb26bf7e98f2cc9a08486c5f059b196 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1chym=20Tou=C5=A1ek?= Date: Thu, 3 Oct 2019 23:56:31 +0200 Subject: [PATCH 106/256] Remove dead code --- src/Deferred.php | 2 -- src/Error/Error.php | 2 +- src/Error/FormattedError.php | 4 ++-- src/Executor/Promise/Adapter/SyncPromise.php | 6 +----- .../Promise/Adapter/SyncPromiseAdapter.php | 2 -- src/Executor/ReferenceExecutor.php | 8 -------- src/Type/Definition/Type.php | 2 -- src/Utils/AST.php | 7 ------- src/Utils/Value.php | 10 ---------- src/Validator/DocumentValidator.php | 4 ++-- src/Validator/Rules/ValuesOfCorrectType.php | 14 -------------- 11 files changed, 6 insertions(+), 55 deletions(-) diff --git a/src/Deferred.php b/src/Deferred.php index 6b7bd7824..e50338c4f 100644 --- a/src/Deferred.php +++ b/src/Deferred.php @@ -56,8 +56,6 @@ public function run() : void try { $cb = $this->callback; $this->promise->resolve($cb()); - } catch (Exception $e) { - $this->promise->reject($e); } catch (Throwable $e) { $this->promise->reject($e); } diff --git a/src/Error/Error.php b/src/Error/Error.php index 2e78221d7..2459e90d9 100644 --- a/src/Error/Error.php +++ b/src/Error/Error.php @@ -164,7 +164,7 @@ public static function createLocatedError($error, $nodes = null, $path = null) $source = $error->source; $positions = $error->positions; $extensions = $error->extensions; - } elseif ($error instanceof Exception || $error instanceof Throwable) { + } elseif ($error instanceof Throwable) { $message = $error->getMessage(); $originalError = $error; } else { diff --git a/src/Error/FormattedError.php b/src/Error/FormattedError.php index 799ee7492..c532e0db5 100644 --- a/src/Error/FormattedError.php +++ b/src/Error/FormattedError.php @@ -177,7 +177,7 @@ private static function lpad($len, $str) public static function createFromException($e, $debug = false, $internalErrorMessage = null) { Utils::invariant( - $e instanceof Exception || $e instanceof Throwable, + $e instanceof Throwable, 'Expected exception, got %s', Utils::getVariableType($e) ); @@ -244,7 +244,7 @@ public static function addDebugEntries(array $formattedError, $e, $debug) } Utils::invariant( - $e instanceof Exception || $e instanceof Throwable, + $e instanceof Throwable, 'Expected exception, got %s', Utils::getVariableType($e) ); diff --git a/src/Executor/Promise/Adapter/SyncPromise.php b/src/Executor/Promise/Adapter/SyncPromise.php index 8d490ed4a..fba2035a2 100644 --- a/src/Executor/Promise/Adapter/SyncPromise.php +++ b/src/Executor/Promise/Adapter/SyncPromise.php @@ -85,7 +85,7 @@ function ($reason) { public function reject($reason) : self { - if (! $reason instanceof Exception && ! $reason instanceof Throwable) { + if (! $reason instanceof Throwable) { throw new Exception('SyncPromise::reject() has to be called with an instance of \Throwable'); } @@ -122,8 +122,6 @@ private function enqueueWaitingPromises() : void if ($this->state === self::FULFILLED) { try { $promise->resolve($onFulfilled === null ? $this->result : $onFulfilled($this->result)); - } catch (Exception $e) { - $promise->reject($e); } catch (Throwable $e) { $promise->reject($e); } @@ -134,8 +132,6 @@ private function enqueueWaitingPromises() : void } else { $promise->resolve($onRejected($this->result)); } - } catch (Exception $e) { - $promise->reject($e); } catch (Throwable $e) { $promise->reject($e); } diff --git a/src/Executor/Promise/Adapter/SyncPromiseAdapter.php b/src/Executor/Promise/Adapter/SyncPromiseAdapter.php index e5900a9d4..5d4348aec 100644 --- a/src/Executor/Promise/Adapter/SyncPromiseAdapter.php +++ b/src/Executor/Promise/Adapter/SyncPromiseAdapter.php @@ -69,8 +69,6 @@ public function create(callable $resolver) 'reject', ] ); - } catch (Exception $e) { - $promise->reject($e); } catch (Throwable $e) { $promise->reject($e); } diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index b179138a6..1c9c3b0a3 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -626,8 +626,6 @@ private function resolveFieldValueOrError($fieldDef, $fieldNode, $resolveFn, $ro $contextValue = $this->exeContext->contextValue; return $resolveFn($rootValue, $args, $contextValue, $info); - } catch (Exception $error) { - return $error; } catch (Throwable $error) { return $error; } @@ -917,12 +915,6 @@ private function completeLeafValue(LeafType $returnType, &$result) { try { return $returnType->serialize($result); - } catch (Exception $error) { - throw new InvariantViolation( - 'Expected a value of type "' . Utils::printSafe($returnType) . '" but received: ' . Utils::printSafe($result), - 0, - $error - ); } catch (Throwable $error) { throw new InvariantViolation( 'Expected a value of type "' . Utils::printSafe($returnType) . '" but received: ' . Utils::printSafe($result), diff --git a/src/Type/Definition/Type.php b/src/Type/Definition/Type.php index eac87a77f..e0b87629e 100644 --- a/src/Type/Definition/Type.php +++ b/src/Type/Definition/Type.php @@ -379,8 +379,6 @@ public function __toString() { try { return $this->toString(); - } catch (Exception $e) { - echo $e; } catch (Throwable $e) { echo $e; } diff --git a/src/Utils/AST.php b/src/Utils/AST.php index 2626c16c7..bd77d1fba 100644 --- a/src/Utils/AST.php +++ b/src/Utils/AST.php @@ -243,11 +243,6 @@ public static function astFromValue($value, InputType $type) // to an externally represented value before converting into an AST. try { $serialized = $type->serialize($value); - } catch (Exception $error) { - if ($error instanceof Error && $type instanceof EnumType) { - return null; - } - throw $error; } catch (Throwable $error) { if ($error instanceof Error && $type instanceof EnumType) { return null; @@ -464,8 +459,6 @@ static function ($field) { // no value is returned. try { return $type->parseLiteral($valueNode, $variables); - } catch (Exception $error) { - return $undefined; } catch (Throwable $error) { return $undefined; } diff --git a/src/Utils/Value.php b/src/Utils/Value.php index 5f1a3616d..595172a25 100644 --- a/src/Utils/Value.php +++ b/src/Utils/Value.php @@ -66,16 +66,6 @@ public static function coerceValue($value, InputType $type, $blameNode = null, ? // the original error. try { return self::ofValue($type->parseValue($value)); - } catch (Exception $error) { - return self::ofErrors([ - self::coercionError( - sprintf('Expected type %s', $type->name), - $blameNode, - $path, - $error->getMessage(), - $error - ), - ]); } catch (Throwable $error) { return self::ofErrors([ self::coercionError( diff --git a/src/Validator/DocumentValidator.php b/src/Validator/DocumentValidator.php index bd06b1518..b74e9f728 100644 --- a/src/Validator/DocumentValidator.php +++ b/src/Validator/DocumentValidator.php @@ -267,10 +267,10 @@ public static function isError($value) ? count(array_filter( $value, static function ($item) { - return $item instanceof Exception || $item instanceof Throwable; + return $item instanceof Throwable; } )) === count($value) - : ($value instanceof Exception || $value instanceof Throwable); + : ($value instanceof Throwable); } public static function append(&$arr, $items) diff --git a/src/Validator/Rules/ValuesOfCorrectType.php b/src/Validator/Rules/ValuesOfCorrectType.php index d64ea7562..d16735fb6 100644 --- a/src/Validator/Rules/ValuesOfCorrectType.php +++ b/src/Validator/Rules/ValuesOfCorrectType.php @@ -214,20 +214,6 @@ private function isValidScalar(ValidationContext $context, ValueNode $node, $fie // may throw to indicate failure. try { $type->parseLiteral($node); - } catch (Exception $error) { - // Ensure a reference to the original error is maintained. - $context->reportError( - new Error( - self::getBadValueMessage( - (string) $locationType, - Printer::doPrint($node), - $error->getMessage(), - $context, - $fieldName - ), - $node - ) - ); } catch (Throwable $error) { // Ensure a reference to the original error is maintained. $context->reportError( From 96db5d2b7b390a532f475ce0ab8b3a152e904231 Mon Sep 17 00:00:00 2001 From: Jeremiah VALERIE Date: Thu, 10 Oct 2019 15:51:03 +0200 Subject: [PATCH 107/256] Add message level when using custom warning handler --- src/Error/Warning.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Error/Warning.php b/src/Error/Warning.php index 672894125..f376ed9cd 100644 --- a/src/Error/Warning.php +++ b/src/Error/Warning.php @@ -96,22 +96,26 @@ public static function enable($enable = true) : void public static function warnOnce(string $errorMessage, int $warningId, ?int $messageLevel = null) : void { + $messageLevel = $messageLevel ?: E_USER_WARNING; + if (self::$warningHandler !== null) { $fn = self::$warningHandler; - $fn($errorMessage, $warningId); + $fn($errorMessage, $warningId, $messageLevel); } elseif ((self::$enableWarnings & $warningId) > 0 && ! isset(self::$warned[$warningId])) { self::$warned[$warningId] = true; - trigger_error($errorMessage, $messageLevel ?: E_USER_WARNING); + trigger_error($errorMessage, $messageLevel); } } public static function warn(string $errorMessage, int $warningId, ?int $messageLevel = null) : void { + $messageLevel = $messageLevel ?: E_USER_WARNING; + if (self::$warningHandler !== null) { $fn = self::$warningHandler; - $fn($errorMessage, $warningId); + $fn($errorMessage, $warningId, $messageLevel); } elseif ((self::$enableWarnings & $warningId) > 0) { - trigger_error($errorMessage, $messageLevel ?: E_USER_WARNING); + trigger_error($errorMessage, $messageLevel); } } } From 98648202d5669c21ded77d01943b5ec5918de581 Mon Sep 17 00:00:00 2001 From: Yury Antonau Date: Sun, 13 Oct 2019 19:37:49 +0300 Subject: [PATCH 108/256] Grouping implementor fields for abstract types in QueryPlan (#513) * Added inline fragments support to ResolveInfo * Revert "Added inline fragments support to ResolveInfo" This reverts commit c35379cee88bc785c46e18331a27ad8da8ffb8f7. * Inline fragments and union type support for QueryPlan * Introduced $options to prevent BC * No more underscores for fragments data * Fixed Scrutinizer * Fixed Scrutinizer #2 * Fixed Scrutinizer #3 * Renamings; merging fields inside fragments * Fixed fields order for merged types * Grouping implementor fields for abstract types --- src/Type/Definition/QueryPlan.php | 77 ++++++-- src/Type/Definition/ResolveInfo.php | 8 +- tests/Type/QueryPlanTest.php | 266 ++++++++++++++++++++++++++++ 3 files changed, 331 insertions(+), 20 deletions(-) diff --git a/src/Type/Definition/QueryPlan.php b/src/Type/Definition/QueryPlan.php index 9cd0aec00..500bc9cb5 100644 --- a/src/Type/Definition/QueryPlan.php +++ b/src/Type/Definition/QueryPlan.php @@ -12,7 +12,9 @@ use GraphQL\Language\AST\InlineFragmentNode; use GraphQL\Language\AST\SelectionSetNode; use GraphQL\Type\Schema; +use function array_diff_key; use function array_filter; +use function array_intersect_key; use function array_key_exists; use function array_keys; use function array_merge; @@ -41,16 +43,21 @@ class QueryPlan /** @var FragmentDefinitionNode[] */ private $fragments; + /** @var bool */ + private $groupImplementorFields; + /** * @param FieldNode[] $fieldNodes * @param mixed[] $variableValues * @param FragmentDefinitionNode[] $fragments + * @param mixed[] $options */ - public function __construct(ObjectType $parentType, Schema $schema, iterable $fieldNodes, array $variableValues, array $fragments) + public function __construct(ObjectType $parentType, Schema $schema, iterable $fieldNodes, array $variableValues, array $fragments, array $options = []) { - $this->schema = $schema; - $this->variableValues = $variableValues; - $this->fragments = $fragments; + $this->schema = $schema; + $this->variableValues = $variableValues; + $this->fragments = $fragments; + $this->groupImplementorFields = in_array('group-implementor-fields', $options, true); $this->analyzeQueryPlan($parentType, $fieldNodes); } @@ -109,7 +116,8 @@ public function subFields(string $typename) : array */ private function analyzeQueryPlan(ObjectType $parentType, iterable $fieldNodes) : void { - $queryPlan = []; + $queryPlan = []; + $implementors = []; /** @var FieldNode $fieldNode */ foreach ($fieldNodes as $fieldNode) { if (! $fieldNode->selectionSet) { @@ -121,7 +129,7 @@ private function analyzeQueryPlan(ObjectType $parentType, iterable $fieldNodes) $type = $type->getWrappedType(); } - $subfields = $this->analyzeSelectionSet($fieldNode->selectionSet, $type); + $subfields = $this->analyzeSelectionSet($fieldNode->selectionSet, $type, $implementors); $this->types[$type->name] = array_unique(array_merge( array_key_exists($type->name, $this->types) ? $this->types[$type->name] : [], @@ -134,17 +142,26 @@ private function analyzeQueryPlan(ObjectType $parentType, iterable $fieldNodes) ); } - $this->queryPlan = $queryPlan; + if ($this->groupImplementorFields) { + $this->queryPlan = ['fields' => $queryPlan]; + + if ($implementors) { + $this->queryPlan['implementors'] = $implementors; + } + } else { + $this->queryPlan = $queryPlan; + } } /** * @param InterfaceType|ObjectType $parentType + * @param mixed[] $implementors * * @return mixed[] * * @throws Error */ - private function analyzeSelectionSet(SelectionSetNode $selectionSet, Type $parentType) : array + private function analyzeSelectionSet(SelectionSetNode $selectionSet, Type $parentType, array &$implementors = []) : array { $fields = []; foreach ($selectionSet->selections as $selectionNode) { @@ -169,20 +186,12 @@ private function analyzeSelectionSet(SelectionSetNode $selectionSet, Type $paren $fragment = $this->fragments[$spreadName]; $type = $this->schema->getType($fragment->typeCondition->name->value); $subfields = $this->analyzeSubFields($type, $fragment->selectionSet); - - $fields = $this->arrayMergeDeep( - $subfields, - $fields - ); + $fields = $this->mergeFields($parentType, $type, $fields, $subfields, $implementors); } } elseif ($selectionNode instanceof InlineFragmentNode) { $type = $this->schema->getType($selectionNode->typeCondition->name->value); $subfields = $this->analyzeSubFields($type, $selectionNode->selectionSet); - - $fields = $this->arrayMergeDeep( - $subfields, - $fields - ); + $fields = $this->mergeFields($parentType, $type, $fields, $subfields, $implementors); } } @@ -210,6 +219,38 @@ private function analyzeSubFields(Type $type, SelectionSetNode $selectionSet) : return $subfields; } + /** + * @param mixed[] $fields + * @param mixed[] $subfields + * @param mixed[] $implementors + * + * @return mixed[] + */ + private function mergeFields(Type $parentType, Type $type, array $fields, array $subfields, array &$implementors) : array + { + if ($this->groupImplementorFields && $parentType instanceof AbstractType && ! $type instanceof AbstractType) { + $implementors[$type->name] = [ + 'type' => $type, + 'fields' => $this->arrayMergeDeep( + $implementors[$type->name]['fields'] ?? [], + array_diff_key($subfields, $fields) + ), + ]; + + $fields = $this->arrayMergeDeep( + $fields, + array_intersect_key($subfields, $fields) + ); + } else { + $fields = $this->arrayMergeDeep( + $subfields, + $fields + ); + } + + return $fields; + } + /** * similar to array_merge_recursive this merges nested arrays, but handles non array values differently * while array_merge_recursive tries to merge non array values, in this implementation they will be overwritten diff --git a/src/Type/Definition/ResolveInfo.php b/src/Type/Definition/ResolveInfo.php index 4ca536ad3..7c73d537a 100644 --- a/src/Type/Definition/ResolveInfo.php +++ b/src/Type/Definition/ResolveInfo.php @@ -198,7 +198,10 @@ public function getFieldSelection($depth = 0) return $fields; } - public function lookAhead() : QueryPlan + /** + * @param mixed[] $options + */ + public function lookAhead(array $options = []) : QueryPlan { if ($this->queryPlan === null) { $this->queryPlan = new QueryPlan( @@ -206,7 +209,8 @@ public function lookAhead() : QueryPlan $this->schema, $this->fieldNodes, $this->variableValues, - $this->fragments + $this->fragments, + $options ); } diff --git a/tests/Type/QueryPlanTest.php b/tests/Type/QueryPlanTest.php index a0d05fd40..0e0e5abfa 100644 --- a/tests/Type/QueryPlanTest.php +++ b/tests/Type/QueryPlanTest.php @@ -11,6 +11,7 @@ use GraphQL\Type\Definition\QueryPlan; use GraphQL\Type\Definition\ResolveInfo; use GraphQL\Type\Definition\Type; +use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Schema; use PHPUnit\Framework\TestCase; @@ -698,4 +699,269 @@ public function testMergedFragmentsQueryPlan() : void self::assertTrue($queryPlan->hasType('Image')); self::assertFalse($queryPlan->hasType('Test')); } + + public function testQueryPlanOnInterfaceGroupingImplementorFields() : void + { + $car = null; + + $item = new InterfaceType([ + 'name' => 'Item', + 'fields' => [ + 'id' => Type::int(), + 'owner' => Type::string(), + ], + 'resolveType' => static function () use (&$car) { + return $car; + }, + ]); + + $car = new ObjectType([ + 'name' => 'Car', + 'fields' => [ + 'id' => Type::int(), + 'owner' => Type::string(), + 'mark' => Type::string(), + 'model' => Type::string(), + ], + 'interfaces' => [$item], + ]); + + $building = new ObjectType([ + 'name' => 'Building', + 'fields' => [ + 'id' => Type::int(), + 'owner' => Type::string(), + 'city' => Type::string(), + 'address' => Type::string(), + ], + 'interfaces' => [$item], + ]); + + $query = '{ + item { + id + owner + ... on Car { + mark + model + } + ... on Building { + city + } + ...BuildingFragment + } + } + fragment BuildingFragment on Building { + address + }'; + + $expectedResult = [ + 'data' => ['item' => null], + ]; + + $expectedQueryPlan = [ + 'fields' => [ + 'id' => [ + 'type' => Type::int(), + 'fields' => [], + 'args' => [], + ], + 'owner' => [ + 'type' => Type::string(), + 'fields' => [], + 'args' => [], + ], + ], + 'implementors' => [ + 'Car' => [ + 'type' => $car, + 'fields' => [ + 'mark' => [ + 'type' => Type::string(), + 'fields' => [], + 'args' => [], + ], + 'model' => [ + 'type' => Type::string(), + 'fields' => [], + 'args' => [], + ], + ], + ], + 'Building' => [ + 'type' => $building, + 'fields' => [ + 'city' => [ + 'type' => Type::string(), + 'fields' => [], + 'args' => [], + ], + 'address' => [ + 'type' => Type::string(), + 'fields' => [], + 'args' => [], + ], + ], + ], + ], + ]; + + $expectedReferencedTypes = ['Car', 'Building', 'Item']; + + $expectedReferencedFields = ['mark', 'model', 'city', 'address', 'id', 'owner']; + + $expectedItemSubFields = ['id', 'owner']; + $expectedBuildingSubFields = ['city', 'address']; + + $hasCalled = false; + /** @var QueryPlan $queryPlan */ + $queryPlan = null; + + $root = new ObjectType([ + 'name' => 'Query', + 'fields' => [ + 'item' => [ + 'type' => $item, + 'resolve' => static function ($value, $args, $context, ResolveInfo $info) use (&$hasCalled, &$queryPlan) { + $hasCalled = true; + $queryPlan = $info->lookAhead(['group-implementor-fields']); + + return null; + }, + ], + ], + ]); + + $schema = new Schema([ + 'query' => $root, + 'types' => [$car, $building], + ]); + $result = GraphQL::executeQuery($schema, $query)->toArray(); + + self::assertTrue($hasCalled); + self::assertEquals($expectedResult, $result); + self::assertEquals($expectedQueryPlan, $queryPlan->queryPlan()); + self::assertEquals($expectedReferencedTypes, $queryPlan->getReferencedTypes()); + self::assertEquals($expectedReferencedFields, $queryPlan->getReferencedFields()); + self::assertEquals($expectedItemSubFields, $queryPlan->subFields('Item')); + self::assertEquals($expectedBuildingSubFields, $queryPlan->subFields('Building')); + } + + public function testQueryPlanOnUnionGroupingImplementorFields() : void + { + $car = new ObjectType([ + 'name' => 'Car', + 'fields' => [ + 'mark' => Type::string(), + 'model' => Type::string(), + ], + ]); + + $building = new ObjectType([ + 'name' => 'Building', + 'fields' => [ + 'city' => Type::string(), + 'address' => Type::string(), + ], + ]); + + $item = new UnionType([ + 'name' => 'Item', + 'types' => [$car, $building], + 'resolveType' => static function () use ($car) { + return $car; + }, + ]); + + $query = '{ + item { + ... on Car { + mark + model + } + ... on Building { + city + } + ...BuildingFragment + } + } + fragment BuildingFragment on Building { + address + }'; + + $expectedResult = [ + 'data' => ['item' => null], + ]; + + $expectedQueryPlan = [ + 'fields' => [], + 'implementors' => [ + 'Car' => [ + 'type' => $car, + 'fields' => [ + 'mark' => [ + 'type' => Type::string(), + 'fields' => [], + 'args' => [], + ], + 'model' => [ + 'type' => Type::string(), + 'fields' => [], + 'args' => [], + ], + ], + ], + 'Building' => [ + 'type' => $building, + 'fields' => [ + 'city' => [ + 'type' => Type::string(), + 'fields' => [], + 'args' => [], + ], + 'address' => [ + 'type' => Type::string(), + 'fields' => [], + 'args' => [], + ], + ], + ], + ], + ]; + + $expectedReferencedTypes = ['Car', 'Building', 'Item']; + + $expectedReferencedFields = ['mark', 'model', 'city', 'address']; + + $expectedBuildingSubFields = ['city', 'address']; + + $hasCalled = false; + /** @var QueryPlan $queryPlan */ + $queryPlan = null; + + $root = new ObjectType([ + 'name' => 'Query', + 'fields' => [ + 'item' => [ + 'type' => $item, + 'resolve' => static function ($value, $args, $context, ResolveInfo $info) use (&$hasCalled, &$queryPlan) { + $hasCalled = true; + $queryPlan = $info->lookAhead(['group-implementor-fields']); + + return null; + }, + ], + ], + ]); + + $schema = new Schema(['query' => $root]); + $result = GraphQL::executeQuery($schema, $query)->toArray(); + + self::assertTrue($hasCalled); + self::assertEquals($expectedResult, $result); + self::assertEquals($expectedQueryPlan, $queryPlan->queryPlan()); + self::assertEquals($expectedReferencedTypes, $queryPlan->getReferencedTypes()); + self::assertEquals($expectedReferencedFields, $queryPlan->getReferencedFields()); + self::assertEquals($expectedBuildingSubFields, $queryPlan->subFields('Building')); + } } From 835c8a5fe09a94c0704c12f7e555f8dde066d293 Mon Sep 17 00:00:00 2001 From: Jake Voytko Date: Tue, 15 Oct 2019 11:48:47 -0400 Subject: [PATCH 109/256] =?UTF-8?q?Modifies=20the=20chr()=20check=20to=20p?= =?UTF-8?q?roperly=20handle=20\u0000=20codes=20in=20the=20range=E2=80=A6?= =?UTF-8?q?=20(#554)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Modifies the chr() check to properly handle \u0000 codes in the range [127, 255] in the lexer This caused the lexer to output invalid UTF-8 for input like 'pok\u00E9mon'. The output for the é would be the decimal byte 233, which would indicate that it should be a 4 byte unicode sequence, but the following bytes didn't have the leading 10 prefix, so the sequence was invalid UTF-8. --- CHANGELOG.md | 1 + src/Utils/Utils.php | 3 --- tests/UtilsTest.php | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3f3a3310..90154afb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Add schema validation: Input Objects must not contain non-nullable circular references (#492) - Added retrieving query complexity once query has been completed (#316) - Allow input types to be passed in from variables using \stdClass instead of associative arrays (#535) +- Fixes parsing of string literals of the form \u0000 for code points in the range [128, 255] inclusive #### v0.13.5 - Fix coroutine executor when using with promise (#486) diff --git a/src/Utils/Utils.php b/src/Utils/Utils.php index deec77fee..331f61923 100644 --- a/src/Utils/Utils.php +++ b/src/Utils/Utils.php @@ -442,9 +442,6 @@ public static function printSafe($var) */ public static function chr($ord, $encoding = 'UTF-8') { - if ($ord <= 255) { - return chr($ord); - } if ($encoding === 'UCS-4BE') { return pack('N', $ord); } diff --git a/tests/UtilsTest.php b/tests/UtilsTest.php index b91238255..8d6c1782e 100644 --- a/tests/UtilsTest.php +++ b/tests/UtilsTest.php @@ -8,6 +8,7 @@ use InvalidArgumentException; use PHPUnit\Framework\TestCase; use stdClass; +use function mb_check_encoding; class UtilsTest extends TestCase { @@ -20,4 +21,39 @@ public function testAssignThrowsExceptionOnMissingRequiredKey() : void $this->expectExceptionMessage('Key requiredKey is expected to be set and not to be null'); Utils::assign($object, [], ['requiredKey']); } + + /** + * @param int $input + * @param string $expected + * + * @dataProvider chrUtf8DataProvider + */ + public function testChrUtf8Generation($input, $expected) : void + { + $result = Utils::chr($input); + self::assertTrue(mb_check_encoding($result, 'UTF-8')); + self::assertEquals($expected, $result); + } + + public function chrUtf8DataProvider() + { + return [ + 'alphabet' => [ + 'input' => 0x0061, + 'expected' => 'a', + ], + 'numeric' => [ + 'input' => 0x0030, + 'expected' => '0', + ], + 'between 128 and 256' => [ + 'input' => 0x00E9, + 'expected' => 'é', + ], + 'emoji' => [ + 'input' => 0x231A, + 'expected' => '⌚', + ], + ]; + } } From de93375e879b5f181f4abcded4f18a9d9c0ad961 Mon Sep 17 00:00:00 2001 From: Jake Voytko Date: Thu, 17 Oct 2019 01:31:44 -0400 Subject: [PATCH 110/256] Modifies the lexer to parse UTF-16 surrogate pairs in string literals (#556) * Modifies the lexer to parse UTF-16 surrogate pairs in string literals * Rejects an additional invalid case * Responds to PR feedback - fixes lint error * Switches to lowerCamelCase per PR feedback --- CHANGELOG.md | 1 + src/Language/Lexer.php | 22 ++++++++++++++++++++++ tests/Language/LexerTest.php | 18 ++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90154afb4..454f40e74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Added retrieving query complexity once query has been completed (#316) - Allow input types to be passed in from variables using \stdClass instead of associative arrays (#535) - Fixes parsing of string literals of the form \u0000 for code points in the range [128, 255] inclusive +- Parse UTF-16 surrogate pairs within string literals #### v0.13.5 - Fix coroutine executor when using with promise (#486) diff --git a/src/Language/Lexer.php b/src/Language/Lexer.php index d2c1d5f8e..14bdc580e 100644 --- a/src/Language/Lexer.php +++ b/src/Language/Lexer.php @@ -9,8 +9,11 @@ use GraphQL\Utils\Utils; use function chr; use function hexdec; +use function mb_convert_encoding; use function ord; +use function pack; use function preg_match; +use function substr; /** * A Lexer is a stateful stream generator in that every time @@ -522,8 +525,27 @@ private function readString($line, $col, Token $prev) 'Invalid character escape sequence: \\u' . $hex ); } + $code = hexdec($hex); + + // UTF-16 surrogate pair detection and handling. + $highOrderByte = $code >> 8; + if (0xD8 <= $highOrderByte && $highOrderByte <= 0xDF) { + [$utf16Continuation] = $this->readChars(6, true); + if (! preg_match('/^\\\u[0-9a-fA-F]{4}$/', $utf16Continuation)) { + throw new SyntaxError( + $this->source, + $this->position - 5, + 'Invalid UTF-16 trailing surrogate: ' . $utf16Continuation + ); + } + $surrogatePairHex = $hex . substr($utf16Continuation, 2, 4); + $value .= mb_convert_encoding(pack('H*', $surrogatePairHex), 'UTF-8', 'UTF-16'); + break; + } + $this->assertValidStringCharacterCode($code, $position - 2); + $value .= Utils::chr($code); break; default: diff --git a/tests/Language/LexerTest.php b/tests/Language/LexerTest.php index 5e7ed1e71..116f3b845 100644 --- a/tests/Language/LexerTest.php +++ b/tests/Language/LexerTest.php @@ -295,6 +295,16 @@ public function testLexesStrings() : void ], (array) $this->lexOne('"\u1234\u5678\u90AB\uCDEF"') ); + + self::assertArraySubset( + [ + 'kind' => Token::STRING, + 'start' => 0, + 'end' => 41, + 'value' => '𝕌𝕋𝔽-16', + ], + (array) $this->lexOne('"\ud835\udd4C\ud835\udd4B\ud835\udd3d-16"') + ); } /** @@ -430,6 +440,14 @@ public function reportsUsefulStringErrors() ['"bad \\uXXXX esc"', "Invalid character escape sequence: \\uXXXX", $this->loc(1, 7)], ['"bad \\uFXXX esc"', "Invalid character escape sequence: \\uFXXX", $this->loc(1, 7)], ['"bad \\uXXXF esc"', "Invalid character escape sequence: \\uXXXF", $this->loc(1, 7)], + ['"bad \\uD835"', 'Invalid UTF-16 trailing surrogate: ', $this->loc(1, 13)], + ['"bad \\uD835\\u1"', "Invalid UTF-16 trailing surrogate: \\u1", $this->loc(1, 13)], + ['"bad \\uD835\\u1 esc"', "Invalid UTF-16 trailing surrogate: \\u1 es", $this->loc(1, 13)], + ['"bad \\uD835uuFFFF esc"', 'Invalid UTF-16 trailing surrogate: uuFFFF', $this->loc(1, 13)], + ['"bad \\uD835\\u0XX1 esc"', "Invalid UTF-16 trailing surrogate: \\u0XX1", $this->loc(1, 13)], + ['"bad \\uD835\\uXXXX esc"', "Invalid UTF-16 trailing surrogate: \\uXXXX", $this->loc(1, 13)], + ['"bad \\uD835\\uFXXX esc"', "Invalid UTF-16 trailing surrogate: \\uFXXX", $this->loc(1, 13)], + ['"bad \\uD835\\uXXXF esc"', "Invalid UTF-16 trailing surrogate: \\uXXXF", $this->loc(1, 13)], ]; } From 8c9c10f9faa1f7cfbc80b713694686f5448e7bd5 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Fri, 18 Oct 2019 01:19:27 +0700 Subject: [PATCH 111/256] Fixed broken build --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 918cbcaca..0bb12e2e1 100644 --- a/composer.json +++ b/composer.json @@ -15,6 +15,7 @@ }, "require-dev": { "doctrine/coding-standard": "^6.0", + "squizlabs/php_codesniffer": "~3.4.0", "phpbench/phpbench": "^0.14.0", "phpstan/phpstan": "^0.11.12", "phpstan/phpstan-phpunit": "^0.11.2", From 24e82c58e51d15c47114a27af81a3af1537ee396 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Fri, 18 Oct 2019 11:06:17 +0200 Subject: [PATCH 112/256] Lock PHPStan version --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 0bb12e2e1..dfd0c3eb6 100644 --- a/composer.json +++ b/composer.json @@ -17,9 +17,9 @@ "doctrine/coding-standard": "^6.0", "squizlabs/php_codesniffer": "~3.4.0", "phpbench/phpbench": "^0.14.0", - "phpstan/phpstan": "^0.11.12", - "phpstan/phpstan-phpunit": "^0.11.2", - "phpstan/phpstan-strict-rules": "^0.11.1", + "phpstan/phpstan": "0.11.16", + "phpstan/phpstan-phpunit": "0.11.2", + "phpstan/phpstan-strict-rules": "0.11.1", "phpunit/phpcov": "^5.0", "phpunit/phpunit": "^7.2", "psr/http-message": "^1.0", From 551d09a600e3c0a82628d168c01ba8121320033d Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Wed, 23 Oct 2019 11:23:47 +0200 Subject: [PATCH 113/256] Cleanup code --- composer.json | 4 ++-- examples/01-blog/Blog/AppContext.php | 3 --- examples/01-blog/Blog/Data/DataSource.php | 4 ---- examples/01-blog/Blog/Type/StoryType.php | 4 ---- examples/01-blog/Blog/Types.php | 4 ---- phpstan.neon.dist | 1 - src/Deferred.php | 1 - src/Error/FormattedError.php | 1 - src/Error/InvariantViolation.php | 4 ++++ src/Executor/Promise/Adapter/SyncPromiseAdapter.php | 1 - src/Executor/ReferenceExecutor.php | 2 -- src/Experimental/Executor/Collector.php | 10 ---------- src/Experimental/Executor/CoroutineExecutor.php | 3 +-- src/Type/Definition/BooleanType.php | 1 - src/Type/Definition/FloatType.php | 3 --- src/Type/Definition/IDType.php | 2 -- src/Type/Definition/IntType.php | 2 -- src/Type/Definition/StringType.php | 1 - src/Type/Definition/Type.php | 1 - src/Type/Introspection.php | 7 ++++++- src/Type/Schema.php | 9 +++++---- src/Utils/AST.php | 3 +-- src/Utils/ASTDefinitionBuilder.php | 1 - src/Utils/BreakingChangesFinder.php | 2 -- src/Utils/BuildSchema.php | 1 - src/Utils/TypeInfo.php | 1 - src/Utils/Utils.php | 1 - src/Validator/DocumentValidator.php | 2 -- src/Validator/Rules/ExecutableDefinitions.php | 2 +- src/Validator/Rules/KnownArgumentNames.php | 3 +-- src/Validator/Rules/NoFragmentCycles.php | 2 -- src/Validator/Rules/QuerySecurityRule.php | 3 --- src/Validator/Rules/UniqueDirectivesPerLocation.php | 2 +- src/Validator/Rules/ValuesOfCorrectType.php | 1 - src/Validator/Rules/VariablesInAllowedPosition.php | 1 - src/Validator/SDLValidationContext.php | 3 --- src/Validator/ValidationContext.php | 7 ++++--- tests/Executor/ExecutorTest.php | 1 - tests/Executor/LazyInterfaceTest.php | 6 ------ tests/Executor/NonNullTest.php | 1 - tests/Executor/VariablesTest.php | 2 -- tests/Experimental/Executor/CollectorTest.php | 5 ++--- tests/Type/ResolveInfoTest.php | 1 - tests/Type/ValidationTest.php | 4 ---- tests/Utils/CoerceValueTest.php | 3 --- tests/Validator/LoneSchemaDefinitionTest.php | 1 - 46 files changed, 28 insertions(+), 99 deletions(-) diff --git a/composer.json b/composer.json index dfd0c3eb6..a16badfe5 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,6 @@ }, "require-dev": { "doctrine/coding-standard": "^6.0", - "squizlabs/php_codesniffer": "~3.4.0", "phpbench/phpbench": "^0.14.0", "phpstan/phpstan": "0.11.16", "phpstan/phpstan-phpunit": "0.11.2", @@ -23,7 +22,8 @@ "phpunit/phpcov": "^5.0", "phpunit/phpunit": "^7.2", "psr/http-message": "^1.0", - "react/promise": "2.*" + "react/promise": "2.*", + "squizlabs/php_codesniffer": "^3.5.2" }, "config": { "preferred-install": "dist", diff --git a/examples/01-blog/Blog/AppContext.php b/examples/01-blog/Blog/AppContext.php index f6c16cf8c..04918ae2c 100644 --- a/examples/01-blog/Blog/AppContext.php +++ b/examples/01-blog/Blog/AppContext.php @@ -4,10 +4,7 @@ use GraphQL\Examples\Blog\Data\User; /** - * Class AppContext * Instance available in all GraphQL resolvers as 3rd argument - * - * @package GraphQL\Examples\Blog */ class AppContext { diff --git a/examples/01-blog/Blog/Data/DataSource.php b/examples/01-blog/Blog/Data/DataSource.php index 09c1e0232..e8acd31fa 100644 --- a/examples/01-blog/Blog/Data/DataSource.php +++ b/examples/01-blog/Blog/Data/DataSource.php @@ -2,12 +2,8 @@ namespace GraphQL\Examples\Blog\Data; /** - * Class DataSource - * * This is just a simple in-memory data holder for the sake of example. * Data layer for real app may use Doctrine or query the database directly (e.g. in CQRS style) - * - * @package GraphQL\Examples\Blog */ class DataSource { diff --git a/examples/01-blog/Blog/Type/StoryType.php b/examples/01-blog/Blog/Type/StoryType.php index 028ec491b..9a1b0416d 100644 --- a/examples/01-blog/Blog/Type/StoryType.php +++ b/examples/01-blog/Blog/Type/StoryType.php @@ -9,10 +9,6 @@ use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ResolveInfo; -/** - * Class StoryType - * @package GraphQL\Examples\Social\Type - */ class StoryType extends ObjectType { const EDIT = 'EDIT'; diff --git a/examples/01-blog/Blog/Types.php b/examples/01-blog/Blog/Types.php index 7ffc65734..a8bb93aa5 100644 --- a/examples/01-blog/Blog/Types.php +++ b/examples/01-blog/Blog/Types.php @@ -18,14 +18,10 @@ use GraphQL\Type\Definition\Type; /** - * Class Types - * * Acts as a registry and factory for your types. * * As simplistic as possible for the sake of clarity of this example. * Your own may be more dynamic (or even code-generated). - * - * @package GraphQL\Examples\Blog */ class Types { diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 1a524a1f5..a92ffadc2 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -17,7 +17,6 @@ parameters: # type system where we can currently use interfaces and lose type safety. # Until we find a better way, we can list related error's here. - "~Access to an undefined property GraphQL\\\\Type\\\\Definition\\\\NamedType::\\$name~" - - "~Access to an undefined property GraphQL\\\\Language\\\\AST\\\\DefinitionNode::\\$name~" # Those come from graphql-php\tests\Language\VisitorTest.php - "~Access to an undefined property GraphQL\\\\Language\\\\AST\\\\.+::\\$didEnter~" - "~Access to an undefined property GraphQL\\\\Language\\\\AST\\\\.+::\\$didLeave~" diff --git a/src/Deferred.php b/src/Deferred.php index e50338c4f..a2df828a6 100644 --- a/src/Deferred.php +++ b/src/Deferred.php @@ -4,7 +4,6 @@ namespace GraphQL; -use Exception; use GraphQL\Executor\Promise\Adapter\SyncPromise; use SplQueue; use Throwable; diff --git a/src/Error/FormattedError.php b/src/Error/FormattedError.php index c532e0db5..74ece1c3a 100644 --- a/src/Error/FormattedError.php +++ b/src/Error/FormattedError.php @@ -6,7 +6,6 @@ use Countable; use ErrorException; -use Exception; use GraphQL\Language\AST\Node; use GraphQL\Language\Source; use GraphQL\Language\SourceLocation; diff --git a/src/Error/InvariantViolation.php b/src/Error/InvariantViolation.php index 24d6dbc37..c3cb8abe1 100644 --- a/src/Error/InvariantViolation.php +++ b/src/Error/InvariantViolation.php @@ -13,4 +13,8 @@ */ class InvariantViolation extends LogicException { + public static function shouldNotHappen() : self + { + return new self('This should not have happened'); + } } diff --git a/src/Executor/Promise/Adapter/SyncPromiseAdapter.php b/src/Executor/Promise/Adapter/SyncPromiseAdapter.php index 5d4348aec..b7735f2d2 100644 --- a/src/Executor/Promise/Adapter/SyncPromiseAdapter.php +++ b/src/Executor/Promise/Adapter/SyncPromiseAdapter.php @@ -4,7 +4,6 @@ namespace GraphQL\Executor\Promise\Adapter; -use Exception; use GraphQL\Deferred; use GraphQL\Error\InvariantViolation; use GraphQL\Executor\ExecutionResult; diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 1c9c3b0a3..1f78edcad 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -17,7 +17,6 @@ use GraphQL\Language\AST\FragmentDefinitionNode; use GraphQL\Language\AST\FragmentSpreadNode; use GraphQL\Language\AST\InlineFragmentNode; -use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\OperationDefinitionNode; use GraphQL\Language\AST\SelectionSetNode; use GraphQL\Type\Definition\AbstractType; @@ -46,7 +45,6 @@ use function array_values; use function get_class; use function is_array; -use function is_object; use function is_string; use function sprintf; diff --git a/src/Experimental/Executor/Collector.php b/src/Experimental/Executor/Collector.php index 61413d667..076518fbd 100644 --- a/src/Experimental/Executor/Collector.php +++ b/src/Experimental/Executor/Collector.php @@ -13,10 +13,8 @@ use GraphQL\Language\AST\FragmentSpreadNode; use GraphQL\Language\AST\InlineFragmentNode; use GraphQL\Language\AST\Node; -use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\OperationDefinitionNode; use GraphQL\Language\AST\SelectionSetNode; -use GraphQL\Language\AST\ValueNode; use GraphQL\Type\Definition\AbstractType; use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\ObjectType; @@ -65,7 +63,6 @@ public function initialize(DocumentNode $documentNode, ?string $operationName = /** @var DefinitionNode|Node $definitionNode */ if ($definitionNode instanceof OperationDefinitionNode) { - /** @var OperationDefinitionNode $definitionNode */ if ($operationName === null && $this->operation !== null) { $hasMultipleAssumedOperations = true; } @@ -75,7 +72,6 @@ public function initialize(DocumentNode $documentNode, ?string $operationName = $this->operation = $definitionNode; } } elseif ($definitionNode instanceof FragmentDefinitionNode) { - /** @var FragmentDefinitionNode $definitionNode */ $this->fragments[$definitionNode->name->value] = $definitionNode; } } @@ -196,8 +192,6 @@ private function doCollectFields(ObjectType $runtimeType, ?SelectionSetNode $sel } if ($selection instanceof FieldNode) { - /** @var FieldNode $selection */ - $resultName = $selection->alias === null ? $selection->name->value : $selection->alias->value; if (! isset($this->fields[$resultName])) { @@ -206,8 +200,6 @@ private function doCollectFields(ObjectType $runtimeType, ?SelectionSetNode $sel $this->fields[$resultName][] = $selection; } elseif ($selection instanceof FragmentSpreadNode) { - /** @var FragmentSpreadNode $selection */ - $fragmentName = $selection->name->value; if (isset($this->visitedFragments[$fragmentName])) { @@ -249,8 +241,6 @@ private function doCollectFields(ObjectType $runtimeType, ?SelectionSetNode $sel $this->doCollectFields($runtimeType, $fragmentDefinition->selectionSet); } elseif ($selection instanceof InlineFragmentNode) { - /** @var InlineFragmentNode $selection */ - if ($selection->typeCondition !== null) { $conditionTypeName = $selection->typeCondition->name->value; diff --git a/src/Experimental/Executor/CoroutineExecutor.php b/src/Experimental/Executor/CoroutineExecutor.php index 087898fec..7fe2c17fa 100644 --- a/src/Experimental/Executor/CoroutineExecutor.php +++ b/src/Experimental/Executor/CoroutineExecutor.php @@ -825,12 +825,11 @@ private function completeValue(CoroutineContext $ctx, Type $type, $value, array } else { $childContexts = []; + /** @var CoroutineContextShared $childShared */ foreach ($this->collector->collectFields( $objectType, $ctx->shared->mergedSelectionSet ?? $this->mergeSelectionSets($ctx) ) as $childShared) { - /** @var CoroutineContextShared $childShared */ - $childPath = $path; $childPath[] = $childShared->resultName; // !!! uses array COW semantics $childCtx = new CoroutineContext( diff --git a/src/Type/Definition/BooleanType.php b/src/Type/Definition/BooleanType.php index 3ef882db6..dcaae9063 100644 --- a/src/Type/Definition/BooleanType.php +++ b/src/Type/Definition/BooleanType.php @@ -9,7 +9,6 @@ use GraphQL\Language\AST\BooleanValueNode; use GraphQL\Language\AST\Node; use GraphQL\Utils\Utils; -use function is_array; use function is_bool; class BooleanType extends ScalarType diff --git a/src/Type/Definition/FloatType.php b/src/Type/Definition/FloatType.php index 2335bcd50..ab3ed158b 100644 --- a/src/Type/Definition/FloatType.php +++ b/src/Type/Definition/FloatType.php @@ -11,14 +11,11 @@ use GraphQL\Language\AST\Node; use GraphQL\Utils\Utils; use function floatval; -use function is_array; use function is_bool; use function is_finite; use function is_float; use function is_int; -use function is_nan; use function is_numeric; -use function sprintf; class FloatType extends ScalarType { diff --git a/src/Type/Definition/IDType.php b/src/Type/Definition/IDType.php index 9f7610811..861a0a353 100644 --- a/src/Type/Definition/IDType.php +++ b/src/Type/Definition/IDType.php @@ -10,10 +10,8 @@ use GraphQL\Language\AST\Node; use GraphQL\Language\AST\StringValueNode; use GraphQL\Utils\Utils; -use function is_array; use function is_int; use function is_object; -use function is_scalar; use function is_string; use function method_exists; diff --git a/src/Type/Definition/IntType.php b/src/Type/Definition/IntType.php index 1fb96a468..3fd36d5de 100644 --- a/src/Type/Definition/IntType.php +++ b/src/Type/Definition/IntType.php @@ -12,12 +12,10 @@ use function floatval; use function floor; use function intval; -use function is_array; use function is_bool; use function is_float; use function is_int; use function is_numeric; -use function sprintf; class IntType extends ScalarType { diff --git a/src/Type/Definition/StringType.php b/src/Type/Definition/StringType.php index 7eafd0107..e28c3da68 100644 --- a/src/Type/Definition/StringType.php +++ b/src/Type/Definition/StringType.php @@ -9,7 +9,6 @@ use GraphQL\Language\AST\Node; use GraphQL\Language\AST\StringValueNode; use GraphQL\Utils\Utils; -use function is_array; use function is_object; use function is_scalar; use function is_string; diff --git a/src/Type/Definition/Type.php b/src/Type/Definition/Type.php index e0b87629e..567ddc5ac 100644 --- a/src/Type/Definition/Type.php +++ b/src/Type/Definition/Type.php @@ -4,7 +4,6 @@ namespace GraphQL\Type\Definition; -use Exception; use GraphQL\Error\InvariantViolation; use GraphQL\Language\AST\TypeDefinitionNode; use GraphQL\Language\AST\TypeExtensionNode; diff --git a/src/Type/Introspection.php b/src/Type/Introspection.php index a15900609..bad874b7a 100644 --- a/src/Type/Introspection.php +++ b/src/Type/Introspection.php @@ -27,7 +27,6 @@ use function array_filter; use function array_key_exists; use function array_values; -use function in_array; use function is_bool; use function method_exists; use function trigger_error; @@ -510,6 +509,8 @@ public static function _inputValue() 'type' => Type::nonNull(Type::string()), 'resolve' => static function ($inputValue) { /** @var FieldArgument|InputObjectField $inputValue */ + $inputValue = $inputValue; + return $inputValue->name; }, ], @@ -517,6 +518,8 @@ public static function _inputValue() 'type' => Type::string(), 'resolve' => static function ($inputValue) { /** @var FieldArgument|InputObjectField $inputValue */ + $inputValue = $inputValue; + return $inputValue->description; }, ], @@ -534,6 +537,8 @@ public static function _inputValue() 'A GraphQL-formatted string representing the default value for this input value.', 'resolve' => static function ($inputValue) { /** @var FieldArgument|InputObjectField $inputValue */ + $inputValue = $inputValue; + return ! $inputValue->defaultValueExists() ? null : Printer::doPrint(AST::astFromValue( diff --git a/src/Type/Schema.php b/src/Type/Schema.php index b05c63212..ee9c95f5b 100644 --- a/src/Type/Schema.php +++ b/src/Type/Schema.php @@ -434,8 +434,6 @@ private function getPossibleTypeMap() * Returns true if object type is concrete type of given abstract type * (implementation for interfaces and members of union type for unions) * - * @param InterfaceType|UnionType $abstractType - * * @api */ public function isPossibleType(AbstractType $abstractType, ObjectType $possibleType) : bool @@ -444,8 +442,11 @@ public function isPossibleType(AbstractType $abstractType, ObjectType $possibleT return $possibleType->implementsInterface($abstractType); } - /** @var UnionType $abstractType */ - return $abstractType->isPossibleType($possibleType); + if ($abstractType instanceof UnionType) { + return $abstractType->isPossibleType($possibleType); + } + + throw InvariantViolation::shouldNotHappen(); } /** diff --git a/src/Utils/AST.php b/src/Utils/AST.php index bd77d1fba..4f47613a6 100644 --- a/src/Utils/AST.php +++ b/src/Utils/AST.php @@ -213,7 +213,6 @@ public static function astFromValue($value, InputType $type) } elseif ($isArray) { $fieldExists = array_key_exists($fieldName, $value); } elseif ($isArrayLike) { - /** @var ArrayAccess $value */ $fieldExists = $value->offsetExists($fieldName); } else { $fieldExists = property_exists($value, $fieldName); @@ -411,8 +410,8 @@ static function ($field) { } ); foreach ($fields as $field) { - /** @var VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode $fieldNode */ $fieldName = $field->name; + /** @var VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode $fieldNode */ $fieldNode = $fieldNodes[$fieldName] ?? null; if ($fieldNode === null || self::isMissingVariable($fieldNode->value, $variables)) { diff --git a/src/Utils/ASTDefinitionBuilder.php b/src/Utils/ASTDefinitionBuilder.php index 567d3439f..851f1f430 100644 --- a/src/Utils/ASTDefinitionBuilder.php +++ b/src/Utils/ASTDefinitionBuilder.php @@ -8,7 +8,6 @@ use GraphQL\Executor\Values; use GraphQL\Language\AST\DirectiveDefinitionNode; use GraphQL\Language\AST\EnumTypeDefinitionNode; -use GraphQL\Language\AST\EnumTypeExtensionNode; use GraphQL\Language\AST\EnumValueDefinitionNode; use GraphQL\Language\AST\FieldDefinitionNode; use GraphQL\Language\AST\InputObjectTypeDefinitionNode; diff --git a/src/Utils/BreakingChangesFinder.php b/src/Utils/BreakingChangesFinder.php index 460052ccf..27fd8b3c9 100644 --- a/src/Utils/BreakingChangesFinder.php +++ b/src/Utils/BreakingChangesFinder.php @@ -305,13 +305,11 @@ public static function findFieldsThatChangedTypeOnInputObjectTypes( ); if (! $isSafe) { if ($oldFieldType instanceof NamedType) { - /** @var ScalarType|ObjectType|InterfaceType|UnionType|EnumType|InputObjectType $oldFieldType */ $oldFieldTypeString = $oldFieldType->name; } else { $oldFieldTypeString = $oldFieldType; } if ($newFieldType instanceof NamedType) { - /** @var ScalarType|ObjectType|InterfaceType|UnionType|EnumType|InputObjectType $newFieldType */ $newFieldTypeString = $newFieldType->name; } else { $newFieldTypeString = $newFieldType; diff --git a/src/Utils/BuildSchema.php b/src/Utils/BuildSchema.php index 14f3e4a93..57e9c16f4 100644 --- a/src/Utils/BuildSchema.php +++ b/src/Utils/BuildSchema.php @@ -21,7 +21,6 @@ use GraphQL\Type\Schema; use GraphQL\Validator\DocumentValidator; use function array_map; -use function array_reduce; use function sprintf; /** diff --git a/src/Utils/TypeInfo.php b/src/Utils/TypeInfo.php index 6f26fd2c6..2e92a16e9 100644 --- a/src/Utils/TypeInfo.php +++ b/src/Utils/TypeInfo.php @@ -5,7 +5,6 @@ namespace GraphQL\Utils; use GraphQL\Error\InvariantViolation; -use GraphQL\Error\Warning; use GraphQL\Language\AST\ArgumentNode; use GraphQL\Language\AST\DirectiveNode; use GraphQL\Language\AST\EnumValueNode; diff --git a/src/Utils/Utils.php b/src/Utils/Utils.php index 331f61923..695591bb6 100644 --- a/src/Utils/Utils.php +++ b/src/Utils/Utils.php @@ -23,7 +23,6 @@ use function array_slice; use function array_values; use function asort; -use function chr; use function count; use function dechex; use function func_get_args; diff --git a/src/Validator/DocumentValidator.php b/src/Validator/DocumentValidator.php index b74e9f728..156c2ec92 100644 --- a/src/Validator/DocumentValidator.php +++ b/src/Validator/DocumentValidator.php @@ -46,10 +46,8 @@ use GraphQL\Validator\Rules\VariablesInAllowedPosition; use Throwable; use function array_filter; -use function array_map; use function array_merge; use function count; -use function implode; use function is_array; use function sprintf; diff --git a/src/Validator/Rules/ExecutableDefinitions.php b/src/Validator/Rules/ExecutableDefinitions.php index a20bb7b39..632953e8a 100644 --- a/src/Validator/Rules/ExecutableDefinitions.php +++ b/src/Validator/Rules/ExecutableDefinitions.php @@ -26,6 +26,7 @@ public function getVisitor(ValidationContext $context) { return [ NodeKind::DOCUMENT => static function (DocumentNode $node) use ($context) { + /** @var FragmentDefinitionNode|OperationDefinitionNode|TypeSystemDefinitionNode $definition */ foreach ($node->definitions as $definition) { if ($definition instanceof OperationDefinitionNode || $definition instanceof FragmentDefinitionNode @@ -33,7 +34,6 @@ public function getVisitor(ValidationContext $context) continue; } - /** @var TypeSystemDefinitionNode $definition */ $context->reportError(new Error( self::nonExecutableDefinitionMessage($definition->name->value), [$definition->name] diff --git a/src/Validator/Rules/KnownArgumentNames.php b/src/Validator/Rules/KnownArgumentNames.php index 638f11cba..e8936c802 100644 --- a/src/Validator/Rules/KnownArgumentNames.php +++ b/src/Validator/Rules/KnownArgumentNames.php @@ -10,7 +10,6 @@ use GraphQL\Language\AST\FieldNode; use GraphQL\Language\AST\Node; use GraphQL\Language\AST\NodeKind; -use GraphQL\Language\AST\NodeList; use GraphQL\Utils\Utils; use GraphQL\Validator\ValidationContext; use function array_map; @@ -29,12 +28,12 @@ public function getVisitor(ValidationContext $context) { return [ NodeKind::ARGUMENT => static function (ArgumentNode $node, $key, $parent, $path, $ancestors) use ($context) { - /** @var NodeList|Node[] $ancestors */ $argDef = $context->getArgument(); if ($argDef !== null) { return; } + /** @var Node|mixed $argumentOf */ $argumentOf = $ancestors[count($ancestors) - 1]; if ($argumentOf instanceof FieldNode) { $fieldDef = $context->getFieldDef(); diff --git a/src/Validator/Rules/NoFragmentCycles.php b/src/Validator/Rules/NoFragmentCycles.php index 5b1c1c520..e01b7631d 100644 --- a/src/Validator/Rules/NoFragmentCycles.php +++ b/src/Validator/Rules/NoFragmentCycles.php @@ -11,12 +11,10 @@ use GraphQL\Language\Visitor; use GraphQL\Utils\Utils; use GraphQL\Validator\ValidationContext; -use function array_merge; use function array_pop; use function array_slice; use function count; use function implode; -use function is_array; use function sprintf; class NoFragmentCycles extends ValidationRule diff --git a/src/Validator/Rules/QuerySecurityRule.php b/src/Validator/Rules/QuerySecurityRule.php index 2837ed788..c6e5b460d 100644 --- a/src/Validator/Rules/QuerySecurityRule.php +++ b/src/Validator/Rules/QuerySecurityRule.php @@ -115,7 +115,6 @@ protected function collectFieldASTsAndDefs( foreach ($selectionSet->selections as $selection) { switch (true) { case $selection instanceof FieldNode: - /** @var FieldNode $selection */ $fieldName = $selection->name->value; $fieldDef = null; if ($parentType && method_exists($parentType, 'getFields')) { @@ -142,7 +141,6 @@ protected function collectFieldASTsAndDefs( $_astAndDefs[$responseName][] = [$selection, $fieldDef]; break; case $selection instanceof InlineFragmentNode: - /** @var InlineFragmentNode $selection */ $_astAndDefs = $this->collectFieldASTsAndDefs( $context, TypeInfo::typeFromAST($context->getSchema(), $selection->typeCondition), @@ -152,7 +150,6 @@ protected function collectFieldASTsAndDefs( ); break; case $selection instanceof FragmentSpreadNode: - /** @var FragmentSpreadNode $selection */ $fragName = $selection->name->value; if (empty($_visitedFragmentNames[$fragName])) { diff --git a/src/Validator/Rules/UniqueDirectivesPerLocation.php b/src/Validator/Rules/UniqueDirectivesPerLocation.php index 28e8fbdca..9b784f19c 100644 --- a/src/Validator/Rules/UniqueDirectivesPerLocation.php +++ b/src/Validator/Rules/UniqueDirectivesPerLocation.php @@ -33,8 +33,8 @@ public function getASTVisitor(ASTValidationContext $context) } $knownDirectives = []; + /** @var DirectiveNode $directive */ foreach ($node->directives as $directive) { - /** @var DirectiveNode $directive */ $directiveName = $directive->name->value; if (isset($knownDirectives[$directiveName])) { $context->reportError(new Error( diff --git a/src/Validator/Rules/ValuesOfCorrectType.php b/src/Validator/Rules/ValuesOfCorrectType.php index d16735fb6..359f4cd1d 100644 --- a/src/Validator/Rules/ValuesOfCorrectType.php +++ b/src/Validator/Rules/ValuesOfCorrectType.php @@ -4,7 +4,6 @@ namespace GraphQL\Validator\Rules; -use Exception; use GraphQL\Error\Error; use GraphQL\Language\AST\BooleanValueNode; use GraphQL\Language\AST\EnumValueNode; diff --git a/src/Validator/Rules/VariablesInAllowedPosition.php b/src/Validator/Rules/VariablesInAllowedPosition.php index 04a89bdf6..a3932fd92 100644 --- a/src/Validator/Rules/VariablesInAllowedPosition.php +++ b/src/Validator/Rules/VariablesInAllowedPosition.php @@ -10,7 +10,6 @@ use GraphQL\Language\AST\OperationDefinitionNode; use GraphQL\Language\AST\ValueNode; use GraphQL\Language\AST\VariableDefinitionNode; -use GraphQL\Type\Definition\ListOfType; use GraphQL\Type\Definition\NonNull; use GraphQL\Type\Definition\Type; use GraphQL\Type\Schema; diff --git a/src/Validator/SDLValidationContext.php b/src/Validator/SDLValidationContext.php index dff94051e..379515e2c 100644 --- a/src/Validator/SDLValidationContext.php +++ b/src/Validator/SDLValidationContext.php @@ -4,9 +4,6 @@ namespace GraphQL\Validator; -use GraphQL\Language\AST\DocumentNode; -use GraphQL\Type\Schema; - class SDLValidationContext extends ASTValidationContext { } diff --git a/src/Validator/ValidationContext.php b/src/Validator/ValidationContext.php index 0ef857d06..47eae21a5 100644 --- a/src/Validator/ValidationContext.php +++ b/src/Validator/ValidationContext.php @@ -4,7 +4,7 @@ namespace GraphQL\Validator; -use GraphQL\Error\Error; +use GraphQL\Error\InvariantViolation; use GraphQL\Language\AST\DocumentNode; use GraphQL\Language\AST\FieldNode; use GraphQL\Language\AST\FragmentDefinitionNode; @@ -182,11 +182,12 @@ public function getFragmentSpreads(HasSelectionSet $node) : array $selection = $set->selections[$i]; if ($selection instanceof FragmentSpreadNode) { $spreads[] = $selection; - } else { - /** @var FieldNode|InlineFragmentNode $selection*/ + } elseif ($selection instanceof FieldNode || $selection instanceof InlineFragmentNode) { if ($selection->selectionSet) { $setsToVisit[] = $selection->selectionSet; } + } else { + throw InvariantViolation::shouldNotHappen(); } } } diff --git a/tests/Executor/ExecutorTest.php b/tests/Executor/ExecutorTest.php index 77ef6e938..ebd4573b8 100644 --- a/tests/Executor/ExecutorTest.php +++ b/tests/Executor/ExecutorTest.php @@ -21,7 +21,6 @@ use GraphQL\Type\Schema; use PHPUnit\Framework\TestCase; use stdClass; -use function array_keys; use function count; use function json_encode; diff --git a/tests/Executor/LazyInterfaceTest.php b/tests/Executor/LazyInterfaceTest.php index c03d40c5b..11ca9a58b 100644 --- a/tests/Executor/LazyInterfaceTest.php +++ b/tests/Executor/LazyInterfaceTest.php @@ -2,12 +2,6 @@ declare(strict_types=1); -/** - * @author: Ivo Meißner - * Date: 03.05.16 - * Time: 13:14 - */ - namespace GraphQL\Tests\Executor; use GraphQL\Executor\Executor; diff --git a/tests/Executor/NonNullTest.php b/tests/Executor/NonNullTest.php index 3801c888e..f223be124 100644 --- a/tests/Executor/NonNullTest.php +++ b/tests/Executor/NonNullTest.php @@ -9,7 +9,6 @@ use GraphQL\Error\FormattedError; use GraphQL\Error\UserError; use GraphQL\Executor\Executor; -use GraphQL\GraphQL; use GraphQL\Language\Parser; use GraphQL\Language\SourceLocation; use GraphQL\Type\Definition\ObjectType; diff --git a/tests/Executor/VariablesTest.php b/tests/Executor/VariablesTest.php index 8c1750529..a2d698bc3 100644 --- a/tests/Executor/VariablesTest.php +++ b/tests/Executor/VariablesTest.php @@ -4,7 +4,6 @@ namespace GraphQL\Tests\Executor; -use GraphQL\Error\Error; use GraphQL\Executor\Executor; use GraphQL\Language\Parser; use GraphQL\Tests\Executor\TestClasses\ComplexScalar; @@ -17,7 +16,6 @@ use PHPUnit\Framework\TestCase; use function acos; use function array_key_exists; -use function json_encode; /** * Execute: Handles inputs diff --git a/tests/Experimental/Executor/CollectorTest.php b/tests/Experimental/Executor/CollectorTest.php index 62dec4feb..b081e9cc6 100644 --- a/tests/Experimental/Executor/CollectorTest.php +++ b/tests/Experimental/Executor/CollectorTest.php @@ -83,8 +83,8 @@ public function addError($error) } if (! empty($shared->argumentValueMap)) { $execution->argumentValueMap = []; + /** @var Node $valueNode */ foreach ($shared->argumentValueMap as $argumentName => $valueNode) { - /** @var Node $valueNode */ $execution->argumentValueMap[$argumentName] = $valueNode->toArray(true); } } @@ -359,10 +359,9 @@ public function provideForTestCollectFields() foreach ($testCases as [$schema, $query, $variableValues]) { $documentNode = Parser::parse($query, ['noLocation' => true]); $operationName = null; + /** @var Node $definitionNode */ foreach ($documentNode->definitions as $definitionNode) { - /** @var Node $definitionNode */ if ($definitionNode instanceof OperationDefinitionNode) { - /** @var OperationDefinitionNode $definitionNode */ self::assertNotNull($definitionNode->name); $operationName = $definitionNode->name->value; break; diff --git a/tests/Type/ResolveInfoTest.php b/tests/Type/ResolveInfoTest.php index 8b7e93065..d05a44c35 100644 --- a/tests/Type/ResolveInfoTest.php +++ b/tests/Type/ResolveInfoTest.php @@ -5,7 +5,6 @@ namespace GraphQL\Tests\Type; use GraphQL\GraphQL; -use GraphQL\Type\Definition\FieldDefinition; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ResolveInfo; use GraphQL\Type\Definition\Type; diff --git a/tests/Type/ValidationTest.php b/tests/Type/ValidationTest.php index 07b4ab056..338e0d528 100644 --- a/tests/Type/ValidationTest.php +++ b/tests/Type/ValidationTest.php @@ -22,11 +22,7 @@ use GraphQL\Utils\SchemaExtender; use GraphQL\Utils\Utils; use PHPUnit\Framework\TestCase; -use function array_map; use function array_merge; -use function implode; -use function print_r; -use function sprintf; class ValidationTest extends TestCase { diff --git a/tests/Utils/CoerceValueTest.php b/tests/Utils/CoerceValueTest.php index 62ccbc647..ee74feb51 100644 --- a/tests/Utils/CoerceValueTest.php +++ b/tests/Utils/CoerceValueTest.php @@ -5,9 +5,7 @@ namespace GraphQL\Tests\Utils; use GraphQL\Type\Definition\EnumType; -use GraphQL\Type\Definition\IDType; use GraphQL\Type\Definition\InputObjectType; -use GraphQL\Type\Definition\StringType; use GraphQL\Type\Definition\Type; use GraphQL\Utils\Utils; use GraphQL\Utils\Value; @@ -15,7 +13,6 @@ use function acos; use function log; use function pow; -use function sprintf; class CoerceValueTest extends TestCase { diff --git a/tests/Validator/LoneSchemaDefinitionTest.php b/tests/Validator/LoneSchemaDefinitionTest.php index 528851f25..9e2e517f4 100644 --- a/tests/Validator/LoneSchemaDefinitionTest.php +++ b/tests/Validator/LoneSchemaDefinitionTest.php @@ -7,7 +7,6 @@ use GraphQL\Error\FormattedError; use GraphQL\Language\SourceLocation; use GraphQL\Utils\BuildSchema; -use GraphQL\Validator\Rules\KnownDirectives; use GraphQL\Validator\Rules\LoneSchemaDefinition; class LoneSchemaDefinitionTest extends ValidatorTestCase From baad32b302dd76f4b2e0134854ae7e458970324f Mon Sep 17 00:00:00 2001 From: Max Loeb Date: Sat, 2 Nov 2019 23:14:42 -0700 Subject: [PATCH 114/256] Phpstan Level 3 (#566) --- docs/type-system/scalar-types.md | 2 +- examples/01-blog/Blog/Type/Scalar/UrlType.php | 2 +- phpstan.neon.dist | 26 +++- src/Error/Error.php | 2 + src/Error/FormattedError.php | 2 + src/Executor/ReferenceExecutor.php | 4 +- src/Executor/Values.php | 3 + src/Experimental/Executor/Collector.php | 2 +- .../Executor/CoroutineContext.php | 2 +- .../Executor/CoroutineExecutor.php | 40 +++-- src/GraphQL.php | 9 +- src/Language/AST/DocumentNode.php | 2 +- src/Language/DirectiveLocation.php | 7 +- src/Language/Parser.php | 2 +- src/Language/Token.php | 11 +- src/Language/Visitor.php | 2 +- src/Server/Helper.php | 11 +- src/Type/Definition/BooleanType.php | 5 +- src/Type/Definition/CustomScalarType.php | 7 +- src/Type/Definition/EnumType.php | 9 +- src/Type/Definition/FieldArgument.php | 24 +-- src/Type/Definition/FieldDefinition.php | 14 +- src/Type/Definition/FloatType.php | 13 +- src/Type/Definition/IDType.php | 9 +- src/Type/Definition/InputObjectField.php | 6 +- src/Type/Definition/IntType.php | 9 +- src/Type/Definition/LeafType.php | 3 +- src/Type/Definition/ListOfType.php | 16 +- src/Type/Definition/NonNull.php | 55 +------ src/Type/Definition/ObjectType.php | 2 +- src/Type/Definition/ResolveInfo.php | 6 +- src/Type/Definition/StringType.php | 5 +- src/Type/Definition/Type.php | 144 +++++++----------- src/Type/Definition/UnionType.php | 7 +- src/Type/Definition/WrappingType.php | 7 +- src/Type/Introspection.php | 2 +- src/Type/Schema.php | 48 ++---- .../Validation/InputObjectCircularRefs.php | 2 +- src/Utils/AST.php | 4 +- src/Utils/BreakingChangesFinder.php | 8 +- src/Utils/TypeComparators.php | 5 +- src/Utils/TypeInfo.php | 102 +++++-------- src/Validator/Rules/KnownArgumentNames.php | 3 +- .../Rules/OverlappingFieldsCanBeMerged.php | 4 +- src/Validator/Rules/UniqueInputFieldNames.php | 5 +- src/Validator/ValidationContext.php | 15 +- tests/Executor/AbstractPromiseTest.php | 18 +-- tests/Executor/DeferredFieldsTest.php | 2 +- tests/Executor/TestClasses/ComplexScalar.php | 3 +- ...ypeStaticMethodTypeSpecifyingExtension.php | 44 ++++++ ...ypeStaticMethodTypeSpecifyingExtension.php | 44 ++++++ ...ypeStaticMethodTypeSpecifyingExtension.php | 45 ++++++ tests/Regression/Issue396Test.php | 2 +- tests/Server/ServerTestCase.php | 4 + tests/Type/DefinitionTest.php | 114 +++----------- tests/Type/ValidationTest.php | 120 +-------------- tests/Utils/BreakingChangesFinderTest.php | 5 +- 57 files changed, 447 insertions(+), 622 deletions(-) create mode 100644 tests/PhpStan/Type/Definition/Type/IsCompositeTypeStaticMethodTypeSpecifyingExtension.php create mode 100644 tests/PhpStan/Type/Definition/Type/IsInputTypeStaticMethodTypeSpecifyingExtension.php create mode 100644 tests/PhpStan/Type/Definition/Type/IsOutputTypeStaticMethodTypeSpecifyingExtension.php diff --git a/docs/type-system/scalar-types.md b/docs/type-system/scalar-types.md index 302923838..074e8c69a 100644 --- a/docs/type-system/scalar-types.md +++ b/docs/type-system/scalar-types.md @@ -100,7 +100,7 @@ class EmailType extends ScalarType * @return string * @throws Error */ - public function parseLiteral($valueNode, array $variables = null) + public function parseLiteral(Node $valueNode, ?array $variables = null) { // Note: throwing GraphQL\Error\Error vs \UnexpectedValueException to benefit from GraphQL // error location in query: diff --git a/examples/01-blog/Blog/Type/Scalar/UrlType.php b/examples/01-blog/Blog/Type/Scalar/UrlType.php index 0590546f4..e9793f0ed 100644 --- a/examples/01-blog/Blog/Type/Scalar/UrlType.php +++ b/examples/01-blog/Blog/Type/Scalar/UrlType.php @@ -48,7 +48,7 @@ public function parseValue($value) * @return null|string * @throws Error */ - public function parseLiteral($valueNode, array $variables = null) + public function parseLiteral(Node $valueNode, ?array $variables = null) { // Note: throwing GraphQL\Error\Error vs \UnexpectedValueException to benefit from GraphQL // error location in query: diff --git a/phpstan.neon.dist b/phpstan.neon.dist index a92ffadc2..31bb564ee 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,5 +1,9 @@ parameters: - level: 2 + level: 3 + + reportUnmatchedIgnoredErrors: false + + inferPrivatePropertyTypeFromConstructor: true paths: - %currentWorkingDirectory%/src @@ -23,6 +27,20 @@ parameters: - "~Access to an undefined property GraphQL\\\\Language\\\\AST\\\\Node::\\$value~" includes: - - vendor/phpstan/phpstan-phpunit/extension.neon - - vendor/phpstan/phpstan-phpunit/rules.neon - - vendor/phpstan/phpstan-strict-rules/rules.neon + - vendor/phpstan/phpstan-phpunit/extension.neon + - vendor/phpstan/phpstan-phpunit/rules.neon + - vendor/phpstan/phpstan-strict-rules/rules.neon + +services: + - + class: GraphQL\Tests\PhpStan\Type\Definition\Type\IsInputTypeStaticMethodTypeSpecifyingExtension + tags: + - phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension + - + class: GraphQL\Tests\PhpStan\Type\Definition\Type\IsOutputTypeStaticMethodTypeSpecifyingExtension + tags: + - phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension + - + class: GraphQL\Tests\PhpStan\Type\Definition\Type\IsCompositeTypeStaticMethodTypeSpecifyingExtension + tags: + - phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension diff --git a/src/Error/Error.php b/src/Error/Error.php index 2459e90d9..75a62ca77 100644 --- a/src/Error/Error.php +++ b/src/Error/Error.php @@ -330,6 +330,8 @@ public function getExtensions() * @deprecated Use FormattedError::createFromException() instead * * @return mixed[] + * + * @codeCoverageIgnore */ public function toSerializableArray() { diff --git a/src/Error/FormattedError.php b/src/Error/FormattedError.php index 74ece1c3a..aa1101b1e 100644 --- a/src/Error/FormattedError.php +++ b/src/Error/FormattedError.php @@ -427,6 +427,8 @@ static function ($loc) { * @deprecated as of v0.10.0, use general purpose method createFromException() instead * * @return mixed[] + * + * @codeCoverageIgnore */ public static function createFromPHPError(ErrorException $e) { diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 1f78edcad..967a7a35e 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -238,7 +238,7 @@ private function buildResponse($data) * * @param mixed $rootValue * - * @return Promise|stdClass|mixed[] + * @return Promise|stdClass|mixed[]|null */ private function executeOperation(OperationDefinitionNode $operation, $rootValue) { @@ -993,7 +993,7 @@ private function completeAbstractValue(AbstractType $returnType, $fieldNodes, Re * @param mixed|null $contextValue * @param InterfaceType|UnionType $abstractType * - * @return ObjectType|Promise|null + * @return Promise|Type|string|null */ private function defaultTypeResolver($value, $contextValue, ResolveInfo $info, AbstractType $abstractType) { diff --git a/src/Executor/Values.php b/src/Executor/Values.php index 653abce24..7c02090ed 100644 --- a/src/Executor/Values.php +++ b/src/Executor/Values.php @@ -303,6 +303,8 @@ public static function getArgumentValuesForMap($fieldDefinition, $argumentValueM * @param mixed[]|null $variables * * @return mixed[]|stdClass|null + * + * @codeCoverageIgnore */ public static function valueFromAST(ValueNode $valueNode, InputType $type, ?array $variables = null) { @@ -316,6 +318,7 @@ public static function valueFromAST(ValueNode $valueNode, InputType $type, ?arra * * @return string[] * + * @codeCoverageIgnore * @paarm ScalarType|EnumType|InputObjectType|ListOfType|NonNull $type */ public static function isValidPHPValue($value, InputType $type) diff --git a/src/Experimental/Executor/Collector.php b/src/Experimental/Executor/Collector.php index 076518fbd..4f150e0d0 100644 --- a/src/Experimental/Executor/Collector.php +++ b/src/Experimental/Executor/Collector.php @@ -46,7 +46,7 @@ class Collector /** @var FieldNode[][] */ private $fields; - /** @var string[] */ + /** @var array */ private $visitedFragments; public function __construct(Schema $schema, Runtime $runtime) diff --git a/src/Experimental/Executor/CoroutineContext.php b/src/Experimental/Executor/CoroutineContext.php index 910b41d1d..d22ec774e 100644 --- a/src/Experimental/Executor/CoroutineContext.php +++ b/src/Experimental/Executor/CoroutineContext.php @@ -27,7 +27,7 @@ class CoroutineContext /** @var string[] */ public $path; - /** @var ResolveInfo|null */ + /** @var ResolveInfo */ public $resolveInfo; /** @var string[]|null */ diff --git a/src/Experimental/Executor/CoroutineExecutor.php b/src/Experimental/Executor/CoroutineExecutor.php index 7fe2c17fa..30c3964f1 100644 --- a/src/Experimental/Executor/CoroutineExecutor.php +++ b/src/Experimental/Executor/CoroutineExecutor.php @@ -33,12 +33,15 @@ use GraphQL\Type\Introspection; use GraphQL\Type\Schema; use GraphQL\Utils\AST; +use GraphQL\Utils\TypeInfo; use GraphQL\Utils\Utils; use SplQueue; use stdClass; use Throwable; use function is_array; use function is_string; +use function json_decode; +use function json_encode; use function sprintf; class CoroutineExecutor implements Runtime, ExecutorImplementation @@ -73,10 +76,10 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation /** @var string|null */ private $operationName; - /** @var Collector */ + /** @var Collector|null */ private $collector; - /** @var Error[] */ + /** @var array */ private $errors; /** @var SplQueue */ @@ -85,10 +88,10 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation /** @var SplQueue */ private $schedule; - /** @var stdClass */ + /** @var stdClass|null */ private $rootResult; - /** @var int */ + /** @var int|null */ private $pending; /** @var callable */ @@ -108,6 +111,9 @@ public function __construct( self::$undefined = Utils::undefined(); } + $this->errors = []; + $this->queue = new SplQueue(); + $this->schedule = new SplQueue(); $this->schema = $schema; $this->fieldResolver = $fieldResolver; $this->promiseAdapter = $promiseAdapter; @@ -143,10 +149,11 @@ public static function create( private static function resultToArray($value, $emptyObjectAsStdClass = true) { if ($value instanceof stdClass) { - $array = []; - foreach ($value as $propertyName => $propertyValue) { + $array = (array) $value; + foreach ($array as $propertyName => $propertyValue) { $array[$propertyName] = self::resultToArray($propertyValue); } + if ($emptyObjectAsStdClass && empty($array)) { return new stdClass(); } @@ -237,9 +244,9 @@ public function doExecute() : Promise private function finishExecute($value, array $errors) : ExecutionResult { $this->rootResult = null; - $this->errors = null; - $this->queue = null; - $this->schedule = null; + $this->errors = []; + $this->queue = new SplQueue(); + $this->schedule = new SplQueue(); $this->pending = null; $this->collector = null; $this->variableValues = null; @@ -825,11 +832,16 @@ private function completeValue(CoroutineContext $ctx, Type $type, $value, array } else { $childContexts = []; + $fields = []; + if ($this->collector !== null) { + $fields = $this->collector->collectFields( + $objectType, + $ctx->shared->mergedSelectionSet ?? $this->mergeSelectionSets($ctx) + ); + } + /** @var CoroutineContextShared $childShared */ - foreach ($this->collector->collectFields( - $objectType, - $ctx->shared->mergedSelectionSet ?? $this->mergeSelectionSets($ctx) - ) as $childShared) { + foreach ($fields as $childShared) { $childPath = $path; $childPath[] = $childShared->resultName; // !!! uses array COW semantics $childCtx = new CoroutineContext( @@ -938,7 +950,7 @@ private function resolveTypeSlow(CoroutineContext $ctx, $value, AbstractType $ab $selectedType = null; foreach ($possibleTypes as $type) { $typeCheck = yield $type->isTypeOf($value, $this->contextValue, $ctx->resolveInfo); - if ($selectedType !== null || $typeCheck !== true) { + if ($selectedType !== null || ! $typeCheck) { continue; } diff --git a/src/GraphQL.php b/src/GraphQL.php index c11de3bf0..ef5d7b4be 100644 --- a/src/GraphQL.php +++ b/src/GraphQL.php @@ -16,6 +16,7 @@ use GraphQL\Language\Parser; use GraphQL\Language\Source; use GraphQL\Type\Definition\Directive; +use GraphQL\Type\Definition\ScalarType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Schema as SchemaType; use GraphQL\Validator\DocumentValidator; @@ -182,6 +183,8 @@ public static function promiseToExecute( * @param mixed[]|null $variableValues * * @return Promise|mixed[] + * + * @codeCoverageIgnore */ public static function execute( SchemaType $schema, @@ -227,6 +230,8 @@ public static function execute( * @param mixed[]|null $variableValues * * @return ExecutionResult|Promise + * + * @codeCoverageIgnore */ public static function executeAndReturnResult( SchemaType $schema, @@ -287,7 +292,7 @@ public static function getStandardTypes() : array * Replaces standard types with types from this list (matching by name) * Standard types not listed here remain untouched. * - * @param Type[] $types + * @param array $types * * @api */ @@ -345,6 +350,8 @@ public static function useReferenceExecutor() * @deprecated Renamed to getStandardDirectives * * @return Directive[] + * + * @codeCoverageIgnore */ public static function getInternalDirectives() : array { diff --git a/src/Language/AST/DocumentNode.php b/src/Language/AST/DocumentNode.php index bcf051bd4..cc3e1d43d 100644 --- a/src/Language/AST/DocumentNode.php +++ b/src/Language/AST/DocumentNode.php @@ -9,6 +9,6 @@ class DocumentNode extends Node /** @var string */ public $kind = NodeKind::DOCUMENT; - /** @var NodeList|DefinitionNode[] */ + /** @var NodeList|array */ public $definitions; } diff --git a/src/Language/DirectiveLocation.php b/src/Language/DirectiveLocation.php index f7ee72df5..3a3273286 100644 --- a/src/Language/DirectiveLocation.php +++ b/src/Language/DirectiveLocation.php @@ -54,12 +54,7 @@ class DirectiveLocation self::INPUT_FIELD_DEFINITION => self::INPUT_FIELD_DEFINITION, ]; - /** - * @param string $name - * - * @return bool - */ - public static function has($name) + public static function has(string $name) : bool { return isset(self::$locations[$name]); } diff --git a/src/Language/Parser.php b/src/Language/Parser.php index c37c4133d..b9e171932 100644 --- a/src/Language/Parser.php +++ b/src/Language/Parser.php @@ -1295,7 +1295,7 @@ private function parseImplementsInterfaces() } /** - * @return FieldDefinitionNode[]|NodeList + * @return array|NodeList * * @throws SyntaxError */ diff --git a/src/Language/Token.php b/src/Language/Token.php index eb0bbdf03..1618103de 100644 --- a/src/Language/Token.php +++ b/src/Language/Token.php @@ -81,18 +81,13 @@ class Token */ public $prev; - /** @var Token */ + /** @var Token|null */ public $next; /** - * @param string $kind - * @param int $start - * @param int $end - * @param int $line - * @param int $column - * @param mixed|null $value + * @param mixed $value */ - public function __construct($kind, $start, $end, $line, $column, ?Token $previous = null, $value = null) + public function __construct(string $kind, int $start, int $end, int $line, int $column, ?Token $previous = null, $value = null) { $this->kind = $kind; $this->start = $start; diff --git a/src/Language/Visitor.php b/src/Language/Visitor.php index 2faeb47d3..dc52a2347 100644 --- a/src/Language/Visitor.php +++ b/src/Language/Visitor.php @@ -393,7 +393,7 @@ public static function removeNode() /** * @param callable[][] $visitors * - * @return callable[][] + * @return array */ public static function visitInParallel($visitors) { diff --git a/src/Server/Helper.php b/src/Server/Helper.php index 9087db86c..f125598fb 100644 --- a/src/Server/Helper.php +++ b/src/Server/Helper.php @@ -146,7 +146,7 @@ public function parseRequestParams($method, array $bodyParams, array $queryParam * Checks validity of OperationParams extracted from HTTP request and returns an array of errors * if params are invalid (or empty array when params are valid) * - * @return Error[] + * @return array * * @api */ @@ -285,6 +285,11 @@ static function (RequestError $err) { } $operationType = AST::getOperation($doc, $op->operation); + + if ($operationType === false) { + throw new RequestError('Failed to determine operation type'); + } + if ($operationType !== 'query' && $op->isReadOnly()) { throw new RequestError('GET supports only query operation'); } @@ -385,11 +390,9 @@ private function resolveValidationRules( } /** - * @param string $operationType - * * @return mixed */ - private function resolveRootValue(ServerConfig $config, OperationParams $params, DocumentNode $doc, $operationType) + private function resolveRootValue(ServerConfig $config, OperationParams $params, DocumentNode $doc, string $operationType) { $rootValue = $config->getRootValue(); diff --git a/src/Type/Definition/BooleanType.php b/src/Type/Definition/BooleanType.php index dcaae9063..5621e3f00 100644 --- a/src/Type/Definition/BooleanType.php +++ b/src/Type/Definition/BooleanType.php @@ -49,14 +49,11 @@ public function parseValue($value) } /** - * @param Node $valueNode * @param mixed[]|null $variables * - * @return bool|null - * * @throws Exception */ - public function parseLiteral($valueNode, ?array $variables = null) + public function parseLiteral(Node $valueNode, ?array $variables = null) { if (! $valueNode instanceof BooleanValueNode) { // Intentionally without message, as all information already in wrapped Exception diff --git a/src/Type/Definition/CustomScalarType.php b/src/Type/Definition/CustomScalarType.php index 50b28ceb2..14ef54faf 100644 --- a/src/Type/Definition/CustomScalarType.php +++ b/src/Type/Definition/CustomScalarType.php @@ -39,17 +39,14 @@ public function parseValue($value) } /** - * @param Node $valueNode * @param mixed[]|null $variables * * @return mixed * * @throws Exception */ - public function parseLiteral(/* GraphQL\Language\AST\ValueNode */ - $valueNode, - ?array $variables = null - ) { + public function parseLiteral(Node $valueNode, ?array $variables = null) + { if (isset($this->config['parseLiteral'])) { return call_user_func($this->config['parseLiteral'], $valueNode, $variables); } diff --git a/src/Type/Definition/EnumType.php b/src/Type/Definition/EnumType.php index a24d04990..47f8b18d4 100644 --- a/src/Type/Definition/EnumType.php +++ b/src/Type/Definition/EnumType.php @@ -71,12 +71,10 @@ public function getValue($name) return $lookup[$name] ?? null; } - /** - * @return ArrayObject - */ - private function getNameLookup() + private function getNameLookup() : ArrayObject { if (! $this->nameLookup) { + /** @var ArrayObject $lookup */ $lookup = new ArrayObject(); foreach ($this->getValues() as $value) { $lookup[$value->name] = $value; @@ -176,14 +174,13 @@ public function parseValue($value) } /** - * @param Node $valueNode * @param mixed[]|null $variables * * @return null * * @throws Exception */ - public function parseLiteral($valueNode, ?array $variables = null) + public function parseLiteral(Node $valueNode, ?array $variables = null) { if ($valueNode instanceof EnumValueNode) { $lookup = $this->getNameLookup(); diff --git a/src/Type/Definition/FieldArgument.php b/src/Type/Definition/FieldArgument.php index e99828f72..895504c94 100644 --- a/src/Type/Definition/FieldArgument.php +++ b/src/Type/Definition/FieldArgument.php @@ -7,6 +7,7 @@ use GraphQL\Error\InvariantViolation; use GraphQL\Language\AST\InputValueDefinitionNode; use GraphQL\Utils\Utils; +use function array_key_exists; use function is_array; use function is_string; use function sprintf; @@ -28,12 +29,9 @@ class FieldArgument /** @var mixed[] */ public $config; - /** @var InputType */ + /** @var InputType&Type */ private $type; - /** @var bool */ - private $defaultValueExists = false; - /** * @param mixed[] $def */ @@ -48,8 +46,7 @@ public function __construct(array $def) $this->name = $value; break; case 'defaultValue': - $this->defaultValue = $value; - $this->defaultValueExists = true; + $this->defaultValue = $value; break; case 'description': $this->description = $value; @@ -67,7 +64,7 @@ public function __construct(array $def) * * @return FieldArgument[] */ - public static function createMap(array $config) + public static function createMap(array $config) : array { $map = []; foreach ($config as $name => $argConfig) { @@ -81,21 +78,16 @@ public static function createMap(array $config) } /** - * Returns an InputType - * - * @return ScalarType|EnumType|InputObjectType|ListOfType|NonNull + * @return InputType&Type */ - public function getType() + public function getType() : Type { return $this->type; } - /** - * @return bool - */ - public function defaultValueExists() + public function defaultValueExists() : bool { - return $this->defaultValueExists; + return array_key_exists('defaultValue', $this->config); } public function assertValid(FieldDefinition $parentField, Type $parentType) diff --git a/src/Type/Definition/FieldDefinition.php b/src/Type/Definition/FieldDefinition.php index 8f409bf8c..7bf0800b6 100644 --- a/src/Type/Definition/FieldDefinition.php +++ b/src/Type/Definition/FieldDefinition.php @@ -30,7 +30,7 @@ class FieldDefinition * Callback for resolving field value given parent value. * Mutually exclusive with `map` * - * @var callable + * @var callable|null */ public $resolveFn; @@ -38,7 +38,7 @@ class FieldDefinition * Callback for mapping list of parent values to list of field values. * Mutually exclusive with `resolve` * - * @var callable + * @var callable|null */ public $mapFn; @@ -58,7 +58,7 @@ class FieldDefinition */ public $config; - /** @var OutputType */ + /** @var OutputType&Type */ public $type; /** @var callable|string */ @@ -175,9 +175,9 @@ public function getArg($name) } /** - * @return Type + * @return OutputType&Type */ - public function getType() + public function getType() : Type { return $this->type; } @@ -239,5 +239,9 @@ public function assertValid(Type $parentType) Utils::printSafe($this->resolveFn) ) ); + + foreach ($this->args as $fieldArgument) { + $fieldArgument->assertValid($this, $type); + } } } diff --git a/src/Type/Definition/FloatType.php b/src/Type/Definition/FloatType.php index ab3ed158b..7d4c8d764 100644 --- a/src/Type/Definition/FloatType.php +++ b/src/Type/Definition/FloatType.php @@ -31,11 +31,9 @@ class FloatType extends ScalarType /** * @param mixed $value * - * @return float|null - * * @throws Error */ - public function serialize($value) + public function serialize($value) : float { $float = is_numeric($value) || is_bool($value) ? floatval($value) : null; @@ -52,11 +50,9 @@ public function serialize($value) /** * @param mixed $value * - * @return float|null - * * @throws Error */ - public function parseValue($value) + public function parseValue($value) : float { $float = is_float($value) || is_int($value) ? floatval($value) : null; @@ -71,14 +67,13 @@ public function parseValue($value) } /** - * @param Node $valueNode * @param mixed[]|null $variables * - * @return float|null + * @return float * * @throws Exception */ - public function parseLiteral($valueNode, ?array $variables = null) + public function parseLiteral(Node $valueNode, ?array $variables = null) { if ($valueNode instanceof FloatValueNode || $valueNode instanceof IntValueNode) { return (float) $valueNode->value; diff --git a/src/Type/Definition/IDType.php b/src/Type/Definition/IDType.php index 861a0a353..b4cc6b809 100644 --- a/src/Type/Definition/IDType.php +++ b/src/Type/Definition/IDType.php @@ -51,11 +51,9 @@ public function serialize($value) /** * @param mixed $value * - * @return string - * * @throws Error */ - public function parseValue($value) + public function parseValue($value) : string { if (is_string($value) || is_int($value)) { return (string) $value; @@ -64,14 +62,13 @@ public function parseValue($value) } /** - * @param Node $valueNode * @param mixed[]|null $variables * - * @return string|null + * @return string * * @throws Exception */ - public function parseLiteral($valueNode, ?array $variables = null) + public function parseLiteral(Node $valueNode, ?array $variables = null) { if ($valueNode instanceof StringValueNode || $valueNode instanceof IntValueNode) { return $valueNode->value; diff --git a/src/Type/Definition/InputObjectField.php b/src/Type/Definition/InputObjectField.php index 82fa5704d..b2bd951f0 100644 --- a/src/Type/Definition/InputObjectField.php +++ b/src/Type/Definition/InputObjectField.php @@ -21,7 +21,7 @@ class InputObjectField /** @var string|null */ public $description; - /** @var mixed */ + /** @var Type&InputType */ public $type; /** @var InputValueDefinitionNode|null */ @@ -58,9 +58,9 @@ public function __construct(array $opts) } /** - * @return mixed + * @return Type&InputType */ - public function getType() + public function getType() : Type { return $this->type; } diff --git a/src/Type/Definition/IntType.php b/src/Type/Definition/IntType.php index 3fd36d5de..0b3c5e699 100644 --- a/src/Type/Definition/IntType.php +++ b/src/Type/Definition/IntType.php @@ -71,11 +71,9 @@ public function serialize($value) /** * @param mixed $value * - * @return int|null - * * @throws Error */ - public function parseValue($value) + public function parseValue($value) : int { $isInt = is_int($value) || (is_float($value) && floor($value) === $value); @@ -97,14 +95,13 @@ public function parseValue($value) } /** - * @param Node $valueNode * @param mixed[]|null $variables * - * @return int|null + * @return int * * @throws Exception */ - public function parseLiteral($valueNode, ?array $variables = null) + public function parseLiteral(Node $valueNode, ?array $variables = null) { if ($valueNode instanceof IntValueNode) { $val = (int) $valueNode->value; diff --git a/src/Type/Definition/LeafType.php b/src/Type/Definition/LeafType.php index cf563db7a..b913823f8 100644 --- a/src/Type/Definition/LeafType.php +++ b/src/Type/Definition/LeafType.php @@ -9,6 +9,7 @@ use GraphQL\Language\AST\BooleanValueNode; use GraphQL\Language\AST\FloatValueNode; use GraphQL\Language\AST\IntValueNode; +use GraphQL\Language\AST\Node; use GraphQL\Language\AST\NullValueNode; use GraphQL\Language\AST\StringValueNode; @@ -56,5 +57,5 @@ public function parseValue($value); * * @throws Exception */ - public function parseLiteral($valueNode, ?array $variables = null); + public function parseLiteral(Node $valueNode, ?array $variables = null); } diff --git a/src/Type/Definition/ListOfType.php b/src/Type/Definition/ListOfType.php index e81db88a8..f9ca85b32 100644 --- a/src/Type/Definition/ListOfType.php +++ b/src/Type/Definition/ListOfType.php @@ -6,15 +6,12 @@ class ListOfType extends Type implements WrappingType, OutputType, NullableType, InputType { - /** @var ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType */ + /** @var Type */ public $ofType; - /** - * @param callable|Type $type - */ - public function __construct($type) + public function __construct(Type $type) { - $this->ofType = Type::assertType($type); + $this->ofType = $type; } public function toString() : string @@ -22,12 +19,7 @@ public function toString() : string return '[' . $this->ofType->toString() . ']'; } - /** - * @param bool $recurse - * - * @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType - */ - public function getWrappedType($recurse = false) + public function getWrappedType(bool $recurse = false) : Type { $type = $this->ofType; diff --git a/src/Type/Definition/NonNull.php b/src/Type/Definition/NonNull.php index 4b6fa487f..716dac3d1 100644 --- a/src/Type/Definition/NonNull.php +++ b/src/Type/Definition/NonNull.php @@ -4,65 +4,24 @@ namespace GraphQL\Type\Definition; -use GraphQL\Utils\Utils; - class NonNull extends Type implements WrappingType, OutputType, InputType { - /** @var NullableType */ + /** @var NullableType&Type */ private $ofType; - /** - * @param NullableType $type - */ - public function __construct($type) + public function __construct(NullableType $type) { - $this->ofType = self::assertNullableType($type); - } - - /** - * @param mixed $type - * - * @return NullableType - */ - public static function assertNullableType($type) - { - Utils::invariant( - Type::isType($type) && ! $type instanceof self, - 'Expected ' . Utils::printSafe($type) . ' to be a GraphQL nullable type.' - ); - - return $type; - } - - /** - * @param mixed $type - * - * @return self - */ - public static function assertNullType($type) - { - Utils::invariant( - $type instanceof self, - 'Expected ' . Utils::printSafe($type) . ' to be a GraphQL Non-Null type.' - ); - - return $type; + /** @var Type&NullableType $nullableType*/ + $nullableType = $type; + $this->ofType = $nullableType; } - /** - * @return string - */ - public function toString() + public function toString() : string { return $this->getWrappedType()->toString() . '!'; } - /** - * @param bool $recurse - * - * @return Type - */ - public function getWrappedType($recurse = false) + public function getWrappedType(bool $recurse = false) : Type { $type = $this->ofType; diff --git a/src/Type/Definition/ObjectType.php b/src/Type/Definition/ObjectType.php index f4abe0e04..5e031df2f 100644 --- a/src/Type/Definition/ObjectType.php +++ b/src/Type/Definition/ObjectType.php @@ -62,7 +62,7 @@ class ObjectType extends Type implements OutputType, CompositeType, NullableType /** @var ObjectTypeExtensionNode[] */ public $extensionASTNodes; - /** @var callable */ + /** @var ?callable */ public $resolveFieldFn; /** @var FieldDefinition[] */ diff --git a/src/Type/Definition/ResolveInfo.php b/src/Type/Definition/ResolveInfo.php index 7c73d537a..ea42f23e1 100644 --- a/src/Type/Definition/ResolveInfo.php +++ b/src/Type/Definition/ResolveInfo.php @@ -40,7 +40,7 @@ class ResolveInfo * Expected return type of the field being resolved. * * @api - * @var ScalarType|ObjectType|InterfaceType|UnionType|EnumType|ListOfType|NonNull + * @var OutputType&Type */ public $returnType; @@ -64,7 +64,7 @@ class ResolveInfo * Path to this field from the very root value. * * @api - * @var string[][] + * @var string[] */ public $path; @@ -113,7 +113,7 @@ class ResolveInfo /** * @param FieldNode[] $fieldNodes - * @param string[][] $path + * @param string[] $path * @param FragmentDefinitionNode[] $fragments * @param mixed|null $rootValue * @param mixed[] $variableValues diff --git a/src/Type/Definition/StringType.php b/src/Type/Definition/StringType.php index e28c3da68..1abfd5dfc 100644 --- a/src/Type/Definition/StringType.php +++ b/src/Type/Definition/StringType.php @@ -66,14 +66,13 @@ public function parseValue($value) } /** - * @param Node $valueNode * @param mixed[]|null $variables * - * @return string|null + * @return string * * @throws Exception */ - public function parseLiteral($valueNode, ?array $variables = null) + public function parseLiteral(Node $valueNode, ?array $variables = null) { if ($valueNode instanceof StringValueNode) { return $valueNode->value; diff --git a/src/Type/Definition/Type.php b/src/Type/Definition/Type.php index 567ddc5ac..a779189e9 100644 --- a/src/Type/Definition/Type.php +++ b/src/Type/Definition/Type.php @@ -11,7 +11,6 @@ use GraphQL\Utils\Utils; use JsonSerializable; use ReflectionClass; -use Throwable; use function array_keys; use function array_merge; use function implode; @@ -32,8 +31,8 @@ abstract class Type implements JsonSerializable public const FLOAT = 'Float'; public const ID = 'ID'; - /** @var Type[] */ - private static $standardTypes; + /** @var array> */ + protected static $standardTypes; /** @var Type[] */ private static $builtInTypes; @@ -54,105 +53,85 @@ abstract class Type implements JsonSerializable public $extensionASTNodes; /** - * @return IDType - * * @api */ - public static function id() + public static function id() : ScalarType { - return self::getStandardType(self::ID); - } - - /** - * @param string $name - * - * @return (IDType|StringType|FloatType|IntType|BooleanType)[]|IDType|StringType|FloatType|IntType|BooleanType - */ - private static function getStandardType($name = null) - { - if (self::$standardTypes === null) { - self::$standardTypes = [ - self::ID => new IDType(), - self::STRING => new StringType(), - self::FLOAT => new FloatType(), - self::INT => new IntType(), - self::BOOLEAN => new BooleanType(), - ]; + if ((static::$standardTypes[self::ID] ?? null) === null) { + static::$standardTypes[self::ID] = new IDType(); } - return $name ? self::$standardTypes[$name] : self::$standardTypes; + return static::$standardTypes[self::ID]; } /** - * @return StringType - * * @api */ - public static function string() + public static function string() : ScalarType { - return self::getStandardType(self::STRING); + if ((static::$standardTypes[self::STRING] ?? null) === null) { + static::$standardTypes[self::STRING] = new StringType(); + } + + return static::$standardTypes[self::STRING]; } /** - * @return BooleanType - * * @api */ - public static function boolean() + public static function boolean() : ScalarType { - return self::getStandardType(self::BOOLEAN); + if ((static::$standardTypes[self::BOOLEAN] ?? null) === null) { + static::$standardTypes[self::BOOLEAN] = new BooleanType(); + } + + return static::$standardTypes[self::BOOLEAN]; } /** - * @return IntType - * * @api */ - public static function int() + public static function int() : ScalarType { - return self::getStandardType(self::INT); + if ((static::$standardTypes[self::INT] ?? null) === null) { + static::$standardTypes[self::INT] = new IntType(); + } + + return static::$standardTypes[self::INT]; } /** - * @return FloatType - * * @api */ - public static function float() + public static function float() : ScalarType { - return self::getStandardType(self::FLOAT); + if ((static::$standardTypes[self::FLOAT] ?? null) === null) { + static::$standardTypes[self::FLOAT] = new FloatType(); + } + + return static::$standardTypes[self::FLOAT]; } /** - * @param Type|ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType|NonNull $wrappedType - * - * @return ListOfType - * * @api */ - public static function listOf($wrappedType) + public static function listOf(Type $wrappedType) : ListOfType { return new ListOfType($wrappedType); } /** - * @param NullableType $wrappedType - * - * @return NonNull - * * @api */ - public static function nonNull($wrappedType) + public static function nonNull(NullableType $wrappedType) : NonNull { return new NonNull($wrappedType); } /** * Checks if the type is a builtin type - * - * @return bool */ - public static function isBuiltInType(Type $type) + public static function isBuiltInType(Type $type) : bool { return in_array($type->name, array_keys(self::getAllBuiltInTypes()), true); } @@ -178,17 +157,25 @@ public static function getAllBuiltInTypes() /** * Returns all builtin scalar types * - * @return Type[] + * @return ScalarType[] */ public static function getStandardTypes() { - return self::getStandardType(); + return [ + self::ID => static::id(), + self::STRING => static::string(), + self::FLOAT => static::float(), + self::INT => static::int(), + self::BOOLEAN => static::boolean(), + ]; } /** * @deprecated Use method getStandardTypes() instead * * @return Type[] + * + * @codeCoverageIgnore */ public static function getInternalTypes() { @@ -198,7 +185,7 @@ public static function getInternalTypes() } /** - * @param Type[] $types + * @param array $types */ public static function overrideStandardTypes(array $types) { @@ -216,35 +203,26 @@ public static function overrideStandardTypes(array $types) implode(', ', array_keys($standardTypes)), Utils::printSafe($type->name ?? null) ); - $standardTypes[$type->name] = $type; + static::$standardTypes[$type->name] = $type; } - self::$standardTypes = $standardTypes; } /** * @param Type $type * - * @return bool - * * @api */ - public static function isInputType($type) + public static function isInputType($type) : bool { - return $type instanceof InputType && - ( - ! $type instanceof WrappingType || - self::getNamedType($type) instanceof InputType - ); + return self::getNamedType($type) instanceof InputType; } /** * @param Type $type * - * @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType - * * @api */ - public static function getNamedType($type) + public static function getNamedType($type) : ?Type { if ($type === null) { return null; @@ -259,17 +237,11 @@ public static function getNamedType($type) /** * @param Type $type * - * @return bool - * * @api */ - public static function isOutputType($type) + public static function isOutputType($type) : bool { - return $type instanceof OutputType && - ( - ! $type instanceof WrappingType || - self::getNamedType($type) instanceof OutputType - ); + return self::getNamedType($type) instanceof OutputType; } /** @@ -324,23 +296,17 @@ public static function assertType($type) /** * @param Type $type * - * @return bool - * * @api */ - public static function isType($type) + public static function isType($type) : bool { return $type instanceof Type; } /** - * @param Type $type - * - * @return NullableType - * * @api */ - public static function getNullableType($type) + public static function getNullableType(Type $type) : Type { return $type instanceof NonNull ? $type->getWrappedType() @@ -376,11 +342,7 @@ public function toString() */ public function __toString() { - try { - return $this->toString(); - } catch (Throwable $e) { - echo $e; - } + return $this->toString(); } /** diff --git a/src/Type/Definition/UnionType.php b/src/Type/Definition/UnionType.php index 3229df44b..f414bdbaa 100644 --- a/src/Type/Definition/UnionType.php +++ b/src/Type/Definition/UnionType.php @@ -22,13 +22,16 @@ class UnionType extends Type implements AbstractType, OutputType, CompositeType, /** @var ObjectType[] */ private $types; - /** @var ObjectType[] */ + /** @var array|null */ private $possibleTypeNames; /** @var UnionTypeExtensionNode[] */ public $extensionASTNodes; - public function __construct($config) + /** + * @param mixed[] $config + */ + public function __construct(array $config) { if (! isset($config['name'])) { $config['name'] = $this->tryInferName(); diff --git a/src/Type/Definition/WrappingType.php b/src/Type/Definition/WrappingType.php index 8bff76805..26fbe85d3 100644 --- a/src/Type/Definition/WrappingType.php +++ b/src/Type/Definition/WrappingType.php @@ -6,10 +6,5 @@ interface WrappingType { - /** - * @param bool $recurse - * - * @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType - */ - public function getWrappedType($recurse = false); + public function getWrappedType(bool $recurse = false) : Type; } diff --git a/src/Type/Introspection.php b/src/Type/Introspection.php index bad874b7a..1d6b86450 100644 --- a/src/Type/Introspection.php +++ b/src/Type/Introspection.php @@ -38,7 +38,7 @@ class Introspection const TYPE_FIELD_NAME = '__type'; const TYPE_NAME_FIELD_NAME = '__typename'; - /** @var Type[] */ + /** @var array */ private static $map = []; /** diff --git a/src/Type/Schema.php b/src/Type/Schema.php index ee9c95f5b..272e9ea44 100644 --- a/src/Type/Schema.php +++ b/src/Type/Schema.php @@ -20,6 +20,7 @@ use GraphQL\Utils\Utils; use Traversable; use function array_values; +use function count; use function implode; use function is_array; use function is_callable; @@ -57,7 +58,7 @@ class Schema */ private $resolvedTypes = []; - /** @var Type[][]|null */ + /** @var array>|null */ private $possibleTypeMap; /** @@ -67,7 +68,7 @@ class Schema */ private $fullyLoaded = false; - /** @var InvariantViolation[]|null */ + /** @var Error[] */ private $validationErrors; /** @var SchemaTypeExtensionNode[] */ @@ -307,15 +308,11 @@ public function getConfig() } /** - * Returns type by it's name - * - * @param string $name - * - * @return Type|null + * Returns type by its name * * @api */ - public function getType($name) + public function getType(string $name) : ?Type { if (! isset($this->resolvedTypes[$name])) { $type = $this->loadType($name); @@ -328,22 +325,12 @@ public function getType($name) return $this->resolvedTypes[$name]; } - /** - * @param string $name - * - * @return bool - */ - public function hasType($name) + public function hasType(string $name) : bool { return $this->getType($name) !== null; } - /** - * @param string $typeName - * - * @return Type - */ - private function loadType($typeName) + private function loadType(string $typeName) : ?Type { $typeLoader = $this->config->typeLoader; @@ -371,12 +358,7 @@ private function loadType($typeName) return $type; } - /** - * @param string $typeName - * - * @return Type - */ - private function defaultTypeLoader($typeName) + private function defaultTypeLoader(string $typeName) : ?Type { // Default type loader simply fallbacks to collecting all types $typeMap = $this->getTypeMap(); @@ -392,19 +374,19 @@ private function defaultTypeLoader($typeName) * * @param InterfaceType|UnionType $abstractType * - * @return ObjectType[] + * @return array * * @api */ - public function getPossibleTypes(AbstractType $abstractType) : array + public function getPossibleTypes(Type $abstractType) : array { $possibleTypeMap = $this->getPossibleTypeMap(); - return isset($possibleTypeMap[$abstractType->name]) ? array_values($possibleTypeMap[$abstractType->name]) : []; + return array_values($possibleTypeMap[$abstractType->name] ?? []); } /** - * @return Type[][] + * @return array> */ private function getPossibleTypeMap() { @@ -452,13 +434,9 @@ public function isPossibleType(AbstractType $abstractType, ObjectType $possibleT /** * Returns instance of directive by name * - * @param string $name - * - * @return Directive - * * @api */ - public function getDirective($name) + public function getDirective(string $name) : ?Directive { foreach ($this->getDirectives() as $directive) { if ($directive->name === $name) { diff --git a/src/Type/Validation/InputObjectCircularRefs.php b/src/Type/Validation/InputObjectCircularRefs.php index ace97dc14..7291ae42b 100644 --- a/src/Type/Validation/InputObjectCircularRefs.php +++ b/src/Type/Validation/InputObjectCircularRefs.php @@ -24,7 +24,7 @@ class InputObjectCircularRefs * Tracks already visited types to maintain O(N) and to ensure that cycles * are not redundantly reported. * - * @var InputObjectType[] + * @var array */ private $visitedTypes = []; diff --git a/src/Utils/AST.php b/src/Utils/AST.php index 4f47613a6..d493c1b2c 100644 --- a/src/Utils/AST.php +++ b/src/Utils/AST.php @@ -149,7 +149,7 @@ public static function toArray(Node $node) * * @param Type|mixed|null $value * - * @return ObjectValueNode|ListValueNode|BooleanValueNode|IntValueNode|FloatValueNode|EnumValueNode|StringValueNode|NullValueNode + * @return ObjectValueNode|ListValueNode|BooleanValueNode|IntValueNode|FloatValueNode|EnumValueNode|StringValueNode|NullValueNode|null * * @api */ @@ -587,7 +587,7 @@ public static function typeFromAST(Schema $schema, $inputTypeNode) * * @param string $operationName * - * @return bool + * @return bool|string * * @api */ diff --git a/src/Utils/BreakingChangesFinder.php b/src/Utils/BreakingChangesFinder.php index 27fd8b3c9..733ca1889 100644 --- a/src/Utils/BreakingChangesFinder.php +++ b/src/Utils/BreakingChangesFinder.php @@ -214,10 +214,10 @@ public static function findFieldsThatChangedTypeOnObjectOrInterfaceTypes( $newFieldType ); if (! $isSafe) { - $oldFieldTypeString = $oldFieldType instanceof NamedType + $oldFieldTypeString = $oldFieldType instanceof NamedType && $oldFieldType instanceof Type ? $oldFieldType->name : $oldFieldType; - $newFieldTypeString = $newFieldType instanceof NamedType + $newFieldTypeString = $newFieldType instanceof NamedType && $newFieldType instanceof Type ? $newFieldType->name : $newFieldType; $breakingChanges[] = [ @@ -270,7 +270,7 @@ private static function isChangeSafeForObjectOrInterfaceField( } /** - * @return string[][] + * @return array>> */ public static function findFieldsThatChangedTypeOnInputObjectTypes( Schema $oldSchema, @@ -471,7 +471,7 @@ public static function findValuesRemovedFromEnums( * (such as removal or change of type of an argument, or a change in an * argument's default value). * - * @return string[][] + * @return array>> */ public static function findArgChanges( Schema $oldSchema, diff --git a/src/Utils/TypeComparators.php b/src/Utils/TypeComparators.php index 2555c1f97..508acc2d5 100644 --- a/src/Utils/TypeComparators.php +++ b/src/Utils/TypeComparators.php @@ -46,12 +46,9 @@ public static function isEqualType(Type $typeA, Type $typeB) * Provided a type and a super type, return true if the first type is either * equal or a subset of the second super type (covariant). * - * @param InterfaceType|UnionType $maybeSubType - * @param InterfaceType|UnionType $superType - * * @return bool */ - public static function isTypeSubTypeOf(Schema $schema, $maybeSubType, $superType) + public static function isTypeSubTypeOf(Schema $schema, Type $maybeSubType, Type $superType) { // Equivalent type is a valid subtype if ($maybeSubType === $superType) { diff --git a/src/Utils/TypeInfo.php b/src/Utils/TypeInfo.php index 2e92a16e9..89ec87f37 100644 --- a/src/Utils/TypeInfo.php +++ b/src/Utils/TypeInfo.php @@ -39,11 +39,13 @@ use GraphQL\Type\Introspection; use GraphQL\Type\Schema; use SplStack; +use Symfony\Component\Console\Output\Output; use function array_map; use function array_merge; use function array_pop; use function count; use function is_array; +use function is_null; use function sprintf; class TypeInfo @@ -51,25 +53,25 @@ class TypeInfo /** @var Schema */ private $schema; - /** @var SplStack */ + /** @var array<(OutputType&Type)|null> */ private $typeStack; - /** @var SplStack */ + /** @var array<(CompositeType&Type)|null> */ private $parentTypeStack; - /** @var SplStack */ + /** @var array<(InputType&Type)|null> */ private $inputTypeStack; - /** @var SplStack */ + /** @var array */ private $fieldDefStack; - /** @var SplStack */ + /** @var array */ private $defaultValueStack; - /** @var Directive */ + /** @var Directive|null */ private $directive; - /** @var FieldArgument */ + /** @var FieldArgument|null */ private $argument; /** @var mixed */ @@ -106,14 +108,18 @@ public function __construct(Schema $schema, $initialType = null) /** * @deprecated moved to GraphQL\Utils\TypeComparators + * + * @codeCoverageIgnore */ - public static function isEqualType(Type $typeA, Type $typeB) + public static function isEqualType(Type $typeA, Type $typeB) : bool { return TypeComparators::isEqualType($typeA, $typeB); } /** * @deprecated moved to GraphQL\Utils\TypeComparators + * + * @codeCoverageIgnore */ public static function isTypeSubTypeOf(Schema $schema, Type $maybeSubType, Type $superType) { @@ -122,6 +128,8 @@ public static function isTypeSubTypeOf(Schema $schema, Type $maybeSubType, Type /** * @deprecated moved to GraphQL\Utils\TypeComparators + * + * @codeCoverageIgnore */ public static function doTypesOverlap(Schema $schema, CompositeType $typeA, CompositeType $typeB) { @@ -234,21 +242,12 @@ public static function extractTypesFromDirectives(Directive $directive, array $t return $typeMap; } - /** - * @return InputType|null - */ - public function getParentInputType() + public function getParentInputType() : ?InputType { - $inputTypeStackLength = count($this->inputTypeStack); - if ($inputTypeStackLength > 1) { - return $this->inputTypeStack[$inputTypeStackLength - 2]; - } + return $this->inputTypeStack[count($this->inputTypeStack) - 2] ?? null; } - /** - * @return FieldArgument|null - */ - public function getArgument() + public function getArgument() : ?FieldArgument { return $this->argument; } @@ -343,7 +342,8 @@ static function ($arg) use ($node) { break; case $node instanceof ListValueNode: - $listType = Type::getNullableType($this->getInputType()); + $type = $this->getInputType(); + $listType = $type === null ? null : Type::getNullableType($type); $itemType = $listType instanceof ListOfType ? $listType->getWrappedType() : $listType; @@ -370,7 +370,7 @@ static function ($arg) use ($node) { $enumType = Type::getNamedType($this->getInputType()); $enumValue = null; if ($enumType instanceof EnumType) { - $enumValue = $enumType->getValue($node->value); + $this->enumValue = $enumType->getValue($node->value); } $this->enumValue = $enumValue; break; @@ -378,37 +378,27 @@ static function ($arg) use ($node) { } /** - * @return Type + * @return (Type & OutputType) | null */ - public function getType() + public function getType() : ?OutputType { - if (! empty($this->typeStack)) { - return $this->typeStack[count($this->typeStack) - 1]; - } - - return null; + return $this->typeStack[count($this->typeStack) - 1] ?? null; } /** - * @return Type + * @return (CompositeType & Type) | null */ - public function getParentType() + public function getParentType() : ?CompositeType { - if (! empty($this->parentTypeStack)) { - return $this->parentTypeStack[count($this->parentTypeStack) - 1]; - } - - return null; + return $this->parentTypeStack[count($this->parentTypeStack) - 1] ?? null; } /** * Not exactly the same as the executor's definition of getFieldDef, in this * statically evaluated environment we do not always have an Object type, * and need to handle Interface and Union types. - * - * @return FieldDefinition */ - private static function getFieldDefinition(Schema $schema, Type $parentType, FieldNode $fieldNode) + private static function getFieldDefinition(Schema $schema, Type $parentType, FieldNode $fieldNode) : ?FieldDefinition { $name = $fieldNode->name->value; $schemaMeta = Introspection::schemaMetaFieldDef(); @@ -437,33 +427,21 @@ private static function getFieldDefinition(Schema $schema, Type $parentType, Fie /** * @param NamedTypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode * - * @return Type|null - * * @throws InvariantViolation */ - public static function typeFromAST(Schema $schema, $inputTypeNode) + public static function typeFromAST(Schema $schema, $inputTypeNode) : ?Type { return AST::typeFromAST($schema, $inputTypeNode); } - /** - * @return Directive|null - */ - public function getDirective() + public function getDirective() : ?Directive { return $this->directive; } - /** - * @return FieldDefinition - */ - public function getFieldDef() + public function getFieldDef() : ?FieldDefinition { - if (! empty($this->fieldDefStack)) { - return $this->fieldDefStack[count($this->fieldDefStack) - 1]; - } - - return null; + return $this->fieldDefStack[count($this->fieldDefStack) - 1] ?? null; } /** @@ -471,23 +449,15 @@ public function getFieldDef() */ public function getDefaultValue() { - if (! empty($this->defaultValueStack)) { - return $this->defaultValueStack[count($this->defaultValueStack) - 1]; - } - - return null; + return $this->defaultValueStack[count($this->defaultValueStack) - 1] ?? null; } /** - * @return ScalarType|EnumType|InputObjectType|ListOfType|NonNull|null + * @return (Type & InputType) | null */ public function getInputType() : ?InputType { - if (! empty($this->inputTypeStack)) { - return $this->inputTypeStack[count($this->inputTypeStack) - 1]; - } - - return null; + return $this->inputTypeStack[count($this->inputTypeStack) - 1] ?? null; } public function leave(Node $node) diff --git a/src/Validator/Rules/KnownArgumentNames.php b/src/Validator/Rules/KnownArgumentNames.php index e8936c802..dfec7886a 100644 --- a/src/Validator/Rules/KnownArgumentNames.php +++ b/src/Validator/Rules/KnownArgumentNames.php @@ -10,6 +10,7 @@ use GraphQL\Language\AST\FieldNode; use GraphQL\Language\AST\Node; use GraphQL\Language\AST\NodeKind; +use GraphQL\Type\Definition\Type; use GraphQL\Utils\Utils; use GraphQL\Validator\ValidationContext; use function array_map; @@ -38,7 +39,7 @@ public function getVisitor(ValidationContext $context) if ($argumentOf instanceof FieldNode) { $fieldDef = $context->getFieldDef(); $parentType = $context->getParentType(); - if ($fieldDef && $parentType) { + if ($fieldDef !== null && $parentType instanceof Type) { $context->reportError(new Error( self::unknownArgMessage( $node->name->value, diff --git a/src/Validator/Rules/OverlappingFieldsCanBeMerged.php b/src/Validator/Rules/OverlappingFieldsCanBeMerged.php index efd9038e8..b2f3ba520 100644 --- a/src/Validator/Rules/OverlappingFieldsCanBeMerged.php +++ b/src/Validator/Rules/OverlappingFieldsCanBeMerged.php @@ -467,10 +467,8 @@ private function sameValue(Node $value1, Node $value2) * Two types conflict if both types could not apply to a value simultaneously. * Composite types are ignored as their individual field types will be compared * later recursively. However List and Non-Null types must match. - * - * @return bool */ - private function doTypesConflict(OutputType $type1, OutputType $type2) + private function doTypesConflict(Type $type1, Type $type2) : bool { if ($type1 instanceof ListOfType) { return $type2 instanceof ListOfType diff --git a/src/Validator/Rules/UniqueInputFieldNames.php b/src/Validator/Rules/UniqueInputFieldNames.php index 0ea374261..da36b9bf8 100644 --- a/src/Validator/Rules/UniqueInputFieldNames.php +++ b/src/Validator/Rules/UniqueInputFieldNames.php @@ -5,6 +5,7 @@ namespace GraphQL\Validator\Rules; use GraphQL\Error\Error; +use GraphQL\Language\AST\NameNode; use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\ObjectFieldNode; use GraphQL\Language\Visitor; @@ -16,10 +17,10 @@ class UniqueInputFieldNames extends ValidationRule { - /** @var string[] */ + /** @var array */ public $knownNames; - /** @var string[][] */ + /** @var array> */ public $knownNameStack; public function getVisitor(ValidationContext $context) diff --git a/src/Validator/ValidationContext.php b/src/Validator/ValidationContext.php index 47eae21a5..67f9fbb0c 100644 --- a/src/Validator/ValidationContext.php +++ b/src/Validator/ValidationContext.php @@ -16,12 +16,14 @@ use GraphQL\Language\AST\SelectionSetNode; use GraphQL\Language\AST\VariableNode; use GraphQL\Language\Visitor; +use GraphQL\Type\Definition\CompositeType; use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\FieldDefinition; use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\InputType; use GraphQL\Type\Definition\ListOfType; use GraphQL\Type\Definition\NonNull; +use GraphQL\Type\Definition\OutputType; use GraphQL\Type\Definition\ScalarType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Schema; @@ -220,26 +222,21 @@ public function getFragment($name) return $fragments[$name] ?? null; } - /** - * Returns OutputType - * - * @return Type - */ - public function getType() + public function getType() : ?OutputType { return $this->typeInfo->getType(); } /** - * @return Type + * @return (CompositeType & Type) | null */ - public function getParentType() + public function getParentType() : ?CompositeType { return $this->typeInfo->getParentType(); } /** - * @return ScalarType|EnumType|InputObjectType|ListOfType|NonNull + * @return (Type & InputType) | null */ public function getInputType() : ?InputType { diff --git a/tests/Executor/AbstractPromiseTest.php b/tests/Executor/AbstractPromiseTest.php index ed39d04e1..52a2c4926 100644 --- a/tests/Executor/AbstractPromiseTest.php +++ b/tests/Executor/AbstractPromiseTest.php @@ -27,7 +27,7 @@ class AbstractPromiseTest extends TestCase */ public function testIsTypeOfUsedToResolveRuntimeTypeForInterface() : void { - $PetType = new InterfaceType([ + $petType = new InterfaceType([ 'name' => 'Pet', 'fields' => [ 'name' => ['type' => Type::string()], @@ -36,7 +36,7 @@ public function testIsTypeOfUsedToResolveRuntimeTypeForInterface() : void $DogType = new ObjectType([ 'name' => 'Dog', - 'interfaces' => [$PetType], + 'interfaces' => [$petType], 'isTypeOf' => static function ($obj) { return new Deferred(static function () use ($obj) { return $obj instanceof Dog; @@ -50,7 +50,7 @@ public function testIsTypeOfUsedToResolveRuntimeTypeForInterface() : void $CatType = new ObjectType([ 'name' => 'Cat', - 'interfaces' => [$PetType], + 'interfaces' => [$petType], 'isTypeOf' => static function ($obj) { return new Deferred(static function () use ($obj) { return $obj instanceof Cat; @@ -67,7 +67,7 @@ public function testIsTypeOfUsedToResolveRuntimeTypeForInterface() : void 'name' => 'Query', 'fields' => [ 'pets' => [ - 'type' => Type::listOf($PetType), + 'type' => Type::listOf($petType), 'resolve' => static function () { return [ new Dog('Odie', true), @@ -204,7 +204,7 @@ public function testIsTypeOfCanBeRejected() : void */ public function testIsTypeOfUsedToResolveRuntimeTypeForUnion() : void { - $DogType = new ObjectType([ + $dogType = new ObjectType([ 'name' => 'Dog', 'isTypeOf' => static function ($obj) { return new Deferred(static function () use ($obj) { @@ -217,7 +217,7 @@ public function testIsTypeOfUsedToResolveRuntimeTypeForUnion() : void ], ]); - $CatType = new ObjectType([ + $catType = new ObjectType([ 'name' => 'Cat', 'isTypeOf' => static function ($obj) { return new Deferred(static function () use ($obj) { @@ -230,9 +230,9 @@ public function testIsTypeOfUsedToResolveRuntimeTypeForUnion() : void ], ]); - $PetType = new UnionType([ + $petType = new UnionType([ 'name' => 'Pet', - 'types' => [$DogType, $CatType], + 'types' => [$dogType, $catType], ]); $schema = new Schema([ @@ -240,7 +240,7 @@ public function testIsTypeOfUsedToResolveRuntimeTypeForUnion() : void 'name' => 'Query', 'fields' => [ 'pets' => [ - 'type' => Type::listOf($PetType), + 'type' => Type::listOf($petType), 'resolve' => static function () { return [new Dog('Odie', true), new Cat('Garfield', false)]; }, diff --git a/tests/Executor/DeferredFieldsTest.php b/tests/Executor/DeferredFieldsTest.php index f1b6f4f4a..e2caf2e52 100644 --- a/tests/Executor/DeferredFieldsTest.php +++ b/tests/Executor/DeferredFieldsTest.php @@ -28,7 +28,7 @@ class DeferredFieldsTest extends TestCase /** @var ObjectType */ private $categoryType; - /** @var string[] */ + /** @var mixed */ private $paths; /** @var mixed[][] */ diff --git a/tests/Executor/TestClasses/ComplexScalar.php b/tests/Executor/TestClasses/ComplexScalar.php index 91197a33e..2ca7a4ee1 100644 --- a/tests/Executor/TestClasses/ComplexScalar.php +++ b/tests/Executor/TestClasses/ComplexScalar.php @@ -5,6 +5,7 @@ namespace GraphQL\Tests\Executor\TestClasses; use GraphQL\Error\Error; +use GraphQL\Language\AST\Node; use GraphQL\Type\Definition\ScalarType; use GraphQL\Utils\Utils; @@ -45,7 +46,7 @@ public function parseValue($value) /** * {@inheritdoc} */ - public function parseLiteral($valueNode, ?array $variables = null) + public function parseLiteral(Node $valueNode, ?array $variables = null) { if ($valueNode->value === 'SerializedValue') { return 'DeserializedValue'; diff --git a/tests/PhpStan/Type/Definition/Type/IsCompositeTypeStaticMethodTypeSpecifyingExtension.php b/tests/PhpStan/Type/Definition/Type/IsCompositeTypeStaticMethodTypeSpecifyingExtension.php new file mode 100644 index 000000000..3041bc942 --- /dev/null +++ b/tests/PhpStan/Type/Definition/Type/IsCompositeTypeStaticMethodTypeSpecifyingExtension.php @@ -0,0 +1,44 @@ +typeSpecifier = $typeSpecifier; + } + + public function isStaticMethodSupported(MethodReflection $staticMethodReflection, StaticCall $node, TypeSpecifierContext $context) : bool + { + // The $context argument tells us if we're in an if condition or not (as in this case). + return $staticMethodReflection->getName() === 'isCompositeType' && ! $context->null(); + } + + public function specifyTypes(MethodReflection $staticMethodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context) : SpecifiedTypes + { + return $this->typeSpecifier->create($node->args[0]->value, new ObjectType(CompositeType::class), $context); + } +} diff --git a/tests/PhpStan/Type/Definition/Type/IsInputTypeStaticMethodTypeSpecifyingExtension.php b/tests/PhpStan/Type/Definition/Type/IsInputTypeStaticMethodTypeSpecifyingExtension.php new file mode 100644 index 000000000..4b802ed13 --- /dev/null +++ b/tests/PhpStan/Type/Definition/Type/IsInputTypeStaticMethodTypeSpecifyingExtension.php @@ -0,0 +1,44 @@ +typeSpecifier = $typeSpecifier; + } + + public function isStaticMethodSupported(MethodReflection $staticMethodReflection, StaticCall $node, TypeSpecifierContext $context) : bool + { + // The $context argument tells us if we're in an if condition or not (as in this case). + return $staticMethodReflection->getName() === 'isInputType' && ! $context->null(); + } + + public function specifyTypes(MethodReflection $staticMethodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context) : SpecifiedTypes + { + return $this->typeSpecifier->create($node->args[0]->value, new ObjectType(InputType::class), $context); + } +} diff --git a/tests/PhpStan/Type/Definition/Type/IsOutputTypeStaticMethodTypeSpecifyingExtension.php b/tests/PhpStan/Type/Definition/Type/IsOutputTypeStaticMethodTypeSpecifyingExtension.php new file mode 100644 index 000000000..6dd1ae744 --- /dev/null +++ b/tests/PhpStan/Type/Definition/Type/IsOutputTypeStaticMethodTypeSpecifyingExtension.php @@ -0,0 +1,45 @@ +typeSpecifier = $typeSpecifier; + } + + public function isStaticMethodSupported(MethodReflection $staticMethodReflection, StaticCall $node, TypeSpecifierContext $context) : bool + { + // The $context argument tells us if we're in an if condition or not (as in this case). + return $staticMethodReflection->getName() === 'isOutputType' && ! $context->null(); + } + + public function specifyTypes(MethodReflection $staticMethodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context) : SpecifiedTypes + { + return $this->typeSpecifier->create($node->args[0]->value, new ObjectType(OutputType::class), $context); + } +} diff --git a/tests/Regression/Issue396Test.php b/tests/Regression/Issue396Test.php index ca5e5c006..06cf5e819 100644 --- a/tests/Regression/Issue396Test.php +++ b/tests/Regression/Issue396Test.php @@ -97,7 +97,7 @@ public function testInterfaceResolveType() 'fields' => [ 'name' => Type::string(), ], - 'resolveType' => static function ($result, $value, ResolveInfo $info) use (&$a, &$b, &$c, &$log) : Type { + 'resolveType' => static function ($result, $value, ResolveInfo $info) use (&$a, &$b, &$c, &$log) : ?Type { $log[] = [$result, $info->path]; if (stristr($result['name'], 'A')) { return $a; diff --git a/tests/Server/ServerTestCase.php b/tests/Server/ServerTestCase.php index d94ac340d..62001988d 100644 --- a/tests/Server/ServerTestCase.php +++ b/tests/Server/ServerTestCase.php @@ -35,6 +35,10 @@ protected function buildSchema() trigger_error('deprecated', E_USER_DEPRECATED); trigger_error('notice', E_USER_NOTICE); trigger_error('warning', E_USER_WARNING); + + /** + * @var array + */ $a = []; $a['test']; // should produce PHP notice diff --git a/tests/Type/DefinitionTest.php b/tests/Type/DefinitionTest.php index d1967ea54..98da41dfa 100644 --- a/tests/Type/DefinitionTest.php +++ b/tests/Type/DefinitionTest.php @@ -7,10 +7,14 @@ use GraphQL\Error\InvariantViolation; use GraphQL\Tests\Type\TestClasses\MyCustomType; use GraphQL\Tests\Type\TestClasses\OtherCustom; +use GraphQL\Type\Definition\BooleanType; use GraphQL\Type\Definition\CustomScalarType; use GraphQL\Type\Definition\EnumType; +use GraphQL\Type\Definition\FloatType; +use GraphQL\Type\Definition\IDType; use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\InterfaceType; +use GraphQL\Type\Definition\IntType; use GraphQL\Type\Definition\ListOfType; use GraphQL\Type\Definition\NonNull; use GraphQL\Type\Definition\ObjectType; @@ -481,6 +485,16 @@ public function testIdentifiesInputTypes() : void [$this->unionType, false], [$this->enumType, true], [$this->inputObjectType, true], + + [Type::boolean(), true], + [Type::float(),true ], + [Type::id(), true], + [Type::int(), true], + [Type::listOf(Type::string()), true], + [Type::listOf($this->objectType), false], + [Type::nonNull(Type::string()), true], + [Type::nonNull($this->objectType), false], + [Type::string(), true], ]; foreach ($expected as $index => $entry) { @@ -504,6 +518,16 @@ public function testIdentifiesOutputTypes() : void [$this->unionType, true], [$this->enumType, true], [$this->inputObjectType, false], + + [Type::boolean(), true], + [Type::float(),true ], + [Type::id(), true], + [Type::int(), true], + [Type::listOf(Type::string()), true], + [Type::listOf($this->objectType), true], + [Type::nonNull(Type::string()), true], + [Type::nonNull($this->objectType), true], + [Type::string(), true], ]; foreach ($expected as $index => $entry) { @@ -515,18 +539,6 @@ public function testIdentifiesOutputTypes() : void } } - /** - * @see it('prohibits nesting NonNull inside NonNull') - */ - public function testProhibitsNestingNonNullInsideNonNull() : void - { - $this->expectException(InvariantViolation::class); - $this->expectExceptionMessage( - 'Expected Int! to be a GraphQL nullable type.' - ); - Type::nonNull(Type::nonNull(Type::int())); - } - /** * @see it('allows a thunk for Union member types') */ @@ -1585,84 +1597,6 @@ public function testDoesNotAllowIsDeprecatedWithoutDeprecationReasonOnEnum() : v $enumType->assertValid(); } - /** - * Type System: List must accept only types - */ - public function testListMustAcceptOnlyTypes() : void - { - $types = [ - Type::string(), - $this->scalarType, - $this->objectType, - $this->unionType, - $this->interfaceType, - $this->enumType, - $this->inputObjectType, - Type::listOf(Type::string()), - Type::nonNull(Type::string()), - ]; - - $badTypes = [[], new stdClass(), '', null]; - - foreach ($types as $type) { - try { - Type::listOf($type); - } catch (Throwable $e) { - self::fail('List is expected to accept type: ' . get_class($type) . ', but got error: ' . $e->getMessage()); - } - } - foreach ($badTypes as $badType) { - $typeStr = Utils::printSafe($badType); - try { - Type::listOf($badType); - self::fail(sprintf('List should not accept %s', $typeStr)); - } catch (InvariantViolation $e) { - self::assertEquals(sprintf('Expected %s to be a GraphQL type.', $typeStr), $e->getMessage()); - } - } - } - - /** - * Type System: NonNull must only accept non-nullable types - */ - public function testNonNullMustOnlyAcceptNonNullableTypes() : void - { - $nullableTypes = [ - Type::string(), - $this->scalarType, - $this->objectType, - $this->unionType, - $this->interfaceType, - $this->enumType, - $this->inputObjectType, - Type::listOf(Type::string()), - Type::listOf(Type::nonNull(Type::string())), - ]; - $notNullableTypes = [ - Type::nonNull(Type::string()), - [], - new stdClass(), - '', - null, - ]; - foreach ($nullableTypes as $type) { - try { - Type::nonNull($type); - } catch (Throwable $e) { - self::fail('NonNull is expected to accept type: ' . get_class($type) . ', but got error: ' . $e->getMessage()); - } - } - foreach ($notNullableTypes as $badType) { - $typeStr = Utils::printSafe($badType); - try { - Type::nonNull($badType); - self::fail(sprintf('Nulls should not accept %s', $typeStr)); - } catch (InvariantViolation $e) { - self::assertEquals(sprintf('Expected %s to be a GraphQL nullable type.', $typeStr), $e->getMessage()); - } - } - } - /** * @see it('rejects a Schema which redefines a built-in type') */ diff --git a/tests/Type/ValidationTest.php b/tests/Type/ValidationTest.php index 338e0d528..d5a68b738 100644 --- a/tests/Type/ValidationTest.php +++ b/tests/Type/ValidationTest.php @@ -1304,20 +1304,6 @@ private function schemaWithObjectFieldOfType($fieldType) : Schema ]); } - /** - * @see it('rejects an empty Object field type') - */ - public function testRejectsAnEmptyObjectFieldType() : void - { - $schema = $this->schemaWithObjectFieldOfType(null); - - $this->assertMatchesValidationMessage( - $schema->validate(), - [['message' => 'The type of BadObject.badField must be Output Type but got: null.'], - ] - ); - } - /** * @see it('rejects a non-output type as an Object field type') */ @@ -1336,21 +1322,6 @@ public function testRejectsANonOutputTypeAsAnObjectFieldType() : void } } - /** - * @see it('rejects a non-type value as an Object field type') - */ - public function testRejectsANonTypeValueAsAnObjectFieldType() - { - $schema = $this->schemaWithObjectFieldOfType($this->Number); - $this->assertMatchesValidationMessage( - $schema->validate(), - [ - ['message' => 'The type of BadObject.badField must be Output Type but got: 1.'], - ['message' => 'Expected GraphQL named type but got: 1.'], - ] - ); - } - /** * @see it('rejects with relevant locations for a non-output type as an Object field type') */ @@ -1665,21 +1636,6 @@ private function schemaWithInterfaceFieldOfType($fieldType) ]); } - /** - * @see it('rejects an empty Interface field type') - */ - public function testRejectsAnEmptyInterfaceFieldType() : void - { - $schema = $this->schemaWithInterfaceFieldOfType(null); - $this->assertMatchesValidationMessage( - $schema->validate(), - [ - ['message' => 'The type of BadInterface.badField must be Output Type but got: null.'], - ['message' => 'The type of BadImplementing.badField must be Output Type but got: null.'], - ] - ); - } - /** * @see it('rejects a non-output type as an Interface field type') */ @@ -1698,22 +1654,6 @@ public function testRejectsANonOutputTypeAsAnInterfaceFieldType() : void } } - /** - * @see it('rejects a non-type value as an Interface field type') - */ - public function testRejectsANonTypeValueAsAnInterfaceFieldType() - { - $schema = $this->schemaWithInterfaceFieldOfType('string'); - $this->assertMatchesValidationMessage( - $schema->validate(), - [ - ['message' => 'The type of BadInterface.badField must be Output Type but got: string.'], - ['message' => 'Expected GraphQL named type but got: string.'], - ['message' => 'The type of BadImplementing.badField must be Output Type but got: string.'], - ] - ); - } - // DESCRIBE: Type System: Input Object fields must have input types /** @@ -1809,19 +1749,6 @@ private function schemaWithArgOfType($argType) ]); } - /** - * @see it('rejects an empty field arg type') - */ - public function testRejectsAnEmptyFieldArgType() : void - { - $schema = $this->schemaWithArgOfType(null); - $this->assertMatchesValidationMessage( - $schema->validate(), - [['message' => 'The type of BadObject.badField(badArg:) must be Input Type but got: null.'], - ] - ); - } - // DESCRIBE: Objects must adhere to Interface they implement /** @@ -1840,21 +1767,6 @@ public function testRejectsANonInputTypeAsAFieldArgType() : void } } - /** - * @see it('rejects a non-type value as a field arg type') - */ - public function testRejectsANonTypeValueAsAFieldArgType() - { - $schema = $this->schemaWithArgOfType('string'); - $this->assertMatchesValidationMessage( - $schema->validate(), - [ - ['message' => 'The type of BadObject.badField(badArg:) must be Input Type but got: string.'], - ['message' => 'Expected GraphQL named type but got: string.'], - ] - ); - } - /** * @see it('rejects a non-input type as a field arg with locations') */ @@ -1892,7 +1804,7 @@ public function testAcceptsAnInputTypeAsAnInputFieldType() : void private function schemaWithInputFieldOfType($inputFieldType) { - $BadInputObjectType = new InputObjectType([ + $badInputObjectType = new InputObjectType([ 'name' => 'BadInputObject', 'fields' => [ 'badField' => ['type' => $inputFieldType], @@ -1906,7 +1818,7 @@ private function schemaWithInputFieldOfType($inputFieldType) 'f' => [ 'type' => Type::string(), 'args' => [ - 'badArg' => ['type' => $BadInputObjectType], + 'badArg' => ['type' => $badInputObjectType], ], ], ], @@ -1915,19 +1827,6 @@ private function schemaWithInputFieldOfType($inputFieldType) ]); } - /** - * @see it('rejects an empty input field type') - */ - public function testRejectsAnEmptyInputFieldType() : void - { - $schema = $this->schemaWithInputFieldOfType(null); - $this->assertMatchesValidationMessage( - $schema->validate(), - [['message' => 'The type of BadInputObject.badField must be Input Type but got: null.'], - ] - ); - } - /** * @see it('rejects a non-input type as an input field type') */ @@ -1945,21 +1844,6 @@ public function testRejectsANonInputTypeAsAnInputFieldType() : void } } - /** - * @see it('rejects a non-type value as an input field type') - */ - public function testRejectsAAonTypeValueAsAnInputFieldType() - { - $schema = $this->schemaWithInputFieldOfType('string'); - $this->assertMatchesValidationMessage( - $schema->validate(), - [ - ['message' => 'The type of BadInputObject.badField must be Input Type but got: string.'], - ['message' => 'Expected GraphQL named type but got: string.'], - ] - ); - } - /** * @see it('rejects a non-input type as an input object field with locations') */ diff --git a/tests/Utils/BreakingChangesFinderTest.php b/tests/Utils/BreakingChangesFinderTest.php index 5e46d330b..db48d3331 100644 --- a/tests/Utils/BreakingChangesFinderTest.php +++ b/tests/Utils/BreakingChangesFinderTest.php @@ -1379,7 +1379,10 @@ public function testShouldDetectIfADirectiveArgumentWasRemoved() : void 'name' => 'DirectiveWithArg', 'locations' => [DirectiveLocation::FIELD_DEFINITION], 'args' => FieldArgument::createMap([ - 'arg1' => ['name' => 'arg1'], + 'arg1' => [ + 'name' => 'arg1', + 'type' => Type::string(), + ], ]), ]), ], From 3d66c7c9cef823584bbe29dcf81c2393c40ec453 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Sun, 3 Nov 2019 13:37:18 +0700 Subject: [PATCH 115/256] Restored several tests that were skipped previously --- tests/Type/ValidationTest.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/Type/ValidationTest.php b/tests/Type/ValidationTest.php index d5a68b738..9676c2059 100644 --- a/tests/Type/ValidationTest.php +++ b/tests/Type/ValidationTest.php @@ -2459,8 +2459,6 @@ public function testRejectsASchemaWithDirectiveDefinedMultipleTimes() */ public function testRejectsASchemaWithSameSchemaDirectiveUsedTwice() { - self::markTestSkipped(); - $schema = BuildSchema::build(' directive @schema on SCHEMA directive @object on OBJECT @@ -2497,7 +2495,7 @@ enum SomeEnum @enum @enum { input SomeInput @input_object @input_object { some_input_field: String @input_field_definition @input_field_definition } - '); + ', null, ['assumeValid' => true]); $this->assertMatchesValidationMessage( $schema->validate(), [ @@ -2579,8 +2577,6 @@ public function testRejectsASchemaWithSameDefinitionDirectiveUsedTwice() */ public function testRejectsASchemaWithDirectivesUsedInWrongLocation() { - self::markTestSkipped(); - $schema = BuildSchema::build(' directive @schema on SCHEMA directive @object on OBJECT @@ -2617,7 +2613,7 @@ enum SomeEnum @input_object { input SomeInput @object { some_input_field: String @union } - '); + ', null, ['assumeValid' => true]); $extensions = Parser::parse(' extend type Query @testA From bcd97a3d127b2531583df57e5227194674e7e09f Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Sun, 3 Nov 2019 13:41:36 +0700 Subject: [PATCH 116/256] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 2698b4772..115505634 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,7 @@ composer require webonyx/graphql-php Full documentation is available on the [Documentation site](https://webonyx.github.io/graphql-php/) as well as in the [docs](docs/) folder of the distribution. -If you don't know what GraphQL is, visit this [official website](http://graphql.org) -by the Facebook engineering team. +If you don't know what GraphQL is, visit the [official website](http://graphql.org) first. ## Examples There are several ready examples in the [examples](examples/) folder of the distribution with specific From d447285aca40a916546e2b9fd050cbc5c87cbc03 Mon Sep 17 00:00:00 2001 From: Markus Podar Date: Tue, 5 Nov 2019 11:29:37 +0100 Subject: [PATCH 117/256] Remove config validation section At first I wanted to improve it but then I realized it's basically a duplicate of https://github.com/webonyx/graphql-php/blob/master/docs/type-system/schema.md#schema-validation This "duplicate" likely exactly lead to this situation that there's now outdated information. TL;DR: I think simply removing it here is enough as there's only one clear location now discussing schema validation --- docs/best-practices.md | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/docs/best-practices.md b/docs/best-practices.md index a5f135eb5..4b7fc247b 100644 --- a/docs/best-practices.md +++ b/docs/best-practices.md @@ -1,13 +1,3 @@ -# Config Validation -Defining types using arrays may be error-prone, but **graphql-php** provides config validation -tool to report when config has unexpected structure. - -This validation tool is **disabled by default** because it is time-consuming operation which only -makes sense during development. - -To enable validation - call: `GraphQL\Type\Definition\Config::enableValidation();` in your bootstrap -but make sure to restrict it to debug/development mode only. - # Type Registry **graphql-php** expects that each type in Schema is presented by single instance. Therefore if you define your types as separate PHP classes you need to ensure that each type is referenced only once. @@ -16,4 +6,4 @@ Technically you can create several instances of your type (for example for tests will throw on attempt to add different instances with the same name. There are several ways to achieve this depending on your preferences. We provide reference -implementation below that introduces TypeRegistry class: \ No newline at end of file +implementation below that introduces TypeRegistry class: From 5e4ad8ecbaefc2c244f88d309ec6d7084340374d Mon Sep 17 00:00:00 2001 From: spawnia Date: Sun, 10 Nov 2019 20:56:33 +0100 Subject: [PATCH 118/256] Shorten the composer scripts commands That makes them quicker to type and saves time. --- .travis.yml | 2 +- CONTRIBUTING.md | 2 +- composer.json | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index ac708190c..e6a6a6d30 100644 --- a/.travis.yml +++ b/.travis.yml @@ -62,5 +62,5 @@ jobs: php: 7.1 env: STATIC_ANALYSIS install: travis_retry composer install --prefer-dist - script: composer static-analysis + script: composer stan diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d51a0ac9a..4179badfd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ For smaller contributions just use this workflow: * Fork the project. * Add your features and or bug fixes. * Add tests. Tests are important for us. -* Check your changes using `composer check-all`. +* Check your changes using `composer check`. * Add an entry to the [Changelog's Unreleases section](CHANGELOG.md#unreleased). * Send a pull request. diff --git a/composer.json b/composer.json index a16badfe5..f27b8c9d4 100644 --- a/composer.json +++ b/composer.json @@ -50,8 +50,8 @@ "bench": "phpbench run .", "test": "phpunit", "lint" : "phpcs", - "fix-style" : "phpcbf", - "static-analysis": "phpstan analyse --ansi --memory-limit 256M", - "check-all": "composer lint && composer static-analysis && composer test" + "fix" : "phpcbf", + "stan": "phpstan analyse --ansi --memory-limit 256M", + "check": "composer lint && composer stan && composer test" } } From a08c00100bf9686497bc391ec4a02988ef95f9ac Mon Sep 17 00:00:00 2001 From: Alan Poulain Date: Tue, 12 Nov 2019 15:21:54 +0100 Subject: [PATCH 119/256] Category should be under extensions --- docs/error-handling.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/error-handling.md b/docs/error-handling.md index 17b0a2a4e..87e79fd36 100644 --- a/docs/error-handling.md +++ b/docs/error-handling.md @@ -17,7 +17,9 @@ By default, each error entry is converted to an associative array with following 'Error message', - 'category' => 'graphql', + 'extensions' => [ + 'category' => 'graphql' + ], 'locations' => [ ['line' => 1, 'column' => 2] ], @@ -67,7 +69,9 @@ When such exception is thrown it will be reported with a full error message: 'My reported error', - 'category' => 'businessLogic', + 'extensions' => [ + 'category' => 'businessLogic' + ], 'locations' => [ ['line' => 10, 'column' => 2] ], @@ -101,7 +105,9 @@ This will make each error entry to look like this: [ 'debugMessage' => 'Actual exception message', 'message' => 'Internal server error', - 'category' => 'internal', + 'extensions' => [ + 'category' => 'internal' + ], 'locations' => [ ['line' => 10, 'column' => 2] ], From 03546fec3e6bdcd40f0bf3eaad6c63728b1a1d20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joshua=20L=C3=BCckers?= Date: Wed, 13 Nov 2019 09:45:10 +0100 Subject: [PATCH 120/256] Fix broken link to the Slim documentation about PSR-7 --- docs/executing-queries.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/executing-queries.md b/docs/executing-queries.md index 29388a2d1..d3e8821d7 100644 --- a/docs/executing-queries.md +++ b/docs/executing-queries.md @@ -88,7 +88,7 @@ PSR-7 is useful when you want to integrate the server into existing framework: - [PSR-7 for Laravel](https://laravel.com/docs/5.1/requests#psr7-requests) - [Symfony PSR-7 Bridge](https://symfony.com/doc/current/components/psr7.html) -- [Slim](https://www.slimframework.com/docs/concepts/value-objects.html) +- [Slim](https://www.slimframework.com/docs/v4/concepts/value-objects.html) - [Zend Expressive](http://zendframework.github.io/zend-expressive/) ## Server configuration options From 46a99a34c1dc20fc85673e45989f2702d4be3671 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Sat, 16 Nov 2019 17:10:22 +0700 Subject: [PATCH 121/256] Add test for deferred chaining --- tests/Executor/DeferredFieldsTest.php | 135 +++++++++++++++++++++++--- 1 file changed, 123 insertions(+), 12 deletions(-) diff --git a/tests/Executor/DeferredFieldsTest.php b/tests/Executor/DeferredFieldsTest.php index e2caf2e52..6cab92c3e 100644 --- a/tests/Executor/DeferredFieldsTest.php +++ b/tests/Executor/DeferredFieldsTest.php @@ -123,12 +123,7 @@ static function ($entry) use ($user) { return new Deferred(function () use ($story) { $this->paths[] = 'deferred-for-story-' . $story['id'] . '-author'; - return Utils::find( - $this->userDataSource, - static function ($entry) use ($story) { - return $entry['id'] === $story['authorId']; - } - ); + return $this->findUserById($story['authorId']); }); }, ], @@ -168,12 +163,24 @@ static function ($story) use ($category) { return new Deferred(function () use ($category) { $this->paths[] = 'deferred-for-category-' . $category['id'] . '-topStory'; - return Utils::find( - $this->storyDataSource, - static function ($story) use ($category) { - return $story['id'] === $category['topStoryId']; - } - ); + return $this->findStoryById($category['topStoryId']); + }); + }, + ], + 'topStoryAuthor' => [ + 'type' => $this->userType, + 'resolve' => function ($category, $args, $context, ResolveInfo $info) { + $this->paths[] = $info->path; + + return new Deferred(function () use ($category) { + $this->paths[] = 'deferred-for-category-' . $category['id'] . '-topStoryAuthor1'; + $story = $this->findStoryById($category['topStoryId']); + + return new Deferred(function () use ($category, $story) { + $this->paths[] = 'deferred-for-category-' . $category['id'] . '-topStoryAuthor2'; + + return $this->findUserById($story['authorId']); + }); }); }, ], @@ -539,9 +546,113 @@ public function testComplexRecursiveDeferredFields() : void ['!dfd for: ', ['deferredNest', 'deferredNest', 'deferred']], ]; + // Note: not using self::assertEquals() because coroutineExecutor has a different sequence of calls self::assertCount(count($expectedPaths), $this->paths); foreach ($expectedPaths as $expectedPath) { self::assertTrue(in_array($expectedPath, $this->paths, true), 'Missing path: ' . json_encode($expectedPath)); } } + + public function testDeferredChaining() + { + $schema = new Schema([ + 'query' => $this->queryType, + ]); + + $query = Parser::parse(' + { + categories { + name + topStory { + title + author { + name + } + } + topStoryAuthor { + name + } + } + } + '); + + $author1 = ['name' => 'John'/*, 'bestFriend' => ['name' => 'Dirk']*/]; + $author2 = ['name' => 'Jane'/*, 'bestFriend' => ['name' => 'Joe']*/]; + $author3 = ['name' => 'Joe'/*, 'bestFriend' => ['name' => 'Jane']*/]; + $author4 = ['name' => 'Dirk'/*, 'bestFriend' => ['name' => 'John']*/]; + + $story1 = ['title' => 'Story #8', 'author' => $author1]; + $story2 = ['title' => 'Story #3', 'author' => $author3]; + $story3 = ['title' => 'Story #9', 'author' => $author2]; + + $result = Executor::execute($schema, $query); + $expected = [ + 'data' => [ + 'categories' => [ + ['name' => 'Category #1', 'topStory' => $story1, 'topStoryAuthor' => $author1], + ['name' => 'Category #2', 'topStory' => $story2, 'topStoryAuthor' => $author3], + ['name' => 'Category #3', 'topStory' => $story3, 'topStoryAuthor' => $author2], + ], + ], + ]; + self::assertEquals($expected, $result->toArray()); + + $expectedPaths = [ + ['categories'], + ['categories', 0, 'name'], + ['categories', 0, 'topStory'], + ['categories', 0, 'topStoryAuthor'], + ['categories', 1, 'name'], + ['categories', 1, 'topStory'], + ['categories', 1, 'topStoryAuthor'], + ['categories', 2, 'name'], + ['categories', 2, 'topStory'], + ['categories', 2, 'topStoryAuthor'], + 'deferred-for-category-1-topStory', + 'deferred-for-category-1-topStoryAuthor1', + 'deferred-for-category-2-topStory', + 'deferred-for-category-2-topStoryAuthor1', + 'deferred-for-category-3-topStory', + 'deferred-for-category-3-topStoryAuthor1', + 'deferred-for-category-1-topStoryAuthor2', + 'deferred-for-category-2-topStoryAuthor2', + 'deferred-for-category-3-topStoryAuthor2', + ['categories', 0, 'topStory', 'title'], + ['categories', 0, 'topStory', 'author'], + ['categories', 1, 'topStory', 'title'], + ['categories', 1, 'topStory', 'author'], + ['categories', 2, 'topStory', 'title'], + ['categories', 2, 'topStory', 'author'], + ['categories', 0, 'topStoryAuthor', 'name'], + ['categories', 1, 'topStoryAuthor', 'name'], + ['categories', 2, 'topStoryAuthor', 'name'], + 'deferred-for-story-8-author', + 'deferred-for-story-3-author', + 'deferred-for-story-9-author', + ['categories', 0, 'topStory', 'author', 'name'], + ['categories', 1, 'topStory', 'author', 'name'], + ['categories', 2, 'topStory', 'author', 'name'], + ]; + self::assertEquals($expectedPaths, $this->paths); + } + + private function findStoryById($id) + { + return Utils::find( + $this->storyDataSource, + static function ($story) use ($id) { + return $story['id'] === $id; + } + ); + } + + private function findUserById($id) + { + return Utils::find( + $this->userDataSource, + static function ($user) use ($id) { + return $user['id'] === $id; + } + ); + } } From 9613ab5e1878978b74dfaedc43df6e1134434843 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Sun, 17 Nov 2019 15:42:45 +0100 Subject: [PATCH 122/256] Drop scrutinizer checks as they're handled by PHPStan and code sniffer --- .scrutinizer.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index de038a3fd..7c9926a5e 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -24,6 +24,4 @@ tools: build_failure_conditions: - 'elements.rating(<= C).new.exists' # No new classes/methods with a rating of C or worse allowed - - 'issues.label("coding-style").new.exists' # No new coding style issues allowed - - 'issues.severity(>= MAJOR).new.exists' # New issues of major or higher severity - 'project.metric_change("scrutinizer.test_coverage", < 0)' # Code Coverage decreased from previous inspection From 8d639ccaf9350535f5a74cd89e44df584de9cf45 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Wed, 20 Nov 2019 11:55:19 +0100 Subject: [PATCH 123/256] Unify implementation of defaultValueExists --- src/Type/Definition/InputObjectField.php | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/Type/Definition/InputObjectField.php b/src/Type/Definition/InputObjectField.php index b2bd951f0..b9b886b3e 100644 --- a/src/Type/Definition/InputObjectField.php +++ b/src/Type/Definition/InputObjectField.php @@ -30,13 +30,6 @@ class InputObjectField /** @var mixed[] */ public $config; - /** - * Helps to differentiate when `defaultValue` is `null` and when it was not even set initially - * - * @var bool - */ - private $defaultValueExists = false; - /** * @param mixed[] $opts */ @@ -46,7 +39,6 @@ public function __construct(array $opts) switch ($k) { case 'defaultValue': $this->defaultValue = $v; - $this->defaultValueExists = true; break; case 'defaultValueExists': break; @@ -65,12 +57,9 @@ public function getType() : Type return $this->type; } - /** - * @return bool - */ - public function defaultValueExists() + public function defaultValueExists() : bool { - return $this->defaultValueExists; + return array_key_exists('defaultValue', $this->config); } /** From 86c383555a1f98b82572bc520903a1f67feb9e34 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Wed, 20 Nov 2019 17:25:32 +0100 Subject: [PATCH 124/256] Scrutinizer: Analyze only src --- .scrutinizer.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 7c9926a5e..21f09aae0 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -22,6 +22,10 @@ tools: external_code_coverage: timeout: 3600 +filter: + paths: + - "src/" + build_failure_conditions: - 'elements.rating(<= C).new.exists' # No new classes/methods with a rating of C or worse allowed - 'project.metric_change("scrutinizer.test_coverage", < 0)' # Code Coverage decreased from previous inspection From c28dd21f397d635f356def44b99422a2a89c922b Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Thu, 21 Nov 2019 12:50:44 +0100 Subject: [PATCH 125/256] composer fix --- src/Type/Definition/InputObjectField.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Type/Definition/InputObjectField.php b/src/Type/Definition/InputObjectField.php index b9b886b3e..0284a72b5 100644 --- a/src/Type/Definition/InputObjectField.php +++ b/src/Type/Definition/InputObjectField.php @@ -8,6 +8,7 @@ use GraphQL\Error\InvariantViolation; use GraphQL\Language\AST\InputValueDefinitionNode; use GraphQL\Utils\Utils; +use function array_key_exists; use function sprintf; class InputObjectField @@ -38,7 +39,7 @@ public function __construct(array $opts) foreach ($opts as $k => $v) { switch ($k) { case 'defaultValue': - $this->defaultValue = $v; + $this->defaultValue = $v; break; case 'defaultValueExists': break; From 97e49bdcaa996dd18db34272a98714579b7cff7d Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Mon, 9 Dec 2019 14:09:15 +0100 Subject: [PATCH 126/256] Drop unused uses --- src/Experimental/Executor/CoroutineExecutor.php | 3 --- src/Type/Schema.php | 1 - src/Utils/TypeComparators.php | 2 -- src/Utils/TypeInfo.php | 5 ----- src/Validator/Rules/OverlappingFieldsCanBeMerged.php | 1 - .../IsOutputTypeStaticMethodTypeSpecifyingExtension.php | 1 - tests/Type/DefinitionTest.php | 7 ------- 7 files changed, 20 deletions(-) diff --git a/src/Experimental/Executor/CoroutineExecutor.php b/src/Experimental/Executor/CoroutineExecutor.php index 30c3964f1..5f14f0999 100644 --- a/src/Experimental/Executor/CoroutineExecutor.php +++ b/src/Experimental/Executor/CoroutineExecutor.php @@ -33,15 +33,12 @@ use GraphQL\Type\Introspection; use GraphQL\Type\Schema; use GraphQL\Utils\AST; -use GraphQL\Utils\TypeInfo; use GraphQL\Utils\Utils; use SplQueue; use stdClass; use Throwable; use function is_array; use function is_string; -use function json_decode; -use function json_encode; use function sprintf; class CoroutineExecutor implements Runtime, ExecutorImplementation diff --git a/src/Type/Schema.php b/src/Type/Schema.php index 272e9ea44..0ec16b6f4 100644 --- a/src/Type/Schema.php +++ b/src/Type/Schema.php @@ -20,7 +20,6 @@ use GraphQL\Utils\Utils; use Traversable; use function array_values; -use function count; use function implode; use function is_array; use function is_callable; diff --git a/src/Utils/TypeComparators.php b/src/Utils/TypeComparators.php index 508acc2d5..fccc06f1c 100644 --- a/src/Utils/TypeComparators.php +++ b/src/Utils/TypeComparators.php @@ -6,12 +6,10 @@ use GraphQL\Type\Definition\AbstractType; use GraphQL\Type\Definition\CompositeType; -use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\ListOfType; use GraphQL\Type\Definition\NonNull; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; -use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Schema; class TypeComparators diff --git a/src/Utils/TypeInfo.php b/src/Utils/TypeInfo.php index 89ec87f37..0cab63cd7 100644 --- a/src/Utils/TypeInfo.php +++ b/src/Utils/TypeInfo.php @@ -29,23 +29,18 @@ use GraphQL\Type\Definition\InputType; use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\ListOfType; -use GraphQL\Type\Definition\NonNull; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\OutputType; -use GraphQL\Type\Definition\ScalarType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Definition\WrappingType; use GraphQL\Type\Introspection; use GraphQL\Type\Schema; -use SplStack; -use Symfony\Component\Console\Output\Output; use function array_map; use function array_merge; use function array_pop; use function count; use function is_array; -use function is_null; use function sprintf; class TypeInfo diff --git a/src/Validator/Rules/OverlappingFieldsCanBeMerged.php b/src/Validator/Rules/OverlappingFieldsCanBeMerged.php index b2f3ba520..350318867 100644 --- a/src/Validator/Rules/OverlappingFieldsCanBeMerged.php +++ b/src/Validator/Rules/OverlappingFieldsCanBeMerged.php @@ -19,7 +19,6 @@ use GraphQL\Type\Definition\ListOfType; use GraphQL\Type\Definition\NonNull; use GraphQL\Type\Definition\ObjectType; -use GraphQL\Type\Definition\OutputType; use GraphQL\Type\Definition\Type; use GraphQL\Utils\PairSet; use GraphQL\Utils\TypeInfo; diff --git a/tests/PhpStan/Type/Definition/Type/IsOutputTypeStaticMethodTypeSpecifyingExtension.php b/tests/PhpStan/Type/Definition/Type/IsOutputTypeStaticMethodTypeSpecifyingExtension.php index 6dd1ae744..c348d21e7 100644 --- a/tests/PhpStan/Type/Definition/Type/IsOutputTypeStaticMethodTypeSpecifyingExtension.php +++ b/tests/PhpStan/Type/Definition/Type/IsOutputTypeStaticMethodTypeSpecifyingExtension.php @@ -4,7 +4,6 @@ namespace GraphQL\Tests\PhpStan\Type\Definition\Type; -use GraphQL\Type\Definition\InputType; use GraphQL\Type\Definition\OutputType; use GraphQL\Type\Definition\Type; use PhpParser\Node\Expr\StaticCall; diff --git a/tests/Type/DefinitionTest.php b/tests/Type/DefinitionTest.php index 98da41dfa..54b0d5a40 100644 --- a/tests/Type/DefinitionTest.php +++ b/tests/Type/DefinitionTest.php @@ -7,26 +7,19 @@ use GraphQL\Error\InvariantViolation; use GraphQL\Tests\Type\TestClasses\MyCustomType; use GraphQL\Tests\Type\TestClasses\OtherCustom; -use GraphQL\Type\Definition\BooleanType; use GraphQL\Type\Definition\CustomScalarType; use GraphQL\Type\Definition\EnumType; -use GraphQL\Type\Definition\FloatType; -use GraphQL\Type\Definition\IDType; use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\InterfaceType; -use GraphQL\Type\Definition\IntType; use GraphQL\Type\Definition\ListOfType; use GraphQL\Type\Definition\NonNull; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Schema; -use GraphQL\Utils\Utils; use PHPUnit\Framework\TestCase; use stdClass; -use Throwable; use function count; -use function get_class; use function json_encode; use function sprintf; From ba4a7cd2c021a0aaf781dc076d3135d391ab8907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1chym=20Tou=C5=A1ek?= Date: Tue, 10 Dec 2019 14:34:47 +0100 Subject: [PATCH 127/256] Remove useless parenthesis --- src/Validator/DocumentValidator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Validator/DocumentValidator.php b/src/Validator/DocumentValidator.php index 156c2ec92..7df559621 100644 --- a/src/Validator/DocumentValidator.php +++ b/src/Validator/DocumentValidator.php @@ -268,7 +268,7 @@ static function ($item) { return $item instanceof Throwable; } )) === count($value) - : ($value instanceof Throwable); + : $value instanceof Throwable; } public static function append(&$arr, $items) From 8c8abac5112a3c2dd9f096c724ddfa0385c9697f Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Thu, 12 Dec 2019 17:51:00 +0100 Subject: [PATCH 128/256] Upgrade to phpstan 0.12 (#592) * wip phpstan 0.12 * Fix some errors, ignore others * improve generics * add required generic "casts" * Clean up phpstan.neon.dist ignoreErrors * Comment out failing type hint in NodeList.php * Strongly type Parser.php * Upgrade to phpstan 0.12.1 and remove ignored errors * Elaborate on NodeList/Visitor strict typing TODO * Revert new self -> new static * Add rationale for ignoring the new static() warning * Bump to phpstan 0.12.2 and adapt ignoreErrors --- composer.json | 10 +- phpstan.neon.dist | 33 +- src/Experimental/Executor/Collector.php | 9 + src/Language/AST/DocumentNode.php | 2 +- src/Language/AST/EnumTypeDefinitionNode.php | 2 +- src/Language/AST/FieldDefinitionNode.php | 4 +- src/Language/AST/FragmentDefinitionNode.php | 4 +- src/Language/AST/ListValueNode.php | 2 +- src/Language/AST/NodeList.php | 97 +++-- src/Language/AST/ObjectValueNode.php | 2 +- src/Language/Parser.php | 364 ++++++------------ src/Type/Definition/QueryPlan.php | 2 +- src/Utils/TypeInfo.php | 2 +- src/Validator/Rules/KnownArgumentNames.php | 2 +- src/Validator/Rules/KnownDirectives.php | 4 + .../ProvidedRequiredArgumentsOnDirectives.php | 2 +- tests/Error/ErrorTest.php | 28 +- tests/Error/PrintErrorTest.php | 9 +- tests/Executor/ExecutorTest.php | 8 +- tests/Language/ParserTest.php | 5 +- tests/Language/VisitorTest.php | 11 +- tests/Server/Psr7/PsrRequestStub.php | 2 +- tests/Utils/SchemaExtenderTest.php | 13 +- 23 files changed, 276 insertions(+), 341 deletions(-) diff --git a/composer.json b/composer.json index f27b8c9d4..5cc3d0514 100644 --- a/composer.json +++ b/composer.json @@ -15,10 +15,10 @@ }, "require-dev": { "doctrine/coding-standard": "^6.0", - "phpbench/phpbench": "^0.14.0", - "phpstan/phpstan": "0.11.16", - "phpstan/phpstan-phpunit": "0.11.2", - "phpstan/phpstan-strict-rules": "0.11.1", + "phpbench/phpbench": "^0.14", + "phpstan/phpstan": "0.12.2", + "phpstan/phpstan-phpunit": "0.12.1", + "phpstan/phpstan-strict-rules": "0.12.0", "phpunit/phpcov": "^5.0", "phpunit/phpunit": "^7.2", "psr/http-message": "^1.0", @@ -51,7 +51,7 @@ "test": "phpunit", "lint" : "phpcs", "fix" : "phpcbf", - "stan": "phpstan analyse --ansi --memory-limit 256M", + "stan": "phpstan analyse --ansi --memory-limit 2048M", "check": "composer lint && composer stan && composer test" } } diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 31bb564ee..2290befea 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,8 +1,6 @@ parameters: level: 3 - reportUnmatchedIgnoredErrors: false - inferPrivatePropertyTypeFromConstructor: true paths: @@ -10,22 +8,33 @@ parameters: - %currentWorkingDirectory%/tests ignoreErrors: - - "~Construct empty\\(\\) is not allowed\\. Use more strict comparison~" - - "~(Method|Property) .+::.+(\\(\\))? (has parameter \\$\\w+ with no|has no return|has no) typehint specified~" - - "~Variable property access on .+~" + # Since this is a library that is supposed to be flexible, we don't + # want to lock down every possible extension point. + - "~Unsafe usage of new static\\(\\)~" + + # This class uses magic methods to reduce a whole lot of boilerplate required to + # allow partial parsing of language fragments. - "~Variable method call on GraphQL\\\\Language\\\\Parser\\.~" - - "~Variable method call on static\\(GraphQL\\\\Server\\\\ServerConfig\\)~" # TODO get rid of - - "~Only booleans are allowed in .*~" # TODO https://github.com/phpstan/phpstan-strict-rules/issues/2 - # A whole class of errors in PHPStan is a result of PHP's lack of union types. - # This commonly happens in the parts of the code that deal with the GraphQL - # type system where we can currently use interfaces and lose type safety. - # Until we find a better way, we can list related error's here. - - "~Access to an undefined property GraphQL\\\\Type\\\\Definition\\\\NamedType::\\$name~" + # Those come from graphql-php\tests\Language\VisitorTest.php - "~Access to an undefined property GraphQL\\\\Language\\\\AST\\\\.+::\\$didEnter~" - "~Access to an undefined property GraphQL\\\\Language\\\\AST\\\\.+::\\$didLeave~" - "~Access to an undefined property GraphQL\\\\Language\\\\AST\\\\Node::\\$value~" + # TODO fix those errors to improve type safety + - "~Construct empty\\(\\) is not allowed\\. Use more strict comparison~" + - "~Variable property access on .+~" + - "~Anonymous function should have native return typehint~" + - "~Anonymous function should return .* but return statement is missing.~" + - "~Anonymous function sometimes return something but return statement at the end is missing.~" + - "~Short ternary operator is not allowed. Use null coalesce operator if applicable or consider using long ternary.~" + + # TODO convert to less magical code + - "~Variable method call on static\\(GraphQL\\\\Server\\\\ServerConfig\\)~" + + # TODO cast booleans explicitly https://github.com/phpstan/phpstan-strict-rules/issues/2 + - "~Only booleans are allowed in .*~" + includes: - vendor/phpstan/phpstan-phpunit/extension.neon - vendor/phpstan/phpstan-phpunit/rules.neon diff --git a/src/Experimental/Executor/Collector.php b/src/Experimental/Executor/Collector.php index 4f150e0d0..9dbffbebe 100644 --- a/src/Experimental/Executor/Collector.php +++ b/src/Experimental/Executor/Collector.php @@ -6,15 +6,24 @@ use Generator; use GraphQL\Error\Error; +use GraphQL\Language\AST\BooleanValueNode; use GraphQL\Language\AST\DefinitionNode; use GraphQL\Language\AST\DocumentNode; +use GraphQL\Language\AST\EnumValueNode; use GraphQL\Language\AST\FieldNode; +use GraphQL\Language\AST\FloatValueNode; use GraphQL\Language\AST\FragmentDefinitionNode; use GraphQL\Language\AST\FragmentSpreadNode; use GraphQL\Language\AST\InlineFragmentNode; +use GraphQL\Language\AST\IntValueNode; +use GraphQL\Language\AST\ListValueNode; use GraphQL\Language\AST\Node; +use GraphQL\Language\AST\NullValueNode; +use GraphQL\Language\AST\ObjectValueNode; use GraphQL\Language\AST\OperationDefinitionNode; use GraphQL\Language\AST\SelectionSetNode; +use GraphQL\Language\AST\StringValueNode; +use GraphQL\Language\AST\VariableNode; use GraphQL\Type\Definition\AbstractType; use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\ObjectType; diff --git a/src/Language/AST/DocumentNode.php b/src/Language/AST/DocumentNode.php index cc3e1d43d..ad421aa21 100644 --- a/src/Language/AST/DocumentNode.php +++ b/src/Language/AST/DocumentNode.php @@ -9,6 +9,6 @@ class DocumentNode extends Node /** @var string */ public $kind = NodeKind::DOCUMENT; - /** @var NodeList|array */ + /** @var NodeList */ public $definitions; } diff --git a/src/Language/AST/EnumTypeDefinitionNode.php b/src/Language/AST/EnumTypeDefinitionNode.php index 4ecd8fd56..d104363c3 100644 --- a/src/Language/AST/EnumTypeDefinitionNode.php +++ b/src/Language/AST/EnumTypeDefinitionNode.php @@ -15,7 +15,7 @@ class EnumTypeDefinitionNode extends Node implements TypeDefinitionNode /** @var DirectiveNode[] */ public $directives; - /** @var EnumValueDefinitionNode[]|NodeList|null */ + /** @var NodeList|null */ public $values; /** @var StringValueNode|null */ diff --git a/src/Language/AST/FieldDefinitionNode.php b/src/Language/AST/FieldDefinitionNode.php index 950db0fd5..9b9f20155 100644 --- a/src/Language/AST/FieldDefinitionNode.php +++ b/src/Language/AST/FieldDefinitionNode.php @@ -12,13 +12,13 @@ class FieldDefinitionNode extends Node /** @var NameNode */ public $name; - /** @var InputValueDefinitionNode[]|NodeList */ + /** @var NodeList */ public $arguments; /** @var NamedTypeNode|ListTypeNode|NonNullTypeNode */ public $type; - /** @var DirectiveNode[]|NodeList */ + /** @var NodeList */ public $directives; /** @var StringValueNode|null */ diff --git a/src/Language/AST/FragmentDefinitionNode.php b/src/Language/AST/FragmentDefinitionNode.php index 5c53e4a40..5cef904c2 100644 --- a/src/Language/AST/FragmentDefinitionNode.php +++ b/src/Language/AST/FragmentDefinitionNode.php @@ -16,14 +16,14 @@ class FragmentDefinitionNode extends Node implements ExecutableDefinitionNode, H * Note: fragment variable definitions are experimental and may be changed * or removed in the future. * - * @var VariableDefinitionNode[]|NodeList + * @var NodeList */ public $variableDefinitions; /** @var NamedTypeNode */ public $typeCondition; - /** @var DirectiveNode[]|NodeList */ + /** @var NodeList */ public $directives; /** @var SelectionSetNode */ diff --git a/src/Language/AST/ListValueNode.php b/src/Language/AST/ListValueNode.php index 3720c85a7..78792c942 100644 --- a/src/Language/AST/ListValueNode.php +++ b/src/Language/AST/ListValueNode.php @@ -9,6 +9,6 @@ class ListValueNode extends Node implements ValueNode /** @var string */ public $kind = NodeKind::LST; - /** @var ValueNode[]|NodeList */ + /** @var NodeList */ public $values; } diff --git a/src/Language/AST/NodeList.php b/src/Language/AST/NodeList.php index 648f68a71..62a776b24 100644 --- a/src/Language/AST/NodeList.php +++ b/src/Language/AST/NodeList.php @@ -6,31 +6,43 @@ use ArrayAccess; use Countable; -use Generator; use GraphQL\Utils\AST; +use InvalidArgumentException; use IteratorAggregate; +use Traversable; use function array_merge; use function array_splice; use function count; use function is_array; +/** + * @template T of Node + * @phpstan-implements ArrayAccess + * @phpstan-implements IteratorAggregate + */ class NodeList implements ArrayAccess, IteratorAggregate, Countable { - /** @var Node[]|mixed[] */ + /** + * @var Node[] + * @phpstan-var array + */ private $nodes; /** - * @param Node[]|mixed[] $nodes + * @param Node[] $nodes * - * @return static + * @phpstan-param array $nodes + * @phpstan-return self */ - public static function create(array $nodes) + public static function create(array $nodes) : self { return new static($nodes); } /** - * @param Node[]|mixed[] $nodes + * @param Node[] $nodes + * + * @phpstan-param array $nodes */ public function __construct(array $nodes) { @@ -38,59 +50,77 @@ public function __construct(array $nodes) } /** - * @param mixed $offset - * - * @return bool + * @param int|string $offset */ - public function offsetExists($offset) + public function offsetExists($offset) : bool { return isset($this->nodes[$offset]); } /** - * @param mixed $offset + * TODO enable strict typing by changing how the Visitor deals with NodeList. + * Ideally, this function should always return a Node instance. + * However, the Visitor currently allows mutation of the NodeList + * and puts arbitrary values in the NodeList, such as strings. + * We will have to switch to using an array or a less strict + * type instead so we can enable strict typing in this class. + * + * @param int|string $offset * - * @return mixed + * @phpstan-return T */ - public function offsetGet($offset) + public function offsetGet($offset)// : Node { $item = $this->nodes[$offset]; if (is_array($item) && isset($item['kind'])) { - $this->nodes[$offset] = $item = AST::fromArray($item); + /** @phpstan-var T $node */ + $node = AST::fromArray($item); + $this->nodes[$offset] = $node; } - return $item; + return $this->nodes[$offset]; } /** - * @param mixed $offset - * @param mixed $value + * @param int|string $offset + * @param Node|mixed[] $value + * + * @phpstan-param T|mixed[] $value */ - public function offsetSet($offset, $value) + public function offsetSet($offset, $value) : void { - if (is_array($value) && isset($value['kind'])) { - $value = AST::fromArray($value); + if (is_array($value)) { + if (isset($value['kind'])) { + /** @phpstan-var T $node */ + $node = AST::fromArray($value); + $this->nodes[$offset] = $node; + + return; + } + + throw new InvalidArgumentException( + 'Expected array value to be valid node data structure, missing key "kind"' + ); } + $this->nodes[$offset] = $value; } /** - * @param mixed $offset + * @param int|string $offset */ - public function offsetUnset($offset) + public function offsetUnset($offset) : void { unset($this->nodes[$offset]); } /** - * @param int $offset - * @param int $length * @param mixed $replacement * - * @return NodeList + * @phpstan-return NodeList */ - public function splice($offset, $length, $replacement = null) + public function splice(int $offset, int $length, $replacement = null) : NodeList { return new NodeList(array_splice($this->nodes, $offset, $length, $replacement)); } @@ -98,9 +128,10 @@ public function splice($offset, $length, $replacement = null) /** * @param NodeList|Node[] $list * - * @return NodeList + * @phpstan-param NodeList|array $list + * @phpstan-return NodeList */ - public function merge($list) + public function merge($list) : NodeList { if ($list instanceof self) { $list = $list->nodes; @@ -109,20 +140,14 @@ public function merge($list) return new NodeList(array_merge($this->nodes, $list)); } - /** - * @return Generator - */ - public function getIterator() + public function getIterator() : Traversable { foreach ($this->nodes as $key => $_) { yield $this->offsetGet($key); } } - /** - * @return int - */ - public function count() + public function count() : int { return count($this->nodes); } diff --git a/src/Language/AST/ObjectValueNode.php b/src/Language/AST/ObjectValueNode.php index 7e0d71caa..f83d74af7 100644 --- a/src/Language/AST/ObjectValueNode.php +++ b/src/Language/AST/ObjectValueNode.php @@ -9,6 +9,6 @@ class ObjectValueNode extends Node implements ValueNode /** @var string */ public $kind = NodeKind::OBJECT; - /** @var ObjectFieldNode[]|NodeList */ + /** @var NodeList */ public $fields; } diff --git a/src/Language/Parser.php b/src/Language/Parser.php index b9e171932..b0ccc7b7c 100644 --- a/src/Language/Parser.php +++ b/src/Language/Parser.php @@ -7,6 +7,7 @@ use GraphQL\Error\SyntaxError; use GraphQL\Language\AST\ArgumentNode; use GraphQL\Language\AST\BooleanValueNode; +use GraphQL\Language\AST\DefinitionNode; use GraphQL\Language\AST\DirectiveDefinitionNode; use GraphQL\Language\AST\DirectiveNode; use GraphQL\Language\AST\DocumentNode; @@ -46,12 +47,15 @@ use GraphQL\Language\AST\ScalarTypeExtensionNode; use GraphQL\Language\AST\SchemaDefinitionNode; use GraphQL\Language\AST\SchemaTypeExtensionNode; +use GraphQL\Language\AST\SelectionNode; use GraphQL\Language\AST\SelectionSetNode; use GraphQL\Language\AST\StringValueNode; use GraphQL\Language\AST\TypeExtensionNode; +use GraphQL\Language\AST\TypeNode; use GraphQL\Language\AST\TypeSystemDefinitionNode; use GraphQL\Language\AST\UnionTypeDefinitionNode; use GraphQL\Language\AST\UnionTypeExtensionNode; +use GraphQL\Language\AST\ValueNode; use GraphQL\Language\AST\VariableDefinitionNode; use GraphQL\Language\AST\VariableNode; use function count; @@ -66,13 +70,13 @@ * @method static ExecutableDefinitionNode executableDefinition(Source|string $source, bool[] $options = []) * @method static OperationDefinitionNode operationDefinition(Source|string $source, bool[] $options = []) * @method static string operationType(Source|string $source, bool[] $options = []) - * @method static NodeList|VariableDefinitionNode[] variableDefinitions(Source|string $source, bool[] $options = []) + * @method static NodeList variableDefinitions(Source|string $source, bool[] $options = []) * @method static VariableDefinitionNode variableDefinition(Source|string $source, bool[] $options = []) * @method static VariableNode variable(Source|string $source, bool[] $options = []) * @method static SelectionSetNode selectionSet(Source|string $source, bool[] $options = []) * @method static mixed selection(Source|string $source, bool[] $options = []) * @method static FieldNode field(Source|string $source, bool[] $options = []) - * @method static NodeList|ArgumentNode[] arguments(Source|string $source, bool[] $options = []) + * @method static NodeList arguments(Source|string $source, bool[] $options = []) * @method static ArgumentNode argument(Source|string $source, bool[] $options = []) * @method static ArgumentNode constArgument(Source|string $source, bool[] $options = []) * @method static FragmentSpreadNode|InlineFragmentNode fragment(Source|string $source, bool[] $options = []) @@ -85,7 +89,7 @@ * @method static ListValueNode array(Source|string $source, bool[] $options = []) * @method static ObjectValueNode object(Source|string $source, bool[] $options = []) * @method static ObjectFieldNode objectField(Source|string $source, bool[] $options = []) - * @method static NodeList|DirectiveNode[] directives(Source|string $source, bool[] $options = []) + * @method static NodeList directives(Source|string $source, bool[] $options = []) * @method static DirectiveNode directive(Source|string $source, bool[] $options = []) * @method static ListTypeNode|NameNode|NonNullTypeNode typeReference(Source|string $source, bool[] $options = []) * @method static NamedTypeNode namedType(Source|string $source, bool[] $options = []) @@ -266,10 +270,8 @@ public function __construct($source, array $options = []) /** * Returns a location object, used to identify the place in * the source that created a given parsed object. - * - * @return Location|null */ - private function loc(Token $startToken) + private function loc(Token $startToken) : ?Location { if (empty($this->lexer->options['noLocation'])) { return new Location($startToken, $this->lexer->lastToken, $this->lexer->source); @@ -280,12 +282,8 @@ private function loc(Token $startToken) /** * Determines if the next token is of a given kind - * - * @param string $kind - * - * @return bool */ - private function peek($kind) + private function peek(string $kind) : bool { return $this->lexer->token->kind === $kind; } @@ -293,12 +291,8 @@ private function peek($kind) /** * If the next token is of the given kind, return true after advancing * the parser. Otherwise, do not change the parser state and return false. - * - * @param string $kind - * - * @return bool */ - private function skip($kind) + private function skip(string $kind) : bool { $match = $this->lexer->token->kind === $kind; @@ -313,13 +307,9 @@ private function skip($kind) * If the next token is of the given kind, return that token after advancing * the parser. Otherwise, do not change the parser state and return false. * - * @param string $kind - * - * @return Token - * * @throws SyntaxError */ - private function expect($kind) + private function expect(string $kind) : Token { $token = $this->lexer->token; @@ -341,13 +331,9 @@ private function expect($kind) * advancing the parser. Otherwise, do not change the parser state and return * false. * - * @param string $value - * - * @return Token - * * @throws SyntaxError */ - private function expectKeyword($value) + private function expectKeyword(string $value) : Token { $token = $this->lexer->token; @@ -363,10 +349,7 @@ private function expectKeyword($value) ); } - /** - * @return SyntaxError - */ - private function unexpected(?Token $atToken = null) + private function unexpected(?Token $atToken = null) : SyntaxError { $token = $atToken ?: $this->lexer->token; @@ -379,15 +362,9 @@ private function unexpected(?Token $atToken = null) * and ends with a lex token of closeKind. Advances the parser * to the next lex token after the closing token. * - * @param string $openKind - * @param callable $parseFn - * @param string $closeKind - * - * @return NodeList - * * @throws SyntaxError */ - private function any($openKind, $parseFn, $closeKind) + private function any(string $openKind, callable $parseFn, string $closeKind) : NodeList { $this->expect($openKind); @@ -405,15 +382,9 @@ private function any($openKind, $parseFn, $closeKind) * and ends with a lex token of closeKind. Advances the parser * to the next lex token after the closing token. * - * @param string $openKind - * @param callable $parseFn - * @param string $closeKind - * - * @return NodeList - * * @throws SyntaxError */ - private function many($openKind, $parseFn, $closeKind) + private function many(string $openKind, callable $parseFn, string $closeKind) : NodeList { $this->expect($openKind); @@ -428,11 +399,9 @@ private function many($openKind, $parseFn, $closeKind) /** * Converts a name lex token into a name parse node. * - * @return NameNode - * * @throws SyntaxError */ - private function parseName() + private function parseName() : NameNode { $token = $this->expect(Token::NAME); @@ -445,11 +414,9 @@ private function parseName() /** * Implements the parsing rules in the Document section. * - * @return DocumentNode - * * @throws SyntaxError */ - private function parseDocument() + private function parseDocument() : DocumentNode { $start = $this->lexer->token; @@ -470,7 +437,7 @@ function () { * * @throws SyntaxError */ - private function parseDefinition() + private function parseDefinition() : DefinitionNode { if ($this->peek(Token::NAME)) { switch ($this->lexer->token->value) { @@ -504,11 +471,9 @@ private function parseDefinition() } /** - * @return ExecutableDefinitionNode - * * @throws SyntaxError */ - private function parseExecutableDefinition() + private function parseExecutableDefinition() : ExecutableDefinitionNode { if ($this->peek(Token::NAME)) { switch ($this->lexer->token->value) { @@ -529,11 +494,9 @@ private function parseExecutableDefinition() // Implements the parsing rules in the Operations section. /** - * @return OperationDefinitionNode - * * @throws SyntaxError */ - private function parseOperationDefinition() + private function parseOperationDefinition() : OperationDefinitionNode { $start = $this->lexer->token; if ($this->peek(Token::BRACE_L)) { @@ -565,11 +528,9 @@ private function parseOperationDefinition() } /** - * @return string - * * @throws SyntaxError */ - private function parseOperationType() + private function parseOperationType() : string { $operationToken = $this->expect(Token::NAME); switch ($operationToken->value) { @@ -584,10 +545,7 @@ private function parseOperationType() throw $this->unexpected($operationToken); } - /** - * @return VariableDefinitionNode[]|NodeList - */ - private function parseVariableDefinitions() + private function parseVariableDefinitions() : NodeList { return $this->peek(Token::PAREN_L) ? $this->many( @@ -601,11 +559,9 @@ function () { } /** - * @return VariableDefinitionNode - * * @throws SyntaxError */ - private function parseVariableDefinition() + private function parseVariableDefinition() : VariableDefinitionNode { $start = $this->lexer->token; $var = $this->parseVariable(); @@ -624,11 +580,9 @@ private function parseVariableDefinition() } /** - * @return VariableNode - * * @throws SyntaxError */ - private function parseVariable() + private function parseVariable() : VariableNode { $start = $this->lexer->token; $this->expect(Token::DOLLAR); @@ -639,10 +593,7 @@ private function parseVariable() ]); } - /** - * @return SelectionSetNode - */ - private function parseSelectionSet() + private function parseSelectionSet() : SelectionSetNode { $start = $this->lexer->token; @@ -665,10 +616,8 @@ function () { * - Field * - FragmentSpread * - InlineFragment - * - * @return mixed */ - private function parseSelection() + private function parseSelection() : SelectionNode { return $this->peek(Token::SPREAD) ? $this->parseFragment() @@ -676,11 +625,9 @@ private function parseSelection() } /** - * @return FieldNode - * * @throws SyntaxError */ - private function parseField() + private function parseField() : FieldNode { $start = $this->lexer->token; $nameOrAlias = $this->parseName(); @@ -704,13 +651,9 @@ private function parseField() } /** - * @param bool $isConst - * - * @return ArgumentNode[]|NodeList - * * @throws SyntaxError */ - private function parseArguments($isConst) + private function parseArguments(bool $isConst) : NodeList { $parseFn = $isConst ? function () { @@ -726,11 +669,9 @@ private function parseArguments($isConst) } /** - * @return ArgumentNode - * * @throws SyntaxError */ - private function parseArgument() + private function parseArgument() : ArgumentNode { $start = $this->lexer->token; $name = $this->parseName(); @@ -746,11 +687,9 @@ private function parseArgument() } /** - * @return ArgumentNode - * * @throws SyntaxError */ - private function parseConstArgument() + private function parseConstArgument() : ArgumentNode { $start = $this->lexer->token; $name = $this->parseName(); @@ -772,7 +711,7 @@ private function parseConstArgument() * * @throws SyntaxError */ - private function parseFragment() + private function parseFragment() : SelectionNode { $start = $this->lexer->token; $this->expect(Token::SPREAD); @@ -800,11 +739,9 @@ private function parseFragment() } /** - * @return FragmentDefinitionNode - * * @throws SyntaxError */ - private function parseFragmentDefinition() + private function parseFragmentDefinition() : FragmentDefinitionNode { $start = $this->lexer->token; $this->expectKeyword('fragment'); @@ -832,11 +769,9 @@ private function parseFragmentDefinition() } /** - * @return NameNode - * * @throws SyntaxError */ - private function parseFragmentName() + private function parseFragmentName() : NameNode { if ($this->lexer->token->value === 'on') { throw $this->unexpected(); @@ -865,13 +800,11 @@ private function parseFragmentName() * * EnumValue : Name but not `true`, `false` or `null` * - * @param bool $isConst - * * @return BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|StringValueNode|VariableNode|ListValueNode|ObjectValueNode|NullValueNode * * @throws SyntaxError */ - private function parseValueLiteral($isConst) + private function parseValueLiteral(bool $isConst) : ValueNode { $token = $this->lexer->token; switch ($token->kind) { @@ -931,10 +864,7 @@ private function parseValueLiteral($isConst) throw $this->unexpected(); } - /** - * @return StringValueNode - */ - private function parseStringLiteral() + private function parseStringLiteral() : StringValueNode { $token = $this->lexer->token; $this->lexer->advance(); @@ -951,7 +881,7 @@ private function parseStringLiteral() * * @throws SyntaxError */ - private function parseConstValue() + private function parseConstValue() : ValueNode { return $this->parseValueLiteral(true); } @@ -959,17 +889,12 @@ private function parseConstValue() /** * @return BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|ListValueNode|ObjectValueNode|StringValueNode|VariableNode */ - private function parseVariableValue() + private function parseVariableValue() : ValueNode { return $this->parseValueLiteral(false); } - /** - * @param bool $isConst - * - * @return ListValueNode - */ - private function parseArray($isConst) + private function parseArray(bool $isConst) : ListValueNode { $start = $this->lexer->token; $parseFn = $isConst ? function () { @@ -986,12 +911,7 @@ private function parseArray($isConst) ); } - /** - * @param bool $isConst - * - * @return ObjectValueNode - */ - private function parseObject($isConst) + private function parseObject(bool $isConst) : ObjectValueNode { $start = $this->lexer->token; $this->expect(Token::BRACE_L); @@ -1006,12 +926,7 @@ private function parseObject($isConst) ]); } - /** - * @param bool $isConst - * - * @return ObjectFieldNode - */ - private function parseObjectField($isConst) + private function parseObjectField(bool $isConst) : ObjectFieldNode { $start = $this->lexer->token; $name = $this->parseName(); @@ -1028,13 +943,9 @@ private function parseObjectField($isConst) // Implements the parsing rules in the Directives section. /** - * @param bool $isConst - * - * @return DirectiveNode[]|NodeList - * * @throws SyntaxError */ - private function parseDirectives($isConst) + private function parseDirectives(bool $isConst) : NodeList { $directives = []; while ($this->peek(Token::AT)) { @@ -1045,13 +956,9 @@ private function parseDirectives($isConst) } /** - * @param bool $isConst - * - * @return DirectiveNode - * * @throws SyntaxError */ - private function parseDirective($isConst) + private function parseDirective(bool $isConst) : DirectiveNode { $start = $this->lexer->token; $this->expect(Token::AT); @@ -1072,7 +979,7 @@ private function parseDirective($isConst) * * @throws SyntaxError */ - private function parseTypeReference() + private function parseTypeReference() : TypeNode { $start = $this->lexer->token; @@ -1096,7 +1003,7 @@ private function parseTypeReference() return $type; } - private function parseNamedType() + private function parseNamedType() : NamedTypeNode { $start = $this->lexer->token; @@ -1123,11 +1030,9 @@ private function parseNamedType() * - EnumTypeDefinition * - InputObjectTypeDefinition * - * @return TypeSystemDefinitionNode - * * @throws SyntaxError */ - private function parseTypeSystemDefinition() + private function parseTypeSystemDefinition() : TypeSystemDefinitionNode { // Many definitions begin with a description and require a lookahead. $keywordToken = $this->peekDescription() @@ -1160,30 +1065,24 @@ private function parseTypeSystemDefinition() throw $this->unexpected($keywordToken); } - /** - * @return bool - */ - private function peekDescription() + private function peekDescription() : bool { return $this->peek(Token::STRING) || $this->peek(Token::BLOCK_STRING); } - /** - * @return StringValueNode|null - */ - private function parseDescription() + private function parseDescription() : ?StringValueNode { if ($this->peekDescription()) { return $this->parseStringLiteral(); } + + return null; } /** - * @return SchemaDefinitionNode - * * @throws SyntaxError */ - private function parseSchemaDefinition() + private function parseSchemaDefinition() : SchemaDefinitionNode { $start = $this->lexer->token; $this->expectKeyword('schema'); @@ -1205,11 +1104,9 @@ function () { } /** - * @return OperationTypeDefinitionNode - * * @throws SyntaxError */ - private function parseOperationTypeDefinition() + private function parseOperationTypeDefinition() : OperationTypeDefinitionNode { $start = $this->lexer->token; $operation = $this->parseOperationType(); @@ -1224,11 +1121,9 @@ private function parseOperationTypeDefinition() } /** - * @return ScalarTypeDefinitionNode - * * @throws SyntaxError */ - private function parseScalarTypeDefinition() + private function parseScalarTypeDefinition() : ScalarTypeDefinitionNode { $start = $this->lexer->token; $description = $this->parseDescription(); @@ -1245,11 +1140,9 @@ private function parseScalarTypeDefinition() } /** - * @return ObjectTypeDefinitionNode - * * @throws SyntaxError */ - private function parseObjectTypeDefinition() + private function parseObjectTypeDefinition() : ObjectTypeDefinitionNode { $start = $this->lexer->token; $description = $this->parseDescription(); @@ -1276,7 +1169,7 @@ private function parseObjectTypeDefinition() * * @return NamedTypeNode[] */ - private function parseImplementsInterfaces() + private function parseImplementsInterfaces() : array { $types = []; if ($this->lexer->token->value === 'implements') { @@ -1295,40 +1188,40 @@ private function parseImplementsInterfaces() } /** - * @return array|NodeList - * * @throws SyntaxError */ - private function parseFieldsDefinition() + private function parseFieldsDefinition() : NodeList { // Legacy support for the SDL? - if (! empty($this->lexer->options['allowLegacySDLEmptyFields']) && - $this->peek(Token::BRACE_L) && - $this->lexer->lookahead()->kind === Token::BRACE_R + if (! empty($this->lexer->options['allowLegacySDLEmptyFields']) + && $this->peek(Token::BRACE_L) + && $this->lexer->lookahead()->kind === Token::BRACE_R ) { $this->lexer->advance(); $this->lexer->advance(); - return []; + /** @phpstan-var NodeList $nodeList */ + $nodeList = new NodeList([]); + } else { + /** @phpstan-var NodeList $nodeList */ + $nodeList = $this->peek(Token::BRACE_L) + ? $this->many( + Token::BRACE_L, + function () { + return $this->parseFieldDefinition(); + }, + Token::BRACE_R + ) + : new NodeList([]); } - return $this->peek(Token::BRACE_L) - ? $this->many( - Token::BRACE_L, - function () { - return $this->parseFieldDefinition(); - }, - Token::BRACE_R - ) - : new NodeList([]); + return $nodeList; } /** - * @return FieldDefinitionNode - * * @throws SyntaxError */ - private function parseFieldDefinition() + private function parseFieldDefinition() : FieldDefinitionNode { $start = $this->lexer->token; $description = $this->parseDescription(); @@ -1349,31 +1242,28 @@ private function parseFieldDefinition() } /** - * @return InputValueDefinitionNode[]|NodeList - * * @throws SyntaxError */ - private function parseArgumentsDefinition() + private function parseArgumentsDefinition() : NodeList { - if (! $this->peek(Token::PAREN_L)) { - return new NodeList([]); - } + /** @var NodeList $nodeList */ + $nodeList = $this->peek(Token::PAREN_L) + ? $this->many( + Token::PAREN_L, + function () { + return $this->parseInputValueDefinition(); + }, + Token::PAREN_R + ) + : new NodeList([]); - return $this->many( - Token::PAREN_L, - function () { - return $this->parseInputValueDefinition(); - }, - Token::PAREN_R - ); + return $nodeList; } /** - * @return InputValueDefinitionNode - * * @throws SyntaxError */ - private function parseInputValueDefinition() + private function parseInputValueDefinition() : InputValueDefinitionNode { $start = $this->lexer->token; $description = $this->parseDescription(); @@ -1397,11 +1287,9 @@ private function parseInputValueDefinition() } /** - * @return InterfaceTypeDefinitionNode - * * @throws SyntaxError */ - private function parseInterfaceTypeDefinition() + private function parseInterfaceTypeDefinition() : InterfaceTypeDefinitionNode { $start = $this->lexer->token; $description = $this->parseDescription(); @@ -1423,11 +1311,9 @@ private function parseInterfaceTypeDefinition() * UnionTypeDefinition : * - Description? union Name Directives[Const]? UnionMemberTypes? * - * @return UnionTypeDefinitionNode - * * @throws SyntaxError */ - private function parseUnionTypeDefinition() + private function parseUnionTypeDefinition() : UnionTypeDefinitionNode { $start = $this->lexer->token; $description = $this->parseDescription(); @@ -1452,7 +1338,7 @@ private function parseUnionTypeDefinition() * * @return NamedTypeNode[] */ - private function parseUnionMemberTypes() + private function parseUnionMemberTypes() : array { $types = []; if ($this->skip(Token::EQUALS)) { @@ -1467,11 +1353,9 @@ private function parseUnionMemberTypes() } /** - * @return EnumTypeDefinitionNode - * * @throws SyntaxError */ - private function parseEnumTypeDefinition() + private function parseEnumTypeDefinition() : EnumTypeDefinitionNode { $start = $this->lexer->token; $description = $this->parseDescription(); @@ -1490,13 +1374,12 @@ private function parseEnumTypeDefinition() } /** - * @return EnumValueDefinitionNode[]|NodeList - * * @throws SyntaxError */ - private function parseEnumValuesDefinition() + private function parseEnumValuesDefinition() : NodeList { - return $this->peek(Token::BRACE_L) + /** @var NodeList $nodeList */ + $nodeList = $this->peek(Token::BRACE_L) ? $this->many( Token::BRACE_L, function () { @@ -1505,14 +1388,14 @@ function () { Token::BRACE_R ) : new NodeList([]); + + return $nodeList; } /** - * @return EnumValueDefinitionNode - * * @throws SyntaxError */ - private function parseEnumValueDefinition() + private function parseEnumValueDefinition() : EnumValueDefinitionNode { $start = $this->lexer->token; $description = $this->parseDescription(); @@ -1528,11 +1411,9 @@ private function parseEnumValueDefinition() } /** - * @return InputObjectTypeDefinitionNode - * * @throws SyntaxError */ - private function parseInputObjectTypeDefinition() + private function parseInputObjectTypeDefinition() : InputObjectTypeDefinitionNode { $start = $this->lexer->token; $description = $this->parseDescription(); @@ -1551,13 +1432,12 @@ private function parseInputObjectTypeDefinition() } /** - * @return InputValueDefinitionNode[]|NodeList - * * @throws SyntaxError */ - private function parseInputFieldsDefinition() + private function parseInputFieldsDefinition() : NodeList { - return $this->peek(Token::BRACE_L) + /** @var NodeList $nodeList */ + $nodeList = $this->peek(Token::BRACE_L) ? $this->many( Token::BRACE_L, function () { @@ -1566,6 +1446,8 @@ function () { Token::BRACE_R ) : new NodeList([]); + + return $nodeList; } /** @@ -1577,11 +1459,9 @@ function () { * - EnumTypeExtension * - InputObjectTypeDefinition * - * @return TypeExtensionNode - * * @throws SyntaxError */ - private function parseTypeExtension() + private function parseTypeExtension() : TypeExtensionNode { $keywordToken = $this->lexer->lookahead(); @@ -1608,11 +1488,9 @@ private function parseTypeExtension() } /** - * @return SchemaTypeExtensionNode - * * @throws SyntaxError */ - private function parseSchemaTypeExtension() + private function parseSchemaTypeExtension() : SchemaTypeExtensionNode { $start = $this->lexer->token; $this->expectKeyword('extend'); @@ -1637,11 +1515,9 @@ private function parseSchemaTypeExtension() } /** - * @return ScalarTypeExtensionNode - * * @throws SyntaxError */ - private function parseScalarTypeExtension() + private function parseScalarTypeExtension() : ScalarTypeExtensionNode { $start = $this->lexer->token; $this->expectKeyword('extend'); @@ -1660,11 +1536,9 @@ private function parseScalarTypeExtension() } /** - * @return ObjectTypeExtensionNode - * * @throws SyntaxError */ - private function parseObjectTypeExtension() + private function parseObjectTypeExtension() : ObjectTypeExtensionNode { $start = $this->lexer->token; $this->expectKeyword('extend'); @@ -1691,11 +1565,9 @@ private function parseObjectTypeExtension() } /** - * @return InterfaceTypeExtensionNode - * * @throws SyntaxError */ - private function parseInterfaceTypeExtension() + private function parseInterfaceTypeExtension() : InterfaceTypeExtensionNode { $start = $this->lexer->token; $this->expectKeyword('extend'); @@ -1722,11 +1594,9 @@ private function parseInterfaceTypeExtension() * - extend union Name Directives[Const]? UnionMemberTypes * - extend union Name Directives[Const] * - * @return UnionTypeExtensionNode - * * @throws SyntaxError */ - private function parseUnionTypeExtension() + private function parseUnionTypeExtension() : UnionTypeExtensionNode { $start = $this->lexer->token; $this->expectKeyword('extend'); @@ -1747,11 +1617,9 @@ private function parseUnionTypeExtension() } /** - * @return EnumTypeExtensionNode - * * @throws SyntaxError */ - private function parseEnumTypeExtension() + private function parseEnumTypeExtension() : EnumTypeExtensionNode { $start = $this->lexer->token; $this->expectKeyword('extend'); @@ -1774,11 +1642,9 @@ private function parseEnumTypeExtension() } /** - * @return InputObjectTypeExtensionNode - * * @throws SyntaxError */ - private function parseInputObjectTypeExtension() + private function parseInputObjectTypeExtension() : InputObjectTypeExtensionNode { $start = $this->lexer->token; $this->expectKeyword('extend'); @@ -1804,11 +1670,9 @@ private function parseInputObjectTypeExtension() * DirectiveDefinition : * - directive @ Name ArgumentsDefinition? on DirectiveLocations * - * @return DirectiveDefinitionNode - * * @throws SyntaxError */ - private function parseDirectiveDefinition() + private function parseDirectiveDefinition() : DirectiveDefinitionNode { $start = $this->lexer->token; $description = $this->parseDescription(); @@ -1833,7 +1697,7 @@ private function parseDirectiveDefinition() * * @throws SyntaxError */ - private function parseDirectiveLocations() + private function parseDirectiveLocations() : array { // Optional leading pipe $this->skip(Token::PIPE); @@ -1846,11 +1710,9 @@ private function parseDirectiveLocations() } /** - * @return NameNode - * * @throws SyntaxError */ - private function parseDirectiveLocation() + private function parseDirectiveLocation() : NameNode { $start = $this->lexer->token; $name = $this->parseName(); diff --git a/src/Type/Definition/QueryPlan.php b/src/Type/Definition/QueryPlan.php index 500bc9cb5..03788232b 100644 --- a/src/Type/Definition/QueryPlan.php +++ b/src/Type/Definition/QueryPlan.php @@ -34,7 +34,7 @@ class QueryPlan /** @var Schema */ private $schema; - /** @var mixed[] */ + /** @var array */ private $queryPlan = []; /** @var mixed[] */ diff --git a/src/Utils/TypeInfo.php b/src/Utils/TypeInfo.php index 0cab63cd7..1468847f9 100644 --- a/src/Utils/TypeInfo.php +++ b/src/Utils/TypeInfo.php @@ -84,7 +84,7 @@ public function __construct(Schema $schema, $initialType = null) $this->fieldDefStack = []; $this->defaultValueStack = []; - if (! $initialType) { + if ($initialType === null) { return; } diff --git a/src/Validator/Rules/KnownArgumentNames.php b/src/Validator/Rules/KnownArgumentNames.php index dfec7886a..0a4945ebd 100644 --- a/src/Validator/Rules/KnownArgumentNames.php +++ b/src/Validator/Rules/KnownArgumentNames.php @@ -31,7 +31,7 @@ public function getVisitor(ValidationContext $context) NodeKind::ARGUMENT => static function (ArgumentNode $node, $key, $parent, $path, $ancestors) use ($context) { $argDef = $context->getArgument(); if ($argDef !== null) { - return; + return null; } /** @var Node|mixed $argumentOf */ diff --git a/src/Validator/Rules/KnownDirectives.php b/src/Validator/Rules/KnownDirectives.php index 43d14a668..e92625dad 100644 --- a/src/Validator/Rules/KnownDirectives.php +++ b/src/Validator/Rules/KnownDirectives.php @@ -4,6 +4,7 @@ namespace GraphQL\Validator\Rules; +use Exception; use GraphQL\Error\Error; use GraphQL\Language\AST\DirectiveDefinitionNode; use GraphQL\Language\AST\DirectiveNode; @@ -40,6 +41,7 @@ use GraphQL\Validator\ValidationContext; use function array_map; use function count; +use function get_class; use function in_array; use function sprintf; @@ -186,6 +188,8 @@ private function getDirectiveLocationForASTPath(array $ancestors) ? DirectiveLocation::INPUT_FIELD_DEFINITION : DirectiveLocation::ARGUMENT_DEFINITION; } + + throw new Exception('Unknown directive location: ' . get_class($appliedTo)); } public static function misplacedDirectiveMessage($directiveName, $location) diff --git a/src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php b/src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php index 262fbad6a..c26c061c6 100644 --- a/src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php +++ b/src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php @@ -89,7 +89,7 @@ static function (NamedTypeNode $argument) : string { $directiveName = $directiveNode->name->value; $requiredArgs = $requiredArgsMap[$directiveName] ?? null; if (! $requiredArgs) { - return; + return null; } $argNodes = $directiveNode->arguments ?: []; diff --git a/tests/Error/ErrorTest.php b/tests/Error/ErrorTest.php index eb0a6c8af..4614177d5 100644 --- a/tests/Error/ErrorTest.php +++ b/tests/Error/ErrorTest.php @@ -6,6 +6,7 @@ use Exception; use GraphQL\Error\Error; +use GraphQL\Language\AST\OperationDefinitionNode; use GraphQL\Language\Parser; use GraphQL\Language\Source; use GraphQL\Language\SourceLocation; @@ -29,12 +30,14 @@ public function testUsesTheStackOfAnOriginalError() : void */ public function testConvertsNodesToPositionsAndLocations() : void { - $source = new Source('{ + $source = new Source('{ field }'); - $ast = Parser::parse($source); - $fieldNode = $ast->definitions[0]->selectionSet->selections[0]; - $e = new Error('msg', [$fieldNode]); + $ast = Parser::parse($source); + /** @var OperationDefinitionNode $operationDefinition */ + $operationDefinition = $ast->definitions[0]; + $fieldNode = $operationDefinition->selectionSet->selections[0]; + $e = new Error('msg', [$fieldNode]); self::assertEquals([$fieldNode], $e->nodes); self::assertEquals($source, $e->getSource()); @@ -47,12 +50,14 @@ public function testConvertsNodesToPositionsAndLocations() : void */ public function testConvertSingleNodeToPositionsAndLocations() : void { - $source = new Source('{ + $source = new Source('{ field }'); - $ast = Parser::parse($source); - $fieldNode = $ast->definitions[0]->selectionSet->selections[0]; - $e = new Error('msg', $fieldNode); // Non-array value. + $ast = Parser::parse($source); + /** @var OperationDefinitionNode $operationDefinition */ + $operationDefinition = $ast->definitions[0]; + $fieldNode = $operationDefinition->selectionSet->selections[0]; + $e = new Error('msg', $fieldNode); // Non-array value. self::assertEquals([$fieldNode], $e->nodes); self::assertEquals($source, $e->getSource()); @@ -108,8 +113,11 @@ public function testSerializesToIncludeMessage() : void */ public function testSerializesToIncludeMessageAndLocations() : void { - $node = Parser::parse('{ field }')->definitions[0]->selectionSet->selections[0]; - $e = new Error('msg', [$node]); + $ast = Parser::parse('{ field }'); + /** @var OperationDefinitionNode $operationDefinition */ + $operationDefinition = $ast->definitions[0]; + $node = $operationDefinition->selectionSet->selections[0]; + $e = new Error('msg', [$node]); self::assertEquals( ['message' => 'msg', 'locations' => [['line' => 1, 'column' => 3]]], diff --git a/tests/Error/PrintErrorTest.php b/tests/Error/PrintErrorTest.php index ce8a2c111..f8839f8c5 100644 --- a/tests/Error/PrintErrorTest.php +++ b/tests/Error/PrintErrorTest.php @@ -6,6 +6,7 @@ use GraphQL\Error\Error; use GraphQL\Error\FormattedError; +use GraphQL\Language\AST\ObjectTypeDefinitionNode; use GraphQL\Language\Parser; use GraphQL\Language\Source; use GraphQL\Language\SourceLocation; @@ -63,7 +64,9 @@ public function testPrintsAnErrorWithNodesFromDifferentSources() : void 'SourceA' )); - $fieldTypeA = $sourceA->definitions[0]->fields[0]->type; + /** @var ObjectTypeDefinitionNode $objectDefinitionA */ + $objectDefinitionA = $sourceA->definitions[0]; + $fieldTypeA = $objectDefinitionA->fields[0]->type; $sourceB = Parser::parse(new Source( 'type Foo { @@ -72,7 +75,9 @@ public function testPrintsAnErrorWithNodesFromDifferentSources() : void 'SourceB' )); - $fieldTypeB = $sourceB->definitions[0]->fields[0]->type; + /** @var ObjectTypeDefinitionNode $objectDefinitionB */ + $objectDefinitionB = $sourceB->definitions[0]; + $fieldTypeB = $objectDefinitionB->fields[0]->type; $error = new Error( 'Example error with two nodes', diff --git a/tests/Executor/ExecutorTest.php b/tests/Executor/ExecutorTest.php index ebd4573b8..3336ddc60 100644 --- a/tests/Executor/ExecutorTest.php +++ b/tests/Executor/ExecutorTest.php @@ -8,6 +8,7 @@ use GraphQL\Error\Error; use GraphQL\Error\UserError; use GraphQL\Executor\Executor; +use GraphQL\Language\AST\OperationDefinitionNode; use GraphQL\Language\Parser; use GraphQL\Tests\Executor\TestClasses\NotSpecial; use GraphQL\Tests\Executor\TestClasses\Special; @@ -285,15 +286,18 @@ public function testProvidesInfoAboutCurrentExecutionState() : void Executor::execute($schema, $ast, $rootValue, null, ['var' => '123']); + /** @var OperationDefinitionNode $operationDefinition */ + $operationDefinition = $ast->definitions[0]; + self::assertEquals('test', $info->fieldName); self::assertEquals(1, count($info->fieldNodes)); - self::assertSame($ast->definitions[0]->selectionSet->selections[0], $info->fieldNodes[0]); + self::assertSame($operationDefinition->selectionSet->selections[0], $info->fieldNodes[0]); self::assertSame(Type::string(), $info->returnType); self::assertSame($schema->getQueryType(), $info->parentType); self::assertEquals(['result'], $info->path); self::assertSame($schema, $info->schema); self::assertSame($rootValue, $info->rootValue); - self::assertEquals($ast->definitions[0], $info->operation); + self::assertEquals($operationDefinition, $info->operation); self::assertEquals(['var' => '123'], $info->variableValues); self::assertInstanceOf(FieldDefinition::class, $info->fieldDefinition); } diff --git a/tests/Language/ParserTest.php b/tests/Language/ParserTest.php index c71ceef69..c61a5870d 100644 --- a/tests/Language/ParserTest.php +++ b/tests/Language/ParserTest.php @@ -13,6 +13,7 @@ use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\NodeList; use GraphQL\Language\AST\ObjectTypeDefinitionNode; +use GraphQL\Language\AST\OperationDefinitionNode; use GraphQL\Language\AST\SelectionSetNode; use GraphQL\Language\AST\StringValueNode; use GraphQL\Language\Parser; @@ -222,7 +223,9 @@ public function testParsesMultiByteCharacters() : void ]), ]); - self::assertEquals($expected, $result->definitions[0]->selectionSet); + /** @var OperationDefinitionNode $operationDefinition */ + $operationDefinition = $result->definitions[0]; + self::assertEquals($expected, $operationDefinition->selectionSet); } /** diff --git a/tests/Language/VisitorTest.php b/tests/Language/VisitorTest.php index 72bf422c6..d612cec7d 100644 --- a/tests/Language/VisitorTest.php +++ b/tests/Language/VisitorTest.php @@ -4,6 +4,7 @@ namespace GraphQL\Tests\Language; +use GraphQL\Language\AST\DefinitionNode; use GraphQL\Language\AST\DocumentNode; use GraphQL\Language\AST\FieldNode; use GraphQL\Language\AST\NameNode; @@ -224,9 +225,11 @@ public function testAllowsEditingRootNodeOnEnterAndLeave() : void NodeKind::DOCUMENT => [ 'enter' => function (DocumentNode $node) use ($ast) { $this->checkVisitorFnArgs($ast, func_get_args()); - $tmp = clone $node; - $tmp->definitions = []; - $tmp->didEnter = true; + /** @var NodeList $definitionNodeList */ + $definitionNodeList = new NodeList([]); + $tmp = clone $node; + $tmp->definitions = $definitionNodeList; + $tmp->didEnter = true; return $tmp; }, @@ -322,7 +325,7 @@ public function testVisitsEditedNode() : void ]); } if ($node !== $addedField) { - return; + return null; } $didVisitAddedField = true; diff --git a/tests/Server/Psr7/PsrRequestStub.php b/tests/Server/Psr7/PsrRequestStub.php index c60dcac99..23892c04b 100644 --- a/tests/Server/Psr7/PsrRequestStub.php +++ b/tests/Server/Psr7/PsrRequestStub.php @@ -314,7 +314,7 @@ public function withMethod($method) */ public function getUri() { - // TODO: Implement getUri() method. + throw new \Exception('Not implemented'); } /** diff --git a/tests/Utils/SchemaExtenderTest.php b/tests/Utils/SchemaExtenderTest.php index c6079a966..4b4fc3355 100644 --- a/tests/Utils/SchemaExtenderTest.php +++ b/tests/Utils/SchemaExtenderTest.php @@ -6,6 +6,7 @@ use GraphQL\Error\Error; use GraphQL\GraphQL; +use GraphQL\Language\AST\DefinitionNode; use GraphQL\Language\AST\DocumentNode; use GraphQL\Language\AST\Node; use GraphQL\Language\AST\NodeList; @@ -217,15 +218,17 @@ protected function extendTestSchema(string $sdl, ?array $options = null) : Schem protected function printTestSchemaChanges(Schema $extendedSchema) : string { - $ast = Parser::parse(SchemaPrinter::doPrint($extendedSchema)); - $ast->definitions = array_values(array_filter( - $ast->definitions instanceof NodeList - ? iterator_to_array($ast->definitions->getIterator()) - : $ast->definitions, + $ast = Parser::parse(SchemaPrinter::doPrint($extendedSchema)); + /** @var array $extraDefinitions */ + $extraDefinitions = array_values(array_filter( + iterator_to_array($ast->definitions->getIterator()), function (Node $node) : bool { return ! in_array(Printer::doPrint($node), $this->testSchemaDefinitions, true); } )); + /** @phpstan-var NodeList $definitionNodeList */ + $definitionNodeList = new NodeList($extraDefinitions); + $ast->definitions = $definitionNodeList; return Printer::doPrint($ast); } From 8d37049aab0f08bb93d83cc45fded8e688b68414 Mon Sep 17 00:00:00 2001 From: Smolevich Date: Mon, 18 Nov 2019 11:04:40 +0300 Subject: [PATCH 129/256] Add support GitHub actions (#584) --- .github/workflows/ci-build.yml | 153 +++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 .github/workflows/ci-build.yml diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml new file mode 100644 index 000000000..6f38a5562 --- /dev/null +++ b/.github/workflows/ci-build.yml @@ -0,0 +1,153 @@ +name: CI + +on: + push: + branches: + tags: + pull_request: + +jobs: + build: + runs-on: ubuntu-18.04 + strategy: + matrix: + php: [7.1, 7.2, 7.3, 7.4] + env: [ + 'EXECUTOR= DEPENDENCIES=--prefer-lowest', + 'EXECUTOR=coroutine DEPENDENCIES=--prefer-lowest', + 'EXECUTOR=', + 'EXECUTOR=coroutine', + ] + name: PHP ${{ matrix.php }} Test ${{ matrix.env }} + + steps: + - uses: actions/checkout@v1 + + - name: Install PHP + uses: shivammathur/setup-php@1.6.0 + with: + php-version: ${{ matrix.php }} + extension-csv: json, mbstring + - name: Get Composer Cache Directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache dependencies + uses: actions/cache@v1 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install Dependencies + run: composer install ${DEPENDENCIES} + + - name: Run unit tests + run: | + export $ENV + ./vendor/bin/phpunit --group default,ReactPromise + env: + ENV: ${{ matrix.env}} + + coding-standard: + runs-on: ubuntu-18.04 + name: Coding Standard + + steps: + - uses: actions/checkout@v1 + + - name: Install PHP + uses: shivammathur/setup-php@1.6.0 + with: + php-version: 7.1 + extension-csv: json, mbstring + + - name: Get Composer Cache Directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache dependencies + uses: actions/cache@v1 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install Dependencies + run: composer install ${DEPENDENCIES} + + - name: Coding Standard + run: composer lint + + phpstan: + runs-on: ubuntu-18.04 + name: PHPStan + + steps: + - uses: actions/checkout@v1 + + - name: Install PHP + uses: shivammathur/setup-php@1.6.0 + with: + php-version: 7.1 + extension-csv: json, mbstring + + - name: Get Composer Cache Directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache dependencies + uses: actions/cache@v1 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install Dependencies + run: composer install ${DEPENDENCIES} + + - name: PHPStan + run: composer stan + + coverage: + runs-on: ubuntu-18.04 + name: Code Coverage + + steps: + - uses: actions/checkout@v1 + + - name: Install PHP + uses: shivammathur/setup-php@1.6.0 + with: + php-version: 7.1 + extension-csv: json, mbstring + coverage: pcov + pecl: true + + - name: Get Composer Cache Directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache dependencies + uses: actions/cache@v1 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install Dependencies + run: composer install ${DEPENDENCIES} + + - name: Update xdebug version + run: | + sudo pecl channel-update pecl.php.net + sudo pecl install xdebug-2.9.0 + continue-on-error: true + + - name: Code coverage + run: | + ./vendor/bin/phpunit --coverage-php /tmp/coverage/clover_executor.cov + EXECUTOR=coroutine ./vendor/bin/phpunit --coverage-php /tmp/coverage/clover_executor-coroutine.cov + ./vendor/bin/phpcov merge /tmp/coverage --clover /tmp/clover.xml + wget https://github.com/scrutinizer-ci/ocular/releases/download/1.5.2/ocular.phar + php7.1 ocular.phar code-coverage:upload --format=php-clover /tmp/clover.xml From 11abce7198626f8008245b75fcf0cc13973ec1a7 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Thu, 27 Dec 2018 23:15:44 +0100 Subject: [PATCH 130/256] Fix FormattedError --- src/Error/FormattedError.php | 24 +++++++++++------------- src/Language/AST/Node.php | 5 +++-- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/Error/FormattedError.php b/src/Error/FormattedError.php index aa1101b1e..e6f0ec611 100644 --- a/src/Error/FormattedError.php +++ b/src/Error/FormattedError.php @@ -66,10 +66,10 @@ public static function setInternalErrorMessage($msg) public static function printError(Error $error) { $printedLocations = []; - if ($error->nodes) { + if ($error->nodes !== null && count($error->nodes) !== 0) { /** @var Node $node */ foreach ($error->nodes as $node) { - if (! $node->loc) { + if ($node->loc === null) { continue; } @@ -82,14 +82,14 @@ public static function printError(Error $error) $node->loc->source->getLocation($node->loc->start) ); } - } elseif ($error->getSource() && $error->getLocations()) { + } elseif ($error->getSource() !== null && count($error->getLocations()) !== 0) { $source = $error->getSource(); foreach ($error->getLocations() as $location) { $printedLocations[] = self::highlightSourceAtLocation($source, $location); } } - return ! $printedLocations + return count($printedLocations) === 0 ? $error->getMessage() : implode("\n\n", array_merge([$error->getMessage()], $printedLocations)) . "\n"; } @@ -250,30 +250,28 @@ public static function addDebugEntries(array $formattedError, $e, $debug) $debug = (int) $debug; - if ($debug & Debug::RETHROW_INTERNAL_EXCEPTIONS) { + if (($debug & Debug::RETHROW_INTERNAL_EXCEPTIONS) !== 0) { if (! $e instanceof Error) { throw $e; } - if ($e->getPrevious()) { + if ($e->getPrevious() !== null) { throw $e->getPrevious(); } } $isUnsafe = ! $e instanceof ClientAware || ! $e->isClientSafe(); - if (($debug & Debug::RETHROW_UNSAFE_EXCEPTIONS) && $isUnsafe) { - if ($e->getPrevious()) { - throw $e->getPrevious(); - } + if (($debug & Debug::RETHROW_UNSAFE_EXCEPTIONS) !== 0 && $isUnsafe && $e->getPrevious() !== null) { + throw $e->getPrevious(); } - if (($debug & Debug::INCLUDE_DEBUG_MESSAGE) && $isUnsafe) { + if (($debug & Debug::INCLUDE_DEBUG_MESSAGE) !== 0 && $isUnsafe) { // Displaying debugMessage as a first entry: $formattedError = ['debugMessage' => $e->getMessage()] + $formattedError; } - if ($debug & Debug::INCLUDE_TRACE) { + if (($debug & Debug::INCLUDE_TRACE) !== 0) { if ($e instanceof ErrorException || $e instanceof \Error) { $formattedError += [ 'file' => $e->getFile(), @@ -281,7 +279,7 @@ public static function addDebugEntries(array $formattedError, $e, $debug) ]; } - $isTrivial = $e instanceof Error && ! $e->getPrevious(); + $isTrivial = $e instanceof Error && $e->getPrevious() === null; if (! $isTrivial) { $debugging = $e->getPrevious() ?: $e; diff --git a/src/Language/AST/Node.php b/src/Language/AST/Node.php index 4e6c54368..7211f7c2b 100644 --- a/src/Language/AST/Node.php +++ b/src/Language/AST/Node.php @@ -5,6 +5,7 @@ namespace GraphQL\Language\AST; use GraphQL\Utils\Utils; +use function count; use function get_object_vars; use function is_array; use function is_scalar; @@ -36,7 +37,7 @@ */ abstract class Node { - /** @var Location */ + /** @var Location|null */ public $loc; /** @var string */ @@ -47,7 +48,7 @@ abstract class Node */ public function __construct(array $vars) { - if (empty($vars)) { + if (count($vars) === 0) { return; } From eed04dd79897d2617bed6cf97b68c4c029e2b5b2 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Sun, 30 Dec 2018 00:13:00 +0100 Subject: [PATCH 131/256] DebugFlag --- docs/error-handling.md | 2 +- examples/01-blog/graphql.php | 6 +- src/Error/Debug.php | 16 ----- src/Error/DebugFlag.php | 16 +++++ src/Error/FormattedError.php | 64 +++++++------------ src/Executor/ExecutionResult.php | 4 +- src/Language/AST/Node.php | 9 +-- src/Server/Helper.php | 4 +- src/Server/ServerConfig.php | 20 ++---- src/Utils/AST.php | 3 +- tests/Executor/AbstractPromiseTest.php | 5 +- tests/Executor/AbstractTest.php | 7 +- tests/Executor/ExecutorLazySchemaTest.php | 7 +- tests/Executor/ListsTest.php | 27 ++++---- tests/Executor/MutationsTest.php | 3 +- tests/Executor/NonNullTest.php | 15 +++-- tests/Executor/UnionInterfaceTest.php | 3 +- tests/Experimental/Executor/CollectorTest.php | 2 +- tests/Language/SchemaParserTest.php | 1 + tests/Server/QueryExecutionTest.php | 12 ++-- tests/Server/ServerConfigTest.php | 15 +++-- tests/Server/StandardServerTest.php | 5 +- tests/Type/EnumTypeTest.php | 5 +- tests/Utils/BuildSchemaTest.php | 9 +-- tools/gendocs.php | 6 +- 25 files changed, 123 insertions(+), 143 deletions(-) delete mode 100644 src/Error/Debug.php create mode 100644 src/Error/DebugFlag.php diff --git a/docs/error-handling.md b/docs/error-handling.md index 87e79fd36..11962bb36 100644 --- a/docs/error-handling.md +++ b/docs/error-handling.md @@ -90,7 +90,7 @@ GraphQL\Error\FormattedError::setInternalErrorMessage("Unexpected error"); # Debugging tools -During development or debugging use `$result->toArray(true)` to add **debugMessage** key to +During development or debugging use `$result->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE)` to add **debugMessage** key to each formatted error entry. If you also want to add exception trace - pass flags instead: ``` diff --git a/examples/01-blog/graphql.php b/examples/01-blog/graphql.php index 4da8f1ee0..246c79cde 100644 --- a/examples/01-blog/graphql.php +++ b/examples/01-blog/graphql.php @@ -9,17 +9,17 @@ use \GraphQL\Type\Schema; use \GraphQL\GraphQL; use \GraphQL\Error\FormattedError; -use \GraphQL\Error\Debug; +use \GraphQL\Error\DebugFlag; // Disable default PHP error reporting - we have better one for debug mode (see bellow) ini_set('display_errors', 0); -$debug = false; +$debug = 0; if (!empty($_GET['debug'])) { set_error_handler(function($severity, $message, $file, $line) use (&$phpErrors) { throw new ErrorException($message, 0, $severity, $file, $line); }); - $debug = Debug::INCLUDE_DEBUG_MESSAGE | Debug::INCLUDE_TRACE; + $debug = DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE; } try { diff --git a/src/Error/Debug.php b/src/Error/Debug.php deleted file mode 100644 index a48b81f43..000000000 --- a/src/Error/Debug.php +++ /dev/null @@ -1,16 +0,0 @@ - $e->isClientSafe() ? $e->getMessage() : $internalErrorMessage, + 'message' => $exception->isClientSafe() ? $exception->getMessage() : $internalErrorMessage, 'extensions' => [ - 'category' => $e->getCategory(), + 'category' => $exception->getCategory(), ], ]; } else { @@ -199,9 +191,9 @@ public static function createFromException($e, $debug = false, $internalErrorMes ]; } - if ($e instanceof Error) { + if ($exception instanceof Error) { $locations = Utils::map( - $e->getLocations(), + $exception->getLocations(), static function (SourceLocation $loc) { return $loc->toSerializableArray(); } @@ -209,16 +201,16 @@ static function (SourceLocation $loc) { if (! empty($locations)) { $formattedError['locations'] = $locations; } - if (! empty($e->path)) { - $formattedError['path'] = $e->path; + if (! empty($exception->path)) { + $formattedError['path'] = $exception->path; } - if (! empty($e->getExtensions())) { - $formattedError['extensions'] = $e->getExtensions() + $formattedError['extensions']; + if (! empty($exception->getExtensions())) { + $formattedError['extensions'] = $exception->getExtensions() + $formattedError['extensions']; } } - if ($debug) { - $formattedError = self::addDebugEntries($formattedError, $e, $debug); + if ($debug !== 0) { + $formattedError = self::addDebugEntries($formattedError, $exception, $debug); } return $formattedError; @@ -228,29 +220,19 @@ static function (SourceLocation $loc) { * Decorates spec-compliant $formattedError with debug entries according to $debug flags * (see GraphQL\Error\Debug for available flags) * - * @param mixed[] $formattedError - * @param Throwable $e - * @param bool|int $debug + * @param mixed[] $formattedError * * @return mixed[] * * @throws Throwable */ - public static function addDebugEntries(array $formattedError, $e, $debug) + public static function addDebugEntries(array $formattedError, Throwable $e, int $debugFlag) { - if (! $debug) { + if ($debugFlag === 0) { return $formattedError; } - Utils::invariant( - $e instanceof Throwable, - 'Expected exception, got %s', - Utils::getVariableType($e) - ); - - $debug = (int) $debug; - - if (($debug & Debug::RETHROW_INTERNAL_EXCEPTIONS) !== 0) { + if (( $debugFlag & DebugFlag::RETHROW_INTERNAL_EXCEPTIONS) !== 0) { if (! $e instanceof Error) { throw $e; } @@ -262,16 +244,16 @@ public static function addDebugEntries(array $formattedError, $e, $debug) $isUnsafe = ! $e instanceof ClientAware || ! $e->isClientSafe(); - if (($debug & Debug::RETHROW_UNSAFE_EXCEPTIONS) !== 0 && $isUnsafe && $e->getPrevious() !== null) { + if (($debugFlag & DebugFlag::RETHROW_UNSAFE_EXCEPTIONS) !== 0 && $isUnsafe && $e->getPrevious() !== null) { throw $e->getPrevious(); } - if (($debug & Debug::INCLUDE_DEBUG_MESSAGE) !== 0 && $isUnsafe) { + if (($debugFlag & DebugFlag::INCLUDE_DEBUG_MESSAGE) !== 0 && $isUnsafe) { // Displaying debugMessage as a first entry: $formattedError = ['debugMessage' => $e->getMessage()] + $formattedError; } - if (($debug & Debug::INCLUDE_TRACE) !== 0) { + if (($debugFlag & DebugFlag::INCLUDE_TRACE) !== 0) { if ($e instanceof ErrorException || $e instanceof \Error) { $formattedError += [ 'file' => $e->getFile(), @@ -294,11 +276,9 @@ public static function addDebugEntries(array $formattedError, $e, $debug) * Prepares final error formatter taking in account $debug flags. * If initial formatter is not set, FormattedError::createFromException is used * - * @param bool|int $debug - * - * @return callable|callable + * @return callable */ - public static function prepareFormatter(?callable $formatter = null, $debug) + public static function prepareFormatter(?callable $formatter = null, int $debug) { $formatter = $formatter ?: static function ($e) { return FormattedError::createFromException($e); diff --git a/src/Executor/ExecutionResult.php b/src/Executor/ExecutionResult.php index db16dd3d4..e4d4fc439 100644 --- a/src/Executor/ExecutionResult.php +++ b/src/Executor/ExecutionResult.php @@ -128,13 +128,11 @@ public function jsonSerialize() * $debug argument must be either bool (only adds "debugMessage" to result) or sum of flags from * GraphQL\Error\Debug * - * @param bool|int $debug - * * @return mixed[] * * @api */ - public function toArray($debug = false) + public function toArray(int $debug = 0) : array { $result = []; diff --git a/src/Language/AST/Node.php b/src/Language/AST/Node.php index 7211f7c2b..3f2688db1 100644 --- a/src/Language/AST/Node.php +++ b/src/Language/AST/Node.php @@ -87,10 +87,7 @@ private function cloneValue($value) return $cloned; } - /** - * @return string - */ - public function __toString() + public function __toString() : string { $tmp = $this->toArray(true); @@ -98,11 +95,9 @@ public function __toString() } /** - * @param bool $recursive - * * @return mixed[] */ - public function toArray($recursive = false) + public function toArray(bool $recursive = false) : array { if ($recursive) { return $this->recursiveToArray($this); diff --git a/src/Server/Helper.php b/src/Server/Helper.php index f125598fb..3986148ff 100644 --- a/src/Server/Helper.php +++ b/src/Server/Helper.php @@ -319,11 +319,11 @@ static function (RequestError $err) { if ($config->getErrorsHandler()) { $result->setErrorsHandler($config->getErrorsHandler()); } - if ($config->getErrorFormatter() || $config->getDebug()) { + if ($config->getErrorFormatter() || $config->getDebugFlag() !== 0) { $result->setErrorFormatter( FormattedError::prepareFormatter( $config->getErrorFormatter(), - $config->getDebug() + $config->getDebugFlag() ) ); } diff --git a/src/Server/ServerConfig.php b/src/Server/ServerConfig.php index cb36cebd1..7773fbd7b 100644 --- a/src/Server/ServerConfig.php +++ b/src/Server/ServerConfig.php @@ -4,6 +4,7 @@ namespace GraphQL\Server; +use GraphQL\Error\DebugFlag; use GraphQL\Error\InvariantViolation; use GraphQL\Executor\Promise\PromiseAdapter; use GraphQL\Type\Schema; @@ -69,8 +70,8 @@ public static function create(array $config = []) /** @var callable|null */ private $errorsHandler; - /** @var bool */ - private $debug = false; + /** @var int */ + private $debugFlag = 0; /** @var bool */ private $queryBatching = false; @@ -209,15 +210,11 @@ public function setPersistentQueryLoader(callable $persistentQueryLoader) /** * Set response debug flags. See GraphQL\Error\Debug class for a list of all available flags * - * @param bool|int $set - * - * @return self - * * @api */ - public function setDebug($set = true) + public function setDebugFlag(int $debugFlag = DebugFlag::INCLUDE_DEBUG_MESSAGE) : self { - $this->debug = $set; + $this->debugFlag = $debugFlag; return $this; } @@ -318,12 +315,9 @@ public function getPersistentQueryLoader() return $this->persistentQueryLoader; } - /** - * @return bool - */ - public function getDebug() + public function getDebugFlag() : int { - return $this->debug; + return $this->debugFlag; } /** diff --git a/src/Utils/AST.php b/src/Utils/AST.php index d493c1b2c..16610bb23 100644 --- a/src/Utils/AST.php +++ b/src/Utils/AST.php @@ -6,6 +6,7 @@ use ArrayAccess; use Exception; +use GraphQL\Error\DebugFlag; use GraphQL\Error\Error; use GraphQL\Error\InvariantViolation; use GraphQL\Language\AST\BooleanValueNode; @@ -124,7 +125,7 @@ public static function fromArray(array $node) : Node * * @api */ - public static function toArray(Node $node) + public static function toArray(Node $node) : array { return $node->toArray(true); } diff --git a/tests/Executor/AbstractPromiseTest.php b/tests/Executor/AbstractPromiseTest.php index 52a2c4926..727470bee 100644 --- a/tests/Executor/AbstractPromiseTest.php +++ b/tests/Executor/AbstractPromiseTest.php @@ -5,6 +5,7 @@ namespace GraphQL\Tests\Executor; use GraphQL\Deferred; +use GraphQL\Error\DebugFlag; use GraphQL\Error\UserError; use GraphQL\GraphQL; use GraphQL\Tests\Executor\TestClasses\Cat; @@ -361,7 +362,7 @@ public function testResolveTypeOnInterfaceYieldsUsefulError() : void } }'; - $result = GraphQL::executeQuery($schema, $query)->toArray(true); + $result = GraphQL::executeQuery($schema, $query)->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE); $expected = [ 'data' => [ @@ -462,7 +463,7 @@ public function testResolveTypeOnUnionYieldsUsefulError() : void } }'; - $result = GraphQL::executeQuery($schema, $query)->toArray(true); + $result = GraphQL::executeQuery($schema, $query)->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE); $expected = [ 'data' => [ diff --git a/tests/Executor/AbstractTest.php b/tests/Executor/AbstractTest.php index eba24ecf3..eba014f2d 100644 --- a/tests/Executor/AbstractTest.php +++ b/tests/Executor/AbstractTest.php @@ -4,6 +4,7 @@ namespace GraphQL\Tests\Executor; +use GraphQL\Error\DebugFlag; use GraphQL\Executor\ExecutionResult; use GraphQL\Executor\Executor; use GraphQL\GraphQL; @@ -267,7 +268,7 @@ public function testResolveTypeOnInterfaceYieldsUsefulError() : void ], ], ]; - $actual = GraphQL::executeQuery($schema, $query)->toArray(true); + $actual = GraphQL::executeQuery($schema, $query)->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE); self::assertArraySubset($expected, $actual); } @@ -347,7 +348,7 @@ public function testResolveTypeOnUnionYieldsUsefulError() : void } }'; - $result = GraphQL::executeQuery($schema, $query)->toArray(true); + $result = GraphQL::executeQuery($schema, $query)->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE); $expected = [ 'data' => [ 'pets' => [ @@ -424,7 +425,7 @@ public function testReturningInvalidValueFromResolveTypeYieldsUsefulError() : vo ], ], ]; - self::assertEquals($expected, $result->toArray(true)); + self::assertEquals($expected, $result->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE)); } /** diff --git a/tests/Executor/ExecutorLazySchemaTest.php b/tests/Executor/ExecutorLazySchemaTest.php index 245b95df4..cb493195a 100644 --- a/tests/Executor/ExecutorLazySchemaTest.php +++ b/tests/Executor/ExecutorLazySchemaTest.php @@ -4,6 +4,7 @@ namespace GraphQL\Tests\Executor; +use GraphQL\Error\DebugFlag; use GraphQL\Error\InvariantViolation; use GraphQL\Error\Warning; use GraphQL\Executor\ExecutionResult; @@ -244,7 +245,7 @@ public function testSimpleQuery() : void 'SomeObject', 'SomeObject.fields', ]; - self::assertEquals($expected, $result->toArray(true)); + self::assertEquals($expected, $result->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE)); self::assertEquals($expectedExecutorCalls, $this->calls); } @@ -380,7 +381,7 @@ public function testDeepQuery() : void 'OtherObject' => true, ]; - self::assertEquals($expected, $result->toArray(true)); + self::assertEquals($expected, $result->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE)); self::assertEquals($expectedLoadedTypes, $this->loadedTypes); $expectedExecutorCalls = [ @@ -428,7 +429,7 @@ public function testResolveUnion() : void 'SomeScalar' => true, ]; - self::assertEquals($expected, $result->toArray(true)); + self::assertEquals($expected, $result->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE)); self::assertEquals($expectedLoadedTypes, $this->loadedTypes); $expectedCalls = [ diff --git a/tests/Executor/ListsTest.php b/tests/Executor/ListsTest.php index 1ebe5962b..0f7616090 100644 --- a/tests/Executor/ListsTest.php +++ b/tests/Executor/ListsTest.php @@ -5,6 +5,7 @@ namespace GraphQL\Tests\Executor; use GraphQL\Deferred; +use GraphQL\Error\DebugFlag; use GraphQL\Error\UserError; use GraphQL\Executor\Executor; use GraphQL\Language\Parser; @@ -46,7 +47,7 @@ private function checkHandlesNullableLists($testData, $expected) $this->check($testType, $testData, $expected); } - private function check($testType, $testData, $expected, $debug = false) + private function check($testType, $testData, $expected, int $debug = 0) { $data = ['test' => $testData]; $dataType = null; @@ -222,11 +223,11 @@ public function testHandlesNonNullableListsWithArray() : void ], ], ], - true + DebugFlag::INCLUDE_DEBUG_MESSAGE ); } - private function checkHandlesNonNullableLists($testData, $expected, $debug = false) + private function checkHandlesNonNullableLists($testData, $expected, int $debug = 0) { $testType = Type::nonNull(Type::listOf(Type::int())); $this->check($testType, $testData, $expected, $debug); @@ -265,7 +266,7 @@ public function testHandlesNonNullableListsWithPromiseArray() : void ], ], ], - true + DebugFlag::INCLUDE_DEBUG_MESSAGE ); // Rejected @@ -373,7 +374,7 @@ public function testHandlesListOfNonNullsWithArray() : void ], ], ], - true + DebugFlag::INCLUDE_DEBUG_MESSAGE ); // Returns null @@ -383,7 +384,7 @@ public function testHandlesListOfNonNullsWithArray() : void ); } - private function checkHandlesListOfNonNulls($testData, $expected, $debug = false) + private function checkHandlesListOfNonNulls($testData, $expected, int $debug = 0) { $testType = Type::listOf(Type::nonNull(Type::int())); $this->check($testType, $testData, $expected, $debug); @@ -416,7 +417,7 @@ public function testHandlesListOfNonNullsWithPromiseArray() : void ], ], ], - true + DebugFlag::INCLUDE_DEBUG_MESSAGE ); // Returns null @@ -532,7 +533,7 @@ public function testHandlesNonNullListOfNonNullsWithArray() : void ], ], ], - true + DebugFlag::INCLUDE_DEBUG_MESSAGE ); // Returns null @@ -547,11 +548,11 @@ public function testHandlesNonNullListOfNonNullsWithArray() : void ], ], ], - true + DebugFlag::INCLUDE_DEBUG_MESSAGE ); } - public function checkHandlesNonNullListOfNonNulls($testData, $expected, $debug = false) + public function checkHandlesNonNullListOfNonNulls($testData, $expected, int $debug = 0) { $testType = Type::nonNull(Type::listOf(Type::nonNull(Type::int()))); $this->check($testType, $testData, $expected, $debug); @@ -584,7 +585,7 @@ public function testHandlesNonNullListOfNonNullsWithPromiseArray() : void ], ], ], - true + DebugFlag::INCLUDE_DEBUG_MESSAGE ); // Returns null @@ -601,7 +602,7 @@ public function testHandlesNonNullListOfNonNullsWithPromiseArray() : void ], ], ], - true + DebugFlag::INCLUDE_DEBUG_MESSAGE ); // Rejected @@ -665,7 +666,7 @@ public function testHandlesNonNullListOfNonNullsWithArrayPromise() : void ], ], ], - true + DebugFlag::INCLUDE_DEBUG_MESSAGE ); // Contains reject diff --git a/tests/Executor/MutationsTest.php b/tests/Executor/MutationsTest.php index e511b7055..036064985 100644 --- a/tests/Executor/MutationsTest.php +++ b/tests/Executor/MutationsTest.php @@ -4,6 +4,7 @@ namespace GraphQL\Tests\Executor; +use GraphQL\Error\DebugFlag; use GraphQL\Executor\Executor; use GraphQL\Language\Parser; use GraphQL\Tests\Executor\TestClasses\Root; @@ -150,6 +151,6 @@ public function testEvaluatesMutationsCorrectlyInThePresenseOfAFailedMutation() ], ], ]; - self::assertArraySubset($expected, $mutationResult->toArray(true)); + self::assertArraySubset($expected, $mutationResult->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE)); } } diff --git a/tests/Executor/NonNullTest.php b/tests/Executor/NonNullTest.php index f223be124..65a3816e5 100644 --- a/tests/Executor/NonNullTest.php +++ b/tests/Executor/NonNullTest.php @@ -6,6 +6,7 @@ use Exception; use GraphQL\Deferred; +use GraphQL\Error\DebugFlag; use GraphQL\Error\FormattedError; use GraphQL\Error\UserError; use GraphQL\Executor\Executor; @@ -554,7 +555,7 @@ public function testNullsASynchronouslyReturnedObjectThatContainsANonNullableFie ]; self::assertArraySubset( $expected, - Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(true) + Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE) ); } @@ -582,7 +583,7 @@ public function testNullsASynchronouslyReturnedObjectThatContainsANonNullableFie self::assertArraySubset( $expected, - Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(true) + Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE) ); } @@ -610,7 +611,7 @@ public function testNullsAnObjectReturnedInAPromiseThatContainsANonNullableField self::assertArraySubset( $expected, - Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(true) + Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE) ); } @@ -638,7 +639,7 @@ public function testNullsAnObjectReturnedInAPromiseThatContainsANonNullableField self::assertArraySubset( $expected, - Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(true) + Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE) ); } @@ -781,7 +782,7 @@ public function testNullsTheFirstNullableObjectAfterAFieldReturnsNullInALongChai self::assertArraySubset( $expected, - Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(true) + Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE) ); } @@ -1028,7 +1029,7 @@ public function testNullsTheTopLevelIfSyncNonNullableFieldReturnsNull() : void ]; self::assertArraySubset( $expected, - Executor::execute($this->schema, Parser::parse($doc), $this->nullingData)->toArray(true) + Executor::execute($this->schema, Parser::parse($doc), $this->nullingData)->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE) ); } @@ -1051,7 +1052,7 @@ public function testNullsTheTopLevelIfAsyncNonNullableFieldResolvesNull() : void self::assertArraySubset( $expected, - Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(true) + Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE) ); } } diff --git a/tests/Executor/UnionInterfaceTest.php b/tests/Executor/UnionInterfaceTest.php index e24ce610e..ab86805b4 100644 --- a/tests/Executor/UnionInterfaceTest.php +++ b/tests/Executor/UnionInterfaceTest.php @@ -4,6 +4,7 @@ namespace GraphQL\Tests\Executor; +use GraphQL\Error\DebugFlag; use GraphQL\Executor\Executor; use GraphQL\GraphQL; use GraphQL\Language\Parser; @@ -303,7 +304,7 @@ public function testExecutesInterfaceTypesWithInlineFragments() : void ], ]; - self::assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray(true)); + self::assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE)); } /** diff --git a/tests/Experimental/Executor/CollectorTest.php b/tests/Experimental/Executor/CollectorTest.php index b081e9cc6..7c679ab91 100644 --- a/tests/Experimental/Executor/CollectorTest.php +++ b/tests/Experimental/Executor/CollectorTest.php @@ -106,7 +106,7 @@ public function addError($error) $result = []; if (! empty($runtime->errors)) { $result['errors'] = array_map( - FormattedError::prepareFormatter(null, false), + FormattedError::prepareFormatter(null, 0), $runtime->errors ); } diff --git a/tests/Language/SchemaParserTest.php b/tests/Language/SchemaParserTest.php index d3ce8731a..99450a75e 100644 --- a/tests/Language/SchemaParserTest.php +++ b/tests/Language/SchemaParserTest.php @@ -4,6 +4,7 @@ namespace GraphQL\Tests\Language; +use GraphQL\Error\DebugFlag; use GraphQL\Error\SyntaxError; use GraphQL\Language\AST\NodeKind; use GraphQL\Language\Parser; diff --git a/tests/Server/QueryExecutionTest.php b/tests/Server/QueryExecutionTest.php index bc16ed622..957684d4b 100644 --- a/tests/Server/QueryExecutionTest.php +++ b/tests/Server/QueryExecutionTest.php @@ -4,7 +4,7 @@ namespace GraphQL\Tests\Server; -use GraphQL\Error\Debug; +use GraphQL\Error\DebugFlag; use GraphQL\Error\Error; use GraphQL\Error\InvariantViolation; use GraphQL\Executor\ExecutionResult; @@ -47,7 +47,7 @@ public function testSimpleQueryExecution() : void private function assertQueryResultEquals($expected, $query, $variables = null) { $result = $this->executeQuery($query, $variables); - self::assertArraySubset($expected, $result->toArray(true)); + self::assertArraySubset($expected, $result->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE)); return $result; } @@ -77,8 +77,8 @@ public function testReturnsSyntaxErrors() : void public function testDebugExceptions() : void { - $debug = Debug::INCLUDE_DEBUG_MESSAGE | Debug::INCLUDE_TRACE; - $this->config->setDebug($debug); + $debugFlag = DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE; + $this->config->setDebugFlag($debugFlag); $query = ' { @@ -107,7 +107,7 @@ public function testDebugExceptions() : void public function testRethrowUnsafeExceptions() : void { - $this->config->setDebug(Debug::RETHROW_UNSAFE_EXCEPTIONS); + $this->config->setDebugFlag(DebugFlag::RETHROW_UNSAFE_EXCEPTIONS); $this->expectException(Unsafe::class); $this->executeQuery(' @@ -644,7 +644,7 @@ public function testAppliesErrorFormatter() : void self::assertInstanceOf(Error::class, $error); // Assert debugging still works even with custom formatter - $formatted = $result->toArray(Debug::INCLUDE_TRACE); + $formatted = $result->toArray(DebugFlag::INCLUDE_TRACE); $expected = [ 'errors' => [ [ diff --git a/tests/Server/ServerConfigTest.php b/tests/Server/ServerConfigTest.php index a596ab300..fc60f8475 100644 --- a/tests/Server/ServerConfigTest.php +++ b/tests/Server/ServerConfigTest.php @@ -4,6 +4,7 @@ namespace GraphQL\Tests\Server; +use GraphQL\Error\DebugFlag; use GraphQL\Error\InvariantViolation; use GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter; use GraphQL\Server\ServerConfig; @@ -27,7 +28,7 @@ public function testDefaults() : void self::assertNull($config->getValidationRules()); self::assertNull($config->getFieldResolver()); self::assertNull($config->getPersistentQueryLoader()); - self::assertFalse($config->getDebug()); + self::assertSame(0, $config->getDebugFlag()); self::assertFalse($config->getQueryBatching()); } @@ -166,11 +167,11 @@ public function testAllowsSettingCatchPhpErrors() : void { $config = ServerConfig::create(); - $config->setDebug(true); - self::assertTrue($config->getDebug()); + $config->setDebugFlag(DebugFlag::INCLUDE_DEBUG_MESSAGE); + self::assertEquals(DebugFlag::INCLUDE_DEBUG_MESSAGE, $config->getDebugFlag()); - $config->setDebug(false); - self::assertFalse($config->getDebug()); + $config->setDebugFlag(0); + self::assertEquals(0, $config->getDebugFlag()); } public function testAcceptsArray() : void @@ -191,7 +192,7 @@ public function testAcceptsArray() : void }, 'persistentQueryLoader' => static function () { }, - 'debug' => true, + 'debugFlag' => DebugFlag::INCLUDE_DEBUG_MESSAGE, 'queryBatching' => true, ]; @@ -205,7 +206,7 @@ public function testAcceptsArray() : void self::assertSame($arr['validationRules'], $config->getValidationRules()); self::assertSame($arr['fieldResolver'], $config->getFieldResolver()); self::assertSame($arr['persistentQueryLoader'], $config->getPersistentQueryLoader()); - self::assertTrue($config->getDebug()); + self::assertSame(DebugFlag::INCLUDE_DEBUG_MESSAGE, $config->getDebugFlag()); self::assertTrue($config->getQueryBatching()); } diff --git a/tests/Server/StandardServerTest.php b/tests/Server/StandardServerTest.php index a672141c3..4ac72bc59 100644 --- a/tests/Server/StandardServerTest.php +++ b/tests/Server/StandardServerTest.php @@ -4,6 +4,7 @@ namespace GraphQL\Tests\Server; +use GraphQL\Error\DebugFlag; use GraphQL\Executor\ExecutionResult; use GraphQL\Server\Helper; use GraphQL\Server\ServerConfig; @@ -35,7 +36,7 @@ public function testSimpleRequestExecutionWithOutsideParsing() : void 'data' => ['f1' => 'f1'], ]; - self::assertEquals($expected, $result->toArray(true)); + self::assertEquals($expected, $result->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE)); } private function parseRawRequest($contentType, $content, $method = 'POST') @@ -75,7 +76,7 @@ private function preparePsrRequest($contentType, $parsedBody) private function assertPsrRequestEquals($expected, $request) { $result = $this->executePsrRequest($request); - self::assertArraySubset($expected, $result->toArray(true)); + self::assertArraySubset($expected, $result->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE)); return $result; } diff --git a/tests/Type/EnumTypeTest.php b/tests/Type/EnumTypeTest.php index b1649bed2..a81b7af19 100644 --- a/tests/Type/EnumTypeTest.php +++ b/tests/Type/EnumTypeTest.php @@ -5,6 +5,7 @@ namespace GraphQL\Tests\Type; use ArrayObject; +use GraphQL\Error\DebugFlag; use GraphQL\GraphQL; use GraphQL\Language\SourceLocation; use GraphQL\Type\Definition\EnumType; @@ -493,7 +494,7 @@ public function testMayBeInternallyRepresentedWithComplexValues() : void good: complexEnum(provideGoodValue: true) bad: complexEnum(provideBadValue: true) }' - )->toArray(true); + )->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE); $expected = [ 'data' => [ @@ -539,7 +540,7 @@ public function testAllowsSimpleArrayAsValues() : void ], ], ], - GraphQL::executeQuery($this->schema, $q)->toArray(true) + GraphQL::executeQuery($this->schema, $q)->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE) ); } } diff --git a/tests/Utils/BuildSchemaTest.php b/tests/Utils/BuildSchemaTest.php index 64b29ae15..1334f744e 100644 --- a/tests/Utils/BuildSchemaTest.php +++ b/tests/Utils/BuildSchemaTest.php @@ -5,6 +5,7 @@ namespace GraphQL\Tests\Utils; use Closure; +use GraphQL\Error\DebugFlag; use GraphQL\Error\Error; use GraphQL\GraphQL; use GraphQL\Language\AST\EnumTypeDefinitionNode; @@ -40,7 +41,7 @@ public function testUseBuiltSchemaForLimitedExecution() : void ')); $result = GraphQL::executeQuery($schema, '{ str }', ['str' => 123]); - self::assertEquals(['str' => 123], $result->toArray(true)['data']); + self::assertEquals(['str' => 123], $result->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE)['data']); } /** @@ -65,7 +66,7 @@ public function testBuildSchemaDirectlyFromSource() : void '{ add(x: 34, y: 55) }', $root ); - self::assertEquals(['data' => ['add' => 89]], $result->toArray(true)); + self::assertEquals(['data' => ['add' => 89]], $result->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE)); } /** @@ -506,7 +507,7 @@ public function testSpecifyingUnionTypeUsingTypename() : void ]; $result = GraphQL::executeQuery($schema, $query, $rootValue); - self::assertEquals($expected, $result->toArray(true)); + self::assertEquals($expected, $result->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE)); } /** @@ -570,7 +571,7 @@ interface Character { ]; $result = GraphQL::executeQuery($schema, $query, $rootValue); - self::assertEquals($expected, $result->toArray(true)); + self::assertEquals($expected, $result->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE)); } /** diff --git a/tools/gendocs.php b/tools/gendocs.php index 7f7484254..262aaf9ea 100644 --- a/tools/gendocs.php +++ b/tools/gendocs.php @@ -20,10 +20,10 @@ \GraphQL\Executor\ExecutionResult::class, \GraphQL\Executor\Promise\PromiseAdapter::class, \GraphQL\Validator\DocumentValidator::class, - \GraphQL\Error\Error::class => ['constants' => true, 'methods' => true, 'props' => true], - \GraphQL\Error\Warning::class => ['constants' => true, 'methods' => true], + \GraphQL\Error\Error::class => ['constants' => true, 'methods' => true, 'props' => true], + \GraphQL\Error\Warning::class => ['constants' => true, 'methods' => true], \GraphQL\Error\ClientAware::class, - \GraphQL\Error\Debug::class => ['constants' => true], + \GraphQL\Error\DebugFlag::class => ['constants' => true], \GraphQL\Error\FormattedError::class, \GraphQL\Server\StandardServer::class, \GraphQL\Server\ServerConfig::class, From 20d293c548e7ddf14783d9326cefa2164f6239bd Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Mon, 30 Dec 2019 13:42:13 +0100 Subject: [PATCH 132/256] revisit --- CHANGELOG.md | 4 +++- docs/executing-queries.md | 2 +- docs/reference.md | 6 +++--- examples/01-blog/graphql.php | 2 +- src/Error/DebugFlag.php | 3 ++- src/Error/FormattedError.php | 16 +++++++--------- src/Executor/ExecutionResult.php | 6 +++--- src/Server/Helper.php | 3 ++- src/Server/ServerConfig.php | 4 ++-- tests/Executor/ListsTest.php | 8 ++++---- tests/Experimental/Executor/CollectorTest.php | 3 ++- tests/Server/ServerConfigTest.php | 6 +++--- 12 files changed, 33 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 454f40e74..f1687b17d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,9 @@ - Added retrieving query complexity once query has been completed (#316) - Allow input types to be passed in from variables using \stdClass instead of associative arrays (#535) - Fixes parsing of string literals of the form \u0000 for code points in the range [128, 255] inclusive -- Parse UTF-16 surrogate pairs within string literals +- Parse UTF-16 surrogate pairs within string literals +- Renamed `GraphQL\Error\Debug` to `GraphQL\Error\DebugFlag`. +- Debug flags in `GraphQL\Executor\ExecutionResult`, `GraphQL\Error\FormattedError` and `GraphQL\Server\ServerConfig` do not accept `boolean` value anymore but `int` only. #### v0.13.5 - Fix coroutine executor when using with promise (#486) diff --git a/docs/executing-queries.md b/docs/executing-queries.md index d3e8821d7..e8decf149 100644 --- a/docs/executing-queries.md +++ b/docs/executing-queries.md @@ -120,7 +120,7 @@ use GraphQL\Server\StandardServer; $config = ServerConfig::create() ->setSchema($schema) ->setErrorFormatter($myFormatter) - ->setDebug($debug) + ->setDebugFlag($debug) ; $server = new StandardServer($config); diff --git a/docs/reference.md b/docs/reference.md index e4b182e4e..7d4b5a1f0 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -1864,15 +1864,15 @@ function setPersistentQueryLoader(callable $persistentQueryLoader) ```php /** - * Set response debug flags. See GraphQL\Error\Debug class for a list of all available flags + * Set response debug flags. @see GraphQL\Error\DebugFlag class for a list of all available flags * - * @param bool|int $set + * @param int $debug * * @return self * * @api */ -function setDebug($set = true) +function setDebugFlag($debugFlag = DebugFlag::NONE) ``` ```php diff --git a/examples/01-blog/graphql.php b/examples/01-blog/graphql.php index 246c79cde..b48052544 100644 --- a/examples/01-blog/graphql.php +++ b/examples/01-blog/graphql.php @@ -14,7 +14,7 @@ // Disable default PHP error reporting - we have better one for debug mode (see bellow) ini_set('display_errors', 0); -$debug = 0; +$debug = DebugFlag::NONE; if (!empty($_GET['debug'])) { set_error_handler(function($severity, $message, $file, $line) use (&$phpErrors) { throw new ErrorException($message, 0, $severity, $file, $line); diff --git a/src/Error/DebugFlag.php b/src/Error/DebugFlag.php index 4cdb2fb3d..154345da5 100644 --- a/src/Error/DebugFlag.php +++ b/src/Error/DebugFlag.php @@ -7,8 +7,9 @@ /** * Collection of flags for [error debugging](error-handling.md#debugging-tools). */ -class DebugFlag +final class DebugFlag { + public const NONE = 0; public const INCLUDE_DEBUG_MESSAGE = 1; public const INCLUDE_TRACE = 2; public const RETHROW_INTERNAL_EXCEPTIONS = 4; diff --git a/src/Error/FormattedError.php b/src/Error/FormattedError.php index f62137784..6e6c716ba 100644 --- a/src/Error/FormattedError.php +++ b/src/Error/FormattedError.php @@ -161,7 +161,7 @@ private static function lpad($len, $str) * This method only exposes exception message when exception implements ClientAware interface * (or when debug flags are passed). * - * For a list of available debug flags see GraphQL\Error\Debug constants. + * For a list of available debug flags @see \GraphQL\Error\DebugFlag constants. * * @param string $internalErrorMessage * @@ -171,7 +171,7 @@ private static function lpad($len, $str) * * @api */ - public static function createFromException(Throwable $exception, int $debug = 0, $internalErrorMessage = null) + public static function createFromException(Throwable $exception, int $debug = DebugFlag::NONE, $internalErrorMessage = null) : array { $internalErrorMessage = $internalErrorMessage ?: self::$internalErrorMessage; @@ -209,7 +209,7 @@ static function (SourceLocation $loc) { } } - if ($debug !== 0) { + if ($debug !== DebugFlag::NONE) { $formattedError = self::addDebugEntries($formattedError, $exception, $debug); } @@ -218,7 +218,7 @@ static function (SourceLocation $loc) { /** * Decorates spec-compliant $formattedError with debug entries according to $debug flags - * (see GraphQL\Error\Debug for available flags) + * (@see \GraphQL\Error\DebugFlag for available flags) * * @param mixed[] $formattedError * @@ -226,9 +226,9 @@ static function (SourceLocation $loc) { * * @throws Throwable */ - public static function addDebugEntries(array $formattedError, Throwable $e, int $debugFlag) + public static function addDebugEntries(array $formattedError, Throwable $e, int $debugFlag) : array { - if ($debugFlag === 0) { + if ($debugFlag === DebugFlag::NONE) { return $formattedError; } @@ -275,10 +275,8 @@ public static function addDebugEntries(array $formattedError, Throwable $e, int /** * Prepares final error formatter taking in account $debug flags. * If initial formatter is not set, FormattedError::createFromException is used - * - * @return callable */ - public static function prepareFormatter(?callable $formatter = null, int $debug) + public static function prepareFormatter(?callable $formatter = null, int $debug) : callable { $formatter = $formatter ?: static function ($e) { return FormattedError::createFromException($e); diff --git a/src/Executor/ExecutionResult.php b/src/Executor/ExecutionResult.php index e4d4fc439..27b136bcd 100644 --- a/src/Executor/ExecutionResult.php +++ b/src/Executor/ExecutionResult.php @@ -4,6 +4,7 @@ namespace GraphQL\Executor; +use GraphQL\Error\DebugFlag; use GraphQL\Error\Error; use GraphQL\Error\FormattedError; use JsonSerializable; @@ -125,14 +126,13 @@ public function jsonSerialize() * If debug argument is passed, output of error formatter is enriched which debugging information * ("debugMessage", "trace" keys depending on flags). * - * $debug argument must be either bool (only adds "debugMessage" to result) or sum of flags from - * GraphQL\Error\Debug + * $debug argument must sum of flags from @see \GraphQL\Error\DebugFlag * * @return mixed[] * * @api */ - public function toArray(int $debug = 0) : array + public function toArray(int $debug = DebugFlag::NONE) : array { $result = []; diff --git a/src/Server/Helper.php b/src/Server/Helper.php index 3986148ff..04a686898 100644 --- a/src/Server/Helper.php +++ b/src/Server/Helper.php @@ -4,6 +4,7 @@ namespace GraphQL\Server; +use GraphQL\Error\DebugFlag; use GraphQL\Error\Error; use GraphQL\Error\FormattedError; use GraphQL\Error\InvariantViolation; @@ -319,7 +320,7 @@ static function (RequestError $err) { if ($config->getErrorsHandler()) { $result->setErrorsHandler($config->getErrorsHandler()); } - if ($config->getErrorFormatter() || $config->getDebugFlag() !== 0) { + if ($config->getErrorFormatter() || $config->getDebugFlag() !== DebugFlag::NONE) { $result->setErrorFormatter( FormattedError::prepareFormatter( $config->getErrorFormatter(), diff --git a/src/Server/ServerConfig.php b/src/Server/ServerConfig.php index 7773fbd7b..2b92ec514 100644 --- a/src/Server/ServerConfig.php +++ b/src/Server/ServerConfig.php @@ -71,7 +71,7 @@ public static function create(array $config = []) private $errorsHandler; /** @var int */ - private $debugFlag = 0; + private $debugFlag = DebugFlag::NONE; /** @var bool */ private $queryBatching = false; @@ -208,7 +208,7 @@ public function setPersistentQueryLoader(callable $persistentQueryLoader) } /** - * Set response debug flags. See GraphQL\Error\Debug class for a list of all available flags + * Set response debug flags. @see \GraphQL\Error\DebugFlag class for a list of all available flags * * @api */ diff --git a/tests/Executor/ListsTest.php b/tests/Executor/ListsTest.php index 0f7616090..5a0122f7f 100644 --- a/tests/Executor/ListsTest.php +++ b/tests/Executor/ListsTest.php @@ -47,7 +47,7 @@ private function checkHandlesNullableLists($testData, $expected) $this->check($testType, $testData, $expected); } - private function check($testType, $testData, $expected, int $debug = 0) + private function check($testType, $testData, $expected, int $debug = DebugFlag::NONE) { $data = ['test' => $testData]; $dataType = null; @@ -227,7 +227,7 @@ public function testHandlesNonNullableListsWithArray() : void ); } - private function checkHandlesNonNullableLists($testData, $expected, int $debug = 0) + private function checkHandlesNonNullableLists($testData, $expected, int $debug = DebugFlag::NONE) { $testType = Type::nonNull(Type::listOf(Type::int())); $this->check($testType, $testData, $expected, $debug); @@ -384,7 +384,7 @@ public function testHandlesListOfNonNullsWithArray() : void ); } - private function checkHandlesListOfNonNulls($testData, $expected, int $debug = 0) + private function checkHandlesListOfNonNulls($testData, $expected, int $debug = DebugFlag::NONE) { $testType = Type::listOf(Type::nonNull(Type::int())); $this->check($testType, $testData, $expected, $debug); @@ -552,7 +552,7 @@ public function testHandlesNonNullListOfNonNullsWithArray() : void ); } - public function checkHandlesNonNullListOfNonNulls($testData, $expected, int $debug = 0) + public function checkHandlesNonNullListOfNonNulls($testData, $expected, int $debug = DebugFlag::NONE) { $testType = Type::nonNull(Type::listOf(Type::nonNull(Type::int()))); $this->check($testType, $testData, $expected, $debug); diff --git a/tests/Experimental/Executor/CollectorTest.php b/tests/Experimental/Executor/CollectorTest.php index 7c679ab91..294f4171a 100644 --- a/tests/Experimental/Executor/CollectorTest.php +++ b/tests/Experimental/Executor/CollectorTest.php @@ -4,6 +4,7 @@ namespace GraphQL\Tests\Experimental\Executor; +use GraphQL\Error\DebugFlag; use GraphQL\Error\FormattedError; use GraphQL\Experimental\Executor\Collector; use GraphQL\Experimental\Executor\Runtime; @@ -106,7 +107,7 @@ public function addError($error) $result = []; if (! empty($runtime->errors)) { $result['errors'] = array_map( - FormattedError::prepareFormatter(null, 0), + FormattedError::prepareFormatter(null, DebugFlag::NONE), $runtime->errors ); } diff --git a/tests/Server/ServerConfigTest.php b/tests/Server/ServerConfigTest.php index fc60f8475..fb9c87c06 100644 --- a/tests/Server/ServerConfigTest.php +++ b/tests/Server/ServerConfigTest.php @@ -28,7 +28,7 @@ public function testDefaults() : void self::assertNull($config->getValidationRules()); self::assertNull($config->getFieldResolver()); self::assertNull($config->getPersistentQueryLoader()); - self::assertSame(0, $config->getDebugFlag()); + self::assertSame(DebugFlag::NONE, $config->getDebugFlag()); self::assertFalse($config->getQueryBatching()); } @@ -170,8 +170,8 @@ public function testAllowsSettingCatchPhpErrors() : void $config->setDebugFlag(DebugFlag::INCLUDE_DEBUG_MESSAGE); self::assertEquals(DebugFlag::INCLUDE_DEBUG_MESSAGE, $config->getDebugFlag()); - $config->setDebugFlag(0); - self::assertEquals(0, $config->getDebugFlag()); + $config->setDebugFlag(DebugFlag::NONE); + self::assertEquals(DebugFlag::NONE, $config->getDebugFlag()); } public function testAcceptsArray() : void From e7bef7c3be56ef35cea6c2f10006e0d596c06cc8 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Mon, 30 Dec 2019 20:21:36 +0700 Subject: [PATCH 133/256] Add test to verify that array values for enums work as expected --- tests/Type/EnumTypeTest.php | 60 +++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/tests/Type/EnumTypeTest.php b/tests/Type/EnumTypeTest.php index b1649bed2..fbebbbf40 100644 --- a/tests/Type/EnumTypeTest.php +++ b/tests/Type/EnumTypeTest.php @@ -64,6 +64,15 @@ public function setUp() ], ]); + $Array1 = ['one', 'ONE']; + $ArrayValuesEnum = new EnumType([ + 'name' => 'ArrayValuesEnum', + 'values' => [ + 'ONE' => ['value' => $Array1], + 'TWO' => ['value' => ['two', 'TWO']], + ], + ]); + $QueryType = new ObjectType([ 'name' => 'Query', 'fields' => [ @@ -144,6 +153,33 @@ public function setUp() return new ArrayObject(['someRandomValue' => 123]); } + return $args['fromEnum']; + }, + ], + 'arrayValuesEnum' => [ + 'type' => $ArrayValuesEnum, + 'args' => [ + 'fromEnum' => [ + 'type' => $ArrayValuesEnum, + // Note: defaultValue is provided an *internal* representation for + // Enums, rather than the string name. + 'defaultValue' => $Array1, + ], + 'provideOneByReference' => [ + 'type' => Type::boolean(), + ], + 'provideTwo' => [ + 'type' => Type::boolean(), + ], + ], + 'resolve' => static function ($rootValue, $args) use (&$Array1) { + if (! empty($args['provideOneByReference'])) { + return $Array1; + } + if (! empty($args['provideTwo'])) { + return ['two', 'TWO']; + } + return $args['fromEnum']; }, ], @@ -513,6 +549,30 @@ public function testMayBeInternallyRepresentedWithComplexValues() : void self::assertArraySubset($expected, $result); } + public function testMayBeInternallyRepresentedWithArrayValues() : void + { + $result = GraphQL::executeQuery( + $this->schema, + '{ + defaultValue: arrayValuesEnum + fromName: arrayValuesEnum(fromEnum: TWO) + oneRef: arrayValuesEnum(provideOneByReference: true) + two: arrayValuesEnum(provideTwo: true) + }' + )->toArray(true); + + $expected = [ + 'data' => [ + 'defaultValue' => 'ONE', + 'fromName' => 'TWO', + 'oneRef' => 'ONE', + 'two' => 'TWO', + ], + ]; + + self::assertEquals($expected, $result); + } + /** * @see it('can be introspected without error') */ From 3228c54741d361027774801971e747e03da141df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Podlipsk=C3=BD?= Date: Sun, 5 Jan 2020 18:21:54 +0100 Subject: [PATCH 134/256] Use Coveralls, drop Scrutinizer (#603) --- .coveralls.yml | 3 +++ .github/workflows/ci-build.yml | 13 ++++++++----- .scrutinizer.yml | 31 ------------------------------- .travis.yml | 13 ------------- README.md | 2 +- 5 files changed, 12 insertions(+), 50 deletions(-) create mode 100644 .coveralls.yml delete mode 100644 .scrutinizer.yml diff --git a/.coveralls.yml b/.coveralls.yml new file mode 100644 index 000000000..3bd089c13 --- /dev/null +++ b/.coveralls.yml @@ -0,0 +1,3 @@ +service_name: github-actions +coverage_clover: /tmp/coverage/*.xml +json_path: /tmp/coverage/coverage.json diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 6f38a5562..b5f39e3b0 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -145,9 +145,12 @@ jobs: continue-on-error: true - name: Code coverage + env: + COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} + COVERALLS_GIT_COMMIT: ${{ github.sha }} + COVERALLS_GIT_BRANCH: ${{ github.ref }} run: | - ./vendor/bin/phpunit --coverage-php /tmp/coverage/clover_executor.cov - EXECUTOR=coroutine ./vendor/bin/phpunit --coverage-php /tmp/coverage/clover_executor-coroutine.cov - ./vendor/bin/phpcov merge /tmp/coverage --clover /tmp/clover.xml - wget https://github.com/scrutinizer-ci/ocular/releases/download/1.5.2/ocular.phar - php7.1 ocular.phar code-coverage:upload --format=php-clover /tmp/clover.xml + ./vendor/bin/phpunit --coverage-clover /tmp/coverage/clover_executor.xml + EXECUTOR=coroutine ./vendor/bin/phpunit --coverage-clover /tmp/coverage/clover_executor-coroutine.xml + wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.2.0/php-coveralls.phar + php7.1 php-coveralls.phar --verbose diff --git a/.scrutinizer.yml b/.scrutinizer.yml deleted file mode 100644 index 21f09aae0..000000000 --- a/.scrutinizer.yml +++ /dev/null @@ -1,31 +0,0 @@ -build: - nodes: - analysis: - environment: - php: - version: 7.1 - cache: - disabled: false - directories: - - ~/.composer/cache - project_setup: - override: true - tests: - override: - - php-scrutinizer-run - - dependencies: - override: - - composer install --ignore-platform-reqs --no-interaction - -tools: - external_code_coverage: - timeout: 3600 - -filter: - paths: - - "src/" - -build_failure_conditions: - - 'elements.rating(<= C).new.exists' # No new classes/methods with a rating of C or worse allowed - - 'project.metric_change("scrutinizer.test_coverage", < 0)' # Code Coverage decreased from previous inspection diff --git a/.travis.yml b/.travis.yml index e6a6a6d30..af385f34a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,19 +38,6 @@ jobs: install: - travis_retry composer update --prefer-dist {$DEPENDENCIES} - - stage: Test - env: COVERAGE - before_script: - - mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{.disabled,} - - if [[ ! $(php -m | grep -si xdebug) ]]; then echo "xdebug required for coverage"; exit 1; fi - script: - - ./vendor/bin/phpunit --coverage-php /tmp/coverage/clover_executor.cov - - EXECUTOR=coroutine ./vendor/bin/phpunit --coverage-php /tmp/coverage/clover_executor-coroutine.cov - after_script: - - ./vendor/bin/phpcov merge /tmp/coverage --clover /tmp/clover.xml - - wget https://github.com/scrutinizer-ci/ocular/releases/download/1.5.2/ocular.phar - - php ocular.phar code-coverage:upload --format=php-clover /tmp/clover.xml - - stage: Code Quality php: 7.1 env: CODING_STANDARD diff --git a/README.md b/README.md index 115505634..acffa4cb1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # graphql-php [![Build Status](https://travis-ci.org/webonyx/graphql-php.svg?branch=master)](https://travis-ci.org/webonyx/graphql-php) -[![Code Coverage](https://scrutinizer-ci.com/g/webonyx/graphql-php/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/webonyx/graphql-php) +[![Coverage Status](https://coveralls.io/repos/github/simPod/graphql-php/badge.svg?branch=master)](https://coveralls.io/github/simPod/graphql-php?branch=master) [![Latest Stable Version](https://poser.pugx.org/webonyx/graphql-php/version)](https://packagist.org/packages/webonyx/graphql-php) [![License](https://poser.pugx.org/webonyx/graphql-php/license)](https://packagist.org/packages/webonyx/graphql-php) From 6fd55fc73b9215ab70e10f21e9b024890d28e992 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Podlipsk=C3=BD?= Date: Sun, 5 Jan 2020 18:22:35 +0100 Subject: [PATCH 135/256] Upgrade php github action (#604) --- .github/workflows/ci-build.yml | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index b5f39e3b0..cd83eab5b 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -24,10 +24,11 @@ jobs: - uses: actions/checkout@v1 - name: Install PHP - uses: shivammathur/setup-php@1.6.0 + uses: shivammathur/setup-php@1.7.0 with: php-version: ${{ matrix.php }} - extension-csv: json, mbstring + coverage: none + extensions: json, mbstring - name: Get Composer Cache Directory id: composer-cache run: echo "::set-output name=dir::$(composer config cache-files-dir)" @@ -57,10 +58,11 @@ jobs: - uses: actions/checkout@v1 - name: Install PHP - uses: shivammathur/setup-php@1.6.0 + uses: shivammathur/setup-php@1.7.0 with: php-version: 7.1 - extension-csv: json, mbstring + coverage: none + extensions: json, mbstring - name: Get Composer Cache Directory id: composer-cache @@ -87,10 +89,11 @@ jobs: - uses: actions/checkout@v1 - name: Install PHP - uses: shivammathur/setup-php@1.6.0 + uses: shivammathur/setup-php@1.7.0 with: php-version: 7.1 - extension-csv: json, mbstring + coverage: none + extensions: json, mbstring - name: Get Composer Cache Directory id: composer-cache @@ -117,12 +120,12 @@ jobs: - uses: actions/checkout@v1 - name: Install PHP - uses: shivammathur/setup-php@1.6.0 + uses: shivammathur/setup-php@1.7.0 with: php-version: 7.1 - extension-csv: json, mbstring - coverage: pcov - pecl: true + coverage: none + extensions: json, mbstring + tools: pecl - name: Get Composer Cache Directory id: composer-cache From 3cccd475d5c045656841a8bb176d8864a7ddb181 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Sun, 5 Jan 2020 18:23:28 +0100 Subject: [PATCH 136/256] Fix vendor in Coveralls badge links --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index acffa4cb1..f17ac4b61 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # graphql-php [![Build Status](https://travis-ci.org/webonyx/graphql-php.svg?branch=master)](https://travis-ci.org/webonyx/graphql-php) -[![Coverage Status](https://coveralls.io/repos/github/simPod/graphql-php/badge.svg?branch=master)](https://coveralls.io/github/simPod/graphql-php?branch=master) +[![Coverage Status](https://coveralls.io/repos/github/webonyx/graphql-php/badge.svg?branch=master)](https://coveralls.io/github/webonyx/graphql-php?branch=master) [![Latest Stable Version](https://poser.pugx.org/webonyx/graphql-php/version)](https://packagist.org/packages/webonyx/graphql-php) [![License](https://poser.pugx.org/webonyx/graphql-php/license)](https://packagist.org/packages/webonyx/graphql-php) From 798b3475e1bd5757121b608d952c12fd91f7e07c Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Sun, 5 Jan 2020 19:06:13 +0100 Subject: [PATCH 137/256] Fix branch coverage reporting --- .coveralls.yml | 1 - .github/workflows/ci-build.yml | 22 ++++++++++++---------- composer.json | 7 +++++++ 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/.coveralls.yml b/.coveralls.yml index 3bd089c13..da2258482 100644 --- a/.coveralls.yml +++ b/.coveralls.yml @@ -1,3 +1,2 @@ -service_name: github-actions coverage_clover: /tmp/coverage/*.xml json_path: /tmp/coverage/coverage.json diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index cd83eab5b..ec6f1edb7 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -21,7 +21,7 @@ jobs: name: PHP ${{ matrix.php }} Test ${{ matrix.env }} steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Install PHP uses: shivammathur/setup-php@1.7.0 @@ -55,7 +55,7 @@ jobs: name: Coding Standard steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Install PHP uses: shivammathur/setup-php@1.7.0 @@ -86,7 +86,7 @@ jobs: name: PHPStan steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Install PHP uses: shivammathur/setup-php@1.7.0 @@ -117,7 +117,9 @@ jobs: name: Code Coverage steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 + with: + ref: ${{ github.ref }} - name: Install PHP uses: shivammathur/setup-php@1.7.0 @@ -148,12 +150,12 @@ jobs: continue-on-error: true - name: Code coverage - env: - COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} - COVERALLS_GIT_COMMIT: ${{ github.sha }} - COVERALLS_GIT_BRANCH: ${{ github.ref }} run: | ./vendor/bin/phpunit --coverage-clover /tmp/coverage/clover_executor.xml EXECUTOR=coroutine ./vendor/bin/phpunit --coverage-clover /tmp/coverage/clover_executor-coroutine.xml - wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.2.0/php-coveralls.phar - php7.1 php-coveralls.phar --verbose + + - name: Report to Coveralls + env: + COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COVERALLS_RUN_LOCALLY: 1 + run: vendor/bin/php-coveralls --verbose diff --git a/composer.json b/composer.json index 5cc3d0514..3aa6a1155 100644 --- a/composer.json +++ b/composer.json @@ -8,6 +8,12 @@ "graphql", "API" ], + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/simPod/php-coveralls" + } + ], "require": { "php": "^7.1||^8.0", "ext-json": "*", @@ -15,6 +21,7 @@ }, "require-dev": { "doctrine/coding-standard": "^6.0", + "php-coveralls/php-coveralls": "dev-add-support-for-github-actions@dev", "phpbench/phpbench": "^0.14", "phpstan/phpstan": "0.12.2", "phpstan/phpstan-phpunit": "0.12.1", From 949e6f2057ab38decfd5242f5eb2a229899357e0 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Thu, 16 Jan 2020 13:29:22 +0100 Subject: [PATCH 138/256] Lock commit for php-coveralls --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3aa6a1155..09d0ad4c5 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ }, "require-dev": { "doctrine/coding-standard": "^6.0", - "php-coveralls/php-coveralls": "dev-add-support-for-github-actions@dev", + "php-coveralls/php-coveralls": "dev-add-support-for-github-actions#b9c9d4c8ffdf837c589aba5da3f83e660350bfea", "phpbench/phpbench": "^0.14", "phpstan/phpstan": "0.12.2", "phpstan/phpstan-phpunit": "0.12.1", From 611f5b04704fe6901737c973d052caaf275bc00f Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Thu, 16 Jan 2020 13:56:30 +0100 Subject: [PATCH 139/256] Temporarily use php-coveralls mirror to bypass Github limits See https://github.com/webonyx/graphql-php/commit/949e6f2057ab38decfd5242f5eb2a229899357e0/checks?check_suite_id=404111226 Until this gets merged and released https://github.com/php-coveralls/php-coveralls/pull/275 --- composer.json | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/composer.json b/composer.json index 09d0ad4c5..e7e878734 100644 --- a/composer.json +++ b/composer.json @@ -8,12 +8,6 @@ "graphql", "API" ], - "repositories": [ - { - "type": "vcs", - "url": "https://github.com/simPod/php-coveralls" - } - ], "require": { "php": "^7.1||^8.0", "ext-json": "*", @@ -21,7 +15,6 @@ }, "require-dev": { "doctrine/coding-standard": "^6.0", - "php-coveralls/php-coveralls": "dev-add-support-for-github-actions#b9c9d4c8ffdf837c589aba5da3f83e660350bfea", "phpbench/phpbench": "^0.14", "phpstan/phpstan": "0.12.2", "phpstan/phpstan-phpunit": "0.12.1", @@ -30,6 +23,7 @@ "phpunit/phpunit": "^7.2", "psr/http-message": "^1.0", "react/promise": "2.*", + "simpod/php-coveralls-mirror": "^3.0", "squizlabs/php_codesniffer": "^3.5.2" }, "config": { From cf0912d5d7281d45914843462e0289fed7c9f3d8 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Mon, 6 Jan 2020 23:47:43 +0100 Subject: [PATCH 140/256] Speedup coverage - Replace XDebug by PCOV (faster for coverage) - Upgrade PHPUnit to v8 (keep v7 for EOLed PHP 7.1) - Run coverage on PHP 7.2 - Fix PHPUnit v8 deprecations - Port assertArraySubset() from dms/phpunit-arraysubset-asserts for the method in PHPUnit it's deprecated. Also library does not support PHP 7.1, therefore must be ported --- .github/workflows/ci-build.yml | 11 +- composer.json | 3 +- phpstan.neon.dist | 5 + tests/Executor/AbstractPromiseTest.php | 3 + tests/Executor/AbstractTest.php | 3 + tests/Executor/DeferredFieldsTest.php | 2 +- tests/Executor/ExecutorTest.php | 5 +- tests/Executor/LazyInterfaceTest.php | 2 +- tests/Executor/ListsTest.php | 4 + tests/Executor/MutationsTest.php | 4 + tests/Executor/NonNullTest.php | 5 +- .../Promise/ReactPromiseAdapterTest.php | 2 +- .../Promise/SyncPromiseAdapterTest.php | 2 +- tests/Executor/SyncTest.php | 5 +- tests/Executor/UnionInterfaceTest.php | 2 +- tests/Executor/VariablesTest.php | 3 + tests/Language/LexerTest.php | 3 + tests/Language/SchemaParserTest.php | 4 + tests/Language/VisitorTest.php | 4 +- tests/PHPUnit/ArraySubsetAsserts.php | 41 +++++ tests/PHPUnit/Constraint/ArraySubset.php | 144 ++++++++++++++++++ tests/Server/QueryExecutionTest.php | 13 +- tests/Server/RequestParsingTest.php | 2 +- tests/Server/StandardServerTest.php | 5 +- tests/Type/DefinitionTest.php | 5 +- tests/Type/EnumTypeTest.php | 5 +- tests/Type/IntrospectionTest.php | 3 + tests/Type/SchemaTest.php | 2 +- tests/Type/StandardTypesTest.php | 4 +- tests/Type/TypeLoaderTest.php | 5 +- tests/Type/ValidationTest.php | 4 +- tests/Utils/BreakingChangesFinderTest.php | 2 +- tests/Utils/BuildSchemaTest.php | 4 + tests/Utils/CoerceValueTest.php | 58 +++---- tests/Utils/ExtractTypesTest.php | 2 +- tests/Utils/MixedStoreTest.php | 2 +- tests/Utils/SchemaExtenderTest.php | 8 +- tests/Validator/KnownDirectivesTest.php | 2 +- tests/Validator/QuerySecurityTestCase.php | 8 +- 39 files changed, 314 insertions(+), 77 deletions(-) create mode 100644 tests/PHPUnit/ArraySubsetAsserts.php create mode 100644 tests/PHPUnit/Constraint/ArraySubset.php diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index ec6f1edb7..d47e79063 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -124,10 +124,9 @@ jobs: - name: Install PHP uses: shivammathur/setup-php@1.7.0 with: - php-version: 7.1 - coverage: none + php-version: 7.2 + coverage: pcov extensions: json, mbstring - tools: pecl - name: Get Composer Cache Directory id: composer-cache @@ -143,12 +142,6 @@ jobs: - name: Install Dependencies run: composer install ${DEPENDENCIES} - - name: Update xdebug version - run: | - sudo pecl channel-update pecl.php.net - sudo pecl install xdebug-2.9.0 - continue-on-error: true - - name: Code coverage run: | ./vendor/bin/phpunit --coverage-clover /tmp/coverage/clover_executor.xml diff --git a/composer.json b/composer.json index e7e878734..90f8f5292 100644 --- a/composer.json +++ b/composer.json @@ -19,8 +19,7 @@ "phpstan/phpstan": "0.12.2", "phpstan/phpstan-phpunit": "0.12.1", "phpstan/phpstan-strict-rules": "0.12.0", - "phpunit/phpcov": "^5.0", - "phpunit/phpunit": "^7.2", + "phpunit/phpunit": "^7.2|^8.5", "psr/http-message": "^1.0", "react/promise": "2.*", "simpod/php-coveralls-mirror": "^3.0", diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 2290befea..b03a01a01 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -7,6 +7,11 @@ parameters: - %currentWorkingDirectory%/src - %currentWorkingDirectory%/tests + excludes_analyse: + # Ported from dms/phpunit-arraysubset-asserts + - tests/PHPUnit/ArraySubsetAsserts.php + - tests/PHPUnit/Constraint/ArraySubset.php + ignoreErrors: # Since this is a library that is supposed to be flexible, we don't # want to lock down every possible extension point. diff --git a/tests/Executor/AbstractPromiseTest.php b/tests/Executor/AbstractPromiseTest.php index 52a2c4926..c5fd7935a 100644 --- a/tests/Executor/AbstractPromiseTest.php +++ b/tests/Executor/AbstractPromiseTest.php @@ -10,6 +10,7 @@ use GraphQL\Tests\Executor\TestClasses\Cat; use GraphQL\Tests\Executor\TestClasses\Dog; use GraphQL\Tests\Executor\TestClasses\Human; +use GraphQL\Tests\PHPUnit\ArraySubsetAsserts; use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; @@ -22,6 +23,8 @@ */ class AbstractPromiseTest extends TestCase { + use ArraySubsetAsserts; + /** * @see it('isTypeOf used to resolve runtime type for Interface') */ diff --git a/tests/Executor/AbstractTest.php b/tests/Executor/AbstractTest.php index eba24ecf3..60f5b793f 100644 --- a/tests/Executor/AbstractTest.php +++ b/tests/Executor/AbstractTest.php @@ -11,6 +11,7 @@ use GraphQL\Tests\Executor\TestClasses\Cat; use GraphQL\Tests\Executor\TestClasses\Dog; use GraphQL\Tests\Executor\TestClasses\Human; +use GraphQL\Tests\PHPUnit\ArraySubsetAsserts; use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; @@ -23,6 +24,8 @@ */ class AbstractTest extends TestCase { + use ArraySubsetAsserts; + /** * @see it('isTypeOf used to resolve runtime type for Interface') */ diff --git a/tests/Executor/DeferredFieldsTest.php b/tests/Executor/DeferredFieldsTest.php index 6cab92c3e..838df8677 100644 --- a/tests/Executor/DeferredFieldsTest.php +++ b/tests/Executor/DeferredFieldsTest.php @@ -43,7 +43,7 @@ class DeferredFieldsTest extends TestCase /** @var ObjectType */ private $queryType; - public function setUp() + public function setUp() : void { $this->storyDataSource = [ ['id' => 1, 'authorId' => 1, 'title' => 'Story #1', 'categoryIds' => [2, 3]], diff --git a/tests/Executor/ExecutorTest.php b/tests/Executor/ExecutorTest.php index 3336ddc60..28b36f9ed 100644 --- a/tests/Executor/ExecutorTest.php +++ b/tests/Executor/ExecutorTest.php @@ -12,6 +12,7 @@ use GraphQL\Language\Parser; use GraphQL\Tests\Executor\TestClasses\NotSpecial; use GraphQL\Tests\Executor\TestClasses\Special; +use GraphQL\Tests\PHPUnit\ArraySubsetAsserts; use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\FieldDefinition; use GraphQL\Type\Definition\InputObjectType; @@ -27,7 +28,9 @@ class ExecutorTest extends TestCase { - public function tearDown() + use ArraySubsetAsserts; + + public function tearDown() : void { Executor::setPromiseAdapter(null); } diff --git a/tests/Executor/LazyInterfaceTest.php b/tests/Executor/LazyInterfaceTest.php index 11ca9a58b..b1602ecec 100644 --- a/tests/Executor/LazyInterfaceTest.php +++ b/tests/Executor/LazyInterfaceTest.php @@ -50,7 +50,7 @@ public function testReturnsFragmentsWithLazyCreatedInterface() : void /** * Setup schema */ - protected function setUp() + protected function setUp() : void { $query = new ObjectType([ 'name' => 'query', diff --git a/tests/Executor/ListsTest.php b/tests/Executor/ListsTest.php index 1ebe5962b..98bbafa79 100644 --- a/tests/Executor/ListsTest.php +++ b/tests/Executor/ListsTest.php @@ -8,6 +8,7 @@ use GraphQL\Error\UserError; use GraphQL\Executor\Executor; use GraphQL\Language\Parser; +use GraphQL\Tests\PHPUnit\ArraySubsetAsserts; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Schema; @@ -15,7 +16,10 @@ class ListsTest extends TestCase { + use ArraySubsetAsserts; + // Describe: Execute: Handles list nullability + /** * [T] */ diff --git a/tests/Executor/MutationsTest.php b/tests/Executor/MutationsTest.php index e511b7055..2f45a186a 100644 --- a/tests/Executor/MutationsTest.php +++ b/tests/Executor/MutationsTest.php @@ -7,6 +7,7 @@ use GraphQL\Executor\Executor; use GraphQL\Language\Parser; use GraphQL\Tests\Executor\TestClasses\Root; +use GraphQL\Tests\PHPUnit\ArraySubsetAsserts; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Schema; @@ -14,7 +15,10 @@ class MutationsTest extends TestCase { + use ArraySubsetAsserts; + // Execute: Handles mutation execution ordering + /** * @see it('evaluates mutations serially') */ diff --git a/tests/Executor/NonNullTest.php b/tests/Executor/NonNullTest.php index f223be124..e24c5093a 100644 --- a/tests/Executor/NonNullTest.php +++ b/tests/Executor/NonNullTest.php @@ -11,6 +11,7 @@ use GraphQL\Executor\Executor; use GraphQL\Language\Parser; use GraphQL\Language\SourceLocation; +use GraphQL\Tests\PHPUnit\ArraySubsetAsserts; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Schema; @@ -22,6 +23,8 @@ class NonNullTest extends TestCase { + use ArraySubsetAsserts; + /** @var Exception */ public $syncError; @@ -46,7 +49,7 @@ class NonNullTest extends TestCase /** @var Schema */ public $schemaWithNonNullArg; - public function setUp() + public function setUp() : void { $this->syncError = new UserError('sync'); $this->syncNonNullError = new UserError('syncNonNull'); diff --git a/tests/Executor/Promise/ReactPromiseAdapterTest.php b/tests/Executor/Promise/ReactPromiseAdapterTest.php index 7e8af70e8..58e821ffe 100644 --- a/tests/Executor/Promise/ReactPromiseAdapterTest.php +++ b/tests/Executor/Promise/ReactPromiseAdapterTest.php @@ -20,7 +20,7 @@ */ class ReactPromiseAdapterTest extends TestCase { - public function setUp() + public function setUp() : void { if (class_exists('React\Promise\Promise')) { return; diff --git a/tests/Executor/Promise/SyncPromiseAdapterTest.php b/tests/Executor/Promise/SyncPromiseAdapterTest.php index b9d97b58d..32c1ef81b 100644 --- a/tests/Executor/Promise/SyncPromiseAdapterTest.php +++ b/tests/Executor/Promise/SyncPromiseAdapterTest.php @@ -19,7 +19,7 @@ class SyncPromiseAdapterTest extends TestCase /** @var SyncPromiseAdapter */ private $promises; - public function setUp() + public function setUp() : void { $this->promises = new SyncPromiseAdapter(); } diff --git a/tests/Executor/SyncTest.php b/tests/Executor/SyncTest.php index e2255facd..6128dfd64 100644 --- a/tests/Executor/SyncTest.php +++ b/tests/Executor/SyncTest.php @@ -13,6 +13,7 @@ use GraphQL\Executor\Promise\Promise; use GraphQL\GraphQL; use GraphQL\Language\Parser; +use GraphQL\Tests\PHPUnit\ArraySubsetAsserts; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Schema; @@ -22,13 +23,15 @@ class SyncTest extends TestCase { + use ArraySubsetAsserts; + /** @var Schema */ private $schema; /** @var SyncPromiseAdapter */ private $promiseAdapter; - public function setUp() + public function setUp() : void { $this->schema = new Schema([ 'query' => new ObjectType([ diff --git a/tests/Executor/UnionInterfaceTest.php b/tests/Executor/UnionInterfaceTest.php index e24ce610e..766a966ee 100644 --- a/tests/Executor/UnionInterfaceTest.php +++ b/tests/Executor/UnionInterfaceTest.php @@ -35,7 +35,7 @@ class UnionInterfaceTest extends TestCase /** @var Person */ public $john; - public function setUp() + public function setUp() : void { $NamedType = new InterfaceType([ 'name' => 'Named', diff --git a/tests/Executor/VariablesTest.php b/tests/Executor/VariablesTest.php index a2d698bc3..7676985d3 100644 --- a/tests/Executor/VariablesTest.php +++ b/tests/Executor/VariablesTest.php @@ -7,6 +7,7 @@ use GraphQL\Executor\Executor; use GraphQL\Language\Parser; use GraphQL\Tests\Executor\TestClasses\ComplexScalar; +use GraphQL\Tests\PHPUnit\ArraySubsetAsserts; use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\ObjectType; @@ -23,6 +24,8 @@ */ class VariablesTest extends TestCase { + use ArraySubsetAsserts; + public function testUsingInlineStructs() : void { // executes with complex input: diff --git a/tests/Language/LexerTest.php b/tests/Language/LexerTest.php index 116f3b845..0ff88b4d1 100644 --- a/tests/Language/LexerTest.php +++ b/tests/Language/LexerTest.php @@ -9,6 +9,7 @@ use GraphQL\Language\Source; use GraphQL\Language\SourceLocation; use GraphQL\Language\Token; +use GraphQL\Tests\PHPUnit\ArraySubsetAsserts; use GraphQL\Utils\Utils; use PHPUnit\Framework\TestCase; use function count; @@ -16,6 +17,8 @@ class LexerTest extends TestCase { + use ArraySubsetAsserts; + /** * @see it('disallows uncommon control characters') */ diff --git a/tests/Language/SchemaParserTest.php b/tests/Language/SchemaParserTest.php index d3ce8731a..ca2677a97 100644 --- a/tests/Language/SchemaParserTest.php +++ b/tests/Language/SchemaParserTest.php @@ -8,11 +8,15 @@ use GraphQL\Language\AST\NodeKind; use GraphQL\Language\Parser; use GraphQL\Language\SourceLocation; +use GraphQL\Tests\PHPUnit\ArraySubsetAsserts; use PHPUnit\Framework\TestCase; class SchemaParserTest extends TestCase { + use ArraySubsetAsserts; + // Describe: Schema Parser + /** * @see it('Simple type') */ diff --git a/tests/Language/VisitorTest.php b/tests/Language/VisitorTest.php index d612cec7d..6f927a6f8 100644 --- a/tests/Language/VisitorTest.php +++ b/tests/Language/VisitorTest.php @@ -139,10 +139,10 @@ private function checkVisitorFnArgs($ast, $args, $isEdited = false) self::assertArrayHasKey($key, $parentArray); - self::assertInternalType('array', $path); + self::assertIsArray($path); self::assertEquals($key, $path[count($path) - 1]); - self::assertInternalType('array', $ancestors); + self::assertIsArray($ancestors); self::assertCount(count($path) - 1, $ancestors); if ($isEdited) { diff --git a/tests/PHPUnit/ArraySubsetAsserts.php b/tests/PHPUnit/ArraySubsetAsserts.php new file mode 100644 index 000000000..caa9c028b --- /dev/null +++ b/tests/PHPUnit/ArraySubsetAsserts.php @@ -0,0 +1,41 @@ +strict = $strict; + $this->subset = $subset; + + if (method_exists(Constraint::class, '__construct')) { + parent::__construct(); + } + } + + /** + * Evaluates the constraint for parameter $other + * + * If $returnResult is set to false (the default), an exception is thrown + * in case of a failure. null is returned otherwise. + * + * If $returnResult is true, the result of the evaluation is returned as + * a boolean value instead: true in case of success, false in case of a + * failure. + * + * @param mixed[]|ArrayAccess $other + * @return mixed[]|null|bool + * + * @throws ExpectationFailedException + * @throws InvalidArgumentException + */ + public function evaluate($other, $description = '', $returnResult = false) + { + //type cast $other & $this->subset as an array to allow + //support in standard array functions. + $other = $this->toArray($other); + $this->subset = $this->toArray($this->subset); + $patched = array_replace_recursive($other, $this->subset); + if ($this->strict) { + $result = $other === $patched; + } else { + $result = $other == $patched; + } + if ($returnResult) { + return $result; + } + if ($result) { + return; + } + + $f = new ComparisonFailure( + $patched, + $other, + var_export($patched, true), + var_export($other, true) + ); + $this->fail($other, $description, $f); + } + + /** + * Returns a string representation of the constraint. + * + * @throws InvalidArgumentException + */ + public function toString(): string + { + $exporter = method_exists($this, 'exporter') ? $this->exporter() : $this->exporter; + + return 'has the subset ' . $exporter->export($this->subset); + } + + /** + * Returns the description of the failure + * + * The beginning of failure messages is "Failed asserting that" in most + * cases. This method should return the second part of that sentence. + * + * @param mixed $other evaluated value or object + * + * @throws InvalidArgumentException + */ + protected function failureDescription($other): string + { + return 'an array ' . $this->toString(); + } + + /** + * @param mixed[]|iterable $other + * + * @return mixed[] + */ + private function toArray(iterable $other): array + { + if (is_array($other)) { + return $other; + } + if ($other instanceof ArrayObject) { + return $other->getArrayCopy(); + } + if ($other instanceof Traversable) { + return iterator_to_array($other); + } + // Keep BC even if we know that array would not be the expected one + return (array) $other; + } +} diff --git a/tests/Server/QueryExecutionTest.php b/tests/Server/QueryExecutionTest.php index bc16ed622..b1fc4db24 100644 --- a/tests/Server/QueryExecutionTest.php +++ b/tests/Server/QueryExecutionTest.php @@ -14,6 +14,7 @@ use GraphQL\Server\OperationParams; use GraphQL\Server\RequestError; use GraphQL\Server\ServerConfig; +use GraphQL\Tests\PHPUnit\ArraySubsetAsserts; use GraphQL\Validator\DocumentValidator; use GraphQL\Validator\Rules\CustomValidationRule; use GraphQL\Validator\ValidationContext; @@ -23,10 +24,12 @@ class QueryExecutionTest extends ServerTestCase { + use ArraySubsetAsserts; + /** @var ServerConfig */ private $config; - public function setUp() + public function setUp() : void { $schema = $this->buildSchema(); $this->config = ServerConfig::create() @@ -69,7 +72,7 @@ public function testReturnsSyntaxErrors() : void $result = $this->executeQuery($query); self::assertNull($result->data); self::assertCount(1, $result->errors); - self::assertContains( + self::assertStringContainsString( 'Syntax Error: Expected Name, found ', $result->errors[0]->getMessage() ); @@ -320,7 +323,7 @@ private function executeBatchedQuery(array $qs) } $helper = new Helper(); $result = $helper->executeBatch($this->config, $batch); - self::assertInternalType('array', $result); + self::assertIsArray($result); self::assertCount(count($qs), $result); foreach ($result as $index => $entry) { @@ -682,9 +685,9 @@ public function testAppliesErrorsHandler() : void ]; self::assertTrue($called); self::assertArraySubset($expected, $formatted); - self::assertInternalType('array', $errors); + self::assertIsArray($errors); self::assertCount(2, $errors); - self::assertInternalType('callable', $formatter); + self::assertIsCallable($formatter); self::assertArraySubset($expected, $formatted); } } diff --git a/tests/Server/RequestParsingTest.php b/tests/Server/RequestParsingTest.php index edb4b31be..8db287daa 100644 --- a/tests/Server/RequestParsingTest.php +++ b/tests/Server/RequestParsingTest.php @@ -378,7 +378,7 @@ public function testParsesBatchJSONRequest() : void 'psr' => $this->parsePsrRequest('application/json', json_encode($body)), ]; foreach ($parsed as $method => $parsedBody) { - self::assertInternalType('array', $parsedBody, $method); + self::assertIsArray($parsedBody, $method); self::assertCount(2, $parsedBody, $method); self::assertValidOperationParams( $parsedBody[0], diff --git a/tests/Server/StandardServerTest.php b/tests/Server/StandardServerTest.php index a672141c3..516bf687d 100644 --- a/tests/Server/StandardServerTest.php +++ b/tests/Server/StandardServerTest.php @@ -8,15 +8,18 @@ use GraphQL\Server\Helper; use GraphQL\Server\ServerConfig; use GraphQL\Server\StandardServer; +use GraphQL\Tests\PHPUnit\ArraySubsetAsserts; use GraphQL\Tests\Server\Psr7\PsrRequestStub; use function json_encode; class StandardServerTest extends ServerTestCase { + use ArraySubsetAsserts; + /** @var ServerConfig */ private $config; - public function setUp() + public function setUp() : void { $schema = $this->buildSchema(); $this->config = ServerConfig::create() diff --git a/tests/Type/DefinitionTest.php b/tests/Type/DefinitionTest.php index 54b0d5a40..7a6ec9c05 100644 --- a/tests/Type/DefinitionTest.php +++ b/tests/Type/DefinitionTest.php @@ -5,6 +5,7 @@ namespace GraphQL\Tests\Type; use GraphQL\Error\InvariantViolation; +use GraphQL\Tests\PHPUnit\ArraySubsetAsserts; use GraphQL\Tests\Type\TestClasses\MyCustomType; use GraphQL\Tests\Type\TestClasses\OtherCustom; use GraphQL\Type\Definition\CustomScalarType; @@ -25,6 +26,8 @@ class DefinitionTest extends TestCase { + use ArraySubsetAsserts; + /** @var ObjectType */ public $blogImage; @@ -64,7 +67,7 @@ class DefinitionTest extends TestCase /** @var CustomScalarType */ public $scalarType; - public function setUp() + public function setUp() : void { $this->objectType = new ObjectType(['name' => 'Object', 'fields' => ['tmp' => Type::string()]]); $this->interfaceType = new InterfaceType(['name' => 'Interface']); diff --git a/tests/Type/EnumTypeTest.php b/tests/Type/EnumTypeTest.php index fbebbbf40..3573613b2 100644 --- a/tests/Type/EnumTypeTest.php +++ b/tests/Type/EnumTypeTest.php @@ -7,6 +7,7 @@ use ArrayObject; use GraphQL\GraphQL; use GraphQL\Language\SourceLocation; +use GraphQL\Tests\PHPUnit\ArraySubsetAsserts; use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; @@ -18,6 +19,8 @@ class EnumTypeTest extends TestCase { + use ArraySubsetAsserts; + /** @var Schema */ private $schema; @@ -30,7 +33,7 @@ class EnumTypeTest extends TestCase /** @var ArrayObject */ private $Complex2; - public function setUp() + public function setUp() : void { $ColorType = new EnumType([ 'name' => 'Color', diff --git a/tests/Type/IntrospectionTest.php b/tests/Type/IntrospectionTest.php index fa95feb66..fac1c9934 100644 --- a/tests/Type/IntrospectionTest.php +++ b/tests/Type/IntrospectionTest.php @@ -7,6 +7,7 @@ use GraphQL\Error\FormattedError; use GraphQL\GraphQL; use GraphQL\Language\SourceLocation; +use GraphQL\Tests\PHPUnit\ArraySubsetAsserts; use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\ObjectType; @@ -20,6 +21,8 @@ class IntrospectionTest extends TestCase { + use ArraySubsetAsserts; + /** * @see it('executes an introspection query') */ diff --git a/tests/Type/SchemaTest.php b/tests/Type/SchemaTest.php index ddb832b77..37075ec94 100644 --- a/tests/Type/SchemaTest.php +++ b/tests/Type/SchemaTest.php @@ -33,7 +33,7 @@ class SchemaTest extends TestCase /** @var Schema */ private $schema; - public function setUp() + public function setUp() : void { $this->interfaceType = new InterfaceType([ 'name' => 'Interface', diff --git a/tests/Type/StandardTypesTest.php b/tests/Type/StandardTypesTest.php index 93f3ceb67..00e182079 100644 --- a/tests/Type/StandardTypesTest.php +++ b/tests/Type/StandardTypesTest.php @@ -15,12 +15,12 @@ class StandardTypesTest extends TestCase /** @var Type[] */ private static $originalStandardTypes; - public static function setUpBeforeClass() + public static function setUpBeforeClass() : void { self::$originalStandardTypes = Type::getStandardTypes(); } - public function tearDown() + public function tearDown() : void { parent::tearDown(); Type::overrideStandardTypes(self::$originalStandardTypes); diff --git a/tests/Type/TypeLoaderTest.php b/tests/Type/TypeLoaderTest.php index e7193ec74..ab87c70aa 100644 --- a/tests/Type/TypeLoaderTest.php +++ b/tests/Type/TypeLoaderTest.php @@ -6,6 +6,7 @@ use Exception; use GraphQL\Error\InvariantViolation; +use GraphQL\Tests\PHPUnit\ArraySubsetAsserts; use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\ObjectType; @@ -18,6 +19,8 @@ class TypeLoaderTest extends TestCase { + use ArraySubsetAsserts; + /** @var ObjectType */ private $query; @@ -45,7 +48,7 @@ class TypeLoaderTest extends TestCase /** @var string[] */ private $calls; - public function setUp() + public function setUp() : void { $this->calls = []; diff --git a/tests/Type/ValidationTest.php b/tests/Type/ValidationTest.php index 9676c2059..ca21338c2 100644 --- a/tests/Type/ValidationTest.php +++ b/tests/Type/ValidationTest.php @@ -59,7 +59,7 @@ class ValidationTest extends TestCase /** @var float */ public $Number; - public function setUp() + public function setUp() : void { $this->Number = 1; @@ -163,7 +163,7 @@ static function ($type) { ); } - public function tearDown() + public function tearDown() : void { parent::tearDown(); Warning::enable(Warning::WARNING_NOT_A_TYPE); diff --git a/tests/Utils/BreakingChangesFinderTest.php b/tests/Utils/BreakingChangesFinderTest.php index db48d3331..cf2d022a6 100644 --- a/tests/Utils/BreakingChangesFinderTest.php +++ b/tests/Utils/BreakingChangesFinderTest.php @@ -23,7 +23,7 @@ class BreakingChangesFinderTest extends TestCase /** @var ObjectType */ private $queryType; - public function setUp() + public function setUp() : void { $this->queryType = new ObjectType([ 'name' => 'Query', diff --git a/tests/Utils/BuildSchemaTest.php b/tests/Utils/BuildSchemaTest.php index 64b29ae15..3990093fa 100644 --- a/tests/Utils/BuildSchemaTest.php +++ b/tests/Utils/BuildSchemaTest.php @@ -12,6 +12,7 @@ use GraphQL\Language\AST\ObjectTypeDefinitionNode; use GraphQL\Language\Parser; use GraphQL\Language\Printer; +use GraphQL\Tests\PHPUnit\ArraySubsetAsserts; use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\InputObjectType; @@ -27,7 +28,10 @@ class BuildSchemaTest extends TestCase { + use ArraySubsetAsserts; + // Describe: Schema Builder + /** * @see it('can use built schema for limited execution') */ diff --git a/tests/Utils/CoerceValueTest.php b/tests/Utils/CoerceValueTest.php index ee74feb51..342a0d4d1 100644 --- a/tests/Utils/CoerceValueTest.php +++ b/tests/Utils/CoerceValueTest.php @@ -22,7 +22,7 @@ class CoerceValueTest extends TestCase /** @var InputObjectType */ private $testInputObject; - public function setUp() + public function setUp() : void { $this->testEnum = new EnumType([ 'name' => 'TestEnum', @@ -53,7 +53,7 @@ public function setUp() public function testCoercingAnArrayToGraphQLStringProducesAnError() : void { $result = Value::coerceValue([1, 2, 3], Type::string()); - $this->expectError( + $this->expectGraphQLError( $result, 'Expected type String; String cannot represent a non string value: [1,2,3]' ); @@ -72,7 +72,7 @@ public function testCoercingAnArrayToGraphQLStringProducesAnError() : void public function testCoercingAnArrayToGraphQLIDProducesAnError() : void { $result = Value::coerceValue([1, 2, 3], Type::id()); - $this->expectError( + $this->expectGraphQLError( $result, 'Expected type ID; ID cannot represent value: [1,2,3]' ); @@ -86,10 +86,10 @@ public function testCoercingAnArrayToGraphQLIDProducesAnError() : void /** * Describe: for GraphQLInt */ - private function expectError($result, $expected) + private function expectGraphQLError($result, $expected) { - self::assertInternalType('array', $result); - self::assertInternalType('array', $result['errors']); + self::assertIsArray($result); + self::assertIsArray($result['errors']); self::assertCount(1, $result['errors']); self::assertEquals($expected, $result['errors'][0]->getMessage()); self::assertEquals(Utils::undefined(), $result['value']); @@ -110,12 +110,12 @@ public function testIntReturnsNoErrorForIntInput() : void public function testReturnsErrorForNumericLookingString() { $result = Value::coerceValue('1', Type::int()); - $this->expectError($result, 'Expected type Int; Int cannot represent non-integer value: 1'); + $this->expectGraphQLError($result, 'Expected type Int; Int cannot represent non-integer value: 1'); } private function expectValue($result, $expected) { - self::assertInternalType('array', $result); + self::assertIsArray($result); self::assertEquals(null, $result['errors']); self::assertNotEquals(Utils::undefined(), $result['value']); self::assertEquals($expected, $result['value']); @@ -154,7 +154,7 @@ public function testIntReturnsASingleErrorNull() : void public function testIntReturnsASingleErrorForEmptyValue() : void { $result = Value::coerceValue('', Type::int()); - $this->expectError( + $this->expectGraphQLError( $result, 'Expected type Int; Int cannot represent non-integer value: (empty string)' ); @@ -166,7 +166,7 @@ public function testIntReturnsASingleErrorForEmptyValue() : void public function testReturnsASingleErrorFor2x32InputAsInt() { $result = Value::coerceValue(pow(2, 32), Type::int()); - $this->expectError( + $this->expectGraphQLError( $result, 'Expected type Int; Int cannot represent non 32-bit signed integer value: 4294967296' ); @@ -178,7 +178,7 @@ public function testReturnsASingleErrorFor2x32InputAsInt() public function testIntReturnsErrorForFloatInputAsInt() : void { $result = Value::coerceValue(1.5, Type::int()); - $this->expectError( + $this->expectGraphQLError( $result, 'Expected type Int; Int cannot represent non-integer value: 1.5' ); @@ -191,7 +191,7 @@ public function testReturnsASingleErrorForInfinityInputAsInt() { $inf = log(0); $result = Value::coerceValue($inf, Type::int()); - $this->expectError( + $this->expectGraphQLError( $result, 'Expected type Int; Int cannot represent non 32-bit signed integer value: -INF' ); @@ -201,7 +201,7 @@ public function testReturnsASingleErrorForNaNInputAsInt() { $nan = acos(8); $result = Value::coerceValue($nan, Type::int()); - $this->expectError( + $this->expectGraphQLError( $result, 'Expected type Int; Int cannot represent non-integer value: NAN' ); @@ -213,7 +213,7 @@ public function testReturnsASingleErrorForNaNInputAsInt() public function testIntReturnsASingleErrorForCharInput() : void { $result = Value::coerceValue('a', Type::int()); - $this->expectError( + $this->expectGraphQLError( $result, 'Expected type Int; Int cannot represent non-integer value: a' ); @@ -225,7 +225,7 @@ public function testIntReturnsASingleErrorForCharInput() : void public function testIntReturnsASingleErrorForMultiCharInput() : void { $result = Value::coerceValue('meow', Type::int()); - $this->expectError( + $this->expectGraphQLError( $result, 'Expected type Int; Int cannot represent non-integer value: meow' ); @@ -266,7 +266,7 @@ public function testFloatReturnsNoErrorForExponentInput() : void public function testFloatReturnsErrorForNumericLookingString() { $result = Value::coerceValue('1', Type::float()); - $this->expectError( + $this->expectGraphQLError( $result, 'Expected type Float; Float cannot represent non numeric value: 1' ); @@ -287,7 +287,7 @@ public function testFloatReturnsASingleErrorNull() : void public function testFloatReturnsASingleErrorForEmptyValue() : void { $result = Value::coerceValue('', Type::float()); - $this->expectError( + $this->expectGraphQLError( $result, 'Expected type Float; Float cannot represent non numeric value: (empty string)' ); @@ -300,7 +300,7 @@ public function testFloatReturnsASingleErrorForInfinityInput() : void { $inf = log(0); $result = Value::coerceValue($inf, Type::float()); - $this->expectError( + $this->expectGraphQLError( $result, 'Expected type Float; Float cannot represent non numeric value: -INF' ); @@ -310,7 +310,7 @@ public function testFloatReturnsASingleErrorForNaNInput() : void { $nan = acos(8); $result = Value::coerceValue($nan, Type::float()); - $this->expectError( + $this->expectGraphQLError( $result, 'Expected type Float; Float cannot represent non numeric value: NAN' ); @@ -324,7 +324,7 @@ public function testFloatReturnsASingleErrorForNaNInput() : void public function testFloatReturnsASingleErrorForCharInput() : void { $result = Value::coerceValue('a', Type::float()); - $this->expectError( + $this->expectGraphQLError( $result, 'Expected type Float; Float cannot represent non numeric value: a' ); @@ -336,7 +336,7 @@ public function testFloatReturnsASingleErrorForCharInput() : void public function testFloatReturnsASingleErrorForMultiCharInput() : void { $result = Value::coerceValue('meow', Type::float()); - $this->expectError( + $this->expectGraphQLError( $result, 'Expected type Float; Float cannot represent non numeric value: meow' ); @@ -362,7 +362,7 @@ public function testReturnsNoErrorForAKnownEnumName() : void public function testReturnsErrorForMisspelledEnumValue() : void { $result = Value::coerceValue('foo', $this->testEnum); - $this->expectError($result, 'Expected type TestEnum; did you mean FOO?'); + $this->expectGraphQLError($result, 'Expected type TestEnum; did you mean FOO?'); } /** @@ -371,10 +371,10 @@ public function testReturnsErrorForMisspelledEnumValue() : void public function testReturnsErrorForIncorrectValueType() : void { $result1 = Value::coerceValue(123, $this->testEnum); - $this->expectError($result1, 'Expected type TestEnum.'); + $this->expectGraphQLError($result1, 'Expected type TestEnum.'); $result2 = Value::coerceValue(['field' => 'value'], $this->testEnum); - $this->expectError($result2, 'Expected type TestEnum.'); + $this->expectGraphQLError($result2, 'Expected type TestEnum.'); } /** @@ -392,7 +392,7 @@ public function testReturnsNoErrorForValidInput() : void public function testReturnsErrorForNonObjectType() : void { $result = Value::coerceValue(123, $this->testInputObject); - $this->expectError($result, 'Expected type TestInputObject to be an object.'); + $this->expectGraphQLError($result, 'Expected type TestInputObject to be an object.'); } public function testReturnsNoErrorForStdClassInput() : void @@ -407,7 +407,7 @@ public function testReturnsNoErrorForStdClassInput() : void public function testReturnErrorForAnInvalidField() : void { $result = Value::coerceValue(['foo' => 'abc'], $this->testInputObject); - $this->expectError( + $this->expectGraphQLError( $result, 'Expected type Int at value.foo; Int cannot represent non-integer value: abc' ); @@ -434,7 +434,7 @@ public function testReturnsMultipleErrorsForMultipleInvalidFields() : void public function testReturnsErrorForAMissingRequiredField() : void { $result = Value::coerceValue(['bar' => 123], $this->testInputObject); - $this->expectError($result, 'Field value.foo of required type Int! was not provided.'); + $this->expectGraphQLError($result, 'Field value.foo of required type Int! was not provided.'); } /** @@ -443,7 +443,7 @@ public function testReturnsErrorForAMissingRequiredField() : void public function testReturnsErrorForAnUnknownField() : void { $result = Value::coerceValue(['foo' => 123, 'unknownField' => 123], $this->testInputObject); - $this->expectError($result, 'Field "unknownField" is not defined by type TestInputObject.'); + $this->expectGraphQLError($result, 'Field "unknownField" is not defined by type TestInputObject.'); } /** @@ -452,6 +452,6 @@ public function testReturnsErrorForAnUnknownField() : void public function testReturnsErrorForAMisspelledField() : void { $result = Value::coerceValue(['foo' => 123, 'bart' => 123], $this->testInputObject); - $this->expectError($result, 'Field "bart" is not defined by type TestInputObject; did you mean bar?'); + $this->expectGraphQLError($result, 'Field "bart" is not defined by type TestInputObject; did you mean bar?'); } } diff --git a/tests/Utils/ExtractTypesTest.php b/tests/Utils/ExtractTypesTest.php index f2a30bc91..757c8ce8a 100644 --- a/tests/Utils/ExtractTypesTest.php +++ b/tests/Utils/ExtractTypesTest.php @@ -54,7 +54,7 @@ class ExtractTypesTest extends TestCase /** @var InputObjectType */ private $postCommentMutationInput; - public function setUp() + public function setUp() : void { $this->node = new InterfaceType([ 'name' => 'Node', diff --git a/tests/Utils/MixedStoreTest.php b/tests/Utils/MixedStoreTest.php index 42e64d7c5..fd04d2e0c 100644 --- a/tests/Utils/MixedStoreTest.php +++ b/tests/Utils/MixedStoreTest.php @@ -14,7 +14,7 @@ class MixedStoreTest extends TestCase /** @var MixedStore */ private $mixedStore; - public function setUp() + public function setUp() : void { $this->mixedStore = new MixedStore(); } diff --git a/tests/Utils/SchemaExtenderTest.php b/tests/Utils/SchemaExtenderTest.php index 4b4fc3355..d83dec0a4 100644 --- a/tests/Utils/SchemaExtenderTest.php +++ b/tests/Utils/SchemaExtenderTest.php @@ -55,7 +55,7 @@ class SchemaExtenderTest extends TestCase /** @var Directive */ protected $FooDirective; - public function setUp() + public function setUp() : void { parent::setUp(); @@ -252,8 +252,8 @@ public function testExtendsWithoutAlteringOriginalSchema() newField: String }'); self::assertNotEquals($extendedSchema, $this->testSchema); - self::assertContains('newField', SchemaPrinter::doPrint($extendedSchema)); - self::assertNotContains('newField', SchemaPrinter::doPrint($this->testSchema)); + self::assertStringContainsString('newField', SchemaPrinter::doPrint($extendedSchema)); + self::assertStringNotContainsString('newField', SchemaPrinter::doPrint($this->testSchema)); } /** @@ -1897,7 +1897,7 @@ public function testOriginalResolversArePreserved() $extendedSchema = SchemaExtender::extend($schema, $documentNode); $helloResolveFn = $extendedSchema->getQueryType()->getField('hello')->resolveFn; - self::assertInternalType('callable', $helloResolveFn); + self::assertIsCallable($helloResolveFn); $query = '{ hello }'; $result = GraphQL::executeQuery($extendedSchema, $query); diff --git a/tests/Validator/KnownDirectivesTest.php b/tests/Validator/KnownDirectivesTest.php index ffcec503b..38c190ea0 100644 --- a/tests/Validator/KnownDirectivesTest.php +++ b/tests/Validator/KnownDirectivesTest.php @@ -15,7 +15,7 @@ class KnownDirectivesTest extends ValidatorTestCase /** @var Schema */ public $schemaWithSDLDirectives; - public function setUp() + public function setUp() : void { $this->schemaWithSDLDirectives = BuildSchema::build(' directive @onSchema on SCHEMA diff --git a/tests/Validator/QuerySecurityTestCase.php b/tests/Validator/QuerySecurityTestCase.php index 4eb5f74ab..8661f01cb 100644 --- a/tests/Validator/QuerySecurityTestCase.php +++ b/tests/Validator/QuerySecurityTestCase.php @@ -10,17 +10,17 @@ use GraphQL\Type\Introspection; use GraphQL\Validator\DocumentValidator; use GraphQL\Validator\Rules\QuerySecurityRule; +use InvalidArgumentException; use PHPUnit\Framework\TestCase; use function array_map; abstract class QuerySecurityTestCase extends TestCase { - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage argument must be greater or equal to 0. - */ public function testMaxQueryDepthMustBeGreaterOrEqualTo0() : void { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('argument must be greater or equal to 0.'); + $this->getRule(-1); } From ce834e2851598eb2422cb749f54afd335f49a607 Mon Sep 17 00:00:00 2001 From: Dorian More Date: Tue, 21 Jan 2020 11:38:57 +0100 Subject: [PATCH 141/256] Fixed assumption that throwables from custom scalar are always client-safe. --- src/Validator/Rules/ValuesOfCorrectType.php | 6 +++++- tests/Validator/ValuesOfCorrectTypeTest.php | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Validator/Rules/ValuesOfCorrectType.php b/src/Validator/Rules/ValuesOfCorrectType.php index 359f4cd1d..21a4e3cde 100644 --- a/src/Validator/Rules/ValuesOfCorrectType.php +++ b/src/Validator/Rules/ValuesOfCorrectType.php @@ -224,7 +224,11 @@ private function isValidScalar(ValidationContext $context, ValueNode $node, $fie $context, $fieldName ), - $node + $node, + null, + null, + null, + $error ) ); } diff --git a/tests/Validator/ValuesOfCorrectTypeTest.php b/tests/Validator/ValuesOfCorrectTypeTest.php index 721f01f6b..2e5f24762 100644 --- a/tests/Validator/ValuesOfCorrectTypeTest.php +++ b/tests/Validator/ValuesOfCorrectTypeTest.php @@ -1361,6 +1361,7 @@ public function testReportsOriginalErrorForCustomScalarWhichThrows() : void 'Field "invalidArg" argument "arg" requires type Invalid, found 123; Invalid scalar is always invalid: 123', $errors[0]->getMessage() ); + self::assertFalse($errors[0]->isClientSafe()); } /** From 5314bf66b8df9f7907ee69a737a6f344b23379b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Podlipsk=C3=BD?= Date: Wed, 26 Feb 2020 10:44:52 +0100 Subject: [PATCH 142/256] PHPStan extension installer, baseline, fixes (#602) * Use PHPStan extension installer * Add few return types to anonymous functions * Add PHPStan baseline --- composer.json | 2 + phpstan-baseline.neon | 2312 +++++++++++++++++ phpstan.neon.dist | 15 +- src/Error/Error.php | 10 +- src/Error/FormattedError.php | 10 +- src/Executor/ExecutionResult.php | 2 +- .../Promise/Adapter/ReactPromiseAdapter.php | 2 +- src/Executor/Promise/Adapter/SyncPromise.php | 6 +- .../Promise/Adapter/SyncPromiseAdapter.php | 2 +- src/Executor/ReferenceExecutor.php | 4 +- src/Executor/Values.php | 4 +- src/Validator/Rules/DisableIntrospection.php | 2 +- src/Validator/Rules/KnownDirectives.php | 4 +- 13 files changed, 2340 insertions(+), 35 deletions(-) create mode 100644 phpstan-baseline.neon diff --git a/composer.json b/composer.json index 90f8f5292..92fec90ee 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,7 @@ "require-dev": { "doctrine/coding-standard": "^6.0", "phpbench/phpbench": "^0.14", + "phpstan/extension-installer": "^1.0", "phpstan/phpstan": "0.12.2", "phpstan/phpstan-phpunit": "0.12.1", "phpstan/phpstan-strict-rules": "0.12.0", @@ -52,6 +53,7 @@ "lint" : "phpcs", "fix" : "phpcbf", "stan": "phpstan analyse --ansi --memory-limit 2048M", + "phpstan-baseline": "phpstan analyse --ansi --error-format baselineNeon > phpstan-baseline.neon", "check": "composer lint && composer stan && composer test" } } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 000000000..19021a884 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,2312 @@ +parameters: + ignoreErrors: + - + message: "#^Only booleans are allowed in &&, GraphQL\\\\Language\\\\AST\\\\Node\\|null given on the left side\\.$#" + count: 1 + path: src/Error/Error.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 6 + path: src/Error/Error.php + + - + message: "#^Only booleans are allowed in &&, Throwable\\|null given on the left side\\.$#" + count: 1 + path: src/Error/Error.php + + - + message: "#^Only booleans are allowed in an elseif condition, Throwable\\|null given\\.$#" + count: 1 + path: src/Error/Error.php + + - + message: "#^Only booleans are allowed in &&, array\\\\|null given on the right side\\.$#" + count: 1 + path: src/Error/Error.php + + - + message: "#^Only booleans are allowed in &&, array\\|null given on the left side\\.$#" + count: 1 + path: src/Error/Error.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 6 + path: src/Error/Error.php + + - + message: "#^Only booleans are allowed in &&, GraphQL\\\\Language\\\\Source\\|null given on the right side\\.$#" + count: 2 + path: src/Error/Error.php + + - + message: "#^Only booleans are allowed in &&, array\\ given on the left side\\.$#" + count: 1 + path: src/Error/Error.php + + - + message: "#^Only booleans are allowed in an elseif condition, array\\\\|null given\\.$#" + count: 1 + path: src/Error/Error.php + + - + message: "#^Anonymous function should have native return typehint \"\\?GraphQL\\\\Language\\\\SourceLocation\"\\.$#" + count: 1 + path: src/Error/Error.php + + - + message: "#^Only booleans are allowed in &&, GraphQL\\\\Language\\\\AST\\\\Location given on the left side\\.$#" + count: 1 + path: src/Error/Error.php + + - + message: "#^Only booleans are allowed in an if condition, array\\\\|null given\\.$#" + count: 1 + path: src/Error/FormattedError.php + + - + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Language\\\\AST\\\\Location given\\.$#" + count: 1 + path: src/Error/FormattedError.php + + - + message: "#^Only booleans are allowed in &&, GraphQL\\\\Language\\\\Source\\|null given on the left side\\.$#" + count: 1 + path: src/Error/FormattedError.php + + - + message: "#^Only booleans are allowed in &&, array\\ given on the right side\\.$#" + count: 1 + path: src/Error/FormattedError.php + + - + message: "#^Only booleans are allowed in a negated boolean, array\\ given\\.$#" + count: 1 + path: src/Error/FormattedError.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 3 + path: src/Error/FormattedError.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 5 + path: src/Error/FormattedError.php + + - + message: "#^Only booleans are allowed in an if condition, int given\\.$#" + count: 2 + path: src/Error/FormattedError.php + + - + message: "#^Only booleans are allowed in an if condition, Throwable\\|null given\\.$#" + count: 2 + path: src/Error/FormattedError.php + + - + message: "#^Only booleans are allowed in &&, int given on the left side\\.$#" + count: 2 + path: src/Error/FormattedError.php + + - + message: "#^Only booleans are allowed in a negated boolean, Throwable\\|null given\\.$#" + count: 1 + path: src/Error/FormattedError.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 2 + path: src/Error/Warning.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Executor/ExecutionContext.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 2 + path: src/Executor/ExecutionResult.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Executor/ExecutionResult.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 2 + path: src/Executor/Executor.php + + - + message: "#^Variable property access on object\\.$#" + count: 2 + path: src/Executor/Executor.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Executor/Promise/Adapter/SyncPromise.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 5 + path: src/Executor/ReferenceExecutor.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 4 + path: src/Executor/ReferenceExecutor.php + + - + message: "#^Anonymous function should have native return typehint \"\\?GraphQL\\\\Type\\\\Definition\\\\ObjectType\"\\.$#" + count: 1 + path: src/Executor/ReferenceExecutor.php + + - + message: "#^Only booleans are allowed in &&, GraphQL\\\\Language\\\\AST\\\\BooleanValueNode\\|GraphQL\\\\Language\\\\AST\\\\EnumValueNode\\|GraphQL\\\\Language\\\\AST\\\\FloatValueNode\\|GraphQL\\\\Language\\\\AST\\\\IntValueNode\\|GraphQL\\\\Language\\\\AST\\\\ListValueNode\\|GraphQL\\\\Language\\\\AST\\\\NullValueNode\\|GraphQL\\\\Language\\\\AST\\\\ObjectValueNode\\|GraphQL\\\\Language\\\\AST\\\\StringValueNode\\|GraphQL\\\\Language\\\\AST\\\\VariableNode\\|null given on the right side\\.$#" + count: 1 + path: src/Executor/Values.php + + - + message: "#^Only booleans are allowed in an if condition, array\\ given\\.$#" + count: 1 + path: src/Executor/Values.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 3 + path: src/Executor/Values.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, array\\|null given\\.$#" + count: 1 + path: src/Executor/Values.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 2 + path: src/Experimental/Executor/Collector.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 3 + path: src/Experimental/Executor/CoroutineExecutor.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 2 + path: src/Experimental/Executor/CoroutineExecutor.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 3 + path: src/Experimental/Executor/CoroutineExecutor.php + + - + message: "#^Variable property access on object\\.$#" + count: 2 + path: src/Experimental/Executor/CoroutineExecutor.php + + - + message: "#^Variable property access on mixed\\.$#" + count: 1 + path: src/Experimental/Executor/CoroutineExecutor.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/GraphQL.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 2 + path: src/GraphQL.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 1 + path: src/GraphQL.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 1 + path: src/Language/AST/Node.php + + - + message: "#^Variable property access on GraphQL\\\\Language\\\\AST\\\\Node\\.$#" + count: 1 + path: src/Language/AST/Node.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 2 + path: src/Language/Lexer.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 3 + path: src/Language/Parser.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Language/Parser.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\AST\\\\VariableDefinitionNode\"\\.$#" + count: 1 + path: src/Language/Parser.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\AST\\\\SelectionNode\"\\.$#" + count: 1 + path: src/Language/Parser.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\AST\\\\ArgumentNode\"\\.$#" + count: 2 + path: src/Language/Parser.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\AST\\\\OperationTypeDefinitionNode\"\\.$#" + count: 1 + path: src/Language/Parser.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\AST\\\\FieldDefinitionNode\"\\.$#" + count: 1 + path: src/Language/Parser.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\AST\\\\InputValueDefinitionNode\"\\.$#" + count: 2 + path: src/Language/Parser.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\AST\\\\EnumValueDefinitionNode\"\\.$#" + count: 1 + path: src/Language/Parser.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Language/Printer.php + + - + message: "#^Anonymous function should have native return typehint \"string\"\\.$#" + count: 18 + path: src/Language/Printer.php + + - + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Language\\\\AST\\\\NameNode given\\.$#" + count: 1 + path: src/Language/Printer.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\AST\\\\NameNode\"\\.$#" + count: 1 + path: src/Language/Printer.php + + - + message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" + count: 3 + path: src/Language/Printer.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, array\\\\|null given\\.$#" + count: 2 + path: src/Language/Printer.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 2 + path: src/Language/Source.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 2 + path: src/Language/Visitor.php + + - + message: "#^Only booleans are allowed in a negated boolean, array\\ given\\.$#" + count: 1 + path: src/Language/Visitor.php + + - + message: "#^Variable property access on GraphQL\\\\Language\\\\AST\\\\Node\\|null\\.$#" + count: 1 + path: src/Language/Visitor.php + + - + message: "#^Variable property access on GraphQL\\\\Language\\\\AST\\\\Node\\.$#" + count: 1 + path: src/Language/Visitor.php + + - + message: "#^Only booleans are allowed in an if condition, \\(callable\\)\\|null given\\.$#" + count: 4 + path: src/Language/Visitor.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 2 + path: src/Language/Visitor.php + + - + message: "#^Only booleans are allowed in a negated boolean, \\(callable\\)\\|null given\\.$#" + count: 1 + path: src/Language/Visitor.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, \\(callable\\)\\|null given\\.$#" + count: 1 + path: src/Language/Visitor.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, \\(callable\\)\\|null given\\.$#" + count: 2 + path: src/Server/Helper.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 4 + path: src/Server/Helper.php + + - + message: "#^Only booleans are allowed in an if condition, int given\\.$#" + count: 1 + path: src/Server/Helper.php + + - + message: "#^Only booleans are allowed in a negated boolean, string given\\.$#" + count: 2 + path: src/Server/Helper.php + + - + message: "#^Only booleans are allowed in &&, string given on the left side\\.$#" + count: 1 + path: src/Server/Helper.php + + - + message: "#^Only booleans are allowed in &&, string given on the right side\\.$#" + count: 1 + path: src/Server/Helper.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 5 + path: src/Server/Helper.php + + - + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Schema given\\.$#" + count: 1 + path: src/Server/Helper.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Error\\\\Error\"\\.$#" + count: 1 + path: src/Server/Helper.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, string given\\.$#" + count: 1 + path: src/Server/Helper.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Executor\\\\ExecutionResult\"\\.$#" + count: 1 + path: src/Server/Helper.php + + - + message: "#^Only booleans are allowed in an if condition, \\(callable\\)\\|null given\\.$#" + count: 1 + path: src/Server/Helper.php + + - + message: "#^Only booleans are allowed in \\|\\|, \\(callable\\)\\|null given on the left side\\.$#" + count: 1 + path: src/Server/Helper.php + + - + message: "#^Only booleans are allowed in a negated boolean, callable given\\.$#" + count: 1 + path: src/Server/Helper.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 2 + path: src/Server/Helper.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 2 + path: src/Server/OperationParams.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 2 + path: src/Server/OperationParams.php + + - + message: "#^Variable property access on \\$this\\(GraphQL\\\\Type\\\\Definition\\\\Directive\\)\\.$#" + count: 1 + path: src/Type/Definition/Directive.php + + - + message: "#^Only booleans are allowed in a negated boolean, ArrayObject\\ given\\.$#" + count: 1 + path: src/Type/Definition/EnumType.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Type/Definition/FieldDefinition.php + + - + message: "#^Variable property access on \\$this\\(GraphQL\\\\Type\\\\Definition\\\\InputObjectField\\)\\.$#" + count: 1 + path: src/Type/Definition/InputObjectField.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 1 + path: src/Type/Definition/InputObjectField.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 1 + path: src/Type/Definition/InputObjectType.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Type/Definition/ObjectType.php + + - + message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" + count: 2 + path: src/Type/Definition/QueryPlan.php + + - + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Language\\\\AST\\\\SelectionSetNode\\|null given\\.$#" + count: 1 + path: src/Type/Definition/QueryPlan.php + + - + message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Language\\\\AST\\\\SelectionSetNode\\|null given\\.$#" + count: 1 + path: src/Type/Definition/QueryPlan.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 1 + path: src/Type/Definition/ResolveInfo.php + + - + message: "#^Only booleans are allowed in an if condition, string given\\.$#" + count: 1 + path: src/Type/Definition/Type.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 4 + path: src/Type/Introspection.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\ObjectType\"\\.$#" + count: 1 + path: src/Type/Introspection.php + + - + message: "#^Anonymous function should have native return typehint \"\\?GraphQL\\\\Type\\\\Definition\\\\ObjectType\"\\.$#" + count: 2 + path: src/Type/Introspection.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 3 + path: src/Type/Introspection.php + + - + message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" + count: 4 + path: src/Type/Introspection.php + + - + message: "#^Only booleans are allowed in a negated boolean, string\\|null given\\.$#" + count: 2 + path: src/Type/Introspection.php + + - + message: "#^Anonymous function should have native return typehint \"\\?array\"\\.$#" + count: 3 + path: src/Type/Introspection.php + + - + message: "#^Anonymous function should have native return typehint \"\\?GraphQL\\\\Type\\\\Definition\\\\Type\"\\.$#" + count: 2 + path: src/Type/Introspection.php + + - + message: "#^Anonymous function should have native return typehint \"string\"\\.$#" + count: 3 + path: src/Type/Introspection.php + + - + message: "#^Anonymous function should have native return typehint \"\\?string\"\\.$#" + count: 4 + path: src/Type/Introspection.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Type/Introspection.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Schema\"\\.$#" + count: 1 + path: src/Type/Introspection.php + + - + message: "#^Only booleans are allowed in a negated boolean, array\\\\|\\(callable\\) given\\.$#" + count: 1 + path: src/Type/Schema.php + + - + message: "#^Only booleans are allowed in a negated boolean, array\\ given\\.$#" + count: 1 + path: src/Type/Schema.php + + - + message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Type\\\\Definition\\\\ObjectType given\\.$#" + count: 3 + path: src/Type/Schema.php + + - + message: "#^Only booleans are allowed in an if condition, \\(callable\\)\\|null given\\.$#" + count: 1 + path: src/Type/Schema.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 2 + path: src/Type/Schema.php + + - + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\Type\\|null given\\.$#" + count: 1 + path: src/Type/Schema.php + + - + message: "#^Only booleans are allowed in a negated boolean, \\(callable\\)\\|null given\\.$#" + count: 2 + path: src/Type/Schema.php + + - + message: "#^Only booleans are allowed in an if condition, array\\ given\\.$#" + count: 1 + path: src/Type/Schema.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 1 + path: src/Type/SchemaConfig.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 2 + path: src/Type/SchemaConfig.php + + - + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\ObjectType given\\.$#" + count: 1 + path: src/Type/SchemaValidationContext.php + + - + message: "#^Only booleans are allowed in &&, GraphQL\\\\Type\\\\Definition\\\\ObjectType\\|null given on the left side\\.$#" + count: 1 + path: src/Type/SchemaValidationContext.php + + - + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\ObjectType\\|null given\\.$#" + count: 1 + path: src/Type/SchemaValidationContext.php + + - + message: "#^Only booleans are allowed in &&, array\\\\|GraphQL\\\\Language\\\\AST\\\\Node\\|GraphQL\\\\Language\\\\AST\\\\TypeDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\TypeNode\\|null given on the left side\\.$#" + count: 1 + path: src/Type/SchemaValidationContext.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Language\\\\AST\\\\OperationTypeDefinitionNode\\|null given\\.$#" + count: 1 + path: src/Type/SchemaValidationContext.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Type\\\\Definition\\\\Type given\\.$#" + count: 1 + path: src/Type/SchemaValidationContext.php + + - + message: "#^Anonymous function should have native return typehint \"\\?GraphQL\\\\Language\\\\AST\\\\DirectiveDefinitionNode\"\\.$#" + count: 1 + path: src/Type/SchemaValidationContext.php + + - + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Error\\\\Error\\|null given\\.$#" + count: 1 + path: src/Type/SchemaValidationContext.php + + - + message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" + count: 6 + path: src/Type/SchemaValidationContext.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Language\\\\AST\\\\InputValueDefinitionNode given\\.$#" + count: 1 + path: src/Type/SchemaValidationContext.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Language\\\\AST\\\\DirectiveDefinitionNode\\|null given\\.$#" + count: 1 + path: src/Type/SchemaValidationContext.php + + - + message: "#^Only booleans are allowed in a negated boolean, array\\ given\\.$#" + count: 1 + path: src/Type/SchemaValidationContext.php + + - + message: "#^Only booleans are allowed in &&, array\\ given on the left side\\.$#" + count: 1 + path: src/Type/SchemaValidationContext.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Language\\\\AST\\\\DirectiveDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\EnumTypeDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\InputObjectTypeDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\InterfaceTypeDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\ObjectTypeDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\SchemaDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\UnionTypeDefinitionNode\\|null given\\.$#" + count: 1 + path: src/Type/SchemaValidationContext.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, array\\ given\\.$#" + count: 1 + path: src/Type/SchemaValidationContext.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Type/SchemaValidationContext.php + + - + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Language\\\\AST\\\\InterfaceTypeDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\InterfaceTypeExtensionNode\\|GraphQL\\\\Language\\\\AST\\\\ObjectTypeDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\ObjectTypeExtensionNode given\\.$#" + count: 1 + path: src/Type/SchemaValidationContext.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Language\\\\AST\\\\FieldDefinitionNode\\|null given\\.$#" + count: 1 + path: src/Type/SchemaValidationContext.php + + - + message: "#^Only booleans are allowed in &&, GraphQL\\\\Language\\\\AST\\\\FieldDefinitionNode\\|null given on the left side\\.$#" + count: 1 + path: src/Type/SchemaValidationContext.php + + - + message: "#^Only booleans are allowed in &&, GraphQL\\\\Language\\\\AST\\\\NodeList\\ given on the right side\\.$#" + count: 1 + path: src/Type/SchemaValidationContext.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Language\\\\AST\\\\InputValueDefinitionNode\\|null given\\.$#" + count: 2 + path: src/Type/SchemaValidationContext.php + + - + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\FieldDefinition\\|null given\\.$#" + count: 1 + path: src/Type/SchemaValidationContext.php + + - + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\FieldArgument\\|null given\\.$#" + count: 1 + path: src/Type/SchemaValidationContext.php + + - + message: "#^Only booleans are allowed in \\|\\|, GraphQL\\\\Type\\\\Definition\\\\FieldArgument\\|null given on the left side\\.$#" + count: 1 + path: src/Type/SchemaValidationContext.php + + - + message: "#^Only booleans are allowed in a negated boolean, array\\ given\\.$#" + count: 1 + path: src/Type/SchemaValidationContext.php + + - + message: "#^Only booleans are allowed in a negated boolean, array\\ given\\.$#" + count: 1 + path: src/Type/SchemaValidationContext.php + + - + message: "#^Only booleans are allowed in &&, array\\ given on the left side\\.$#" + count: 1 + path: src/Type/SchemaValidationContext.php + + - + message: "#^Only booleans are allowed in a negated boolean, array\\ given\\.$#" + count: 1 + path: src/Type/SchemaValidationContext.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 1 + path: src/Utils/AST.php + + - + message: "#^Variable property access on mixed\\.$#" + count: 2 + path: src/Utils/AST.php + + - + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Language\\\\AST\\\\BooleanValueNode\\|GraphQL\\\\Language\\\\AST\\\\EnumValueNode\\|GraphQL\\\\Language\\\\AST\\\\FloatValueNode\\|GraphQL\\\\Language\\\\AST\\\\IntValueNode\\|GraphQL\\\\Language\\\\AST\\\\ListValueNode\\|GraphQL\\\\Language\\\\AST\\\\NullValueNode\\|GraphQL\\\\Language\\\\AST\\\\ObjectValueNode\\|GraphQL\\\\Language\\\\AST\\\\StringValueNode\\|null given\\.$#" + count: 2 + path: src/Utils/AST.php + + - + message: "#^Only booleans are allowed in a negated boolean, array\\|null given\\.$#" + count: 1 + path: src/Utils/AST.php + + - + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\EnumValueDefinition\\|null given\\.$#" + count: 1 + path: src/Utils/AST.php + + - + message: "#^Only booleans are allowed in &&, array\\|null given on the left side\\.$#" + count: 1 + path: src/Utils/AST.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Type\\\\Definition\\\\Type\\|null given\\.$#" + count: 2 + path: src/Utils/AST.php + + - + message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Language\\\\AST\\\\NodeList\\ given\\.$#" + count: 1 + path: src/Utils/AST.php + + - + message: "#^Only booleans are allowed in a negated boolean, string\\|null given\\.$#" + count: 1 + path: src/Utils/AST.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, array\\ given\\.$#" + count: 1 + path: src/Utils/ASTDefinitionBuilder.php + + - + message: "#^Only booleans are allowed in an if condition, callable given\\.$#" + count: 1 + path: src/Utils/ASTDefinitionBuilder.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Language\\\\AST\\\\NodeList\\ given\\.$#" + count: 1 + path: src/Utils/ASTDefinitionBuilder.php + + - + message: "#^Only booleans are allowed in an if condition, array\\ given\\.$#" + count: 1 + path: src/Utils/ASTDefinitionBuilder.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\Type\"\\.$#" + count: 2 + path: src/Utils/ASTDefinitionBuilder.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Language\\\\AST\\\\NodeList\\\\|null given\\.$#" + count: 1 + path: src/Utils/ASTDefinitionBuilder.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 1 + path: src/Utils/ASTDefinitionBuilder.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, array\\\\|null given\\.$#" + count: 1 + path: src/Utils/ASTDefinitionBuilder.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, array\\\\|null given\\.$#" + count: 1 + path: src/Utils/ASTDefinitionBuilder.php + + - + message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Language\\\\AST\\\\BooleanValueNode\\|GraphQL\\\\Language\\\\AST\\\\EnumValueNode\\|GraphQL\\\\Language\\\\AST\\\\FloatValueNode\\|GraphQL\\\\Language\\\\AST\\\\IntValueNode\\|GraphQL\\\\Language\\\\AST\\\\ListValueNode\\|GraphQL\\\\Language\\\\AST\\\\NullValueNode\\|GraphQL\\\\Language\\\\AST\\\\ObjectValueNode\\|GraphQL\\\\Language\\\\AST\\\\StringValueNode\\|GraphQL\\\\Language\\\\AST\\\\VariableNode given\\.$#" + count: 1 + path: src/Utils/ASTDefinitionBuilder.php + + - + message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" + count: 2 + path: src/Utils/BreakingChangesFinder.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Utils/BreakingChangesFinder.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 3 + path: src/Utils/BuildSchema.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 1 + path: src/Utils/BuildSchema.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\Type\"\\.$#" + count: 1 + path: src/Utils/BuildSchema.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 1 + path: src/Utils/BuildSchema.php + + - + message: "#^Only booleans are allowed in &&, array\\\\|null given on the left side\\.$#" + count: 1 + path: src/Utils/PairSet.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 5 + path: src/Utils/SchemaExtender.php + + - + message: "#^Anonymous function should have native return typehint \"string\"\\.$#" + count: 1 + path: src/Utils/SchemaExtender.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\Directive\"\\.$#" + count: 1 + path: src/Utils/SchemaExtender.php + + - + message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Type\\\\Definition\\\\Type\\|null given\\.$#" + count: 1 + path: src/Utils/SchemaExtender.php + + - + message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Language\\\\AST\\\\SchemaDefinitionNode\\|null given\\.$#" + count: 1 + path: src/Utils/SchemaExtender.php + + - + message: "#^Only booleans are allowed in a negated boolean, array\\\\|null given\\.$#" + count: 1 + path: src/Utils/SchemaExtender.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, array\\ given\\.$#" + count: 1 + path: src/Utils/SchemaExtender.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\Type\"\\.$#" + count: 1 + path: src/Utils/SchemaExtender.php + + - + message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" + count: 3 + path: src/Utils/SchemaPrinter.php + + - + message: "#^Anonymous function should have native return typehint \"string\"\\.$#" + count: 7 + path: src/Utils/SchemaPrinter.php + + - + message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Type\\\\Definition\\\\ObjectType given\\.$#" + count: 1 + path: src/Utils/SchemaPrinter.php + + - + message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Type\\\\Definition\\\\ObjectType\\|null given\\.$#" + count: 2 + path: src/Utils/SchemaPrinter.php + + - + message: "#^Only booleans are allowed in &&, GraphQL\\\\Type\\\\Definition\\\\ObjectType given on the left side\\.$#" + count: 1 + path: src/Utils/SchemaPrinter.php + + - + message: "#^Only booleans are allowed in &&, GraphQL\\\\Type\\\\Definition\\\\ObjectType\\|null given on the left side\\.$#" + count: 1 + path: src/Utils/SchemaPrinter.php + + - + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\ObjectType\\|null given\\.$#" + count: 1 + path: src/Utils/SchemaPrinter.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 3 + path: src/Utils/SchemaPrinter.php + + - + message: "#^Only booleans are allowed in a negated boolean, array\\\\|null given\\.$#" + count: 1 + path: src/Utils/TypeInfo.php + + - + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\Type\\|null given\\.$#" + count: 1 + path: src/Utils/TypeInfo.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 2 + path: src/Utils/TypeInfo.php + + - + message: "#^Only booleans are allowed in an if condition, \\(GraphQL\\\\Type\\\\Definition\\\\CompositeType&GraphQL\\\\Type\\\\Definition\\\\Type\\)\\|null given\\.$#" + count: 1 + path: src/Utils/TypeInfo.php + + - + message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Type\\\\Definition\\\\FieldDefinition\\|null given\\.$#" + count: 1 + path: src/Utils/TypeInfo.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Language\\\\AST\\\\NamedTypeNode given\\.$#" + count: 1 + path: src/Utils/TypeInfo.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Utils/TypeInfo.php + + - + message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Type\\\\Definition\\\\Directive\\|GraphQL\\\\Type\\\\Definition\\\\FieldDefinition\\|null given\\.$#" + count: 1 + path: src/Utils/TypeInfo.php + + - + message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" + count: 1 + path: src/Utils/TypeInfo.php + + - + message: "#^Only booleans are allowed in &&, GraphQL\\\\Type\\\\Definition\\\\FieldArgument\\|null given on the left side\\.$#" + count: 1 + path: src/Utils/TypeInfo.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Type\\\\Definition\\\\InputObjectField\\|null given\\.$#" + count: 1 + path: src/Utils/TypeInfo.php + + - + message: "#^Only booleans are allowed in &&, GraphQL\\\\Type\\\\Definition\\\\InputObjectField\\|null given on the left side\\.$#" + count: 1 + path: src/Utils/TypeInfo.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Utils/Utils.php + + - + message: "#^Variable property access on object\\.$#" + count: 1 + path: src/Utils/Utils.php + + - + message: "#^Only booleans are allowed in a negated boolean, string given\\.$#" + count: 1 + path: src/Utils/Utils.php + + - + message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Error\\\\Error\\|null given\\.$#" + count: 1 + path: src/Utils/Utils.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 1 + path: src/Utils/Utils.php + + - + message: "#^Anonymous function should have native return typehint \"string\"\\.$#" + count: 2 + path: src/Utils/Utils.php + + - + message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Type\\\\Definition\\\\EnumValueDefinition\\|null given\\.$#" + count: 1 + path: src/Utils/Value.php + + - + message: "#^Anonymous function should have native return typehint \"string\"\\.$#" + count: 1 + path: src/Utils/Value.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, array\\ given\\.$#" + count: 2 + path: src/Utils/Value.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, array\\ given\\.$#" + count: 2 + path: src/Utils/Value.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, string given\\.$#" + count: 2 + path: src/Utils/Value.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, string\\|null given\\.$#" + count: 1 + path: src/Utils/Value.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Validator/DocumentValidator.php + + - + message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" + count: 1 + path: src/Validator/DocumentValidator.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\VisitorOperation\"\\.$#" + count: 1 + path: src/Validator/Rules/ExecutableDefinitions.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 1 + path: src/Validator/Rules/FieldsOnCorrectType.php + + - + message: "#^Only booleans are allowed in a negated boolean, \\(GraphQL\\\\Type\\\\Definition\\\\CompositeType&GraphQL\\\\Type\\\\Definition\\\\Type\\)\\|null given\\.$#" + count: 1 + path: src/Validator/Rules/FieldsOnCorrectType.php + + - + message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Type\\\\Definition\\\\FieldDefinition given\\.$#" + count: 1 + path: src/Validator/Rules/FieldsOnCorrectType.php + + - + message: "#^Only booleans are allowed in an if condition, array\\ given\\.$#" + count: 1 + path: src/Validator/Rules/FieldsOnCorrectType.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 1 + path: src/Validator/Rules/FieldsOnCorrectType.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 2 + path: src/Validator/Rules/FragmentsOnCompositeTypes.php + + - + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Language\\\\AST\\\\NamedTypeNode given\\.$#" + count: 1 + path: src/Validator/Rules/FragmentsOnCompositeTypes.php + + - + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\Type\\|null given\\.$#" + count: 2 + path: src/Validator/Rules/FragmentsOnCompositeTypes.php + + - + message: "#^Anonymous function should have native return typehint \"string\"\\.$#" + count: 1 + path: src/Validator/Rules/KnownArgumentNames.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 2 + path: src/Validator/Rules/KnownArgumentNames.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 1 + path: src/Validator/Rules/KnownArgumentNamesOnDirectives.php + + - + message: "#^Only booleans are allowed in a negated boolean, array\\\\|null given\\.$#" + count: 1 + path: src/Validator/Rules/KnownArgumentNamesOnDirectives.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Type\\\\Schema\\|null given\\.$#" + count: 1 + path: src/Validator/Rules/KnownDirectives.php + + - + message: "#^Only booleans are allowed in a negated boolean, array\\\\|null given\\.$#" + count: 1 + path: src/Validator/Rules/KnownDirectives.php + + - + message: "#^Only booleans are allowed in a negated boolean, string given\\.$#" + count: 1 + path: src/Validator/Rules/KnownDirectives.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 1 + path: src/Validator/Rules/KnownFragmentNames.php + + - + message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Language\\\\AST\\\\FragmentDefinitionNode\\|null given\\.$#" + count: 1 + path: src/Validator/Rules/KnownFragmentNames.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\VisitorOperation\"\\.$#" + count: 1 + path: src/Validator/Rules/KnownTypeNames.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 1 + path: src/Validator/Rules/KnownTypeNames.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 1 + path: src/Validator/Rules/KnownTypeNames.php + + - + message: "#^Anonymous function sometimes return something but return statement at the end is missing\\.$#" + count: 1 + path: src/Validator/Rules/LoneAnonymousOperation.php + + - + message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" + count: 1 + path: src/Validator/Rules/LoneAnonymousOperation.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 1 + path: src/Validator/Rules/LoneAnonymousOperation.php + + - + message: "#^Only booleans are allowed in \\|\\|, GraphQL\\\\Language\\\\AST\\\\NameNode given on the left side\\.$#" + count: 1 + path: src/Validator/Rules/LoneAnonymousOperation.php + + - + message: "#^Only booleans are allowed in \\|\\|, GraphQL\\\\Language\\\\AST\\\\SchemaDefinitionNode given on the left side\\.$#" + count: 1 + path: src/Validator/Rules/LoneSchemaDefinition.php + + - + message: "#^Only booleans are allowed in \\|\\|, GraphQL\\\\Type\\\\Definition\\\\ObjectType given on the right side\\.$#" + count: 1 + path: src/Validator/Rules/LoneSchemaDefinition.php + + - + message: "#^Only booleans are allowed in \\|\\|, GraphQL\\\\Type\\\\Definition\\\\ObjectType\\|null given on the right side\\.$#" + count: 2 + path: src/Validator/Rules/LoneSchemaDefinition.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 1 + path: src/Validator/Rules/LoneSchemaDefinition.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\VisitorOperation\"\\.$#" + count: 2 + path: src/Validator/Rules/NoFragmentCycles.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 3 + path: src/Validator/Rules/NoFragmentCycles.php + + - + message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Language\\\\AST\\\\FragmentDefinitionNode\\|null given\\.$#" + count: 1 + path: src/Validator/Rules/NoFragmentCycles.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 3 + path: src/Validator/Rules/NoUndefinedVariables.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 1 + path: src/Validator/Rules/NoUndefinedVariables.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Language\\\\AST\\\\NameNode given\\.$#" + count: 1 + path: src/Validator/Rules/NoUndefinedVariables.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\VisitorOperation\"\\.$#" + count: 2 + path: src/Validator/Rules/NoUnusedFragments.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 1 + path: src/Validator/Rules/NoUnusedFragments.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 1 + path: src/Validator/Rules/NoUnusedFragments.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 3 + path: src/Validator/Rules/NoUnusedVariables.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Language\\\\AST\\\\NameNode given\\.$#" + count: 1 + path: src/Validator/Rules/NoUnusedVariables.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 1 + path: src/Validator/Rules/NoUnusedVariables.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 1 + path: src/Validator/Rules/OverlappingFieldsCanBeMerged.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Language\\\\AST\\\\NameNode\\|null given\\.$#" + count: 1 + path: src/Validator/Rules/OverlappingFieldsCanBeMerged.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Language\\\\AST\\\\NamedTypeNode given\\.$#" + count: 1 + path: src/Validator/Rules/OverlappingFieldsCanBeMerged.php + + - + message: "#^Only booleans are allowed in a negated boolean, array\\|null given\\.$#" + count: 2 + path: src/Validator/Rules/OverlappingFieldsCanBeMerged.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 2 + path: src/Validator/Rules/OverlappingFieldsCanBeMerged.php + + - + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Language\\\\AST\\\\ArgumentNode\\|null given\\.$#" + count: 1 + path: src/Validator/Rules/OverlappingFieldsCanBeMerged.php + + - + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Language\\\\AST\\\\Node given\\.$#" + count: 2 + path: src/Validator/Rules/OverlappingFieldsCanBeMerged.php + + - + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Language\\\\AST\\\\FragmentDefinitionNode\\|null given\\.$#" + count: 3 + path: src/Validator/Rules/OverlappingFieldsCanBeMerged.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 2 + path: src/Validator/Rules/OverlappingFieldsCanBeMerged.php + + - + message: "#^Anonymous function should have native return typehint \"string\"\\.$#" + count: 1 + path: src/Validator/Rules/OverlappingFieldsCanBeMerged.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 2 + path: src/Validator/Rules/PossibleFragmentSpreads.php + + - + message: "#^Only booleans are allowed in a negated boolean, \\(GraphQL\\\\Type\\\\Definition\\\\CompositeType&GraphQL\\\\Type\\\\Definition\\\\Type\\)\\|null given\\.$#" + count: 1 + path: src/Validator/Rules/PossibleFragmentSpreads.php + + - + message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Language\\\\AST\\\\FragmentDefinitionNode\\|null given\\.$#" + count: 1 + path: src/Validator/Rules/PossibleFragmentSpreads.php + + - + message: "#^Anonymous function sometimes return something but return statement at the end is missing\\.$#" + count: 2 + path: src/Validator/Rules/ProvidedRequiredArguments.php + + - + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\FieldDefinition given\\.$#" + count: 1 + path: src/Validator/Rules/ProvidedRequiredArguments.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 2 + path: src/Validator/Rules/ProvidedRequiredArguments.php + + - + message: "#^Only booleans are allowed in \\|\\|, GraphQL\\\\Language\\\\AST\\\\ArgumentNode\\|null given on the left side\\.$#" + count: 1 + path: src/Validator/Rules/ProvidedRequiredArguments.php + + - + message: "#^Only booleans are allowed in \\|\\|, array\\\\|null given on the left side\\.$#" + count: 1 + path: src/Validator/Rules/ProvidedRequiredArguments.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Type\\\\Schema\\|null given\\.$#" + count: 1 + path: src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, array\\ given\\.$#" + count: 1 + path: src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php + + - + message: "#^Anonymous function sometimes return something but return statement at the end is missing\\.$#" + count: 1 + path: src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php + + - + message: "#^Only booleans are allowed in a negated boolean, array\\|null given\\.$#" + count: 1 + path: src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 2 + path: src/Validator/Rules/QueryComplexity.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\VisitorOperation\"\\.$#" + count: 1 + path: src/Validator/Rules/QueryComplexity.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 3 + path: src/Validator/Rules/QueryComplexity.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Validator/Rules/QueryComplexity.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 1 + path: src/Validator/Rules/QueryDepth.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 2 + path: src/Validator/Rules/QuerySecurityRule.php + + - + message: "#^Only booleans are allowed in &&, GraphQL\\\\Type\\\\Definition\\\\Type\\|null given on the left side\\.$#" + count: 1 + path: src/Validator/Rules/QuerySecurityRule.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 1 + path: src/Validator/Rules/QuerySecurityRule.php + + - + message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Language\\\\AST\\\\FragmentDefinitionNode\\|null given\\.$#" + count: 1 + path: src/Validator/Rules/QuerySecurityRule.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Language\\\\AST\\\\NameNode\\|null given\\.$#" + count: 1 + path: src/Validator/Rules/QuerySecurityRule.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 1 + path: src/Validator/Rules/ScalarLeafs.php + + - + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\OutputType\\|null given\\.$#" + count: 1 + path: src/Validator/Rules/ScalarLeafs.php + + - + message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Language\\\\AST\\\\SelectionSetNode\\|null given\\.$#" + count: 1 + path: src/Validator/Rules/ScalarLeafs.php + + - + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Language\\\\AST\\\\SelectionSetNode\\|null given\\.$#" + count: 1 + path: src/Validator/Rules/ScalarLeafs.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 2 + path: src/Validator/Rules/UniqueArgumentNames.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\VisitorOperation\"\\.$#" + count: 1 + path: src/Validator/Rules/UniqueArgumentNames.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 1 + path: src/Validator/Rules/UniqueArgumentNames.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 1 + path: src/Validator/Rules/UniqueDirectivesPerLocation.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\VisitorOperation\"\\.$#" + count: 2 + path: src/Validator/Rules/UniqueFragmentNames.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 1 + path: src/Validator/Rules/UniqueFragmentNames.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 2 + path: src/Validator/Rules/UniqueInputFieldNames.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\VisitorOperation\"\\.$#" + count: 1 + path: src/Validator/Rules/UniqueInputFieldNames.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 1 + path: src/Validator/Rules/UniqueInputFieldNames.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\VisitorOperation\"\\.$#" + count: 2 + path: src/Validator/Rules/UniqueOperationNames.php + + - + message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Language\\\\AST\\\\NameNode given\\.$#" + count: 1 + path: src/Validator/Rules/UniqueOperationNames.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 1 + path: src/Validator/Rules/UniqueOperationNames.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 2 + path: src/Validator/Rules/UniqueVariableNames.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 1 + path: src/Validator/Rules/UniqueVariableNames.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: src/Validator/Rules/ValidationRule.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 8 + path: src/Validator/Rules/ValuesOfCorrectType.php + + - + message: "#^Anonymous function sometimes return something but return statement at the end is missing\\.$#" + count: 1 + path: src/Validator/Rules/ValuesOfCorrectType.php + + - + message: "#^Only booleans are allowed in \\|\\|, GraphQL\\\\Type\\\\Definition\\\\EnumType\\|GraphQL\\\\Type\\\\Definition\\\\InputObjectType\\|GraphQL\\\\Type\\\\Definition\\\\ListOfType\\|GraphQL\\\\Type\\\\Definition\\\\NonNull\\|GraphQL\\\\Type\\\\Definition\\\\ScalarType given on the left side\\.$#" + count: 1 + path: src/Validator/Rules/ValuesOfCorrectType.php + + - + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\EnumValueDefinition\\|null given\\.$#" + count: 1 + path: src/Validator/Rules/ValuesOfCorrectType.php + + - + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\EnumType\\|GraphQL\\\\Type\\\\Definition\\\\InputObjectType\\|GraphQL\\\\Type\\\\Definition\\\\ListOfType\\|GraphQL\\\\Type\\\\Definition\\\\NonNull\\|GraphQL\\\\Type\\\\Definition\\\\ScalarType given\\.$#" + count: 1 + path: src/Validator/Rules/ValuesOfCorrectType.php + + - + message: "#^Anonymous function should have native return typehint \"string\"\\.$#" + count: 1 + path: src/Validator/Rules/ValuesOfCorrectType.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, array\\ given\\.$#" + count: 1 + path: src/Validator/Rules/ValuesOfCorrectType.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 1 + path: src/Validator/Rules/VariablesAreInputTypes.php + + - + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\Type\\|null given\\.$#" + count: 1 + path: src/Validator/Rules/VariablesAreInputTypes.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 3 + path: src/Validator/Rules/VariablesInAllowedPosition.php + + - + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\Type\\|null given\\.$#" + count: 1 + path: src/Validator/Rules/VariablesInAllowedPosition.php + + - + message: "#^Only booleans are allowed in &&, GraphQL\\\\Language\\\\AST\\\\ValueNode\\|null given on the left side\\.$#" + count: 1 + path: src/Validator/Rules/VariablesInAllowedPosition.php + + - + message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" + count: 1 + path: src/Validator/ValidationContext.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 1 + path: src/Validator/ValidationContext.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 3 + path: src/Validator/ValidationContext.php + + - + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Language\\\\AST\\\\FragmentDefinitionNode\\|null given\\.$#" + count: 1 + path: src/Validator/ValidationContext.php + + - + message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Language\\\\AST\\\\SelectionSetNode\\|null given\\.$#" + count: 1 + path: src/Validator/ValidationContext.php + + - + message: "#^Only booleans are allowed in a negated boolean, array\\ given\\.$#" + count: 1 + path: src/Validator/ValidationContext.php + + - + message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" + count: 5 + path: tests/Executor/AbstractPromiseTest.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 7 + path: tests/Executor/AbstractPromiseTest.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Deferred\"\\.$#" + count: 2 + path: tests/Executor/AbstractPromiseTest.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 2 + path: tests/Executor/AbstractPromiseTest.php + + - + message: "#^Anonymous function should have native return typehint \"\\?GraphQL\\\\Deferred\"\\.$#" + count: 1 + path: tests/Executor/AbstractPromiseTest.php + + - + message: "#^Anonymous function should have native return typehint \"\\?GraphQL\\\\Type\\\\Definition\\\\ObjectType\"\\.$#" + count: 1 + path: tests/Executor/AbstractPromiseTest.php + + - + message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" + count: 4 + path: tests/Executor/AbstractTest.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 7 + path: tests/Executor/AbstractTest.php + + - + message: "#^Anonymous function sometimes return something but return statement at the end is missing\\.$#" + count: 1 + path: tests/Executor/AbstractTest.php + + - + message: "#^Anonymous function should have native return typehint \"string\"\\.$#" + count: 1 + path: tests/Executor/AbstractTest.php + + - + message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" + count: 5 + path: tests/Executor/DeferredFieldsTest.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 4 + path: tests/Executor/DeferredFieldsTest.php + + - + message: "#^Anonymous function should have native return typehint \"string\"\\.$#" + count: 2 + path: tests/Executor/DeferredFieldsTest.php + + - + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Schema given\\.$#" + count: 1 + path: tests/Executor/DirectivesTest.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: tests/Executor/DirectivesTest.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 13 + path: tests/Executor/ExecutorLazySchemaTest.php + + - + message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" + count: 2 + path: tests/Executor/ExecutorLazySchemaTest.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 7 + path: tests/Executor/ExecutorLazySchemaTest.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 1 + path: tests/Executor/ExecutorLazySchemaTest.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 3 + path: tests/Executor/ExecutorSchemaTest.php + + - + message: "#^Anonymous function should have native return typehint \"\\?GraphQL\\\\Deferred\"\\.$#" + count: 1 + path: tests/Executor/ExecutorTest.php + + - + message: "#^Anonymous function should have native return typehint \"string\"\\.$#" + count: 19 + path: tests/Executor/ExecutorTest.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 6 + path: tests/Executor/ExecutorTest.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 12 + path: tests/Executor/ExecutorTest.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Error\\\\UserError\"\\.$#" + count: 1 + path: tests/Executor/ExecutorTest.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Deferred\"\\.$#" + count: 7 + path: tests/Executor/ExecutorTest.php + + - + message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" + count: 1 + path: tests/Executor/ExecutorTest.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\ObjectType\"\\.$#" + count: 1 + path: tests/Executor/ExecutorTest.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 1 + path: tests/Executor/LazyInterfaceTest.php + + - + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\InterfaceType given\\.$#" + count: 1 + path: tests/Executor/LazyInterfaceTest.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\ObjectType\"\\.$#" + count: 1 + path: tests/Executor/LazyInterfaceTest.php + + - + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\ObjectType given\\.$#" + count: 1 + path: tests/Executor/LazyInterfaceTest.php + + - + message: "#^Anonymous function should have native return typehint \"string\"\\.$#" + count: 1 + path: tests/Executor/LazyInterfaceTest.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 9 + path: tests/Executor/ListsTest.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Deferred\"\\.$#" + count: 4 + path: tests/Executor/ListsTest.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 8 + path: tests/Executor/ListsTest.php + + - + message: "#^Anonymous function should have native return typehint \"int\"\\.$#" + count: 24 + path: tests/Executor/ListsTest.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Tests\\\\Executor\\\\TestClasses\\\\NumberHolder\"\\.$#" + count: 1 + path: tests/Executor/MutationsTest.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Deferred\"\\.$#" + count: 2 + path: tests/Executor/MutationsTest.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 1 + path: tests/Executor/MutationsTest.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 4 + path: tests/Executor/NonNullTest.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Deferred\"\\.$#" + count: 2 + path: tests/Executor/NonNullTest.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 9 + path: tests/Executor/NonNullTest.php + + - + message: "#^Anonymous function should have native return typehint \"\\?GraphQL\\\\Deferred\"\\.$#" + count: 2 + path: tests/Executor/NonNullTest.php + + - + message: "#^Anonymous function sometimes return something but return statement at the end is missing\\.$#" + count: 1 + path: tests/Executor/NonNullTest.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 9 + path: tests/Executor/Promise/ReactPromiseAdapterTest.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 7 + path: tests/Executor/Promise/SyncPromiseAdapterTest.php + + - + message: "#^Anonymous function should have native return typehint \"string\"\\.$#" + count: 1 + path: tests/Executor/Promise/SyncPromiseAdapterTest.php + + - + message: "#^Anonymous function should have native return typehint \"int\"\\.$#" + count: 4 + path: tests/Executor/Promise/SyncPromiseAdapterTest.php + + - + message: "#^Anonymous function should have native return typehint \"string\"\\.$#" + count: 3 + path: tests/Executor/Promise/SyncPromiseTest.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 10 + path: tests/Executor/Promise/SyncPromiseTest.php + + - + message: "#^Anonymous function should have native return typehint \"int\"\\.$#" + count: 1 + path: tests/Executor/Promise/SyncPromiseTest.php + + - + message: "#^Anonymous function should have native return typehint \"string\"\\.$#" + count: 1 + path: tests/Executor/ResolveTest.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 1 + path: tests/Executor/SyncTest.php + + - + message: "#^Anonymous function should have native return typehint \"float\"\\.$#" + count: 1 + path: tests/Executor/TestClasses/Adder.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Tests\\\\Executor\\\\TestClasses\\\\NumberHolder\"\\.$#" + count: 1 + path: tests/Executor/TestClasses/Root.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 1 + path: tests/Executor/TestClasses/Root.php + + - + message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" + count: 3 + path: tests/Executor/UnionInterfaceTest.php + + - + message: "#^Anonymous function sometimes return something but return statement at the end is missing\\.$#" + count: 1 + path: tests/Executor/UnionInterfaceTest.php + + - + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Schema given\\.$#" + count: 1 + path: tests/Executor/ValuesTest.php + + - + message: "#^Anonymous function should have native return typehint \"\\?string\"\\.$#" + count: 1 + path: tests/Executor/VariablesTest.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 6 + path: tests/Experimental/Executor/CollectorTest.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 1 + path: tests/Experimental/Executor/CollectorTest.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Executor\\\\Promise\\\\Promise\"\\.$#" + count: 1 + path: tests/GraphQLTest.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 1 + path: tests/Language/LexerTest.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 1 + path: tests/Language/ParserTest.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 20 + path: tests/Language/SchemaParserTest.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 31 + path: tests/Language/VisitorTest.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\AST\\\\OperationDefinitionNode\"\\.$#" + count: 2 + path: tests/Language/VisitorTest.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\AST\\\\DocumentNode\"\\.$#" + count: 1 + path: tests/Language/VisitorTest.php + + - + message: "#^Anonymous function sometimes return something but return statement at the end is missing\\.$#" + count: 18 + path: tests/Language/VisitorTest.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, \\(GraphQL\\\\Type\\\\Definition\\\\CompositeType&GraphQL\\\\Type\\\\Definition\\\\Type\\)\\|null given\\.$#" + count: 4 + path: tests/Language/VisitorTest.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, \\(GraphQL\\\\Type\\\\Definition\\\\OutputType&GraphQL\\\\Type\\\\Definition\\\\Type\\)\\|null given\\.$#" + count: 4 + path: tests/Language/VisitorTest.php + + - + message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Type\\\\Definition\\\\EnumType\\|GraphQL\\\\Type\\\\Definition\\\\InputObjectType\\|GraphQL\\\\Type\\\\Definition\\\\ListOfType\\|GraphQL\\\\Type\\\\Definition\\\\NonNull\\|GraphQL\\\\Type\\\\Definition\\\\ScalarType\\|null given\\.$#" + count: 4 + path: tests/Language/VisitorTest.php + + - + message: "#^Anonymous function should return GraphQL\\\\Type\\\\Definition\\\\Type but return statement is missing\\.$#" + count: 1 + path: tests/Regression/Issue396Test.php + + - + message: "#^Anonymous function should return GraphQL\\\\Type\\\\Definition\\\\Type\\|null but return statement is missing\\.$#" + count: 1 + path: tests/Regression/Issue396Test.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: tests/Server/Psr7/PsrStreamStub.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 7 + path: tests/Server/QueryExecutionTest.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 4 + path: tests/Server/QueryExecutionTest.php + + - + message: "#^Anonymous function should have native return typehint \"string\"\\.$#" + count: 3 + path: tests/Server/QueryExecutionTest.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\AST\\\\DocumentNode\"\\.$#" + count: 1 + path: tests/Server/QueryExecutionTest.php + + - + message: "#^Anonymous function should have native return typehint \"stdClass\"\\.$#" + count: 1 + path: tests/Server/QueryExecutionTest.php + + - + message: "#^Anonymous function should have native return typehint \"string\"\\.$#" + count: 1 + path: tests/Server/RequestParsingTest.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 3 + path: tests/Server/RequestParsingTest.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 1 + path: tests/Server/RequestValidationTest.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 10 + path: tests/Server/ServerConfigTest.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 1 + path: tests/Server/ServerConfigTest.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 2 + path: tests/Server/ServerTestCase.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 4 + path: tests/StarWarsSchema.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 2 + path: tests/StarWarsSchema.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 7 + path: tests/StarWarsValidationTest.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 16 + path: tests/Type/DefinitionTest.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 16 + path: tests/Type/DefinitionTest.php + + - + message: "#^Anonymous function should have native return typehint \"stdClass\"\\.$#" + count: 1 + path: tests/Type/DefinitionTest.php + + - + message: "#^Anonymous function should have native return typehint \"int\"\\.$#" + count: 1 + path: tests/Type/DefinitionTest.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 1 + path: tests/Type/EnumTypeTest.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 4 + path: tests/Type/EnumTypeTest.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 5 + path: tests/Type/QueryPlanTest.php + + - + message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" + count: 1 + path: tests/Type/QueryPlanTest.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\ObjectType\"\\.$#" + count: 1 + path: tests/Type/QueryPlanTest.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 2 + path: tests/Type/ResolveInfoTest.php + + - + message: "#^Anonymous function should have native return typehint \"string\"\\.$#" + count: 1 + path: tests/Type/SchemaTest.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 1 + path: tests/Type/SchemaTest.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 3 + path: tests/Type/StandardTypesTest.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 5 + path: tests/Type/TypeLoaderTest.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 5 + path: tests/Type/TypeLoaderTest.php + + - + message: "#^Variable property access on \\$this\\(GraphQL\\\\Tests\\\\Type\\\\TypeLoaderTest\\)\\.$#" + count: 1 + path: tests/Type/TypeLoaderTest.php + + - + message: "#^Anonymous function should have native return typehint \"stdClass\"\\.$#" + count: 1 + path: tests/Type/TypeLoaderTest.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\InterfaceType\"\\.$#" + count: 1 + path: tests/Type/TypeLoaderTest.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 3 + path: tests/Type/ValidationTest.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 5 + path: tests/Type/ValidationTest.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\ListOfType\"\\.$#" + count: 1 + path: tests/Type/ValidationTest.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\NonNull\"\\.$#" + count: 2 + path: tests/Type/ValidationTest.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\ObjectType\"\\.$#" + count: 1 + path: tests/Type/ValidationTest.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\EnumType\"\\.$#" + count: 1 + path: tests/Type/ValidationTest.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\InputObjectType\"\\.$#" + count: 1 + path: tests/Type/ValidationTest.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\UnionType\"\\.$#" + count: 1 + path: tests/Type/ValidationTest.php + + - + message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\InterfaceType\"\\.$#" + count: 1 + path: tests/Type/ValidationTest.php + + - + message: "#^Anonymous function should have native return typehint \"\\?GraphQL\\\\Type\\\\Definition\\\\ObjectType\"\\.$#" + count: 1 + path: tests/Type/ValidationTest.php + + - + message: "#^Only booleans are allowed in a negated boolean, stdClass given\\.$#" + count: 1 + path: tests/Utils/AstFromValueTest.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 7 + path: tests/Utils/ExtractTypesTest.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 2 + path: tests/Utils/MixedStoreTest.php + + - + message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" + count: 2 + path: tests/Utils/MixedStoreTest.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 5 + path: tests/Utils/SchemaExtenderTest.php + + - + message: "#^Anonymous function should have native return typehint \"string\"\\.$#" + count: 5 + path: tests/Utils/SchemaExtenderTest.php + + - + message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + count: 1 + path: tests/Utils/ValueFromAstTest.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 3 + path: tests/Validator/OverlappingFieldsCanBeMergedTest.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 1 + path: tests/Validator/QueryComplexityTest.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 1 + path: tests/Validator/QueryComplexityTest.php + + - + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" + count: 3 + path: tests/Validator/ValidatorTestCase.php + + - + message: "#^Anonymous function should have native return typehint \"void\"\\.$#" + count: 2 + path: tests/Validator/ValidatorTestCase.php + diff --git a/phpstan.neon.dist b/phpstan.neon.dist index b03a01a01..2d8ea2096 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -26,24 +26,11 @@ parameters: - "~Access to an undefined property GraphQL\\\\Language\\\\AST\\\\.+::\\$didLeave~" - "~Access to an undefined property GraphQL\\\\Language\\\\AST\\\\Node::\\$value~" - # TODO fix those errors to improve type safety - - "~Construct empty\\(\\) is not allowed\\. Use more strict comparison~" - - "~Variable property access on .+~" - - "~Anonymous function should have native return typehint~" - - "~Anonymous function should return .* but return statement is missing.~" - - "~Anonymous function sometimes return something but return statement at the end is missing.~" - - "~Short ternary operator is not allowed. Use null coalesce operator if applicable or consider using long ternary.~" - # TODO convert to less magical code - "~Variable method call on static\\(GraphQL\\\\Server\\\\ServerConfig\\)~" - # TODO cast booleans explicitly https://github.com/phpstan/phpstan-strict-rules/issues/2 - - "~Only booleans are allowed in .*~" - includes: - - vendor/phpstan/phpstan-phpunit/extension.neon - - vendor/phpstan/phpstan-phpunit/rules.neon - - vendor/phpstan/phpstan-strict-rules/rules.neon + - phpstan-baseline.neon services: - diff --git a/src/Error/Error.php b/src/Error/Error.php index 75a62ca77..dfa98715e 100644 --- a/src/Error/Error.php +++ b/src/Error/Error.php @@ -227,7 +227,7 @@ public function getPositions() { if ($this->positions === null && ! empty($this->nodes)) { $positions = array_map( - static function ($node) { + static function ($node) : ?int { return isset($node->loc) ? $node->loc->start : null; }, $this->nodes @@ -235,7 +235,7 @@ static function ($node) { $positions = array_filter( $positions, - static function ($p) { + static function ($p) : bool { return $p !== null; } ); @@ -270,7 +270,7 @@ public function getLocations() if ($positions && $source) { $this->locations = array_map( - static function ($pos) use ($source) { + static function ($pos) use ($source) : SourceLocation { return $source->getLocation($pos); }, $positions @@ -282,6 +282,8 @@ static function ($node) { if ($node->loc && $node->loc->source) { return $node->loc->source->getLocation($node->loc->start); } + + return null; }, $nodes ) @@ -341,7 +343,7 @@ public function toSerializableArray() $locations = Utils::map( $this->getLocations(), - static function (SourceLocation $loc) { + static function (SourceLocation $loc) : array { return $loc->toSerializableArray(); } ); diff --git a/src/Error/FormattedError.php b/src/Error/FormattedError.php index aa1101b1e..46c781e26 100644 --- a/src/Error/FormattedError.php +++ b/src/Error/FormattedError.php @@ -202,7 +202,7 @@ public static function createFromException($e, $debug = false, $internalErrorMes if ($e instanceof Error) { $locations = Utils::map( $e->getLocations(), - static function (SourceLocation $loc) { + static function (SourceLocation $loc) : array { return $loc->toSerializableArray(); } ); @@ -302,11 +302,11 @@ public static function addDebugEntries(array $formattedError, $e, $debug) */ public static function prepareFormatter(?callable $formatter = null, $debug) { - $formatter = $formatter ?: static function ($e) { + $formatter = $formatter ?: static function ($e) : array { return FormattedError::createFromException($e); }; if ($debug) { - $formatter = static function ($e) use ($formatter, $debug) { + $formatter = static function ($e) use ($formatter, $debug) : array { return FormattedError::addDebugEntries($formatter($e), $e, $debug); }; } @@ -337,7 +337,7 @@ public static function toSafeTrace($error) } return array_map( - static function ($err) { + static function ($err) : array { $safeErr = array_intersect_key($err, ['file' => true, 'line' => true]); if (isset($err['function'])) { @@ -413,7 +413,7 @@ public static function create($error, array $locations = []) if (! empty($locations)) { $formatted['locations'] = array_map( - static function ($loc) { + static function ($loc) : array { return $loc->toArray(); }, $locations diff --git a/src/Executor/ExecutionResult.php b/src/Executor/ExecutionResult.php index db16dd3d4..1fc47c8db 100644 --- a/src/Executor/ExecutionResult.php +++ b/src/Executor/ExecutionResult.php @@ -139,7 +139,7 @@ public function toArray($debug = false) $result = []; if (! empty($this->errors)) { - $errorsHandler = $this->errorsHandler ?: static function (array $errors, callable $formatter) { + $errorsHandler = $this->errorsHandler ?: static function (array $errors, callable $formatter) : array { return array_map($formatter, $errors); }; diff --git a/src/Executor/Promise/Adapter/ReactPromiseAdapter.php b/src/Executor/Promise/Adapter/ReactPromiseAdapter.php index 446d90438..14ff5027e 100644 --- a/src/Executor/Promise/Adapter/ReactPromiseAdapter.php +++ b/src/Executor/Promise/Adapter/ReactPromiseAdapter.php @@ -85,7 +85,7 @@ static function ($item) { } ); - $promise = all($promisesOrValues)->then(static function ($values) use ($promisesOrValues) { + $promise = all($promisesOrValues)->then(static function ($values) use ($promisesOrValues) : array { $orderedResults = []; foreach ($promisesOrValues as $key => $value) { diff --git a/src/Executor/Promise/Adapter/SyncPromise.php b/src/Executor/Promise/Adapter/SyncPromise.php index fba2035a2..90230d8bc 100644 --- a/src/Executor/Promise/Adapter/SyncPromise.php +++ b/src/Executor/Promise/Adapter/SyncPromise.php @@ -56,10 +56,10 @@ public function resolve($value) : self } if (is_object($value) && method_exists($value, 'then')) { $value->then( - function ($resolvedValue) { + function ($resolvedValue) : void { $this->resolve($resolvedValue); }, - function ($reason) { + function ($reason) : void { $this->reject($reason); } ); @@ -115,7 +115,7 @@ private function enqueueWaitingPromises() : void ); foreach ($this->waiting as $descriptor) { - self::getQueue()->enqueue(function () use ($descriptor) { + self::getQueue()->enqueue(function () use ($descriptor) : void { /** @var self $promise */ [$promise, $onFulfilled, $onRejected] = $descriptor; diff --git a/src/Executor/Promise/Adapter/SyncPromiseAdapter.php b/src/Executor/Promise/Adapter/SyncPromiseAdapter.php index b7735f2d2..189d51059 100644 --- a/src/Executor/Promise/Adapter/SyncPromiseAdapter.php +++ b/src/Executor/Promise/Adapter/SyncPromiseAdapter.php @@ -110,7 +110,7 @@ public function all(array $promisesOrValues) if ($promiseOrValue instanceof Promise) { $result[$index] = null; $promiseOrValue->then( - static function ($value) use ($index, &$count, $total, &$result, $all) { + static function ($value) use ($index, &$count, $total, &$result, $all) : void { $result[$index] = $value; $count++; if ($count < $total) { diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 967a7a35e..b5d700896 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -257,12 +257,14 @@ private function executeOperation(OperationDefinitionNode $operation, $rootValue if ($this->isPromise($result)) { return $result->then( null, - function ($error) { + function ($error) : ?Promise { if ($error instanceof Error) { $this->exeContext->addError($error); return $this->exeContext->promiseAdapter->createFulfilled(null); } + + return null; } ); } diff --git a/src/Executor/Values.php b/src/Executor/Values.php index 7c02090ed..e7a71deaa 100644 --- a/src/Executor/Values.php +++ b/src/Executor/Values.php @@ -160,7 +160,7 @@ public static function getDirectiveValues(Directive $directiveDef, $node, $varia if (isset($node->directives) && $node->directives instanceof NodeList) { $directiveNode = Utils::find( $node->directives, - static function (DirectiveNode $directive) use ($directiveDef) { + static function (DirectiveNode $directive) use ($directiveDef) : bool { return $directive->name->value === $directiveDef->name; } ); @@ -327,7 +327,7 @@ public static function isValidPHPValue($value, InputType $type) return $errors ? array_map( - static function (Throwable $error) { + static function (Throwable $error) : string { return $error->getMessage(); }, $errors diff --git a/src/Validator/Rules/DisableIntrospection.php b/src/Validator/Rules/DisableIntrospection.php index 6e0efab18..01a93183b 100644 --- a/src/Validator/Rules/DisableIntrospection.php +++ b/src/Validator/Rules/DisableIntrospection.php @@ -31,7 +31,7 @@ public function getVisitor(ValidationContext $context) return $this->invokeIfNeeded( $context, [ - NodeKind::FIELD => static function (FieldNode $node) use ($context) { + NodeKind::FIELD => static function (FieldNode $node) use ($context) : void { if ($node->name->value !== '__type' && $node->name->value !== '__schema') { return; } diff --git a/src/Validator/Rules/KnownDirectives.php b/src/Validator/Rules/KnownDirectives.php index e92625dad..6dbf8497e 100644 --- a/src/Validator/Rules/KnownDirectives.php +++ b/src/Validator/Rules/KnownDirectives.php @@ -77,7 +77,7 @@ public function getASTVisitor(ASTValidationContext $context) } $locationsMap[$def->name->value] = array_map( - static function ($name) { + static function ($name) : string { return $name->value; }, $def->locations @@ -94,7 +94,7 @@ static function ($name) { ) use ( $context, $locationsMap - ) { + ) : void { $name = $node->name->value; $locations = $locationsMap[$name] ?? null; From 21c599683f175b227e9cb9343b8ee2f79b5c2ae4 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Wed, 26 Feb 2020 10:50:17 +0100 Subject: [PATCH 143/256] Implement BuildClientSchema (#539) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wip * wip * Finish implementation * Add Introspection::fromSchema * wip test * format * types * Use array instead of stdClass * Add tests * Mention TypeKind constants change in UPGRADE.md * Adress review comments * Some codestyle fixes * Fix codestyle and static analysis * Build some tests * Add more tests * Fix test and implementation * Fix codestyle * Change wording * Revert implementation of defaultValueExists * Fix codestyle * Call instance method not statically * Fix codestyle * Make callable functions public * Update src/Type/Introspection.php Co-Authored-By: Šimon Podlipský * Update tests/Utils/BuildClientSchemaTest.php Co-Authored-By: Šimon Podlipský * Iterable * Revert change in Utils.php * Re-add minimal docs fix Co-authored-by: Šimon Podlipský --- UPGRADE.md | 4 + src/Type/Introspection.php | 37 +- src/Type/Schema.php | 2 +- src/Type/TypeKind.php | 16 +- src/Utils/BuildClientSchema.php | 479 +++++++++++ src/Utils/Utils.php | 4 +- tests/Utils/BuildClientSchemaTest.php | 1082 +++++++++++++++++++++++++ 7 files changed, 1609 insertions(+), 15 deletions(-) create mode 100644 src/Utils/BuildClientSchema.php create mode 100644 tests/Utils/BuildClientSchemaTest.php diff --git a/UPGRADE.md b/UPGRADE.md index 99d21509f..ce5c93ea9 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -3,6 +3,10 @@ ### Breaking (major): dropped deprecations - dropped deprecated `GraphQL\Schema`. Use `GraphQL\Type\Schema`. +### Breaking: change TypeKind constants +The constants in `\GraphQL\Type\TypeKind` were partly renamed and their values +have been changed to match their name instead of a numeric index. + ## Upgrade v0.12.x > v0.13.x ### Breaking (major): minimum supported version of PHP diff --git a/src/Type/Introspection.php b/src/Type/Introspection.php index 1d6b86450..63229c10e 100644 --- a/src/Type/Introspection.php +++ b/src/Type/Introspection.php @@ -5,6 +5,7 @@ namespace GraphQL\Type; use Exception; +use GraphQL\GraphQL; use GraphQL\Language\DirectiveLocation; use GraphQL\Language\Printer; use GraphQL\Type\Definition\Directive; @@ -184,6 +185,34 @@ public static function getTypes() ]; } + /** + * Build an introspection query from a Schema + * + * Introspection is useful for utilities that care about type and field + * relationships, but do not need to traverse through those relationships. + * + * This is the inverse of BuildClientSchema::build(). The primary use case is outside + * of the server context, for instance when doing schema comparisons. + * + * Options: + * - descriptions + * Whether to include descriptions in the introspection result. + * Default: true + * + * @param array $options + * + * @return array>|null + */ + public static function fromSchema(Schema $schema, array $options = []) : ?array + { + $result = GraphQL::executeQuery( + $schema, + self::getIntrospectionQuery($options) + ); + + return $result->data; + } + public static function _schema() { if (! isset(self::$map['__Schema'])) { @@ -263,7 +292,7 @@ public static function _type() 'resolve' => static function (Type $type) { switch (true) { case $type instanceof ListOfType: - return TypeKind::LIST_KIND; + return TypeKind::LIST; case $type instanceof NonNull: return TypeKind::NON_NULL; case $type instanceof ScalarType: @@ -275,7 +304,7 @@ public static function _type() case $type instanceof InputObjectType: return TypeKind::INPUT_OBJECT; case $type instanceof InterfaceType: - return TypeKind::INTERFACE_KIND; + return TypeKind::INTERFACE; case $type instanceof UnionType: return TypeKind::UNION; default: @@ -408,7 +437,7 @@ public static function _typeKind() 'description' => 'Indicates this type is an object. `fields` and `interfaces` are valid fields.', ], 'INTERFACE' => [ - 'value' => TypeKind::INTERFACE_KIND, + 'value' => TypeKind::INTERFACE, 'description' => 'Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.', ], 'UNION' => [ @@ -424,7 +453,7 @@ public static function _typeKind() 'description' => 'Indicates this type is an input object. `inputFields` is a valid field.', ], 'LIST' => [ - 'value' => TypeKind::LIST_KIND, + 'value' => TypeKind::LIST, 'description' => 'Indicates this type is a list. `ofType` is a valid field.', ], 'NON_NULL' => [ diff --git a/src/Type/Schema.php b/src/Type/Schema.php index 0ec16b6f4..547e6843f 100644 --- a/src/Type/Schema.php +++ b/src/Type/Schema.php @@ -238,7 +238,7 @@ private function collectAllTypes() */ public function getDirectives() { - return $this->config->directives ?: GraphQL::getStandardDirectives(); + return $this->config->directives ?? GraphQL::getStandardDirectives(); } /** diff --git a/src/Type/TypeKind.php b/src/Type/TypeKind.php index 7cd38ff07..69cdf2e2c 100644 --- a/src/Type/TypeKind.php +++ b/src/Type/TypeKind.php @@ -6,12 +6,12 @@ class TypeKind { - const SCALAR = 0; - const OBJECT = 1; - const INTERFACE_KIND = 2; - const UNION = 3; - const ENUM = 4; - const INPUT_OBJECT = 5; - const LIST_KIND = 6; - const NON_NULL = 7; + const SCALAR = 'SCALAR'; + const OBJECT = 'OBJECT'; + const INTERFACE = 'INTERFACE'; + const UNION = 'UNION'; + const ENUM = 'ENUM'; + const INPUT_OBJECT = 'INPUT_OBJECT'; + const LIST = 'LIST'; + const NON_NULL = 'NON_NULL'; } diff --git a/src/Utils/BuildClientSchema.php b/src/Utils/BuildClientSchema.php new file mode 100644 index 000000000..c6542700d --- /dev/null +++ b/src/Utils/BuildClientSchema.php @@ -0,0 +1,479 @@ + */ + private $introspection; + + /** @var array */ + private $options; + + /** @var array */ + private $typeMap; + + /** + * @param array $introspectionQuery + * @param array $options + */ + public function __construct(array $introspectionQuery, array $options = []) + { + $this->introspection = $introspectionQuery; + $this->options = $options; + } + + /** + * Build a schema for use by client tools. + * + * Given the result of a client running the introspection query, creates and + * returns a \GraphQL\Type\Schema instance which can be then used with all graphql-php + * tools, but cannot be used to execute a query, as introspection does not + * represent the "resolver", "parse" or "serialize" functions or any other + * server-internal mechanisms. + * + * This function expects a complete introspection result. Don't forget to check + * the "errors" field of a server response before calling this function. + * + * Accepts options as a third argument: + * + * - assumeValid: + * When building a schema from a GraphQL service's introspection result, it + * might be safe to assume the schema is valid. Set to true to assume the + * produced schema is valid. + * + * Default: false + * + * @param array $introspectionQuery + * @param array $options + * + * @api + */ + public static function build(array $introspectionQuery, array $options = []) : Schema + { + $builder = new self($introspectionQuery, $options); + + return $builder->buildSchema(); + } + + public function buildSchema() : Schema + { + if (! array_key_exists('__schema', $this->introspection)) { + throw new InvariantViolation('Invalid or incomplete introspection result. Ensure that you are passing "data" property of introspection response and no "errors" was returned alongside: ' . json_encode($this->introspection) . '.'); + } + + $schemaIntrospection = $this->introspection['__schema']; + + $this->typeMap = Utils::keyValMap( + $schemaIntrospection['types'], + static function (array $typeIntrospection) { + return $typeIntrospection['name']; + }, + function (array $typeIntrospection) { + return $this->buildType($typeIntrospection); + } + ); + + $builtInTypes = array_merge( + Type::getStandardTypes(), + Introspection::getTypes() + ); + foreach ($builtInTypes as $name => $type) { + if (! isset($this->typeMap[$name])) { + continue; + } + + $this->typeMap[$name] = $type; + } + + $queryType = isset($schemaIntrospection['queryType']) + ? $this->getObjectType($schemaIntrospection['queryType']) + : null; + + $mutationType = isset($schemaIntrospection['mutationType']) + ? $this->getObjectType($schemaIntrospection['mutationType']) + : null; + + $subscriptionType = isset($schemaIntrospection['subscriptionType']) + ? $this->getObjectType($schemaIntrospection['subscriptionType']) + : null; + + $directives = isset($schemaIntrospection['directives']) + ? array_map( + [$this, 'buildDirective'], + $schemaIntrospection['directives'] + ) + : []; + + $schemaConfig = new SchemaConfig(); + $schemaConfig->setQuery($queryType) + ->setMutation($mutationType) + ->setSubscription($subscriptionType) + ->setTypes($this->typeMap) + ->setDirectives($directives) + ->setAssumeValid( + isset($this->options) + && isset($this->options['assumeValid']) + && $this->options['assumeValid'] + ); + + return new Schema($schemaConfig); + } + + /** + * @param array $typeRef + */ + private function getType(array $typeRef) : Type + { + if (isset($typeRef['kind'])) { + if ($typeRef['kind'] === TypeKind::LIST) { + if (! isset($typeRef['ofType'])) { + throw new InvariantViolation('Decorated type deeper than introspection query.'); + } + + return new ListOfType($this->getType($typeRef['ofType'])); + } + + if ($typeRef['kind'] === TypeKind::NON_NULL) { + if (! isset($typeRef['ofType'])) { + throw new InvariantViolation('Decorated type deeper than introspection query.'); + } + /** @var NullableType $nullableType */ + $nullableType = $this->getType($typeRef['ofType']); + + return new NonNull($nullableType); + } + } + + if (! isset($typeRef['name'])) { + throw new InvariantViolation('Unknown type reference: ' . json_encode($typeRef) . '.'); + } + + return $this->getNamedType($typeRef['name']); + } + + /** + * @return NamedType&Type + */ + private function getNamedType(string $typeName) : NamedType + { + if (! isset($this->typeMap[$typeName])) { + throw new InvariantViolation( + "Invalid or incomplete schema, unknown type: ${typeName}. Ensure that a full introspection query is used in order to build a client schema." + ); + } + + return $this->typeMap[$typeName]; + } + + /** + * @param array $typeRef + */ + private function getInputType(array $typeRef) : InputType + { + $type = $this->getType($typeRef); + + if ($type instanceof InputType) { + return $type; + } + + throw new InvariantViolation('Introspection must provide input type for arguments, but received: ' . json_encode($type) . '.'); + } + + /** + * @param array $typeRef + */ + private function getOutputType(array $typeRef) : OutputType + { + $type = $this->getType($typeRef); + + if ($type instanceof OutputType) { + return $type; + } + + throw new InvariantViolation('Introspection must provide output type for fields, but received: ' . json_encode($type) . '.'); + } + + /** + * @param array $typeRef + */ + private function getObjectType(array $typeRef) : ObjectType + { + $type = $this->getType($typeRef); + + return ObjectType::assertObjectType($type); + } + + /** + * @param array $typeRef + */ + public function getInterfaceType(array $typeRef) : InterfaceType + { + $type = $this->getType($typeRef); + + return InterfaceType::assertInterfaceType($type); + } + + /** + * @param array $type + */ + private function buildType(array $type) : NamedType + { + if (array_key_exists('name', $type) && array_key_exists('kind', $type)) { + switch ($type['kind']) { + case TypeKind::SCALAR: + return $this->buildScalarDef($type); + case TypeKind::OBJECT: + return $this->buildObjectDef($type); + case TypeKind::INTERFACE: + return $this->buildInterfaceDef($type); + case TypeKind::UNION: + return $this->buildUnionDef($type); + case TypeKind::ENUM: + return $this->buildEnumDef($type); + case TypeKind::INPUT_OBJECT: + return $this->buildInputObjectDef($type); + } + } + + throw new InvariantViolation( + 'Invalid or incomplete introspection result. Ensure that a full introspection query is used in order to build a client schema: ' . json_encode($type) . '.' + ); + } + + /** + * @param array $scalar + */ + private function buildScalarDef(array $scalar) : ScalarType + { + return new CustomScalarType([ + 'name' => $scalar['name'], + 'description' => $scalar['description'], + 'serialize' => static function ($value) : string { + return (string) $value; + }, + ]); + } + + /** + * @param array $object + */ + private function buildObjectDef(array $object) : ObjectType + { + if (! array_key_exists('interfaces', $object)) { + throw new InvariantViolation('Introspection result missing interfaces: ' . json_encode($object) . '.'); + } + + return new ObjectType([ + 'name' => $object['name'], + 'description' => $object['description'], + 'interfaces' => function () use ($object) { + return array_map( + [$this, 'getInterfaceType'], + // Legacy support for interfaces with null as interfaces field + $object['interfaces'] ?? [] + ); + }, + 'fields' => function () use ($object) { + return $this->buildFieldDefMap($object); + }, + ]); + } + + /** + * @param array $interface + */ + private function buildInterfaceDef(array $interface) : InterfaceType + { + return new InterfaceType([ + 'name' => $interface['name'], + 'description' => $interface['description'], + 'fields' => function () use ($interface) { + return $this->buildFieldDefMap($interface); + }, + ]); + } + + /** + * @param array> $union + */ + private function buildUnionDef(array $union) : UnionType + { + if (! array_key_exists('possibleTypes', $union)) { + throw new InvariantViolation('Introspection result missing possibleTypes: ' . json_encode($union) . '.'); + } + + return new UnionType([ + 'name' => $union['name'], + 'description' => $union['description'], + 'types' => function () use ($union) { + return array_map( + [$this, 'getObjectType'], + $union['possibleTypes'] + ); + }, + ]); + } + + /** + * @param array> $enum + */ + private function buildEnumDef(array $enum) : EnumType + { + if (! array_key_exists('enumValues', $enum)) { + throw new InvariantViolation('Introspection result missing enumValues: ' . json_encode($enum) . '.'); + } + + return new EnumType([ + 'name' => $enum['name'], + 'description' => $enum['description'], + 'values' => Utils::keyValMap( + $enum['enumValues'], + static function (array $enumValue) : string { + return $enumValue['name']; + }, + static function (array $enumValue) { + return [ + 'description' => $enumValue['description'], + 'deprecationReason' => $enumValue['deprecationReason'], + ]; + } + ), + ]); + } + + /** + * @param array $inputObject + */ + private function buildInputObjectDef(array $inputObject) : InputObjectType + { + if (! array_key_exists('inputFields', $inputObject)) { + throw new InvariantViolation('Introspection result missing inputFields: ' . json_encode($inputObject) . '.'); + } + + return new InputObjectType([ + 'name' => $inputObject['name'], + 'description' => $inputObject['description'], + 'fields' => function () use ($inputObject) { + return $this->buildInputValueDefMap($inputObject['inputFields']); + }, + ]); + } + + /** + * @param array $typeIntrospection + */ + private function buildFieldDefMap(array $typeIntrospection) + { + if (! array_key_exists('fields', $typeIntrospection)) { + throw new InvariantViolation('Introspection result missing fields: ' . json_encode($typeIntrospection) . '.'); + } + + return Utils::keyValMap( + $typeIntrospection['fields'], + static function (array $fieldIntrospection) : string { + return $fieldIntrospection['name']; + }, + function (array $fieldIntrospection) { + if (! array_key_exists('args', $fieldIntrospection)) { + throw new InvariantViolation('Introspection result missing field args: ' . json_encode($fieldIntrospection) . '.'); + } + + return [ + 'description' => $fieldIntrospection['description'], + 'deprecationReason' => $fieldIntrospection['deprecationReason'], + 'type' => $this->getOutputType($fieldIntrospection['type']), + 'args' => $this->buildInputValueDefMap($fieldIntrospection['args']), + ]; + } + ); + } + + /** + * @param array> $inputValueIntrospections + * + * @return array> + */ + private function buildInputValueDefMap(array $inputValueIntrospections) : array + { + return Utils::keyValMap( + $inputValueIntrospections, + static function (array $inputValue) : string { + return $inputValue['name']; + }, + [$this, 'buildInputValue'] + ); + } + + /** + * @param array $inputValueIntrospection + * + * @return array + */ + public function buildInputValue(array $inputValueIntrospection) : array + { + $type = $this->getInputType($inputValueIntrospection['type']); + + $inputValue = [ + 'description' => $inputValueIntrospection['description'], + 'type' => $type, + ]; + + if (isset($inputValueIntrospection['defaultValue'])) { + $inputValue['defaultValue'] = AST::valueFromAST( + Parser::parseValue($inputValueIntrospection['defaultValue']), + $type + ); + } + + return $inputValue; + } + + /** + * @param array $directive + */ + public function buildDirective(array $directive) : Directive + { + if (! array_key_exists('args', $directive)) { + throw new InvariantViolation('Introspection result missing directive args: ' . json_encode($directive) . '.'); + } + if (! array_key_exists('locations', $directive)) { + throw new InvariantViolation('Introspection result missing directive locations: ' . json_encode($directive) . '.'); + } + + return new Directive([ + 'name' => $directive['name'], + 'description' => $directive['description'], + 'locations' => $directive['locations'], + 'args' => $this->buildInputValueDefMap($directive['args']), + ]); + } +} diff --git a/src/Utils/Utils.php b/src/Utils/Utils.php index 695591bb6..863c3ab62 100644 --- a/src/Utils/Utils.php +++ b/src/Utils/Utils.php @@ -272,9 +272,9 @@ public static function groupBy($traversable, callable $keyFn) } /** - * @param mixed[]|Traversable $traversable + * @param iterable $traversable * - * @return mixed[][] + * @return array */ public static function keyValMap($traversable, callable $keyFn, callable $valFn) { diff --git a/tests/Utils/BuildClientSchemaTest.php b/tests/Utils/BuildClientSchemaTest.php new file mode 100644 index 000000000..814f69dd7 --- /dev/null +++ b/tests/Utils/BuildClientSchemaTest.php @@ -0,0 +1,1082 @@ +> + */ + protected static function introspectionFromSDL(string $sdl) : array + { + $schema = BuildSchema::build($sdl); + + return Introspection::fromSchema($schema); + } + + protected static function clientSchemaFromSDL(string $sdl) : Schema + { + $introspection = self::introspectionFromSDL($sdl); + + return BuildClientSchema::build($introspection); + } + + // describe('Type System: build schema from introspection', () => { + + /** + * @see it('builds a simple schema', () => { + */ + public function testBuildsASimpleSchema() : void + { + self::assertCycleIntrospection(' + schema { + query: Simple + } + + """This is a simple type""" + type Simple { + """This is a string field""" + string: String + } + '); + } + + /** + * it('builds a schema without the query type', () => { + */ + public function testBuildsASchemaWithoutTheQueryType() : void + { + $sdl = <<getQueryType()); + self::assertSame($sdl, SchemaPrinter::doPrint($clientSchema)); + } + + /** + * it('builds a simple schema with all operation types', () => { + */ + public function testBuildsASimpleSchemaWithAllOperationTypes() : void + { + self::assertCycleIntrospection(' + schema { + query: QueryType + mutation: MutationType + subscription: SubscriptionType + } + + """This is a simple mutation type""" + type MutationType { + """Set the string field""" + string: String + } + + """This is a simple query type""" + type QueryType { + """This is a string field""" + string: String + } + + """This is a simple subscription type""" + type SubscriptionType { + """This is a string field""" + string: String + } + '); + } + + /** + * it('uses built-in scalars when possible', () => { + */ + public function testUsesBuiltInScalarsWhenPossible() : void + { + $sdl = ' + scalar CustomScalar + + type Query { + int: Int + float: Float + string: String + boolean: Boolean + id: ID + custom: CustomScalar + } + '; + + self::assertCycleIntrospection($sdl); + + $schema = BuildSchema::build($sdl); + $introspection = Introspection::fromSchema($schema); + $clientSchema = BuildClientSchema::build($introspection); + + // Built-ins are used + self::assertSame(Type::int(), $clientSchema->getType('Int')); + self::assertSame(Type::float(), $clientSchema->getType('Float')); + self::assertSame(Type::string(), $clientSchema->getType('String')); + self::assertSame(Type::boolean(), $clientSchema->getType('Boolean')); + self::assertSame(Type::id(), $clientSchema->getType('ID')); + + // Custom are built + self::assertNotSame( + $schema->getType('CustomScalar'), + $clientSchema->getType('CustomScalar') + ); + } + + /** + * it('includes standard types only if they are used', () => { + */ + public function testIncludesStandardTypesOnlyIfTheyAreUsed() : void + { + self::markTestSkipped('Introspection currently does not follow the reference implementation.'); + $clientSchema = self::clientSchemaFromSDL(' + type Query { + foo: String + } + '); + + self::assertNull($clientSchema->getType('Int')); + } + + /** + * it('builds a schema with a recursive type reference', () => { + */ + public function testBuildsASchemaWithARecursiveTypeReference() : void + { + self::assertCycleIntrospection(' + schema { + query: Recur + } + + type Recur { + recur: Recur + } + '); + } + + /** + * it('builds a schema with a circular type reference', () => { + */ + public function testBuildsASchemaWithACircularTypeReference() : void + { + self::assertCycleIntrospection(' + type Dog { + bestFriend: Human + } + + type Human { + bestFriend: Dog + } + + type Query { + dog: Dog + human: Human + } + '); + } + + /** + * it('builds a schema with an interface', () => { + */ + public function testBuildsASchemaWithAnInterface() : void + { + self::assertCycleIntrospection(' + type Dog implements Friendly { + bestFriend: Friendly + } + + interface Friendly { + """The best friend of this friendly thing""" + bestFriend: Friendly + } + + type Human implements Friendly { + bestFriend: Friendly + } + + type Query { + friendly: Friendly + } + '); + } + + /** + * it('builds a schema with an interface hierarchy', () => { + */ + public function testBuildsASchemaWithAnInterfaceHierarchy() : void + { + self::markTestSkipped('Will work only once intermediate interfaces are possible'); + self::assertCycleIntrospection(' + type Dog implements Friendly & Named { + bestFriend: Friendly + name: String + } + + interface Friendly implements Named { + """The best friend of this friendly thing""" + bestFriend: Friendly + name: String + } + + type Human implements Friendly & Named { + bestFriend: Friendly + name: String + } + + interface Named { + name: String + } + + type Query { + friendly: Friendly + } + '); + } + + /** + * it('builds a schema with an implicit interface', () => { + */ + public function testBuildsASchemaWithAnImplicitInterface() : void + { + self::assertCycleIntrospection(' + type Dog implements Friendly { + bestFriend: Friendly + } + + interface Friendly { + """The best friend of this friendly thing""" + bestFriend: Friendly + } + + type Query { + dog: Dog + } + '); + } + + /** + * it('builds a schema with a union', () => { + */ + public function testBuildsASchemaWithAUnion() : void + { + self::assertCycleIntrospection(' + type Dog { + bestFriend: Friendly + } + + union Friendly = Dog | Human + + type Human { + bestFriend: Friendly + } + + type Query { + friendly: Friendly + } + '); + } + + /** + * it('builds a schema with complex field values', () => { + */ + public function testBuildsASchemaWithComplexFieldValues() : void + { + self::assertCycleIntrospection(' + type Query { + string: String + listOfString: [String] + nonNullString: String! + nonNullListOfString: [String]! + nonNullListOfNonNullString: [String!]! + } + '); + } + + /** + * it('builds a schema with field arguments', () => { + */ + public function testBuildsASchemaWithFieldArguments() : void + { + self::assertCycleIntrospection(' + type Query { + """A field with a single arg""" + one( + """This is an int arg""" + intArg: Int + ): String + + """A field with a two args""" + two( + """This is an list of int arg""" + listArg: [Int] + + """This is a required arg""" + requiredArg: Boolean! + ): String + } + '); + } + + /** + * it('builds a schema with default value on custom scalar field', () => { + */ + public function testBuildsASchemaWithDefaultValueOnCustomScalarField() : void + { + self::assertCycleIntrospection(' + scalar CustomScalar + + type Query { + testField(testArg: CustomScalar = "default"): String + } + '); + } + + /** + * it('builds a schema with an enum', () => { + */ + public function testBuildsASchemaWithAnEnum() : void + { + $foodEnum = new EnumType([ + 'name' => 'Food', + 'description' => 'Varieties of food stuffs', + 'values' => [ + 'VEGETABLES' => [ + 'description' => 'Foods that are vegetables', + 'value' => 1, + ], + 'FRUITS' => ['value' => 2], + 'OILS' => [ + 'value' => 3, + 'deprecationReason' => 'Too fatty', + ], + ], + ]); + $schema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'EnumFields', + 'fields' => [ + 'food' => [ + 'description' => 'Repeats the arg you give it', + 'type' => $foodEnum, + 'args' => [ + 'kind' => [ + 'description' => 'what kind of food?', + 'type' => $foodEnum, + ], + ], + ], + ], + ]), + ]); + + $introspection = Introspection::fromSchema($schema); + $clientSchema = BuildClientSchema::build($introspection); + + $introspectionFromClientSchema = Introspection::fromSchema($clientSchema); + self::assertSame($introspection, $introspectionFromClientSchema); + + /** @var EnumType $clientFoodEnum */ + $clientFoodEnum = $clientSchema->getType('Food'); + self::assertInstanceOf(EnumType::class, $clientFoodEnum); + + self::assertCount(3, $clientFoodEnum->getValues()); + + $vegetables = $clientFoodEnum->getValue('VEGETABLES'); + + // Client types do not get server-only values, so `value` mirrors `name`, + // rather than using the integers defined in the "server" schema. + self::assertSame('VEGETABLES', $vegetables->value); + self::assertSame('Foods that are vegetables', $vegetables->description); + self::assertFalse($vegetables->isDeprecated()); + self::assertNull($vegetables->deprecationReason); + self::assertNull($vegetables->astNode); + + $fruits = $clientFoodEnum->getValue('FRUITS'); + self::assertNull($fruits->description); + + $oils = $clientFoodEnum->getValue('OILS'); + self::assertTrue($oils->isDeprecated()); + self::assertSame('Too fatty', $oils->deprecationReason); + } + + /** + * it('builds a schema with an input object', () => { + */ + public function testBuildsASchemaWithAnInputObject() : void + { + self::assertCycleIntrospection(' + """An input address""" + input Address { + """What street is this address?""" + street: String! + + """The city the address is within?""" + city: String! + + """The country (blank will assume USA).""" + country: String = "USA" + } + + type Query { + """Get a geocode from an address""" + geocode( + """The address to lookup""" + address: Address + ): String + } + '); + } + + /** + * it('builds a schema with field arguments with default values', () => { + */ + public function testBuildsASchemaWithFieldArgumentsWithDefaultValues() : void + { + self::assertCycleIntrospection(' + input Geo { + lat: Float + lon: Float + } + + type Query { + defaultInt(intArg: Int = 30): String + defaultList(listArg: [Int] = [1, 2, 3]): String + defaultObject(objArg: Geo = {lat: 37.485, lon: -122.148}): String + defaultNull(intArg: Int = null): String + noDefault(intArg: Int): String + } + '); + } + + /** + * it('builds a schema with custom directives', () => { + */ + public function testBuildsASchemaWithCustomDirectives() : void + { + self::assertCycleIntrospection(' + """This is a custom directive""" + directive @customDirective on FIELD + + type Query { + string: String + } + '); + } + + /** + * it('builds a schema without directives', () => { + */ + public function testBuildsASchemaWithoutDirectives() : void + { + $sdl = <<getDirectives()); + self::assertSame([], $clientSchema->getDirectives()); + self::assertSame($sdl, SchemaPrinter::doPrint($clientSchema)); + } + + /** + * it('builds a schema aware of deprecation', () => { + */ + public function testBuildsASchemaAwareOfDeprecation() : void + { + self::assertCycleIntrospection(' + enum Color { + """So rosy""" + RED + + """So grassy""" + GREEN + + """So calming""" + BLUE + + """So sickening""" + MAUVE @deprecated(reason: "No longer in fashion") + } + + type Query { + """This is a shiny string field""" + shinyString: String + + """This is a deprecated string field""" + deprecatedString: String @deprecated(reason: "Use shinyString") + color: Color + } + '); + } + + /** + * it('builds a schema with empty deprecation reasons', () => { + */ + public function testBuildsASchemaWithEmptyDeprecationReasons() : void + { + self::assertCycleIntrospection(' + type Query { + someField: String @deprecated(reason: "") + } + + enum SomeEnum { + SOME_VALUE @deprecated(reason: "") + } + '); + } + + /** + * it('can use client schema for limited execution', () => { + */ + public function testUseClientSchemaForLimitedExecution() : void + { + $schema = BuildSchema::build(' + scalar CustomScalar + + type Query { + foo(custom1: CustomScalar, custom2: CustomScalar): String + } + '); + + $introspection = Introspection::fromSchema($schema); + $clientSchema = BuildClientSchema::build($introspection); + + $result = GraphQL::executeQuery( + $clientSchema, + 'query Limited($v: CustomScalar) { foo(custom1: 123, custom2: $v) }', + ['foo' => 'bar', 'unused' => 'value'], + null, + ['v' => 'baz'] + ); + + self::assertSame(['foo' => 'bar'], $result->data); + } + + // describe('throws when given invalid introspection', () => { + + /** + * Construct a default dummy schema that is used in the following tests. + */ + protected static function dummySchema() : Schema + { + return BuildSchema::build(' + type Query { + foo(bar: String): String + } + + interface SomeInterface { + foo: String + } + + union SomeUnion = Query + + enum SomeEnum { FOO } + + input SomeInputObject { + foo: String + } + + directive @SomeDirective on QUERY + '); + } + + /** + * it('throws when introspection is missing __schema property', () => { + */ + public function testThrowsWhenIntrospectionIsMissingSchemaProperty() : void + { + $this->expectExceptionMessage( + 'Invalid or incomplete introspection result. Ensure that you are passing "data" property of introspection response and no "errors" was returned alongside: [].' + ); + BuildClientSchema::build([]); + } + + /** + * it('throws when referenced unknown type', () => { + */ + public function testThrowsWhenReferencedUnknownType() : void + { + $introspection = Introspection::fromSchema(self::dummySchema()); + + $introspection['__schema']['types'] = array_filter( + $introspection['__schema']['types'], + static function (array $type) : bool { + return $type['name'] !== 'Query'; + } + ); + + $this->expectExceptionMessage( + 'Invalid or incomplete schema, unknown type: Query. Ensure that a full introspection query is used in order to build a client schema.' + ); + BuildClientSchema::build($introspection); + } + + /** + * it('throws when missing definition for one of the standard scalars', () => { + */ + public function testThrowsWhenMissingDefinitionForOneOfTheStandardScalars() : void + { + $schema = BuildSchema::build(' + type Query { + foo: Float + } + '); + $introspection = Introspection::fromSchema($schema); + + $introspection['__schema']['types'] = array_filter( + $introspection['__schema']['types'], + static function (array $type) : bool { + return $type['name'] !== 'Float'; + } + ); + + $this->expectExceptionMessage( + 'Invalid or incomplete schema, unknown type: Float. Ensure that a full introspection query is used in order to build a client schema.' + ); + BuildClientSchema::build($introspection); + } + + /** + * it('throws when type reference is missing name', () => { + */ + public function testThrowsWhenTypeReferenceIsMissingName() : void + { + $introspection = Introspection::fromSchema(self::dummySchema()); + + self::assertNotEmpty($introspection['__schema']['queryType']['name']); + + unset($introspection['__schema']['queryType']['name']); + + $this->expectExceptionMessage('Unknown type reference: [].'); + BuildClientSchema::build($introspection); + } + + /** + * it('throws when missing kind', () => { + */ + public function testThrowsWhenMissingKind() : void + { + $introspection = Introspection::fromSchema(self::dummySchema()); + $queryTypeIntrospection = null; + foreach ($introspection['__schema']['types'] as &$type) { + if ($type['name'] !== 'Query') { + continue; + } + + $queryTypeIntrospection = &$type; + } + + self::assertArrayHasKey('kind', $queryTypeIntrospection); + + unset($queryTypeIntrospection['kind']); + + $this->expectExceptionMessageRegExp( + '/Invalid or incomplete introspection result. Ensure that a full introspection query is used in order to build a client schema: {"name":"Query",.*}\./' + ); + BuildClientSchema::build($introspection); + } + + /** + * it('throws when missing interfaces', () => { + */ + public function testThrowsWhenMissingInterfaces() : void + { + $introspection = Introspection::fromSchema(self::dummySchema()); + $queryTypeIntrospection = null; + foreach ($introspection['__schema']['types'] as &$type) { + if ($type['name'] !== 'Query') { + continue; + } + + $queryTypeIntrospection = &$type; + } + + self::assertArrayHasKey('interfaces', $queryTypeIntrospection); + + unset($queryTypeIntrospection['interfaces']); + + $this->expectExceptionMessageRegExp( + '/Introspection result missing interfaces: {"kind":"OBJECT","name":"Query",.*}\./' + ); + BuildClientSchema::build($introspection); + } + + /** + * it('Legacy support for interfaces with null as interfaces field', () => { + */ + public function testLegacySupportForInterfacesWithNullAsInterfacesField() : void + { + $dummySchema = self::dummySchema(); + $introspection = Introspection::fromSchema($dummySchema); + $queryTypeIntrospection = null; + foreach ($introspection['__schema']['types'] as &$type) { + if ($type['name'] !== 'Query') { + continue; + } + + $queryTypeIntrospection = &$type; + } + + self::assertArrayHasKey('interfaces', $queryTypeIntrospection); + + $queryTypeIntrospection['interfaces'] = null; + + $clientSchema = BuildClientSchema::build($introspection); + self::assertSame( + SchemaPrinter::doPrint($dummySchema), + SchemaPrinter::doPrint($clientSchema) + ); + } + + /** + * it('throws when missing fields', () => { + */ + public function testThrowsWhenMissingFields() : void + { + $introspection = Introspection::fromSchema(self::dummySchema()); + $queryTypeIntrospection = null; + foreach ($introspection['__schema']['types'] as &$type) { + if ($type['name'] !== 'Query') { + continue; + } + + $queryTypeIntrospection = &$type; + } + + self::assertArrayHasKey('fields', $queryTypeIntrospection); + + unset($queryTypeIntrospection['fields']); + + $this->expectExceptionMessageRegExp( + '/Introspection result missing fields: {"kind":"OBJECT","name":"Query",.*}\./' + ); + BuildClientSchema::build($introspection); + } + + /** + * it('throws when missing field args', () => { + */ + public function testThrowsWhenMissingFieldArgs() : void + { + $introspection = Introspection::fromSchema(self::dummySchema()); + $queryTypeIntrospection = null; + foreach ($introspection['__schema']['types'] as &$type) { + if ($type['name'] !== 'Query') { + continue; + } + + $queryTypeIntrospection = &$type; + } + + $firstField = &$queryTypeIntrospection['fields'][0]; + self::assertArrayHasKey('args', $firstField); + + unset($firstField['args']); + + $this->expectExceptionMessageRegExp( + '/Introspection result missing field args: {"name":"foo",.*}\./' + ); + BuildClientSchema::build($introspection); + } + + /** + * it('throws when output type is used as an arg type', () => { + */ + public function testThrowsWhenOutputTypeIsUsedAsAnArgType() : void + { + $introspection = Introspection::fromSchema(self::dummySchema()); + $queryTypeIntrospection = null; + foreach ($introspection['__schema']['types'] as &$type) { + if ($type['name'] !== 'Query') { + continue; + } + + $queryTypeIntrospection = &$type; + } + + $firstArgType = &$queryTypeIntrospection['fields'][0]['args'][0]['type']; + self::assertArrayHasKey('name', $firstArgType); + + $firstArgType['name'] = 'SomeUnion'; + + $this->expectExceptionMessage( + 'Introspection must provide input type for arguments, but received: "SomeUnion".' + ); + BuildClientSchema::build($introspection); + } + + /** + * it('throws when input type is used as a field type', () => { + */ + public function testThrowsWhenInputTypeIsUsedAsAFieldType() : void + { + $introspection = Introspection::fromSchema(self::dummySchema()); + $queryTypeIntrospection = null; + foreach ($introspection['__schema']['types'] as &$type) { + if ($type['name'] !== 'Query') { + continue; + } + + $queryTypeIntrospection = &$type; + } + + $firstFieldType = &$queryTypeIntrospection['fields'][0]['type']; + self::assertArrayHasKey('name', $firstFieldType); + + $firstFieldType['name'] = 'SomeInputObject'; + + $this->expectExceptionMessage( + 'Introspection must provide output type for fields, but received: "SomeInputObject".' + ); + BuildClientSchema::build($introspection); + } + + /** + * it('throws when missing possibleTypes', () => { + */ + public function testThrowsWhenMissingPossibleTypes() : void + { + $introspection = Introspection::fromSchema(self::dummySchema()); + $someUnionIntrospection = null; + foreach ($introspection['__schema']['types'] as &$type) { + if ($type['name'] !== 'SomeUnion') { + continue; + } + + $someUnionIntrospection = &$type; + } + + self::assertArrayHasKey('possibleTypes', $someUnionIntrospection); + + unset($someUnionIntrospection['possibleTypes']); + + $this->expectExceptionMessageRegExp( + '/Introspection result missing possibleTypes: {"kind":"UNION","name":"SomeUnion",.*}\./' + ); + BuildClientSchema::build($introspection); + } + + /** + * it('throws when missing enumValues', () => { + */ + public function testThrowsWhenMissingEnumValues() : void + { + $introspection = Introspection::fromSchema(self::dummySchema()); + $someEnumIntrospection = null; + foreach ($introspection['__schema']['types'] as &$type) { + if ($type['name'] !== 'SomeEnum') { + continue; + } + + $someEnumIntrospection = &$type; + } + + self::assertArrayHasKey('enumValues', $someEnumIntrospection); + + unset($someEnumIntrospection['enumValues']); + + $this->expectExceptionMessageRegExp( + '/Introspection result missing enumValues: {"kind":"ENUM","name":"SomeEnum",.*}\./' + ); + BuildClientSchema::build($introspection); + } + + /** + * it('throws when missing inputFields', () => { + */ + public function testThrowsWhenMissingInputFields() : void + { + $introspection = Introspection::fromSchema(self::dummySchema()); + $someInputObjectIntrospection = null; + foreach ($introspection['__schema']['types'] as &$type) { + if ($type['name'] !== 'SomeInputObject') { + continue; + } + + $someInputObjectIntrospection = &$type; + } + + self::assertArrayHasKey('inputFields', $someInputObjectIntrospection); + + unset($someInputObjectIntrospection['inputFields']); + + $this->expectExceptionMessageRegExp( + '/Introspection result missing inputFields: {"kind":"INPUT_OBJECT","name":"SomeInputObject",.*}\./' + ); + BuildClientSchema::build($introspection); + } + + /** + * it('throws when missing directive locations', () => { + */ + public function testThrowsWhenMissingDirectiveLocations() : void + { + $introspection = Introspection::fromSchema(self::dummySchema()); + + $someDirectiveIntrospection = &$introspection['__schema']['directives'][0]; + self::assertSame('SomeDirective', $someDirectiveIntrospection['name']); + self::assertSame(['QUERY'], $someDirectiveIntrospection['locations']); + + unset($someDirectiveIntrospection['locations']); + + $this->expectExceptionMessageRegExp( + '/Introspection result missing directive locations: {"name":"SomeDirective",.*}\./' + ); + BuildClientSchema::build($introspection); + } + + /** + * it('throws when missing directive args', () => { + */ + public function testThrowsWhenMissingDirectiveArgs() : void + { + $introspection = Introspection::fromSchema(self::dummySchema()); + + $someDirectiveIntrospection = &$introspection['__schema']['directives'][0]; + self::assertSame('SomeDirective', $someDirectiveIntrospection['name']); + self::assertSame([], $someDirectiveIntrospection['args']); + + unset($someDirectiveIntrospection['args']); + + $this->expectExceptionMessageRegExp( + '/Introspection result missing directive args: {"name":"SomeDirective",.*}\./' + ); + BuildClientSchema::build($introspection); + } + + // describe('very deep decorators are not supported', () => { + + /** + * it('fails on very deep (> 7 levels) lists', () => { + */ + public function testFailsOnVeryDeepListsWithMoreThan7Levels() : void + { + $schema = BuildSchema::build(' + type Query { + foo: [[[[[[[[String]]]]]]]] + } + '); + $introspection = Introspection::fromSchema($schema); + + $this->expectExceptionMessage( + 'Decorated type deeper than introspection query.' + ); + BuildClientSchema::build($introspection); + } + + /** + * it('fails on very deep (> 7 levels) non-null', () => { + */ + public function testFailsOnVeryDeepNonNullWithMoreThan7Levels() : void + { + $schema = BuildSchema::build(' + type Query { + foo: [[[[String!]!]!]!] + } + '); + $introspection = Introspection::fromSchema($schema); + + $this->expectExceptionMessage( + 'Decorated type deeper than introspection query.' + ); + BuildClientSchema::build($introspection); + } + + /** + * it('succeeds on deep (<= 7 levels) types', () => { + */ + public function testSucceedsOnDeepTypesWithMoreThanOrEqualTo7Levels() : void + { + // e.g., fully non-null 3D matrix + self::assertCycleIntrospection(' + type Query { + foo: [[[String!]!]!]! + } + '); + } + + // describe('prevents infinite recursion on invalid introspection', () => { + + /** + * it('recursive interfaces', () => { + */ + public function testRecursiveInterfaces() : void + { + $sdl = ' + type Query { + foo: Foo + } + + type Foo implements Foo { + foo: String + } + '; + $schema = BuildSchema::build($sdl); + $introspection = Introspection::fromSchema($schema); + + $this->expectExceptionMessage('Expected Foo to be a GraphQL Interface type.'); + BuildClientSchema::build($introspection); + } + + /** + * it('recursive union', () => { + */ + public function testRecursiveUnion() : void + { + $sdl = ' + type Query { + foo: Foo + } + + union Foo = Foo + '; + $schema = BuildSchema::build($sdl); + $introspection = Introspection::fromSchema($schema); + + $this->expectExceptionMessage('Expected Foo to be a GraphQL Object type.'); + BuildClientSchema::build($introspection); + } +} From c6da1e2fd596e88629d0d38c5c4b0676c5f5cb9d Mon Sep 17 00:00:00 2001 From: Samuel Date: Wed, 26 Feb 2020 10:52:59 +0100 Subject: [PATCH 144/256] Added Altair GraphQL Client (#610) --- docs/complementary-tools.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/complementary-tools.md b/docs/complementary-tools.md index e343b1b09..5dac38789 100644 --- a/docs/complementary-tools.md +++ b/docs/complementary-tools.md @@ -24,3 +24,4 @@ * [ChromeiQL](https://chrome.google.com/webstore/detail/chromeiql/fkkiamalmpiidkljmicmjfbieiclmeij) or [GraphiQL Feen](https://chrome.google.com/webstore/detail/graphiql-feen/mcbfdonlkfpbfdpimkjilhdneikhfklp) – GraphiQL as Google Chrome extension +* [Altair GraphQL Client](https://altair.sirmuel.design/) - A beautiful feature-rich GraphQL Client for all platforms From 458640b822de5bc8ef2021c74a3b1e67c04e08bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Podlipsk=C3=BD?= Date: Wed, 26 Feb 2020 11:27:26 +0100 Subject: [PATCH 145/256] Fix CS (#624) --- tests/Executor/ExecutorSchemaTest.php | 1 + tests/Executor/ResolveTest.php | 1 + tests/StarWarsIntrospectionTest.php | 1 + tests/StarWarsValidationTest.php | 1 + tests/Type/ScalarSerializationTest.php | 1 + tests/Utils/AssertValidNameTest.php | 1 + tests/Utils/AstFromValueUntypedTest.php | 1 + tests/Utils/IsValidLiteralValueTest.php | 1 + tests/Utils/QuotedOrListTest.php | 1 + tests/Utils/SchemaPrinterTest.php | 1 + tests/Utils/SuggestionListTest.php | 1 + tests/Validator/DisableIntrospectionTest.php | 1 + tests/Validator/ExecutableDefinitionsTest.php | 1 + tests/Validator/FieldsOnCorrectTypeTest.php | 1 + tests/Validator/FragmentsOnCompositeTypesTest.php | 1 + tests/Validator/KnownArgumentNamesTest.php | 1 + tests/Validator/KnownFragmentNamesTest.php | 1 + tests/Validator/KnownTypeNamesTest.php | 1 + tests/Validator/LoneAnonymousOperationTest.php | 1 + tests/Validator/NoFragmentCyclesTest.php | 1 + tests/Validator/NoUndefinedVariablesTest.php | 1 + tests/Validator/NoUnusedFragmentsTest.php | 1 + tests/Validator/NoUnusedVariablesTest.php | 1 + tests/Validator/OverlappingFieldsCanBeMergedTest.php | 1 + tests/Validator/PossibleFragmentSpreadsTest.php | 1 + tests/Validator/ProvidedRequiredArgumentsTest.php | 1 + tests/Validator/ScalarLeafsTest.php | 1 + tests/Validator/UniqueArgumentNamesTest.php | 1 + tests/Validator/UniqueFragmentNamesTest.php | 1 + tests/Validator/UniqueInputFieldNamesTest.php | 1 + tests/Validator/UniqueOperationNamesTest.php | 1 + tests/Validator/UniqueVariableNamesTest.php | 1 + tests/Validator/ValidationTest.php | 1 + tests/Validator/VariablesAreInputTypesTest.php | 1 + tests/Validator/VariablesInAllowedPositionTest.php | 1 + 35 files changed, 35 insertions(+) diff --git a/tests/Executor/ExecutorSchemaTest.php b/tests/Executor/ExecutorSchemaTest.php index 42b5dc2e6..494d49301 100644 --- a/tests/Executor/ExecutorSchemaTest.php +++ b/tests/Executor/ExecutorSchemaTest.php @@ -16,6 +16,7 @@ class ExecutorSchemaTest extends TestCase { // Execute: Handles execution with a complex schema + /** * @see it('executes using a schema') */ diff --git a/tests/Executor/ResolveTest.php b/tests/Executor/ResolveTest.php index 0baddef0f..8fe4ab7ca 100644 --- a/tests/Executor/ResolveTest.php +++ b/tests/Executor/ResolveTest.php @@ -16,6 +16,7 @@ class ResolveTest extends TestCase { // Execute: resolve function + /** * @see it('default function accesses properties') */ diff --git a/tests/StarWarsIntrospectionTest.php b/tests/StarWarsIntrospectionTest.php index 35da7daca..94c2b7e70 100644 --- a/tests/StarWarsIntrospectionTest.php +++ b/tests/StarWarsIntrospectionTest.php @@ -11,6 +11,7 @@ class StarWarsIntrospectionTest extends TestCase { // Star Wars Introspection Tests // Basic Introspection + /** * @see it('Allows querying the schema for types') */ diff --git a/tests/StarWarsValidationTest.php b/tests/StarWarsValidationTest.php index cdee2e317..7e736c136 100644 --- a/tests/StarWarsValidationTest.php +++ b/tests/StarWarsValidationTest.php @@ -12,6 +12,7 @@ class StarWarsValidationTest extends TestCase { // Star Wars Validation Tests // Basic Queries + /** * @see it('Validates a complex but valid query') */ diff --git a/tests/Type/ScalarSerializationTest.php b/tests/Type/ScalarSerializationTest.php index f5d9ec546..7f6734aff 100644 --- a/tests/Type/ScalarSerializationTest.php +++ b/tests/Type/ScalarSerializationTest.php @@ -16,6 +16,7 @@ class ScalarSerializationTest extends TestCase { // Type System: Scalar coercion + /** * @see it('serializes output as Int') */ diff --git a/tests/Utils/AssertValidNameTest.php b/tests/Utils/AssertValidNameTest.php index 0876acd80..d4dc80e1a 100644 --- a/tests/Utils/AssertValidNameTest.php +++ b/tests/Utils/AssertValidNameTest.php @@ -12,6 +12,7 @@ class AssertValidNameTest extends TestCase { // Describe: assertValidName() + /** * @see it('throws for use of leading double underscores') */ diff --git a/tests/Utils/AstFromValueUntypedTest.php b/tests/Utils/AstFromValueUntypedTest.php index 2a1049037..bfd5a8cd8 100644 --- a/tests/Utils/AstFromValueUntypedTest.php +++ b/tests/Utils/AstFromValueUntypedTest.php @@ -11,6 +11,7 @@ class AstFromValueUntypedTest extends TestCase { // Describe: valueFromASTUntyped + /** * @see it('parses simple values') */ diff --git a/tests/Utils/IsValidLiteralValueTest.php b/tests/Utils/IsValidLiteralValueTest.php index e97271247..d5f5491f3 100644 --- a/tests/Utils/IsValidLiteralValueTest.php +++ b/tests/Utils/IsValidLiteralValueTest.php @@ -13,6 +13,7 @@ class IsValidLiteralValueTest extends TestCase { // DESCRIBE: isValidLiteralValue + /** * @see it('Returns no errors for a valid value') */ diff --git a/tests/Utils/QuotedOrListTest.php b/tests/Utils/QuotedOrListTest.php index 53e8f69cb..9be1f6fe3 100644 --- a/tests/Utils/QuotedOrListTest.php +++ b/tests/Utils/QuotedOrListTest.php @@ -11,6 +11,7 @@ class QuotedOrListTest extends TestCase { // DESCRIBE: quotedOrList + /** * @see it('Does not accept an empty list') */ diff --git a/tests/Utils/SchemaPrinterTest.php b/tests/Utils/SchemaPrinterTest.php index 74072ff50..7131875f1 100644 --- a/tests/Utils/SchemaPrinterTest.php +++ b/tests/Utils/SchemaPrinterTest.php @@ -21,6 +21,7 @@ class SchemaPrinterTest extends TestCase { // Describe: Type System Printer + /** * @see it('Prints String Field') */ diff --git a/tests/Utils/SuggestionListTest.php b/tests/Utils/SuggestionListTest.php index ae72b2e61..642382f06 100644 --- a/tests/Utils/SuggestionListTest.php +++ b/tests/Utils/SuggestionListTest.php @@ -10,6 +10,7 @@ class SuggestionListTest extends TestCase { // DESCRIBE: suggestionList + /** * @see it('Returns results when input is empty') */ diff --git a/tests/Validator/DisableIntrospectionTest.php b/tests/Validator/DisableIntrospectionTest.php index f5d61b9eb..dddceabe1 100644 --- a/tests/Validator/DisableIntrospectionTest.php +++ b/tests/Validator/DisableIntrospectionTest.php @@ -11,6 +11,7 @@ class DisableIntrospectionTest extends ValidatorTestCase { // Validate: Disable Introspection + /** * @see it('fails if the query contains __schema') */ diff --git a/tests/Validator/ExecutableDefinitionsTest.php b/tests/Validator/ExecutableDefinitionsTest.php index 07517553f..524fbbde1 100644 --- a/tests/Validator/ExecutableDefinitionsTest.php +++ b/tests/Validator/ExecutableDefinitionsTest.php @@ -11,6 +11,7 @@ class ExecutableDefinitionsTest extends ValidatorTestCase { // Validate: Executable definitions + /** * @see it('with only operation') */ diff --git a/tests/Validator/FieldsOnCorrectTypeTest.php b/tests/Validator/FieldsOnCorrectTypeTest.php index 057ae8b2c..7daef09f5 100644 --- a/tests/Validator/FieldsOnCorrectTypeTest.php +++ b/tests/Validator/FieldsOnCorrectTypeTest.php @@ -11,6 +11,7 @@ class FieldsOnCorrectTypeTest extends ValidatorTestCase { // Validate: Fields on correct type + /** * @see it('Object field selection') */ diff --git a/tests/Validator/FragmentsOnCompositeTypesTest.php b/tests/Validator/FragmentsOnCompositeTypesTest.php index deec5c4a2..e8a77800a 100644 --- a/tests/Validator/FragmentsOnCompositeTypesTest.php +++ b/tests/Validator/FragmentsOnCompositeTypesTest.php @@ -11,6 +11,7 @@ class FragmentsOnCompositeTypesTest extends ValidatorTestCase { // Validate: Fragments on composite types + /** * @see it('object is valid fragment type') */ diff --git a/tests/Validator/KnownArgumentNamesTest.php b/tests/Validator/KnownArgumentNamesTest.php index 4c3c163f4..7cb034f45 100644 --- a/tests/Validator/KnownArgumentNamesTest.php +++ b/tests/Validator/KnownArgumentNamesTest.php @@ -11,6 +11,7 @@ class KnownArgumentNamesTest extends ValidatorTestCase { // Validate: Known argument names: + /** * @see it('single arg is known') */ diff --git a/tests/Validator/KnownFragmentNamesTest.php b/tests/Validator/KnownFragmentNamesTest.php index cc9ce862c..5e1f01092 100644 --- a/tests/Validator/KnownFragmentNamesTest.php +++ b/tests/Validator/KnownFragmentNamesTest.php @@ -11,6 +11,7 @@ class KnownFragmentNamesTest extends ValidatorTestCase { // Validate: Known fragment names + /** * @see it('known fragment names are valid') */ diff --git a/tests/Validator/KnownTypeNamesTest.php b/tests/Validator/KnownTypeNamesTest.php index 12834aa35..4c5a142eb 100644 --- a/tests/Validator/KnownTypeNamesTest.php +++ b/tests/Validator/KnownTypeNamesTest.php @@ -11,6 +11,7 @@ class KnownTypeNamesTest extends ValidatorTestCase { // Validate: Known type names + /** * @see it('known type names are valid') */ diff --git a/tests/Validator/LoneAnonymousOperationTest.php b/tests/Validator/LoneAnonymousOperationTest.php index cdd7983b0..5f081fe48 100644 --- a/tests/Validator/LoneAnonymousOperationTest.php +++ b/tests/Validator/LoneAnonymousOperationTest.php @@ -11,6 +11,7 @@ class LoneAnonymousOperationTest extends ValidatorTestCase { // Validate: Anonymous operation must be alone + /** * @see it('no operations') */ diff --git a/tests/Validator/NoFragmentCyclesTest.php b/tests/Validator/NoFragmentCyclesTest.php index f0a2a634b..49403d4ef 100644 --- a/tests/Validator/NoFragmentCyclesTest.php +++ b/tests/Validator/NoFragmentCyclesTest.php @@ -11,6 +11,7 @@ class NoFragmentCyclesTest extends ValidatorTestCase { // Validate: No circular fragment spreads + /** * @see it('single reference is valid') */ diff --git a/tests/Validator/NoUndefinedVariablesTest.php b/tests/Validator/NoUndefinedVariablesTest.php index ea2e25e1f..318e9da52 100644 --- a/tests/Validator/NoUndefinedVariablesTest.php +++ b/tests/Validator/NoUndefinedVariablesTest.php @@ -11,6 +11,7 @@ class NoUndefinedVariablesTest extends ValidatorTestCase { // Validate: No undefined variables + /** * @see it('all variables defined') */ diff --git a/tests/Validator/NoUnusedFragmentsTest.php b/tests/Validator/NoUnusedFragmentsTest.php index 9d794a818..eccc6b193 100644 --- a/tests/Validator/NoUnusedFragmentsTest.php +++ b/tests/Validator/NoUnusedFragmentsTest.php @@ -11,6 +11,7 @@ class NoUnusedFragmentsTest extends ValidatorTestCase { // Validate: No unused fragments + /** * @see it('all fragment names are used') */ diff --git a/tests/Validator/NoUnusedVariablesTest.php b/tests/Validator/NoUnusedVariablesTest.php index 7dc7d401b..846ecd2ec 100644 --- a/tests/Validator/NoUnusedVariablesTest.php +++ b/tests/Validator/NoUnusedVariablesTest.php @@ -11,6 +11,7 @@ class NoUnusedVariablesTest extends ValidatorTestCase { // Validate: No unused variables + /** * @see it('uses all variables') */ diff --git a/tests/Validator/OverlappingFieldsCanBeMergedTest.php b/tests/Validator/OverlappingFieldsCanBeMergedTest.php index 30291fa7d..e66ee71f1 100644 --- a/tests/Validator/OverlappingFieldsCanBeMergedTest.php +++ b/tests/Validator/OverlappingFieldsCanBeMergedTest.php @@ -15,6 +15,7 @@ class OverlappingFieldsCanBeMergedTest extends ValidatorTestCase { // Validate: Overlapping fields can be merged + /** * @see it('unique fields') */ diff --git a/tests/Validator/PossibleFragmentSpreadsTest.php b/tests/Validator/PossibleFragmentSpreadsTest.php index c4a8b293b..a8f6f8514 100644 --- a/tests/Validator/PossibleFragmentSpreadsTest.php +++ b/tests/Validator/PossibleFragmentSpreadsTest.php @@ -11,6 +11,7 @@ class PossibleFragmentSpreadsTest extends ValidatorTestCase { // Validate: Possible fragment spreads + /** * @see it('of the same object') */ diff --git a/tests/Validator/ProvidedRequiredArgumentsTest.php b/tests/Validator/ProvidedRequiredArgumentsTest.php index 7a5c2d759..bf1d98e34 100644 --- a/tests/Validator/ProvidedRequiredArgumentsTest.php +++ b/tests/Validator/ProvidedRequiredArgumentsTest.php @@ -11,6 +11,7 @@ class ProvidedRequiredArgumentsTest extends ValidatorTestCase { // Validate: Provided required arguments + /** * @see it('ignores unknown arguments') */ diff --git a/tests/Validator/ScalarLeafsTest.php b/tests/Validator/ScalarLeafsTest.php index c787be320..d7f2c54a2 100644 --- a/tests/Validator/ScalarLeafsTest.php +++ b/tests/Validator/ScalarLeafsTest.php @@ -11,6 +11,7 @@ class ScalarLeafsTest extends ValidatorTestCase { // Validate: Scalar leafs + /** * @see it('valid scalar selection') */ diff --git a/tests/Validator/UniqueArgumentNamesTest.php b/tests/Validator/UniqueArgumentNamesTest.php index 44b0ac99d..3629ab856 100644 --- a/tests/Validator/UniqueArgumentNamesTest.php +++ b/tests/Validator/UniqueArgumentNamesTest.php @@ -11,6 +11,7 @@ class UniqueArgumentNamesTest extends ValidatorTestCase { // Validate: Unique argument names + /** * @see it('no arguments on field') */ diff --git a/tests/Validator/UniqueFragmentNamesTest.php b/tests/Validator/UniqueFragmentNamesTest.php index b34b76f7b..abec62168 100644 --- a/tests/Validator/UniqueFragmentNamesTest.php +++ b/tests/Validator/UniqueFragmentNamesTest.php @@ -11,6 +11,7 @@ class UniqueFragmentNamesTest extends ValidatorTestCase { // Validate: Unique fragment names + /** * @see it('no fragments') */ diff --git a/tests/Validator/UniqueInputFieldNamesTest.php b/tests/Validator/UniqueInputFieldNamesTest.php index 81c35a025..985441589 100644 --- a/tests/Validator/UniqueInputFieldNamesTest.php +++ b/tests/Validator/UniqueInputFieldNamesTest.php @@ -11,6 +11,7 @@ class UniqueInputFieldNamesTest extends ValidatorTestCase { // Validate: Unique input field names + /** * @see it('input object with fields') */ diff --git a/tests/Validator/UniqueOperationNamesTest.php b/tests/Validator/UniqueOperationNamesTest.php index 3bc90b5a4..c8083247f 100644 --- a/tests/Validator/UniqueOperationNamesTest.php +++ b/tests/Validator/UniqueOperationNamesTest.php @@ -11,6 +11,7 @@ class UniqueOperationNamesTest extends ValidatorTestCase { // Validate: Unique operation names + /** * @see it('no operations') */ diff --git a/tests/Validator/UniqueVariableNamesTest.php b/tests/Validator/UniqueVariableNamesTest.php index f587efb38..b4dc60906 100644 --- a/tests/Validator/UniqueVariableNamesTest.php +++ b/tests/Validator/UniqueVariableNamesTest.php @@ -11,6 +11,7 @@ class UniqueVariableNamesTest extends ValidatorTestCase { // Validate: Unique variable names + /** * @see it('unique variable names') */ diff --git a/tests/Validator/ValidationTest.php b/tests/Validator/ValidationTest.php index e823cdc8d..428c342a5 100644 --- a/tests/Validator/ValidationTest.php +++ b/tests/Validator/ValidationTest.php @@ -7,6 +7,7 @@ class ValidationTest extends ValidatorTestCase { // Validate: Supports full validation + /** * @see it('validates queries') */ diff --git a/tests/Validator/VariablesAreInputTypesTest.php b/tests/Validator/VariablesAreInputTypesTest.php index 52643140b..bd010e2bf 100644 --- a/tests/Validator/VariablesAreInputTypesTest.php +++ b/tests/Validator/VariablesAreInputTypesTest.php @@ -11,6 +11,7 @@ class VariablesAreInputTypesTest extends ValidatorTestCase { // Validate: Variables are input types + /** * @see it('input types are valid') */ diff --git a/tests/Validator/VariablesInAllowedPositionTest.php b/tests/Validator/VariablesInAllowedPositionTest.php index 9f7cc2a24..94a3cde11 100644 --- a/tests/Validator/VariablesInAllowedPositionTest.php +++ b/tests/Validator/VariablesInAllowedPositionTest.php @@ -11,6 +11,7 @@ class VariablesInAllowedPositionTest extends ValidatorTestCase { // Validate: Variables are in allowed positions + /** * @see it('Boolean => Boolean') */ From 3227717d18fe297c180ef086f855ddb3b527c662 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Wed, 26 Feb 2020 12:06:56 +0100 Subject: [PATCH 146/256] Fix PHPStan --- phpstan-baseline.neon | 2 +- src/Utils/BuildClientSchema.php | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 19021a884..7116ea097 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -592,7 +592,7 @@ parameters: - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 2 + count: 1 path: src/Type/Schema.php - diff --git a/src/Utils/BuildClientSchema.php b/src/Utils/BuildClientSchema.php index c6542700d..57cef3284 100644 --- a/src/Utils/BuildClientSchema.php +++ b/src/Utils/BuildClientSchema.php @@ -97,7 +97,7 @@ public function buildSchema() : Schema static function (array $typeIntrospection) { return $typeIntrospection['name']; }, - function (array $typeIntrospection) { + function (array $typeIntrospection) : NamedType { return $this->buildType($typeIntrospection); } ); @@ -295,7 +295,7 @@ private function buildObjectDef(array $object) : ObjectType return new ObjectType([ 'name' => $object['name'], 'description' => $object['description'], - 'interfaces' => function () use ($object) { + 'interfaces' => function () use ($object) : array { return array_map( [$this, 'getInterfaceType'], // Legacy support for interfaces with null as interfaces field @@ -334,7 +334,7 @@ private function buildUnionDef(array $union) : UnionType return new UnionType([ 'name' => $union['name'], 'description' => $union['description'], - 'types' => function () use ($union) { + 'types' => function () use ($union) : array { return array_map( [$this, 'getObjectType'], $union['possibleTypes'] @@ -360,7 +360,7 @@ private function buildEnumDef(array $enum) : EnumType static function (array $enumValue) : string { return $enumValue['name']; }, - static function (array $enumValue) { + static function (array $enumValue) : array { return [ 'description' => $enumValue['description'], 'deprecationReason' => $enumValue['deprecationReason'], @@ -382,7 +382,7 @@ private function buildInputObjectDef(array $inputObject) : InputObjectType return new InputObjectType([ 'name' => $inputObject['name'], 'description' => $inputObject['description'], - 'fields' => function () use ($inputObject) { + 'fields' => function () use ($inputObject) : array { return $this->buildInputValueDefMap($inputObject['inputFields']); }, ]); @@ -402,7 +402,7 @@ private function buildFieldDefMap(array $typeIntrospection) static function (array $fieldIntrospection) : string { return $fieldIntrospection['name']; }, - function (array $fieldIntrospection) { + function (array $fieldIntrospection) : array { if (! array_key_exists('args', $fieldIntrospection)) { throw new InvariantViolation('Introspection result missing field args: ' . json_encode($fieldIntrospection) . '.'); } From 318018abcec6bfc9bdfe842de02551e7727ab60c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1chym=20Tou=C5=A1ek?= Date: Fri, 28 Feb 2020 18:11:23 +0100 Subject: [PATCH 147/256] Add AmpPromiseAdapter (#551) * Increase memory limit for phpstan * Add AmpPromiseAdapter --- composer.json | 1 + .../Promise/Adapter/AmpPromiseAdapter.php | 147 ++++++++++++++ .../Promise/AmpPromiseAdapterTest.php | 182 ++++++++++++++++++ 3 files changed, 330 insertions(+) create mode 100644 src/Executor/Promise/Adapter/AmpPromiseAdapter.php create mode 100644 tests/Executor/Promise/AmpPromiseAdapterTest.php diff --git a/composer.json b/composer.json index 92fec90ee..3d8e22f69 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,7 @@ "ext-mbstring": "*" }, "require-dev": { + "amphp/amp": "^2.3", "doctrine/coding-standard": "^6.0", "phpbench/phpbench": "^0.14", "phpstan/extension-installer": "^1.0", diff --git a/src/Executor/Promise/Adapter/AmpPromiseAdapter.php b/src/Executor/Promise/Adapter/AmpPromiseAdapter.php new file mode 100644 index 000000000..d1a4ca69b --- /dev/null +++ b/src/Executor/Promise/Adapter/AmpPromiseAdapter.php @@ -0,0 +1,147 @@ +resolve($value); + } elseif ($onRejected !== null) { + self::resolveWithCallable($deferred, $onRejected, $reason); + } else { + $deferred->fail($reason); + } + }; + + /** @var AmpPromise $adoptedPromise */ + $adoptedPromise = $promise->adoptedPromise; + $adoptedPromise->onResolve($onResolve); + + return new Promise($deferred->promise(), $this); + } + + /** + * @inheritdoc + */ + public function create(callable $resolver) : Promise + { + $deferred = new Deferred(); + + $resolver( + static function ($value) use ($deferred) : void { + $deferred->resolve($value); + }, + static function (Throwable $exception) use ($deferred) : void { + $deferred->fail($exception); + } + ); + + return new Promise($deferred->promise(), $this); + } + + /** + * @inheritdoc + */ + public function createFulfilled($value = null) : Promise + { + $promise = new Success($value); + + return new Promise($promise, $this); + } + + /** + * @inheritdoc + */ + public function createRejected($reason) : Promise + { + $promise = new Failure($reason); + + return new Promise($promise, $this); + } + + /** + * @inheritdoc + */ + public function all(array $promisesOrValues) : Promise + { + /** @var AmpPromise[] $promises */ + $promises = []; + foreach ($promisesOrValues as $key => $item) { + if ($item instanceof Promise) { + $promises[$key] = $item->adoptedPromise; + } elseif ($item instanceof AmpPromise) { + $promises[$key] = $item; + } + } + + $deferred = new Deferred(); + + $onResolve = static function (?Throwable $reason, ?array $values) use ($promisesOrValues, $deferred) : void { + if ($reason) { + $deferred->fail($reason); + + return; + } + + $deferred->resolve(array_replace($promisesOrValues, $values)); + }; + + all($promises)->onResolve($onResolve); + + return new Promise($deferred->promise(), $this); + } + + private static function resolveWithCallable(Deferred $deferred, callable $callback, $argument) : void + { + try { + $result = $callback($argument); + } catch (Throwable $exception) { + $deferred->fail($exception); + + return; + } + + if ($result instanceof Promise) { + $result = $result->adoptedPromise; + } + + $deferred->resolve($result); + } +} diff --git a/tests/Executor/Promise/AmpPromiseAdapterTest.php b/tests/Executor/Promise/AmpPromiseAdapterTest.php new file mode 100644 index 000000000..41a0f38db --- /dev/null +++ b/tests/Executor/Promise/AmpPromiseAdapterTest.php @@ -0,0 +1,182 @@ +isThenable(call(static function () { + yield from []; + })) + ); + self::assertTrue($ampAdapter->isThenable(new Success())); + self::assertTrue($ampAdapter->isThenable(new Failure(new Exception()))); + self::assertTrue($ampAdapter->isThenable(new Delayed(0))); + self::assertTrue( + $ampAdapter->isThenable(new LazyPromise(static function () { + })) + ); + self::assertFalse($ampAdapter->isThenable(false)); + self::assertFalse($ampAdapter->isThenable(true)); + self::assertFalse($ampAdapter->isThenable(1)); + self::assertFalse($ampAdapter->isThenable(0)); + self::assertFalse($ampAdapter->isThenable('test')); + self::assertFalse($ampAdapter->isThenable('')); + self::assertFalse($ampAdapter->isThenable([])); + self::assertFalse($ampAdapter->isThenable(new stdClass())); + } + + public function testConvertsReactPromisesToGraphQlOnes() : void + { + $ampAdapter = new AmpPromiseAdapter(); + $ampPromise = new Success(1); + + $promise = $ampAdapter->convertThenable($ampPromise); + + self::assertInstanceOf('GraphQL\Executor\Promise\Promise', $promise); + self::assertInstanceOf(Success::class, $promise->adoptedPromise); + } + + public function testThen() : void + { + $ampAdapter = new AmpPromiseAdapter(); + $ampPromise = new Success(1); + $promise = $ampAdapter->convertThenable($ampPromise); + + $result = null; + + $resultPromise = $ampAdapter->then( + $promise, + static function ($value) use (&$result) { + $result = $value; + } + ); + + self::assertSame(1, $result); + self::assertInstanceOf('GraphQL\Executor\Promise\Promise', $resultPromise); + self::assertInstanceOf(Promise::class, $resultPromise->adoptedPromise); + } + + public function testCreate() : void + { + $ampAdapter = new AmpPromiseAdapter(); + $resolvedPromise = $ampAdapter->create(static function ($resolve) { + $resolve(1); + }); + + self::assertInstanceOf('GraphQL\Executor\Promise\Promise', $resolvedPromise); + self::assertInstanceOf(Promise::class, $resolvedPromise->adoptedPromise); + + $result = null; + + $resolvedPromise->then(static function ($value) use (&$result) { + $result = $value; + }); + + self::assertSame(1, $result); + } + + public function testCreateFulfilled() : void + { + $ampAdapter = new AmpPromiseAdapter(); + $fulfilledPromise = $ampAdapter->createFulfilled(1); + + self::assertInstanceOf('GraphQL\Executor\Promise\Promise', $fulfilledPromise); + self::assertInstanceOf(Success::class, $fulfilledPromise->adoptedPromise); + + $result = null; + + $fulfilledPromise->then(static function ($value) use (&$result) { + $result = $value; + }); + + self::assertSame(1, $result); + } + + public function testCreateRejected() : void + { + $ampAdapter = new AmpPromiseAdapter(); + $rejectedPromise = $ampAdapter->createRejected(new Exception('I am a bad promise')); + + self::assertInstanceOf('GraphQL\Executor\Promise\Promise', $rejectedPromise); + self::assertInstanceOf(Failure::class, $rejectedPromise->adoptedPromise); + + $exception = null; + + $rejectedPromise->then( + null, + static function ($error) use (&$exception) { + $exception = $error; + } + ); + + self::assertInstanceOf('\Exception', $exception); + self::assertEquals('I am a bad promise', $exception->getMessage()); + } + + public function testAll() : void + { + $ampAdapter = new AmpPromiseAdapter(); + $promises = [new Success(1), new Success(2), new Success(3)]; + + $allPromise = $ampAdapter->all($promises); + + self::assertInstanceOf('GraphQL\Executor\Promise\Promise', $allPromise); + self::assertInstanceOf(Promise::class, $allPromise->adoptedPromise); + + $result = null; + + $allPromise->then(static function ($values) use (&$result) { + $result = $values; + }); + + self::assertSame([1, 2, 3], $result); + } + + public function testAllShouldPreserveTheOrderOfTheArrayWhenResolvingAsyncPromises() : void + { + $ampAdapter = new AmpPromiseAdapter(); + $deferred = new Deferred(); + $promises = [new Success(1), 2, $deferred->promise(), new Success(4)]; + $result = null; + + $ampAdapter->all($promises)->then(static function ($values) use (&$result) { + $result = $values; + }); + + // Resolve the async promise + $deferred->resolve(3); + self::assertSame([1, 2, 3, 4], $result); + } +} From 782e66374c1db1f589f2d9fcc2d5297c413dc0f5 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Sat, 29 Feb 2020 00:13:53 +0700 Subject: [PATCH 148/256] Simplify deferred/promise implementation (#581) * Simplify deferred/promise implementation * Add convenience `catch` method to sync promise * Address review comments * More type hints for PHPStan --- CHANGELOG.md | 4 +- src/Deferred.php | 58 ++++--------------- src/Executor/Promise/Adapter/SyncPromise.php | 42 +++++++++++++- .../Promise/Adapter/SyncPromiseAdapter.php | 14 ++--- tests/Executor/DeferredFieldsTest.php | 19 +++--- .../Promise/SyncPromiseAdapterTest.php | 9 ++- 6 files changed, 72 insertions(+), 74 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 454f40e74..a1145ed9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,13 @@ - **BREAKING:** Removed deprecated directive introspection fields (onOperation, onFragment, onField) - **BREAKING:** Removal of `VariablesDefaultValueAllowed` validation rule. All variables may now specify a default value. - **BREAKING:** renamed `ProvidedNonNullArguments` to `ProvidedRequiredArguments` (no longer require values to be provided to non-null arguments which provide a default value). +- **BREAKING:** `GraphQL\Deferred` now extends `GraphQL\Executor\Promise\Adapter\SyncPromise` - Add schema validation: Input Objects must not contain non-nullable circular references (#492) - Added retrieving query complexity once query has been completed (#316) - Allow input types to be passed in from variables using \stdClass instead of associative arrays (#535) - Fixes parsing of string literals of the form \u0000 for code points in the range [128, 255] inclusive -- Parse UTF-16 surrogate pairs within string literals +- Parse UTF-16 surrogate pairs within string literals +- Simplified Deferred implementation #### v0.13.5 - Fix coroutine executor when using with promise (#486) diff --git a/src/Deferred.php b/src/Deferred.php index a2df828a6..ff79ea6bd 100644 --- a/src/Deferred.php +++ b/src/Deferred.php @@ -5,58 +5,22 @@ namespace GraphQL; use GraphQL\Executor\Promise\Adapter\SyncPromise; -use SplQueue; -use Throwable; -class Deferred +class Deferred extends SyncPromise { - /** @var SplQueue|null */ - private static $queue; - - /** @var callable */ - private $callback; - - /** @var SyncPromise */ - public $promise; - - public function __construct(callable $callback) - { - $this->callback = $callback; - $this->promise = new SyncPromise(); - self::getQueue()->enqueue($this); - } - - public static function getQueue() : SplQueue - { - if (self::$queue === null) { - self::$queue = new SplQueue(); - } - - return self::$queue; - } - - public static function runQueue() : void - { - $queue = self::getQueue(); - while (! $queue->isEmpty()) { - /** @var self $dequeuedNodeValue */ - $dequeuedNodeValue = $queue->dequeue(); - $dequeuedNodeValue->run(); - } - } - - public function then($onFulfilled = null, $onRejected = null) + /** + * @param callable() : mixed $executor + */ + public static function create(callable $executor) : self { - return $this->promise->then($onFulfilled, $onRejected); + return new self($executor); } - public function run() : void + /** + * @param callable() : mixed $executor + */ + public function __construct(callable $executor) { - try { - $cb = $this->callback; - $this->promise->resolve($cb()); - } catch (Throwable $e) { - $this->promise->reject($e); - } + parent::__construct($executor); } } diff --git a/src/Executor/Promise/Adapter/SyncPromise.php b/src/Executor/Promise/Adapter/SyncPromise.php index 90230d8bc..884630d38 100644 --- a/src/Executor/Promise/Adapter/SyncPromise.php +++ b/src/Executor/Promise/Adapter/SyncPromise.php @@ -5,7 +5,6 @@ namespace GraphQL\Executor\Promise\Adapter; use Exception; -use GraphQL\Executor\ExecutionResult; use GraphQL\Utils\Utils; use SplQueue; use Throwable; @@ -15,6 +14,14 @@ /** * Simplistic (yet full-featured) implementation of Promises A+ spec for regular PHP `sync` mode * (using queue to defer promises execution) + * + * Note: + * Library users are not supposed to use SyncPromise class in their resolvers. + * Instead they should use GraphQL\Deferred which enforces $executor callback in the constructor. + * + * Root SyncPromise without explicit $executor will never resolve (actually throw while trying). + * The whole point of Deferred is to ensure it never happens and that any resolver creates + * at least one $executor to start the promise chain. */ class SyncPromise { @@ -28,7 +35,7 @@ class SyncPromise /** @var string */ public $state = self::PENDING; - /** @var ExecutionResult|Throwable */ + /** @var mixed */ public $result; /** @@ -47,6 +54,23 @@ public static function runQueue() : void } } + /** + * @param callable() : mixed $executor + */ + public function __construct(?callable $executor = null) + { + if ($executor === null) { + return; + } + self::getQueue()->enqueue(function () use ($executor) { + try { + $this->resolve($executor()); + } catch (Throwable $e) { + $this->reject($e); + } + }); + } + public function resolve($value) : self { switch ($this->state) { @@ -146,7 +170,11 @@ public static function getQueue() : SplQueue return self::$queue ?: self::$queue = new SplQueue(); } - public function then(?callable $onFulfilled = null, ?callable $onRejected = null) + /** + * @param callable(mixed) : mixed $onFulfilled + * @param callable(Throwable) : mixed $onRejected + */ + public function then(?callable $onFulfilled = null, ?callable $onRejected = null) : self { if ($this->state === self::REJECTED && $onRejected === null) { return $this; @@ -163,4 +191,12 @@ public function then(?callable $onFulfilled = null, ?callable $onRejected = null return $tmp; } + + /** + * @param callable(Throwable) : mixed $onRejected + */ + public function catch(callable $onRejected) : self + { + return $this->then(null, $onRejected); + } } diff --git a/src/Executor/Promise/Adapter/SyncPromiseAdapter.php b/src/Executor/Promise/Adapter/SyncPromiseAdapter.php index 189d51059..f088aec24 100644 --- a/src/Executor/Promise/Adapter/SyncPromiseAdapter.php +++ b/src/Executor/Promise/Adapter/SyncPromiseAdapter.php @@ -4,7 +4,6 @@ namespace GraphQL\Executor\Promise\Adapter; -use GraphQL\Deferred; use GraphQL\Error\InvariantViolation; use GraphQL\Executor\ExecutionResult; use GraphQL\Executor\Promise\Promise; @@ -24,7 +23,7 @@ class SyncPromiseAdapter implements PromiseAdapter */ public function isThenable($value) { - return $value instanceof Deferred; + return $value instanceof SyncPromise; } /** @@ -32,11 +31,12 @@ public function isThenable($value) */ public function convertThenable($thenable) { - if (! $thenable instanceof Deferred) { + if (! $thenable instanceof SyncPromise) { + // End-users should always use Deferred (and don't use SyncPromise directly) throw new InvariantViolation('Expected instance of GraphQL\Deferred, got ' . Utils::printSafe($thenable)); } - return new Promise($thenable->promise, $this); + return new Promise($thenable, $this); } /** @@ -141,13 +141,11 @@ static function ($value) use ($index, &$count, $total, &$result, $all) : void { public function wait(Promise $promise) { $this->beforeWait($promise); - $dfdQueue = Deferred::getQueue(); - $promiseQueue = SyncPromise::getQueue(); + $taskQueue = SyncPromise::getQueue(); while ($promise->adoptedPromise->state === SyncPromise::PENDING && - ! ($dfdQueue->isEmpty() && $promiseQueue->isEmpty()) + ! $taskQueue->isEmpty() ) { - Deferred::runQueue(); SyncPromise::runQueue(); $this->onWait($promise); } diff --git a/tests/Executor/DeferredFieldsTest.php b/tests/Executor/DeferredFieldsTest.php index 838df8677..a897614a0 100644 --- a/tests/Executor/DeferredFieldsTest.php +++ b/tests/Executor/DeferredFieldsTest.php @@ -91,12 +91,7 @@ public function setUp() : void return new Deferred(function () use ($user) { $this->paths[] = 'deferred-for-best-friend-of-' . $user['id']; - return Utils::find( - $this->userDataSource, - static function ($entry) use ($user) { - return $entry['id'] === $user['bestFriendId']; - } - ); + return $this->findUserById($user['bestFriendId']); }); }, ], @@ -614,24 +609,24 @@ public function testDeferredChaining() 'deferred-for-category-2-topStoryAuthor1', 'deferred-for-category-3-topStory', 'deferred-for-category-3-topStoryAuthor1', - 'deferred-for-category-1-topStoryAuthor2', - 'deferred-for-category-2-topStoryAuthor2', - 'deferred-for-category-3-topStoryAuthor2', ['categories', 0, 'topStory', 'title'], ['categories', 0, 'topStory', 'author'], + 'deferred-for-category-1-topStoryAuthor2', ['categories', 1, 'topStory', 'title'], ['categories', 1, 'topStory', 'author'], + 'deferred-for-category-2-topStoryAuthor2', ['categories', 2, 'topStory', 'title'], ['categories', 2, 'topStory', 'author'], - ['categories', 0, 'topStoryAuthor', 'name'], - ['categories', 1, 'topStoryAuthor', 'name'], - ['categories', 2, 'topStoryAuthor', 'name'], + 'deferred-for-category-3-topStoryAuthor2', 'deferred-for-story-8-author', 'deferred-for-story-3-author', 'deferred-for-story-9-author', ['categories', 0, 'topStory', 'author', 'name'], + ['categories', 0, 'topStoryAuthor', 'name'], ['categories', 1, 'topStory', 'author', 'name'], + ['categories', 1, 'topStoryAuthor', 'name'], ['categories', 2, 'topStory', 'author', 'name'], + ['categories', 2, 'topStoryAuthor', 'name'], ]; self::assertEquals($expectedPaths, $this->paths); } diff --git a/tests/Executor/Promise/SyncPromiseAdapterTest.php b/tests/Executor/Promise/SyncPromiseAdapterTest.php index 32c1ef81b..a7c7fa219 100644 --- a/tests/Executor/Promise/SyncPromiseAdapterTest.php +++ b/tests/Executor/Promise/SyncPromiseAdapterTest.php @@ -208,10 +208,13 @@ public function testWait() : void $all = $this->promises->all([0, $p1, $p2, $p3, $p4]); $result = $this->promises->wait($p2); + + // Having single promise queue means that we won't stop in wait + // until all pending promises are resolved self::assertEquals(2, $result); - self::assertEquals(SyncPromise::PENDING, $p3->adoptedPromise->state); - self::assertEquals(SyncPromise::PENDING, $all->adoptedPromise->state); - self::assertEquals([1, 2], $called); + self::assertEquals(SyncPromise::FULFILLED, $p3->adoptedPromise->state); + self::assertEquals(SyncPromise::FULFILLED, $all->adoptedPromise->state); + self::assertEquals([1, 2, 3, 4], $called); $expectedResult = [0, 1, 2, 3, 4]; $result = $this->promises->wait($all); From 0b88b7dcd673801cce73c7e07de59007d5c02b09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Podlipsk=C3=BD?= Date: Fri, 28 Feb 2020 18:16:51 +0100 Subject: [PATCH 149/256] Wrap field path in quotes (#587) --- CHANGELOG.md | 7 ++++++- src/Executor/ReferenceExecutor.php | 2 +- .../Executor/CoroutineExecutor.php | 4 ++-- tests/Executor/ListsTest.php | 18 ++++++++--------- tests/Executor/NonNullTest.php | 20 +++++++++---------- 5 files changed, 28 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1145ed9b..5500eec86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,12 @@ - Allow input types to be passed in from variables using \stdClass instead of associative arrays (#535) - Fixes parsing of string literals of the form \u0000 for code points in the range [128, 255] inclusive - Parse UTF-16 surrogate pairs within string literals -- Simplified Deferred implementation +- Added quotes around `parentType.fieldName` in error message: +```diff +- Cannot return null for non-nullable field parentType.fieldName. ++ Cannot return null for non-nullable field "parentType.fieldName". +``` +- Simplified Deferred implementation #### v0.13.5 - Fix coroutine executor when using with promise (#486) diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index b5d700896..7093fdd58 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -747,7 +747,7 @@ private function completeValue( ); if ($completed === null) { throw new InvariantViolation( - 'Cannot return null for non-nullable field ' . $info->parentType . '.' . $info->fieldName . '.' + sprintf('Cannot return null for non-nullable field "%s.%s".', $info->parentType, $info->fieldName) ); } diff --git a/src/Experimental/Executor/CoroutineExecutor.php b/src/Experimental/Executor/CoroutineExecutor.php index 5f14f0999..185f54a74 100644 --- a/src/Experimental/Executor/CoroutineExecutor.php +++ b/src/Experimental/Executor/CoroutineExecutor.php @@ -551,7 +551,7 @@ private function completeValueFast(CoroutineContext $ctx, Type $type, $value, ar if ($nonNull && $returnValue === null) { $this->addError(Error::createLocatedError( new InvariantViolation(sprintf( - 'Cannot return null for non-nullable field %s.%s.', + 'Cannot return null for non-nullable field "%s.%s".', $ctx->type->name, $ctx->shared->fieldName )), @@ -879,7 +879,7 @@ private function completeValue(CoroutineContext $ctx, Type $type, $value, array if ($nonNull && $returnValue === null) { $this->addError(Error::createLocatedError( new InvariantViolation(sprintf( - 'Cannot return null for non-nullable field %s.%s.', + 'Cannot return null for non-nullable field "%s.%s".', $ctx->type->name, $ctx->shared->fieldName )), diff --git a/tests/Executor/ListsTest.php b/tests/Executor/ListsTest.php index 98bbafa79..036a908f2 100644 --- a/tests/Executor/ListsTest.php +++ b/tests/Executor/ListsTest.php @@ -221,7 +221,7 @@ public function testHandlesNonNullableListsWithArray() : void 'data' => ['nest' => null], 'errors' => [ [ - 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', + 'debugMessage' => 'Cannot return null for non-nullable field "DataType.test".', 'locations' => [['line' => 1, 'column' => 10]], ], ], @@ -264,7 +264,7 @@ public function testHandlesNonNullableListsWithPromiseArray() : void 'data' => ['nest' => null], 'errors' => [ [ - 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', + 'debugMessage' => 'Cannot return null for non-nullable field "DataType.test".', 'locations' => [['line' => 1, 'column' => 10]], ], ], @@ -372,7 +372,7 @@ public function testHandlesListOfNonNullsWithArray() : void 'data' => ['nest' => ['test' => null]], 'errors' => [ [ - 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', + 'debugMessage' => 'Cannot return null for non-nullable field "DataType.test".', 'locations' => [['line' => 1, 'column' => 10]], ], ], @@ -415,7 +415,7 @@ public function testHandlesListOfNonNullsWithPromiseArray() : void 'data' => ['nest' => ['test' => null]], 'errors' => [ [ - 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', + 'debugMessage' => 'Cannot return null for non-nullable field "DataType.test".', 'locations' => [['line' => 1, 'column' => 10]], ], ], @@ -531,7 +531,7 @@ public function testHandlesNonNullListOfNonNullsWithArray() : void 'data' => ['nest' => null], 'errors' => [ [ - 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', + 'debugMessage' => 'Cannot return null for non-nullable field "DataType.test".', 'locations' => [['line' => 1, 'column' => 10]], ], ], @@ -546,7 +546,7 @@ public function testHandlesNonNullListOfNonNullsWithArray() : void 'data' => ['nest' => null], 'errors' => [ [ - 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', + 'debugMessage' => 'Cannot return null for non-nullable field "DataType.test".', 'locations' => [['line' => 1, 'column' => 10]], ], ], @@ -583,7 +583,7 @@ public function testHandlesNonNullListOfNonNullsWithPromiseArray() : void 'data' => ['nest' => null], 'errors' => [ [ - 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', + 'debugMessage' => 'Cannot return null for non-nullable field "DataType.test".', 'locations' => [['line' => 1, 'column' => 10]], ], ], @@ -600,7 +600,7 @@ public function testHandlesNonNullListOfNonNullsWithPromiseArray() : void 'data' => ['nest' => null], 'errors' => [ [ - 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', + 'debugMessage' => 'Cannot return null for non-nullable field "DataType.test".', 'locations' => [['line' => 1, 'column' => 10]], ], ], @@ -664,7 +664,7 @@ public function testHandlesNonNullListOfNonNullsWithArrayPromise() : void 'data' => ['nest' => null], 'errors' => [ [ - 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', + 'debugMessage' => 'Cannot return null for non-nullable field "DataType.test".', 'locations' => [['line' => 1, 'column' => 10]], ], ], diff --git a/tests/Executor/NonNullTest.php b/tests/Executor/NonNullTest.php index e24c5093a..a0f36ea25 100644 --- a/tests/Executor/NonNullTest.php +++ b/tests/Executor/NonNullTest.php @@ -550,7 +550,7 @@ public function testNullsASynchronouslyReturnedObjectThatContainsANonNullableFie 'data' => ['syncNest' => null], 'errors' => [ [ - 'debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', + 'debugMessage' => 'Cannot return null for non-nullable field "DataType.syncNonNull".', 'locations' => [['line' => 4, 'column' => 11]], ], ], @@ -577,7 +577,7 @@ public function testNullsASynchronouslyReturnedObjectThatContainsANonNullableFie 'data' => ['syncNest' => null], 'errors' => [ [ - 'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', + 'debugMessage' => 'Cannot return null for non-nullable field "DataType.promiseNonNull".', 'locations' => [['line' => 4, 'column' => 11]], ], ], @@ -605,7 +605,7 @@ public function testNullsAnObjectReturnedInAPromiseThatContainsANonNullableField 'data' => ['promiseNest' => null], 'errors' => [ [ - 'debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', + 'debugMessage' => 'Cannot return null for non-nullable field "DataType.syncNonNull".', 'locations' => [['line' => 4, 'column' => 11]], ], ], @@ -633,7 +633,7 @@ public function testNullsAnObjectReturnedInAPromiseThatContainsANonNullableField 'data' => ['promiseNest' => null], 'errors' => [ [ - 'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', + 'debugMessage' => 'Cannot return null for non-nullable field "DataType.promiseNonNull".', 'locations' => [['line' => 4, 'column' => 11]], ], ], @@ -775,10 +775,10 @@ public function testNullsTheFirstNullableObjectAfterAFieldReturnsNullInALongChai 'anotherPromiseNest' => null, ], 'errors' => [ - ['debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', 'locations' => [['line' => 8, 'column' => 19]]], - ['debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', 'locations' => [['line' => 19, 'column' => 19]]], - ['debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', 'locations' => [['line' => 30, 'column' => 19]]], - ['debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', 'locations' => [['line' => 41, 'column' => 19]]], + ['debugMessage' => 'Cannot return null for non-nullable field "DataType.syncNonNull".', 'locations' => [['line' => 8, 'column' => 19]]], + ['debugMessage' => 'Cannot return null for non-nullable field "DataType.syncNonNull".', 'locations' => [['line' => 19, 'column' => 19]]], + ['debugMessage' => 'Cannot return null for non-nullable field "DataType.promiseNonNull".', 'locations' => [['line' => 30, 'column' => 19]]], + ['debugMessage' => 'Cannot return null for non-nullable field "DataType.promiseNonNull".', 'locations' => [['line' => 41, 'column' => 19]]], ], ]; @@ -1024,7 +1024,7 @@ public function testNullsTheTopLevelIfSyncNonNullableFieldReturnsNull() : void $expected = [ 'errors' => [ [ - 'debugMessage' => 'Cannot return null for non-nullable field DataType.syncNonNull.', + 'debugMessage' => 'Cannot return null for non-nullable field "DataType.syncNonNull".', 'locations' => [['line' => 2, 'column' => 17]], ], ], @@ -1046,7 +1046,7 @@ public function testNullsTheTopLevelIfAsyncNonNullableFieldResolvesNull() : void $expected = [ 'errors' => [ [ - 'debugMessage' => 'Cannot return null for non-nullable field DataType.promiseNonNull.', + 'debugMessage' => 'Cannot return null for non-nullable field "DataType.promiseNonNull".', 'locations' => [['line' => 2, 'column' => 17]], ], ], From 3a276cb8a5c45dbef4a4b7bb3dbf906534ad8142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1chym=20Tou=C5=A1ek?= Date: Fri, 28 Feb 2020 18:18:27 +0100 Subject: [PATCH 150/256] Fix ServerConfig and SchemaConfig (#591) --- src/Server/ServerConfig.php | 26 ++++++++++++------------ src/Type/Schema.php | 4 ++-- src/Type/SchemaConfig.php | 38 ++++++++++++++++++------------------ src/Utils/SchemaExtender.php | 6 +----- 4 files changed, 35 insertions(+), 39 deletions(-) diff --git a/src/Server/ServerConfig.php b/src/Server/ServerConfig.php index cb36cebd1..147d52b5f 100644 --- a/src/Server/ServerConfig.php +++ b/src/Server/ServerConfig.php @@ -54,7 +54,7 @@ public static function create(array $config = []) return $instance; } - /** @var Schema */ + /** @var Schema|null */ private $schema; /** @var mixed|callable */ @@ -69,22 +69,22 @@ public static function create(array $config = []) /** @var callable|null */ private $errorsHandler; - /** @var bool */ + /** @var bool|int */ private $debug = false; /** @var bool */ private $queryBatching = false; - /** @var ValidationRule[]|callable */ + /** @var ValidationRule[]|callable|null */ private $validationRules; - /** @var callable */ + /** @var callable|null */ private $fieldResolver; - /** @var PromiseAdapter */ + /** @var PromiseAdapter|null */ private $promiseAdapter; - /** @var callable */ + /** @var callable|null */ private $persistentQueryLoader; /** @@ -158,7 +158,7 @@ public function setErrorsHandler(callable $handler) /** * Set validation rules for this server. * - * @param ValidationRule[]|callable $validationRules + * @param ValidationRule[]|callable|null $validationRules * * @return self * @@ -263,7 +263,7 @@ public function getRootValue() } /** - * @return Schema + * @return Schema|null */ public function getSchema() { @@ -287,7 +287,7 @@ public function getErrorsHandler() } /** - * @return PromiseAdapter + * @return PromiseAdapter|null */ public function getPromiseAdapter() { @@ -295,7 +295,7 @@ public function getPromiseAdapter() } /** - * @return ValidationRule[]|callable + * @return ValidationRule[]|callable|null */ public function getValidationRules() { @@ -303,7 +303,7 @@ public function getValidationRules() } /** - * @return callable + * @return callable|null */ public function getFieldResolver() { @@ -311,7 +311,7 @@ public function getFieldResolver() } /** - * @return callable + * @return callable|null */ public function getPersistentQueryLoader() { @@ -319,7 +319,7 @@ public function getPersistentQueryLoader() } /** - * @return bool + * @return bool|int */ public function getDebug() { diff --git a/src/Type/Schema.php b/src/Type/Schema.php index 547e6843f..586970c18 100644 --- a/src/Type/Schema.php +++ b/src/Type/Schema.php @@ -71,7 +71,7 @@ class Schema private $validationErrors; /** @var SchemaTypeExtensionNode[] */ - public $extensionASTNodes; + public $extensionASTNodes = []; /** * @param mixed[]|SchemaConfig $config @@ -447,7 +447,7 @@ public function getDirective(string $name) : ?Directive } /** - * @return SchemaDefinitionNode + * @return SchemaDefinitionNode|null */ public function getAstNode() { diff --git a/src/Type/SchemaConfig.php b/src/Type/SchemaConfig.php index 1b2177593..2046e81b5 100644 --- a/src/Type/SchemaConfig.php +++ b/src/Type/SchemaConfig.php @@ -27,32 +27,32 @@ */ class SchemaConfig { - /** @var ObjectType */ + /** @var ObjectType|null */ public $query; - /** @var ObjectType */ + /** @var ObjectType|null */ public $mutation; - /** @var ObjectType */ + /** @var ObjectType|null */ public $subscription; /** @var Type[]|callable */ - public $types; + public $types = []; /** @var Directive[] */ - public $directives; + public $directives = []; /** @var callable|null */ public $typeLoader; - /** @var SchemaDefinitionNode */ + /** @var SchemaDefinitionNode|null */ public $astNode; /** @var bool */ - public $assumeValid; + public $assumeValid = false; /** @var SchemaTypeExtensionNode[] */ - public $extensionASTNodes; + public $extensionASTNodes = []; /** * Converts an array of options to instance of SchemaConfig @@ -115,7 +115,7 @@ public static function create(array $options = []) } /** - * @return SchemaDefinitionNode + * @return SchemaDefinitionNode|null */ public function getAstNode() { @@ -133,7 +133,7 @@ public function setAstNode(SchemaDefinitionNode $astNode) } /** - * @return ObjectType + * @return ObjectType|null * * @api */ @@ -143,7 +143,7 @@ public function getQuery() } /** - * @param ObjectType $query + * @param ObjectType|null $query * * @return SchemaConfig * @@ -157,7 +157,7 @@ public function setQuery($query) } /** - * @return ObjectType + * @return ObjectType|null * * @api */ @@ -167,7 +167,7 @@ public function getMutation() } /** - * @param ObjectType $mutation + * @param ObjectType|null $mutation * * @return SchemaConfig * @@ -181,7 +181,7 @@ public function setMutation($mutation) } /** - * @return ObjectType + * @return ObjectType|null * * @api */ @@ -191,7 +191,7 @@ public function getSubscription() } /** - * @param ObjectType $subscription + * @param ObjectType|null $subscription * * @return SchemaConfig * @@ -205,13 +205,13 @@ public function setSubscription($subscription) } /** - * @return Type[] + * @return Type[]|callable * * @api */ public function getTypes() { - return $this->types ?: []; + return $this->types; } /** @@ -235,7 +235,7 @@ public function setTypes($types) */ public function getDirectives() { - return $this->directives ?: []; + return $this->directives; } /** @@ -253,7 +253,7 @@ public function setDirectives(array $directives) } /** - * @return callable + * @return callable|null * * @api */ diff --git a/src/Utils/SchemaExtender.php b/src/Utils/SchemaExtender.php index 997e113d5..b92743468 100644 --- a/src/Utils/SchemaExtender.php +++ b/src/Utils/SchemaExtender.php @@ -609,11 +609,7 @@ static function (string $typeName) use ($schema) { } } - $schemaExtensionASTNodes = count($schemaExtensions) > 0 - ? ($schema->extensionASTNodes - ? array_merge($schema->extensionASTNodes, $schemaExtensions) - : $schemaExtensions) - : $schema->extensionASTNodes; + $schemaExtensionASTNodes = array_merge($schema->extensionASTNodes, $schemaExtensions); $types = array_merge( // Iterate through all types, getting the type definition for each, ensuring From b3fac125a595f3e11a5dd53a943d8dafee06318f Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Fri, 28 Feb 2020 18:20:42 +0100 Subject: [PATCH 151/256] Change type hints in Utils.php to iterable pseudo-type (#613) * Change type hints in Utils.php to iterable pseudo-type * Fix codestyle * Fix phpdoc --- src/Utils/Utils.php | 91 ++++++++++++++++++++++----------------------- 1 file changed, 45 insertions(+), 46 deletions(-) diff --git a/src/Utils/Utils.php b/src/Utils/Utils.php index 863c3ab62..9f0defa55 100644 --- a/src/Utils/Utils.php +++ b/src/Utils/Utils.php @@ -103,18 +103,18 @@ public static function assign($obj, array $vars, array $requiredKeys = []) } /** - * @param mixed|Traversable $traversable + * @param iterable $iterable * * @return mixed|null */ - public static function find($traversable, callable $predicate) + public static function find($iterable, callable $predicate) { self::invariant( - is_array($traversable) || $traversable instanceof Traversable, + is_array($iterable) || $iterable instanceof Traversable, __METHOD__ . ' expects array or Traversable' ); - foreach ($traversable as $key => $value) { + foreach ($iterable as $key => $value) { if ($predicate($value, $key)) { return $value; } @@ -124,22 +124,22 @@ public static function find($traversable, callable $predicate) } /** - * @param mixed|Traversable $traversable + * @param iterable $iterable * - * @return mixed[] + * @return array * * @throws Exception */ - public static function filter($traversable, callable $predicate) + public static function filter($iterable, callable $predicate) : array { self::invariant( - is_array($traversable) || $traversable instanceof Traversable, + is_array($iterable) || $iterable instanceof Traversable, __METHOD__ . ' expects array or Traversable' ); $result = []; $assoc = false; - foreach ($traversable as $key => $value) { + foreach ($iterable as $key => $value) { if (! $assoc && ! is_int($key)) { $assoc = true; } @@ -154,21 +154,21 @@ public static function filter($traversable, callable $predicate) } /** - * @param mixed|Traversable $traversable + * @param iterable $iterable * - * @return mixed[] + * @return array * * @throws Exception */ - public static function map($traversable, callable $fn) + public static function map($iterable, callable $fn) : array { self::invariant( - is_array($traversable) || $traversable instanceof Traversable, + is_array($iterable) || $iterable instanceof Traversable, __METHOD__ . ' expects array or Traversable' ); $map = []; - foreach ($traversable as $key => $value) { + foreach ($iterable as $key => $value) { $map[$key] = $fn($value, $key); } @@ -176,21 +176,21 @@ public static function map($traversable, callable $fn) } /** - * @param mixed|Traversable $traversable + * @param iterable $iterable * - * @return mixed[] + * @return array * * @throws Exception */ - public static function mapKeyValue($traversable, callable $fn) + public static function mapKeyValue($iterable, callable $fn) : array { self::invariant( - is_array($traversable) || $traversable instanceof Traversable, + is_array($iterable) || $iterable instanceof Traversable, __METHOD__ . ' expects array or Traversable' ); $map = []; - foreach ($traversable as $key => $value) { + foreach ($iterable as $key => $value) { [$newKey, $newValue] = $fn($value, $key); $map[$newKey] = $newValue; } @@ -199,21 +199,21 @@ public static function mapKeyValue($traversable, callable $fn) } /** - * @param mixed|Traversable $traversable + * @param iterable $iterable * - * @return mixed[] + * @return array * * @throws Exception */ - public static function keyMap($traversable, callable $keyFn) + public static function keyMap($iterable, callable $keyFn) : array { self::invariant( - is_array($traversable) || $traversable instanceof Traversable, + is_array($iterable) || $iterable instanceof Traversable, __METHOD__ . ' expects array or Traversable' ); $map = []; - foreach ($traversable as $key => $value) { + foreach ($iterable as $key => $value) { $newKey = $keyFn($value, $key); if (! is_scalar($newKey)) { continue; @@ -225,20 +225,23 @@ public static function keyMap($traversable, callable $keyFn) return $map; } - public static function each($traversable, callable $fn) + /** + * @param iterable $iterable + */ + public static function each($iterable, callable $fn) : void { self::invariant( - is_array($traversable) || $traversable instanceof Traversable, + is_array($iterable) || $iterable instanceof Traversable, __METHOD__ . ' expects array or Traversable' ); - foreach ($traversable as $key => $item) { + foreach ($iterable as $key => $item) { $fn($item, $key); } } /** - * Splits original traversable to several arrays with keys equal to $keyFn return + * Splits original iterable to several arrays with keys equal to $keyFn return * * E.g. Utils::groupBy([1, 2, 3, 4, 5], function($value) {return $value % 3}) will output: * [ @@ -249,19 +252,19 @@ public static function each($traversable, callable $fn) * * $keyFn is also allowed to return array of keys. Then value will be added to all arrays with given keys * - * @param mixed[]|Traversable $traversable + * @param iterable $iterable * - * @return mixed[] + * @return array> */ - public static function groupBy($traversable, callable $keyFn) + public static function groupBy($iterable, callable $keyFn) : array { self::invariant( - is_array($traversable) || $traversable instanceof Traversable, + is_array($iterable) || $iterable instanceof Traversable, __METHOD__ . ' expects array or Traversable' ); $grouped = []; - foreach ($traversable as $key => $value) { + foreach ($iterable as $key => $value) { $newKeys = (array) $keyFn($value, $key); foreach ($newKeys as $newKey) { $grouped[$newKey][] = $value; @@ -272,14 +275,14 @@ public static function groupBy($traversable, callable $keyFn) } /** - * @param iterable $traversable + * @param iterable $iterable * * @return array */ - public static function keyValMap($traversable, callable $keyFn, callable $valFn) + public static function keyValMap($iterable, callable $keyFn, callable $valFn) : array { $map = []; - foreach ($traversable as $item) { + foreach ($iterable as $item) { $map[$keyFn($item)] = $valFn($item); } @@ -287,13 +290,11 @@ public static function keyValMap($traversable, callable $keyFn, callable $valFn) } /** - * @param mixed[] $traversable - * - * @return bool + * @param iterable $iterable */ - public static function every($traversable, callable $predicate) + public static function every($iterable, callable $predicate) : bool { - foreach ($traversable as $key => $value) { + foreach ($iterable as $key => $value) { if (! $predicate($value, $key)) { return false; } @@ -303,13 +304,11 @@ public static function every($traversable, callable $predicate) } /** - * @param mixed[] $traversable - * - * @return bool + * @param iterable $iterable */ - public static function some($traversable, callable $predicate) + public static function some($iterable, callable $predicate) : bool { - foreach ($traversable as $key => $value) { + foreach ($iterable as $key => $value) { if ($predicate($value, $key)) { return true; } From 21a78ccd46b703cee414a816c1c7303b5bf6f519 Mon Sep 17 00:00:00 2001 From: Sergey Tatarintsev Date: Fri, 28 Feb 2020 18:24:16 +0100 Subject: [PATCH 152/256] Make sure resolveField function is preserved in SchemaExtender (#626) For ObjectTypes, SchemaExtender did not copy over `resolveType` config option. See test for an example. --- src/Utils/SchemaExtender.php | 1 + tests/Utils/SchemaExtenderTest.php | 32 ++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/Utils/SchemaExtender.php b/src/Utils/SchemaExtender.php index b92743468..3249738d2 100644 --- a/src/Utils/SchemaExtender.php +++ b/src/Utils/SchemaExtender.php @@ -391,6 +391,7 @@ protected static function extendObjectType(ObjectType $type) : ObjectType 'astNode' => $type->astNode, 'extensionASTNodes' => static::getExtensionASTNodes($type), 'isTypeOf' => $type->config['isTypeOf'] ?? null, + 'resolveField' => $type->resolveFieldFn ?? null, ]); } diff --git a/tests/Utils/SchemaExtenderTest.php b/tests/Utils/SchemaExtenderTest.php index d83dec0a4..61cdb18a0 100644 --- a/tests/Utils/SchemaExtenderTest.php +++ b/tests/Utils/SchemaExtenderTest.php @@ -1904,6 +1904,38 @@ public function testOriginalResolversArePreserved() self::assertSame(['data' => ['hello' => 'Hello World!']], $result->toArray()); } + public function testOriginalResolveFieldIsPreserved() + { + $queryType = new ObjectType([ + 'name' => 'Query', + 'fields' => [ + 'hello' => [ + 'type' => Type::string(), + ], + ], + 'resolveField' => static function () : string { + return 'Hello World!'; + }, + ]); + + $schema = new Schema(['query' => $queryType]); + + $documentNode = Parser::parse(' +extend type Query { + misc: String +} +'); + + $extendedSchema = SchemaExtender::extend($schema, $documentNode); + $queryResolveFieldFn = $extendedSchema->getQueryType()->resolveFieldFn; + + self::assertIsCallable($queryResolveFieldFn); + + $query = '{ hello }'; + $result = GraphQL::executeQuery($extendedSchema, $query); + self::assertSame(['data' => ['hello' => 'Hello World!']], $result->toArray()); + } + /** * @see https://github.com/webonyx/graphql-php/issues/180 */ From 6fd38bf3405e679f2977ff1437f0169271d181dc Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Sat, 29 Feb 2020 01:08:27 +0700 Subject: [PATCH 153/256] Fix lint error --- src/Executor/Promise/Adapter/AmpPromiseAdapter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Executor/Promise/Adapter/AmpPromiseAdapter.php b/src/Executor/Promise/Adapter/AmpPromiseAdapter.php index d1a4ca69b..3b6db302f 100644 --- a/src/Executor/Promise/Adapter/AmpPromiseAdapter.php +++ b/src/Executor/Promise/Adapter/AmpPromiseAdapter.php @@ -37,7 +37,7 @@ public function convertThenable($thenable) : Promise */ public function then(Promise $promise, ?callable $onFulfilled = null, ?callable $onRejected = null) : Promise { - $deferred = new Deferred(); + $deferred = new Deferred(); $onResolve = static function (?Throwable $reason, $value) use ($onFulfilled, $onRejected, $deferred) : void { if ($reason === null && $onFulfilled !== null) { self::resolveWithCallable($deferred, $onFulfilled, $value); From a472409e37053983c0c803ffbec943268a0df6d1 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Sat, 29 Feb 2020 01:47:42 +0700 Subject: [PATCH 154/256] Fix broken tests --- src/Type/SchemaConfig.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Type/SchemaConfig.php b/src/Type/SchemaConfig.php index 2046e81b5..e6b0fb725 100644 --- a/src/Type/SchemaConfig.php +++ b/src/Type/SchemaConfig.php @@ -39,8 +39,8 @@ class SchemaConfig /** @var Type[]|callable */ public $types = []; - /** @var Directive[] */ - public $directives = []; + /** @var Directive[]|null */ + public $directives; /** @var callable|null */ public $typeLoader; @@ -229,7 +229,7 @@ public function setTypes($types) } /** - * @return Directive[] + * @return Directive[]|null * * @api */ From acf326369353f38af5ee32316607604d435dbee2 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Sat, 29 Feb 2020 01:52:47 +0700 Subject: [PATCH 155/256] More test fixes --- tests/Executor/Promise/AmpPromiseAdapterTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Executor/Promise/AmpPromiseAdapterTest.php b/tests/Executor/Promise/AmpPromiseAdapterTest.php index 41a0f38db..fde86ade6 100644 --- a/tests/Executor/Promise/AmpPromiseAdapterTest.php +++ b/tests/Executor/Promise/AmpPromiseAdapterTest.php @@ -22,7 +22,7 @@ */ class AmpPromiseAdapterTest extends TestCase { - public function setUp() + public function setUp(): void { if (interface_exists(Promise::class)) { return; From e1d3e359f7c20c6c05888c391b99d7ec17900f0e Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Sat, 29 Feb 2020 01:59:25 +0700 Subject: [PATCH 156/256] Fix another lint error --- tests/Executor/Promise/AmpPromiseAdapterTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Executor/Promise/AmpPromiseAdapterTest.php b/tests/Executor/Promise/AmpPromiseAdapterTest.php index fde86ade6..53f6a5918 100644 --- a/tests/Executor/Promise/AmpPromiseAdapterTest.php +++ b/tests/Executor/Promise/AmpPromiseAdapterTest.php @@ -22,7 +22,7 @@ */ class AmpPromiseAdapterTest extends TestCase { - public function setUp(): void + public function setUp() : void { if (interface_exists(Promise::class)) { return; From 6ed8c9b61d42eae9f3d32908278180f72ed0ab3b Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Fri, 28 Feb 2020 20:43:01 +0100 Subject: [PATCH 157/256] Fix PHPStan --- phpstan-baseline.neon | 86 +------------------ .../Promise/Adapter/AmpPromiseAdapter.php | 6 +- src/Executor/Promise/Adapter/SyncPromise.php | 2 +- src/Server/Helper.php | 4 +- src/Type/Introspection.php | 4 +- src/Type/Schema.php | 19 ++-- src/Type/SchemaValidationContext.php | 4 +- src/Utils/SchemaPrinter.php | 18 ++-- src/Validator/Rules/LoneSchemaDefinition.php | 8 +- .../Promise/AmpPromiseAdapterTest.php | 19 ++-- 10 files changed, 44 insertions(+), 126 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 7116ea097..0fbb97f52 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -400,11 +400,6 @@ parameters: count: 5 path: src/Server/Helper.php - - - message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Schema given\\.$#" - count: 1 - path: src/Server/Helper.php - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Error\\\\Error\"\\.$#" count: 1 @@ -430,11 +425,6 @@ parameters: count: 1 path: src/Server/Helper.php - - - message: "#^Only booleans are allowed in a negated boolean, callable given\\.$#" - count: 1 - path: src/Server/Helper.php - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" count: 2 @@ -512,12 +502,7 @@ parameters: - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 4 - path: src/Type/Introspection.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\ObjectType\"\\.$#" - count: 1 + count: 3 path: src/Type/Introspection.php - @@ -575,16 +560,6 @@ parameters: count: 1 path: src/Type/Schema.php - - - message: "#^Only booleans are allowed in a negated boolean, array\\ given\\.$#" - count: 1 - path: src/Type/Schema.php - - - - message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Type\\\\Definition\\\\ObjectType given\\.$#" - count: 3 - path: src/Type/Schema.php - - message: "#^Only booleans are allowed in an if condition, \\(callable\\)\\|null given\\.$#" count: 1 @@ -616,12 +591,7 @@ parameters: path: src/Type/SchemaConfig.php - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 2 - path: src/Type/SchemaConfig.php - - - - message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\ObjectType given\\.$#" + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\ObjectType\\|null given\\.$#" count: 1 path: src/Type/SchemaValidationContext.php @@ -630,11 +600,6 @@ parameters: count: 1 path: src/Type/SchemaValidationContext.php - - - message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\ObjectType\\|null given\\.$#" - count: 1 - path: src/Type/SchemaValidationContext.php - - message: "#^Only booleans are allowed in &&, array\\\\|GraphQL\\\\Language\\\\AST\\\\Node\\|GraphQL\\\\Language\\\\AST\\\\TypeDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\TypeNode\\|null given on the left side\\.$#" count: 1 @@ -920,11 +885,6 @@ parameters: count: 1 path: src/Utils/SchemaExtender.php - - - message: "#^Only booleans are allowed in a ternary operator condition, array\\ given\\.$#" - count: 1 - path: src/Utils/SchemaExtender.php - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\Type\"\\.$#" count: 1 @@ -940,31 +900,6 @@ parameters: count: 7 path: src/Utils/SchemaPrinter.php - - - message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Type\\\\Definition\\\\ObjectType given\\.$#" - count: 1 - path: src/Utils/SchemaPrinter.php - - - - message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Type\\\\Definition\\\\ObjectType\\|null given\\.$#" - count: 2 - path: src/Utils/SchemaPrinter.php - - - - message: "#^Only booleans are allowed in &&, GraphQL\\\\Type\\\\Definition\\\\ObjectType given on the left side\\.$#" - count: 1 - path: src/Utils/SchemaPrinter.php - - - - message: "#^Only booleans are allowed in &&, GraphQL\\\\Type\\\\Definition\\\\ObjectType\\|null given on the left side\\.$#" - count: 1 - path: src/Utils/SchemaPrinter.php - - - - message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\ObjectType\\|null given\\.$#" - count: 1 - path: src/Utils/SchemaPrinter.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 3 @@ -1225,21 +1160,6 @@ parameters: count: 1 path: src/Validator/Rules/LoneAnonymousOperation.php - - - message: "#^Only booleans are allowed in \\|\\|, GraphQL\\\\Language\\\\AST\\\\SchemaDefinitionNode given on the left side\\.$#" - count: 1 - path: src/Validator/Rules/LoneSchemaDefinition.php - - - - message: "#^Only booleans are allowed in \\|\\|, GraphQL\\\\Type\\\\Definition\\\\ObjectType given on the right side\\.$#" - count: 1 - path: src/Validator/Rules/LoneSchemaDefinition.php - - - - message: "#^Only booleans are allowed in \\|\\|, GraphQL\\\\Type\\\\Definition\\\\ObjectType\\|null given on the right side\\.$#" - count: 2 - path: src/Validator/Rules/LoneSchemaDefinition.php - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" count: 1 @@ -1707,7 +1627,7 @@ parameters: - message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" - count: 5 + count: 4 path: tests/Executor/DeferredFieldsTest.php - diff --git a/src/Executor/Promise/Adapter/AmpPromiseAdapter.php b/src/Executor/Promise/Adapter/AmpPromiseAdapter.php index 3b6db302f..e683daefe 100644 --- a/src/Executor/Promise/Adapter/AmpPromiseAdapter.php +++ b/src/Executor/Promise/Adapter/AmpPromiseAdapter.php @@ -114,13 +114,13 @@ public function all(array $promisesOrValues) : Promise $deferred = new Deferred(); $onResolve = static function (?Throwable $reason, ?array $values) use ($promisesOrValues, $deferred) : void { - if ($reason) { - $deferred->fail($reason); + if ($reason === null) { + $deferred->resolve(array_replace($promisesOrValues, $values)); return; } - $deferred->resolve(array_replace($promisesOrValues, $values)); + $deferred->fail($reason); }; all($promises)->onResolve($onResolve); diff --git a/src/Executor/Promise/Adapter/SyncPromise.php b/src/Executor/Promise/Adapter/SyncPromise.php index 884630d38..29d41c36d 100644 --- a/src/Executor/Promise/Adapter/SyncPromise.php +++ b/src/Executor/Promise/Adapter/SyncPromise.php @@ -62,7 +62,7 @@ public function __construct(?callable $executor = null) if ($executor === null) { return; } - self::getQueue()->enqueue(function () use ($executor) { + self::getQueue()->enqueue(function () use ($executor) : void { try { $this->resolve($executor()); } catch (Throwable $e) { diff --git a/src/Server/Helper.php b/src/Server/Helper.php index f125598fb..396589ae2 100644 --- a/src/Server/Helper.php +++ b/src/Server/Helper.php @@ -253,7 +253,7 @@ private function promiseToExecuteOperation( $isBatch = false ) { try { - if (! $config->getSchema()) { + if ($config->getSchema() === null) { throw new InvariantViolation('Schema is required for the server'); } @@ -344,7 +344,7 @@ private function loadPersistedQuery(ServerConfig $config, OperationParams $opera // Load query if we got persisted query id: $loader = $config->getPersistentQueryLoader(); - if (! $loader) { + if ($loader === null) { throw new RequestError('Persisted queries are not supported by this server'); } diff --git a/src/Type/Introspection.php b/src/Type/Introspection.php index 63229c10e..5bd928b31 100644 --- a/src/Type/Introspection.php +++ b/src/Type/Introspection.php @@ -251,14 +251,14 @@ public static function _schema() 'subscriptionType' => [ 'description' => 'If this server support subscription, the type that subscription operations will be rooted at.', 'type' => self::_type(), - 'resolve' => static function (Schema $schema) { + 'resolve' => static function (Schema $schema) : ?ObjectType { return $schema->getSubscriptionType(); }, ], 'directives' => [ 'description' => 'A list of all directives supported by this server.', 'type' => Type::nonNull(Type::listOf(Type::nonNull(self::_directive()))), - 'resolve' => static function (Schema $schema) { + 'resolve' => static function (Schema $schema) : array { return $schema->getDirectives(); }, ], diff --git a/src/Type/Schema.php b/src/Type/Schema.php index 586970c18..610d5e944 100644 --- a/src/Type/Schema.php +++ b/src/Type/Schema.php @@ -112,7 +112,7 @@ public function __construct($config) '"types" must be array or callable if provided but got: ' . Utils::getVariableType($config->types) ); Utils::invariant( - ! $config->directives || is_array($config->directives), + $config->directives === null || is_array($config->directives), '"directives" must be Array if provided but got: ' . Utils::getVariableType($config->directives) ); } @@ -120,13 +120,13 @@ public function __construct($config) $this->config = $config; $this->extensionASTNodes = $config->extensionASTNodes; - if ($config->query) { + if ($config->query !== null) { $this->resolvedTypes[$config->query->name] = $config->query; } - if ($config->mutation) { + if ($config->mutation !== null) { $this->resolvedTypes[$config->mutation->name] = $config->mutation; } - if ($config->subscription) { + if ($config->subscription !== null) { $this->resolvedTypes[$config->subscription->name] = $config->subscription; } if (is_array($this->config->types)) { @@ -267,7 +267,7 @@ public function getOperationType($operation) * * @api */ - public function getQueryType() + public function getQueryType() : ?Type { return $this->config->query; } @@ -279,7 +279,7 @@ public function getQueryType() * * @api */ - public function getMutationType() + public function getMutationType() : ?Type { return $this->config->mutation; } @@ -291,7 +291,7 @@ public function getMutationType() * * @api */ - public function getSubscriptionType() + public function getSubscriptionType() : ?Type { return $this->config->subscription; } @@ -446,10 +446,7 @@ public function getDirective(string $name) : ?Directive return null; } - /** - * @return SchemaDefinitionNode|null - */ - public function getAstNode() + public function getAstNode() : ?SchemaDefinitionNode { return $this->config->getAstNode(); } diff --git a/src/Type/SchemaValidationContext.php b/src/Type/SchemaValidationContext.php index c7fbb27d3..dcaee67d0 100644 --- a/src/Type/SchemaValidationContext.php +++ b/src/Type/SchemaValidationContext.php @@ -71,7 +71,7 @@ public function getErrors() return $this->errors; } - public function validateRootTypes() + public function validateRootTypes() : void { $queryType = $this->schema->getQueryType(); if (! $queryType) { @@ -95,7 +95,7 @@ public function validateRootTypes() } $subscriptionType = $this->schema->getSubscriptionType(); - if (! $subscriptionType || $subscriptionType instanceof ObjectType) { + if ($subscriptionType === null || $subscriptionType instanceof ObjectType) { return; } diff --git a/src/Utils/SchemaPrinter.php b/src/Utils/SchemaPrinter.php index a45c99cf4..098912d8c 100644 --- a/src/Utils/SchemaPrinter.php +++ b/src/Utils/SchemaPrinter.php @@ -102,26 +102,26 @@ static function ($type) use ($options) { ); } - private static function printSchemaDefinition(Schema $schema) + private static function printSchemaDefinition(Schema $schema) : string { if (self::isSchemaOfCommonNames($schema)) { - return; + return ''; } $operationTypes = []; $queryType = $schema->getQueryType(); - if ($queryType) { + if ($queryType !== null) { $operationTypes[] = sprintf(' query: %s', $queryType->name); } $mutationType = $schema->getMutationType(); - if ($mutationType) { + if ($mutationType !== null) { $operationTypes[] = sprintf(' mutation: %s', $mutationType->name); } $subscriptionType = $schema->getSubscriptionType(); - if ($subscriptionType) { + if ($subscriptionType !== null) { $operationTypes[] = sprintf(' subscription: %s', $subscriptionType->name); } @@ -140,21 +140,21 @@ private static function printSchemaDefinition(Schema $schema) * * When using this naming convention, the schema description can be omitted. */ - private static function isSchemaOfCommonNames(Schema $schema) + private static function isSchemaOfCommonNames(Schema $schema) : bool { $queryType = $schema->getQueryType(); - if ($queryType && $queryType->name !== 'Query') { + if ($queryType !== null && $queryType->name !== 'Query') { return false; } $mutationType = $schema->getMutationType(); - if ($mutationType && $mutationType->name !== 'Mutation') { + if ($mutationType !== null && $mutationType->name !== 'Mutation') { return false; } $subscriptionType = $schema->getSubscriptionType(); - return ! $subscriptionType || $subscriptionType->name === 'Subscription'; + return $subscriptionType === null || $subscriptionType->name === 'Subscription'; } private static function printDirective($directive, $options) : string diff --git a/src/Validator/Rules/LoneSchemaDefinition.php b/src/Validator/Rules/LoneSchemaDefinition.php index 547b0fdd8..bd89f8c46 100644 --- a/src/Validator/Rules/LoneSchemaDefinition.php +++ b/src/Validator/Rules/LoneSchemaDefinition.php @@ -31,10 +31,10 @@ public function getSDLVisitor(SDLValidationContext $context) $oldSchema = $context->getSchema(); $alreadyDefined = $oldSchema !== null ? ( - $oldSchema->getAstNode() || - $oldSchema->getQueryType() || - $oldSchema->getMutationType() || - $oldSchema->getSubscriptionType() + $oldSchema->getAstNode() !== null || + $oldSchema->getQueryType() !== null || + $oldSchema->getMutationType() !== null || + $oldSchema->getSubscriptionType() !== null ) : false; diff --git a/tests/Executor/Promise/AmpPromiseAdapterTest.php b/tests/Executor/Promise/AmpPromiseAdapterTest.php index 53f6a5918..09e2090be 100644 --- a/tests/Executor/Promise/AmpPromiseAdapterTest.php +++ b/tests/Executor/Promise/AmpPromiseAdapterTest.php @@ -11,6 +11,7 @@ use Amp\Promise; use Amp\Success; use Exception; +use Generator; use GraphQL\Executor\Promise\Adapter\AmpPromiseAdapter; use PHPUnit\Framework\TestCase; use stdClass; @@ -36,7 +37,7 @@ public function testIsThenableReturnsTrueWhenAnAmpPromiseIsGiven() : void $ampAdapter = new AmpPromiseAdapter(); self::assertTrue( - $ampAdapter->isThenable(call(static function () { + $ampAdapter->isThenable(call(static function () : Generator { yield from []; })) ); @@ -44,7 +45,7 @@ public function testIsThenableReturnsTrueWhenAnAmpPromiseIsGiven() : void self::assertTrue($ampAdapter->isThenable(new Failure(new Exception()))); self::assertTrue($ampAdapter->isThenable(new Delayed(0))); self::assertTrue( - $ampAdapter->isThenable(new LazyPromise(static function () { + $ampAdapter->isThenable(new LazyPromise(static function () : void { })) ); self::assertFalse($ampAdapter->isThenable(false)); @@ -78,7 +79,7 @@ public function testThen() : void $resultPromise = $ampAdapter->then( $promise, - static function ($value) use (&$result) { + static function ($value) use (&$result) : void { $result = $value; } ); @@ -91,7 +92,7 @@ static function ($value) use (&$result) { public function testCreate() : void { $ampAdapter = new AmpPromiseAdapter(); - $resolvedPromise = $ampAdapter->create(static function ($resolve) { + $resolvedPromise = $ampAdapter->create(static function ($resolve) : void { $resolve(1); }); @@ -100,7 +101,7 @@ public function testCreate() : void $result = null; - $resolvedPromise->then(static function ($value) use (&$result) { + $resolvedPromise->then(static function ($value) use (&$result) : void { $result = $value; }); @@ -117,7 +118,7 @@ public function testCreateFulfilled() : void $result = null; - $fulfilledPromise->then(static function ($value) use (&$result) { + $fulfilledPromise->then(static function ($value) use (&$result) : void { $result = $value; }); @@ -136,7 +137,7 @@ public function testCreateRejected() : void $rejectedPromise->then( null, - static function ($error) use (&$exception) { + static function ($error) use (&$exception) : void { $exception = $error; } ); @@ -157,7 +158,7 @@ public function testAll() : void $result = null; - $allPromise->then(static function ($values) use (&$result) { + $allPromise->then(static function ($values) use (&$result) : void { $result = $values; }); @@ -171,7 +172,7 @@ public function testAllShouldPreserveTheOrderOfTheArrayWhenResolvingAsyncPromise $promises = [new Success(1), 2, $deferred->promise(), new Success(4)]; $result = null; - $ampAdapter->all($promises)->then(static function ($values) use (&$result) { + $ampAdapter->all($promises)->then(static function ($values) use (&$result) : void { $result = $values; }); From 486f90c1dd5627ff32224c6436a9b130f6dabbee Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Fri, 28 Feb 2020 20:43:01 +0100 Subject: [PATCH 158/256] Upgrade PHPStan patch version --- composer.json | 6 +++--- src/Utils/AST.php | 2 +- src/Validator/Rules/ValuesOfCorrectType.php | 2 +- tests/Language/VisitorTest.php | 1 + 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 3d8e22f69..46f6a8cce 100644 --- a/composer.json +++ b/composer.json @@ -18,9 +18,9 @@ "doctrine/coding-standard": "^6.0", "phpbench/phpbench": "^0.14", "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "0.12.2", - "phpstan/phpstan-phpunit": "0.12.1", - "phpstan/phpstan-strict-rules": "0.12.0", + "phpstan/phpstan": "0.12.11", + "phpstan/phpstan-phpunit": "0.12.6", + "phpstan/phpstan-strict-rules": "0.12.2", "phpunit/phpunit": "^7.2|^8.5", "psr/http-message": "^1.0", "react/promise": "2.*", diff --git a/src/Utils/AST.php b/src/Utils/AST.php index d493c1b2c..15e157518 100644 --- a/src/Utils/AST.php +++ b/src/Utils/AST.php @@ -529,7 +529,7 @@ static function ($node) use ($variables) { case $valueNode instanceof ObjectValueNode: return array_combine( array_map( - static function ($field) { + static function ($field) : string { return $field->name->value; }, iterator_to_array($valueNode->fields) diff --git a/src/Validator/Rules/ValuesOfCorrectType.php b/src/Validator/Rules/ValuesOfCorrectType.php index 359f4cd1d..2e70abff6 100644 --- a/src/Validator/Rules/ValuesOfCorrectType.php +++ b/src/Validator/Rules/ValuesOfCorrectType.php @@ -239,7 +239,7 @@ private function enumTypeSuggestion($type, ValueNode $node) $suggestions = Utils::suggestionList( Printer::doPrint($node), array_map( - static function (EnumValueDefinition $value) { + static function (EnumValueDefinition $value) : string { return $value->name; }, $type->getValues() diff --git a/tests/Language/VisitorTest.php b/tests/Language/VisitorTest.php index 6f927a6f8..0e0787832 100644 --- a/tests/Language/VisitorTest.php +++ b/tests/Language/VisitorTest.php @@ -207,6 +207,7 @@ public function testAllowsEditingNodeOnEnterAndOnLeave() : void self::assertNotEquals($ast, $editedAst); + /** @var DocumentNode $expected */ $expected = $ast->cloneDeep(); $expected->definitions[0]->didEnter = true; $expected->definitions[0]->didLeave = true; From 5a9d2dae7a335201f92a896a3be46bfb5c19d636 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Podlipsk=C3=BD?= Date: Fri, 28 Feb 2020 21:08:29 +0100 Subject: [PATCH 159/256] Drop Travis (#627) --- .travis.yml | 53 ----------------------------------------------------- 1 file changed, 53 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index af385f34a..000000000 --- a/.travis.yml +++ /dev/null @@ -1,53 +0,0 @@ -dist: trusty -language: php - -php: - - 7.1 - - 7.2 - - 7.3 - - 7.4snapshot - - nightly - -env: - matrix: - - EXECUTOR= DEPENDENCIES=--prefer-lowest - - EXECUTOR=coroutine DEPENDENCIES=--prefer-lowest - - EXECUTOR= - - EXECUTOR=coroutine - - -cache: - directories: - - $HOME/.composer/cache - -before_install: - - mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{,.disabled} || echo "xdebug not available" - - travis_retry composer self-update - -install: travis_retry composer update --prefer-dist - -script: ./vendor/bin/phpunit --group default,ReactPromise - -jobs: - allow_failures: - - php: 7.4snapshot - - php: nightly - - include: - - stage: Test - install: - - travis_retry composer update --prefer-dist {$DEPENDENCIES} - - - stage: Code Quality - php: 7.1 - env: CODING_STANDARD - install: travis_retry composer install --prefer-dist - script: - - ./vendor/bin/phpcs - - - stage: Code Quality - php: 7.1 - env: STATIC_ANALYSIS - install: travis_retry composer install --prefer-dist - script: composer stan - From 91eda4470842563b32fcf85fccc1e3136722eda6 Mon Sep 17 00:00:00 2001 From: Yury Antonau Date: Thu, 12 Mar 2020 22:50:23 +0300 Subject: [PATCH 160/256] Working at any nesting level, impoved tests --- src/Type/Definition/QueryPlan.php | 21 ++-- tests/Type/QueryPlanTest.php | 196 ++++++++++++------------------ 2 files changed, 89 insertions(+), 128 deletions(-) diff --git a/src/Type/Definition/QueryPlan.php b/src/Type/Definition/QueryPlan.php index 03788232b..8a4f55921 100644 --- a/src/Type/Definition/QueryPlan.php +++ b/src/Type/Definition/QueryPlan.php @@ -161,18 +161,20 @@ private function analyzeQueryPlan(ObjectType $parentType, iterable $fieldNodes) * * @throws Error */ - private function analyzeSelectionSet(SelectionSetNode $selectionSet, Type $parentType, array &$implementors = []) : array + private function analyzeSelectionSet(SelectionSetNode $selectionSet, Type $parentType, array &$implementors) : array { - $fields = []; + $fields = []; + $implementors = []; foreach ($selectionSet->selections as $selectionNode) { if ($selectionNode instanceof FieldNode) { $fieldName = $selectionNode->name->value; $type = $parentType->getField($fieldName); $selectionType = $type->getType(); - $subfields = []; + $subfields = []; + $subImplementors = []; if ($selectionNode->selectionSet) { - $subfields = $this->analyzeSubFields($selectionType, $selectionNode->selectionSet); + $subfields = $this->analyzeSubFields($selectionType, $selectionNode->selectionSet, $subImplementors); } $fields[$fieldName] = [ @@ -180,6 +182,9 @@ private function analyzeSelectionSet(SelectionSetNode $selectionSet, Type $paren 'fields' => $subfields ?? [], 'args' => Values::getArgumentValues($type, $selectionNode, $this->variableValues), ]; + if ($this->groupImplementorFields && $subImplementors) { + $fields[$fieldName]['implementors'] = $subImplementors; + } } elseif ($selectionNode instanceof FragmentSpreadNode) { $spreadName = $selectionNode->name->value; if (isset($this->fragments[$spreadName])) { @@ -199,17 +204,19 @@ private function analyzeSelectionSet(SelectionSetNode $selectionSet, Type $paren } /** + * @param mixed[] $implementors + * * @return mixed[] */ - private function analyzeSubFields(Type $type, SelectionSetNode $selectionSet) : array + private function analyzeSubFields(Type $type, SelectionSetNode $selectionSet, array &$implementors = []) : array { if ($type instanceof WrappingType) { $type = $type->getWrappedType(); } $subfields = []; - if ($type instanceof ObjectType) { - $subfields = $this->analyzeSelectionSet($selectionSet, $type); + if ($type instanceof ObjectType || $type instanceof AbstractType) { + $subfields = $this->analyzeSelectionSet($selectionSet, $type, $implementors); $this->types[$type->name] = array_unique(array_merge( array_key_exists($type->name, $this->types) ? $this->types[$type->name] : [], array_keys($subfields) diff --git a/tests/Type/QueryPlanTest.php b/tests/Type/QueryPlanTest.php index 0e0e5abfa..adb9f0c36 100644 --- a/tests/Type/QueryPlanTest.php +++ b/tests/Type/QueryPlanTest.php @@ -700,7 +700,7 @@ public function testMergedFragmentsQueryPlan() : void self::assertFalse($queryPlan->hasType('Test')); } - public function testQueryPlanOnInterfaceGroupingImplementorFields() : void + public function testQueryPlanGroupingImplementorFieldsForAbstractTypes() : void { $car = null; @@ -715,6 +715,30 @@ public function testQueryPlanOnInterfaceGroupingImplementorFields() : void }, ]); + $manualTransmission = new ObjectType([ + 'name' => 'ManualTransmission', + 'fields' => [ + 'speed' => Type::int(), + 'overdrive' => Type::boolean(), + ], + ]); + + $automaticTransmission = new ObjectType([ + 'name' => 'AutomaticTransmission', + 'fields' => [ + 'speed' => Type::int(), + 'sportMode' => Type::boolean(), + ], + ]); + + $transmission = new UnionType([ + 'name' => 'Transmission', + 'types' => [$manualTransmission, $automaticTransmission], + 'resolveType' => static function () use ($manualTransmission) { + return $manualTransmission; + }, + ]); + $car = new ObjectType([ 'name' => 'Car', 'fields' => [ @@ -722,6 +746,7 @@ public function testQueryPlanOnInterfaceGroupingImplementorFields() : void 'owner' => Type::string(), 'mark' => Type::string(), 'model' => Type::string(), + 'transmission' => $transmission, ], 'interfaces' => [$item], ]); @@ -744,6 +769,16 @@ public function testQueryPlanOnInterfaceGroupingImplementorFields() : void ... on Car { mark model + transmission { + ... on ManualTransmission { + speed + overdrive + } + ... on AutomaticTransmission { + speed + sportMode + } + } } ... on Building { city @@ -786,6 +821,43 @@ public function testQueryPlanOnInterfaceGroupingImplementorFields() : void 'fields' => [], 'args' => [], ], + 'transmission' => [ + 'type' => $transmission, + 'fields' => [], + 'args' => [], + 'implementors' => [ + 'ManualTransmission' => [ + 'type' => $manualTransmission, + 'fields' => [ + 'speed' => [ + 'type' => Type::int(), + 'fields' => [], + 'args' => [], + ], + 'overdrive' => [ + 'type' => Type::boolean(), + 'fields' => [], + 'args' => [], + ], + ], + ], + 'AutomaticTransmission' => [ + 'type' => $automaticTransmission, + 'fields' => [ + 'speed' => [ + 'type' => Type::int(), + 'fields' => [], + 'args' => [], + ], + 'sportMode' => [ + 'type' => Type::boolean(), + 'fields' => [], + 'args' => [], + ], + ], + ], + ], + ], ], ], 'Building' => [ @@ -806,9 +878,9 @@ public function testQueryPlanOnInterfaceGroupingImplementorFields() : void ], ]; - $expectedReferencedTypes = ['Car', 'Building', 'Item']; + $expectedReferencedTypes = ['ManualTransmission', 'AutomaticTransmission', 'Transmission', 'Car', 'Building', 'Item']; - $expectedReferencedFields = ['mark', 'model', 'city', 'address', 'id', 'owner']; + $expectedReferencedFields = ['speed', 'overdrive', 'sportMode', 'mark', 'model', 'transmission', 'city', 'address', 'id', 'owner']; $expectedItemSubFields = ['id', 'owner']; $expectedBuildingSubFields = ['city', 'address']; @@ -846,122 +918,4 @@ public function testQueryPlanOnInterfaceGroupingImplementorFields() : void self::assertEquals($expectedItemSubFields, $queryPlan->subFields('Item')); self::assertEquals($expectedBuildingSubFields, $queryPlan->subFields('Building')); } - - public function testQueryPlanOnUnionGroupingImplementorFields() : void - { - $car = new ObjectType([ - 'name' => 'Car', - 'fields' => [ - 'mark' => Type::string(), - 'model' => Type::string(), - ], - ]); - - $building = new ObjectType([ - 'name' => 'Building', - 'fields' => [ - 'city' => Type::string(), - 'address' => Type::string(), - ], - ]); - - $item = new UnionType([ - 'name' => 'Item', - 'types' => [$car, $building], - 'resolveType' => static function () use ($car) { - return $car; - }, - ]); - - $query = '{ - item { - ... on Car { - mark - model - } - ... on Building { - city - } - ...BuildingFragment - } - } - fragment BuildingFragment on Building { - address - }'; - - $expectedResult = [ - 'data' => ['item' => null], - ]; - - $expectedQueryPlan = [ - 'fields' => [], - 'implementors' => [ - 'Car' => [ - 'type' => $car, - 'fields' => [ - 'mark' => [ - 'type' => Type::string(), - 'fields' => [], - 'args' => [], - ], - 'model' => [ - 'type' => Type::string(), - 'fields' => [], - 'args' => [], - ], - ], - ], - 'Building' => [ - 'type' => $building, - 'fields' => [ - 'city' => [ - 'type' => Type::string(), - 'fields' => [], - 'args' => [], - ], - 'address' => [ - 'type' => Type::string(), - 'fields' => [], - 'args' => [], - ], - ], - ], - ], - ]; - - $expectedReferencedTypes = ['Car', 'Building', 'Item']; - - $expectedReferencedFields = ['mark', 'model', 'city', 'address']; - - $expectedBuildingSubFields = ['city', 'address']; - - $hasCalled = false; - /** @var QueryPlan $queryPlan */ - $queryPlan = null; - - $root = new ObjectType([ - 'name' => 'Query', - 'fields' => [ - 'item' => [ - 'type' => $item, - 'resolve' => static function ($value, $args, $context, ResolveInfo $info) use (&$hasCalled, &$queryPlan) { - $hasCalled = true; - $queryPlan = $info->lookAhead(['group-implementor-fields']); - - return null; - }, - ], - ], - ]); - - $schema = new Schema(['query' => $root]); - $result = GraphQL::executeQuery($schema, $query)->toArray(); - - self::assertTrue($hasCalled); - self::assertEquals($expectedResult, $result); - self::assertEquals($expectedQueryPlan, $queryPlan->queryPlan()); - self::assertEquals($expectedReferencedTypes, $queryPlan->getReferencedTypes()); - self::assertEquals($expectedReferencedFields, $queryPlan->getReferencedFields()); - self::assertEquals($expectedBuildingSubFields, $queryPlan->subFields('Building')); - } } From ab5b950980961474d9f581dffbb9ffeb13835b6a Mon Sep 17 00:00:00 2001 From: Oliver THEBAULT Date: Fri, 10 Apr 2020 10:32:22 +0200 Subject: [PATCH 161/256] print @deprecated directive when deprecationReason is empty string (#631) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test(deprecated): add @deprecated directive tests for SchemaPrinter * fix(deprecated): put @deprecated directive when deprecationReason is empty string Co-authored-by: Oliver Thébault --- CHANGELOG.md | 1 + phpstan-baseline.neon | 2 +- src/Utils/SchemaPrinter.php | 2 +- tests/Utils/SchemaPrinterTest.php | 42 +++++++++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5500eec86..6da18f874 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ + Cannot return null for non-nullable field "parentType.fieldName". ``` - Simplified Deferred implementation +- Having an empty string in `deprecationReason` will now print the `@deprecated` directive (only a `null` `deprecationReason` won't print the `@deprecated` directive). #### v0.13.5 - Fix coroutine executor when using with promise (#486) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 0fbb97f52..09f3df772 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -902,7 +902,7 @@ parameters: - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 3 + count: 2 path: src/Utils/SchemaPrinter.php - diff --git a/src/Utils/SchemaPrinter.php b/src/Utils/SchemaPrinter.php index 098912d8c..ccbb00734 100644 --- a/src/Utils/SchemaPrinter.php +++ b/src/Utils/SchemaPrinter.php @@ -393,7 +393,7 @@ static function ($f, $i) use ($options) { private static function printDeprecated($fieldOrEnumVal) : string { $reason = $fieldOrEnumVal->deprecationReason; - if (empty($reason)) { + if ($reason === null) { return ''; } if ($reason === '' || $reason === Directive::DEFAULT_DEPRECATION_REASON) { diff --git a/tests/Utils/SchemaPrinterTest.php b/tests/Utils/SchemaPrinterTest.php index 7131875f1..ab54cdf1b 100644 --- a/tests/Utils/SchemaPrinterTest.php +++ b/tests/Utils/SchemaPrinterTest.php @@ -4,6 +4,7 @@ namespace GraphQL\Tests\Utils; +use Generator; use GraphQL\Language\DirectiveLocation; use GraphQL\Type\Definition\CustomScalarType; use GraphQL\Type\Definition\Directive; @@ -148,6 +149,47 @@ public function testPrintNonNullArrayNonNullStringField() : void ); } + /** + * @see it('Prints Field With "@deprecated" Directive') + * + * @dataProvider deprecationReasonDataProvider + */ + public function testPrintDeprecatedField(?string $deprecationReason, string $expectedDeprecationDirective) : void + { + $output = $this->printSingleFieldSchema([ + 'type' => Type::int(), + 'deprecationReason' => $deprecationReason, + ]); + self::assertSame( + ' +type Query { + singleField: Int' . $expectedDeprecationDirective . ' +} +', + $output + ); + } + + public function deprecationReasonDataProvider() : Generator + { + yield 'when deprecationReason is null' => [ + null, + '', + ]; + yield 'when deprecationReason is empty string' => [ + '', + ' @deprecated', + ]; + yield 'when deprecationReason is the default deprecation reason' => [ + Directive::DEFAULT_DEPRECATION_REASON, + ' @deprecated', + ]; + yield 'when deprecationReason is not empty string' => [ + 'this is deprecated', + ' @deprecated(reason: "this is deprecated")', + ]; + } + /** * @see it('Print Object Field') */ From ba8309d36c136fbb54d324076af41871602b7516 Mon Sep 17 00:00:00 2001 From: Leo Cavalcante Date: Fri, 10 Apr 2020 05:33:00 -0300 Subject: [PATCH 162/256] Update complementary-tools.md (#639) --- docs/complementary-tools.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/complementary-tools.md b/docs/complementary-tools.md index 5dac38789..5262649e7 100644 --- a/docs/complementary-tools.md +++ b/docs/complementary-tools.md @@ -6,6 +6,7 @@ * [Laravel GraphQL](https://github.com/rebing/graphql-laravel) - Laravel wrapper for Facebook's GraphQL * [OverblogGraphQLBundle](https://github.com/overblog/GraphQLBundle) – Bundle for Symfony * [WP-GraphQL](https://github.com/wp-graphql/wp-graphql) - GraphQL API for WordPress +* [Siler](https://github.com/leocavalcante/siler) - Straightforward way to map GraphQL SDL to resolver callables, also built-in support for Swoole # GraphQL PHP Tools From af5c04b7fac81b174b3b3bfc4433fe6912329477 Mon Sep 17 00:00:00 2001 From: Nicolas Ettlin Date: Fri, 10 Apr 2020 10:33:27 +0200 Subject: [PATCH 163/256] Fix typo in example (#635) --- examples/01-blog/graphql.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/01-blog/graphql.php b/examples/01-blog/graphql.php index 4da8f1ee0..31657b1eb 100644 --- a/examples/01-blog/graphql.php +++ b/examples/01-blog/graphql.php @@ -11,7 +11,7 @@ use \GraphQL\Error\FormattedError; use \GraphQL\Error\Debug; -// Disable default PHP error reporting - we have better one for debug mode (see bellow) +// Disable default PHP error reporting - we have better one for debug mode (see below) ini_set('display_errors', 0); $debug = false; From edf1ff719a9d8d37e36b3d9f2de2c25eada0a91e Mon Sep 17 00:00:00 2001 From: spawnia Date: Mon, 13 Apr 2020 11:38:02 +0200 Subject: [PATCH 164/256] Add .phpunit.result.cache to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2bf79fe99..a83e3c93b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .phpcs-cache +.phpunit.result.cache composer.lock composer.phar phpcs.xml From a2780a0da01f6f106940d82e8ddff51314afd697 Mon Sep 17 00:00:00 2001 From: Max Loeb Date: Thu, 23 Apr 2020 21:58:33 -0700 Subject: [PATCH 165/256] Lock CodeSniffer to 3.5.4 https://github.com/webonyx/graphql-php/pull/645#issuecomment-615956718 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 46f6a8cce..6588612e4 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "psr/http-message": "^1.0", "react/promise": "2.*", "simpod/php-coveralls-mirror": "^3.0", - "squizlabs/php_codesniffer": "^3.5.2" + "squizlabs/php_codesniffer": "3.5.4" }, "config": { "preferred-install": "dist", From 7412bf62663485aed49ee4d0b0797fa8a52a5692 Mon Sep 17 00:00:00 2001 From: Max Loeb Date: Sat, 25 Apr 2020 09:34:22 -0700 Subject: [PATCH 166/256] Fix return type (#650) --- src/Type/Definition/ResolveInfo.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Type/Definition/ResolveInfo.php b/src/Type/Definition/ResolveInfo.php index ea42f23e1..a2620ea2b 100644 --- a/src/Type/Definition/ResolveInfo.php +++ b/src/Type/Definition/ResolveInfo.php @@ -175,7 +175,7 @@ public function __construct( * * @param int $depth How many levels to include in output * - * @return bool[] + * @return array * * @api */ From 2f72dd6af05027ef0e291b95459caa847f1a1013 Mon Sep 17 00:00:00 2001 From: Max Loeb Date: Sun, 26 Apr 2020 01:08:50 -0700 Subject: [PATCH 167/256] fix return type (#652) --- src/Type/Definition/ResolveInfo.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Type/Definition/ResolveInfo.php b/src/Type/Definition/ResolveInfo.php index a2620ea2b..7049a1940 100644 --- a/src/Type/Definition/ResolveInfo.php +++ b/src/Type/Definition/ResolveInfo.php @@ -175,7 +175,7 @@ public function __construct( * * @param int $depth How many levels to include in output * - * @return array + * @return array * * @api */ From 3c37c518ff558f63fd0ce0be64ab0fed576ea48f Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Mon, 27 Apr 2020 09:50:34 +0200 Subject: [PATCH 168/256] Make type hint for OperationDefinitionNode#name nullable (#653) --- phpstan-baseline.neon | 33 +++---------------- src/Language/AST/OperationDefinitionNode.php | 2 +- src/Language/Printer.php | 2 +- .../Rules/LoneAnonymousOperation.php | 2 +- src/Validator/Rules/NoUndefinedVariables.php | 4 ++- src/Validator/Rules/NoUnusedVariables.php | 4 ++- src/Validator/Rules/UniqueOperationNames.php | 2 +- 7 files changed, 14 insertions(+), 35 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 09f3df772..af4e81d7b 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -300,11 +300,6 @@ parameters: count: 18 path: src/Language/Printer.php - - - message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Language\\\\AST\\\\NameNode given\\.$#" - count: 1 - path: src/Language/Printer.php - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\AST\\\\NameNode\"\\.$#" count: 1 @@ -1155,11 +1150,6 @@ parameters: count: 1 path: src/Validator/Rules/LoneAnonymousOperation.php - - - message: "#^Only booleans are allowed in \\|\\|, GraphQL\\\\Language\\\\AST\\\\NameNode given on the left side\\.$#" - count: 1 - path: src/Validator/Rules/LoneAnonymousOperation.php - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" count: 1 @@ -1190,11 +1180,6 @@ parameters: count: 1 path: src/Validator/Rules/NoUndefinedVariables.php - - - message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Language\\\\AST\\\\NameNode given\\.$#" - count: 1 - path: src/Validator/Rules/NoUndefinedVariables.php - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\VisitorOperation\"\\.$#" count: 2 @@ -1215,11 +1200,6 @@ parameters: count: 3 path: src/Validator/Rules/NoUnusedVariables.php - - - message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Language\\\\AST\\\\NameNode given\\.$#" - count: 1 - path: src/Validator/Rules/NoUnusedVariables.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 1 @@ -1460,11 +1440,6 @@ parameters: count: 2 path: src/Validator/Rules/UniqueOperationNames.php - - - message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Language\\\\AST\\\\NameNode given\\.$#" - count: 1 - path: src/Validator/Rules/UniqueOperationNames.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 1 @@ -1496,22 +1471,22 @@ parameters: path: src/Validator/Rules/ValuesOfCorrectType.php - - message: "#^Only booleans are allowed in \\|\\|, GraphQL\\\\Type\\\\Definition\\\\EnumType\\|GraphQL\\\\Type\\\\Definition\\\\InputObjectType\\|GraphQL\\\\Type\\\\Definition\\\\ListOfType\\|GraphQL\\\\Type\\\\Definition\\\\NonNull\\|GraphQL\\\\Type\\\\Definition\\\\ScalarType given on the left side\\.$#" + message: "#^Anonymous function should have native return typehint \"string\"\\.$#" count: 1 path: src/Validator/Rules/ValuesOfCorrectType.php - - message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\EnumValueDefinition\\|null given\\.$#" + message: "#^Only booleans are allowed in \\|\\|, GraphQL\\\\Type\\\\Definition\\\\EnumType\\|GraphQL\\\\Type\\\\Definition\\\\InputObjectType\\|GraphQL\\\\Type\\\\Definition\\\\ListOfType\\|GraphQL\\\\Type\\\\Definition\\\\NonNull\\|GraphQL\\\\Type\\\\Definition\\\\ScalarType given on the left side\\.$#" count: 1 path: src/Validator/Rules/ValuesOfCorrectType.php - - message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\EnumType\\|GraphQL\\\\Type\\\\Definition\\\\InputObjectType\\|GraphQL\\\\Type\\\\Definition\\\\ListOfType\\|GraphQL\\\\Type\\\\Definition\\\\NonNull\\|GraphQL\\\\Type\\\\Definition\\\\ScalarType given\\.$#" + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\EnumValueDefinition\\|null given\\.$#" count: 1 path: src/Validator/Rules/ValuesOfCorrectType.php - - message: "#^Anonymous function should have native return typehint \"string\"\\.$#" + message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\EnumType\\|GraphQL\\\\Type\\\\Definition\\\\InputObjectType\\|GraphQL\\\\Type\\\\Definition\\\\ListOfType\\|GraphQL\\\\Type\\\\Definition\\\\NonNull\\|GraphQL\\\\Type\\\\Definition\\\\ScalarType given\\.$#" count: 1 path: src/Validator/Rules/ValuesOfCorrectType.php diff --git a/src/Language/AST/OperationDefinitionNode.php b/src/Language/AST/OperationDefinitionNode.php index cef005353..ed5d2fd29 100644 --- a/src/Language/AST/OperationDefinitionNode.php +++ b/src/Language/AST/OperationDefinitionNode.php @@ -9,7 +9,7 @@ class OperationDefinitionNode extends Node implements ExecutableDefinitionNode, /** @var string */ public $kind = NodeKind::OPERATION_DEFINITION; - /** @var NameNode */ + /** @var NameNode|null */ public $name; /** @var string (oneOf 'query', 'mutation')) */ diff --git a/src/Language/Printer.php b/src/Language/Printer.php index e54eee601..54b526d55 100644 --- a/src/Language/Printer.php +++ b/src/Language/Printer.php @@ -120,7 +120,7 @@ public function printAST($ast) // Anonymous queries with no directives or variable definitions can use // the query short form. - return ! $name && ! $directives && ! $varDefs && $op === 'query' + return $name === null && ! $directives && ! $varDefs && $op === 'query' ? $selectionSet : $this->join([$op, $this->join([$name, $varDefs]), $directives, $selectionSet], ' '); }, diff --git a/src/Validator/Rules/LoneAnonymousOperation.php b/src/Validator/Rules/LoneAnonymousOperation.php index e87530955..ebfba6299 100644 --- a/src/Validator/Rules/LoneAnonymousOperation.php +++ b/src/Validator/Rules/LoneAnonymousOperation.php @@ -40,7 +40,7 @@ static function (Node $definition) { &$operationCount, $context ) { - if ($node->name || $operationCount <= 1) { + if ($node->name !== null || $operationCount <= 1) { return; } diff --git a/src/Validator/Rules/NoUndefinedVariables.php b/src/Validator/Rules/NoUndefinedVariables.php index c0cd22c75..f59d03f6a 100644 --- a/src/Validator/Rules/NoUndefinedVariables.php +++ b/src/Validator/Rules/NoUndefinedVariables.php @@ -40,7 +40,9 @@ public function getVisitor(ValidationContext $context) $context->reportError(new Error( self::undefinedVarMessage( $varName, - $operation->name ? $operation->name->value : null + $operation->name !== null + ? $operation->name->value + : null ), [$node, $operation] )); diff --git a/src/Validator/Rules/NoUnusedVariables.php b/src/Validator/Rules/NoUnusedVariables.php index e8f7ff353..d6a4486d7 100644 --- a/src/Validator/Rules/NoUnusedVariables.php +++ b/src/Validator/Rules/NoUnusedVariables.php @@ -28,7 +28,9 @@ public function getVisitor(ValidationContext $context) 'leave' => function (OperationDefinitionNode $operation) use ($context) { $variableNameUsed = []; $usages = $context->getRecursiveVariableUsages($operation); - $opName = $operation->name ? $operation->name->value : null; + $opName = $operation->name !== null + ? $operation->name->value + : null; foreach ($usages as $usage) { $node = $usage['node']; diff --git a/src/Validator/Rules/UniqueOperationNames.php b/src/Validator/Rules/UniqueOperationNames.php index 969b90e7f..761ce2cba 100644 --- a/src/Validator/Rules/UniqueOperationNames.php +++ b/src/Validator/Rules/UniqueOperationNames.php @@ -25,7 +25,7 @@ public function getVisitor(ValidationContext $context) NodeKind::OPERATION_DEFINITION => function (OperationDefinitionNode $node) use ($context) { $operationName = $node->name; - if ($operationName) { + if ($operationName !== null) { if (empty($this->knownOperationNames[$operationName->value])) { $this->knownOperationNames[$operationName->value] = $operationName; } else { From 2b9f2ecc2c046b7b1958d8f880e2bc3547b31156 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Mon, 27 Apr 2020 09:51:35 +0200 Subject: [PATCH 169/256] Make type hint for InputValueDefinitionNode#defaultValue nullable (#654) --- phpstan-baseline.neon | 5 ----- src/Language/AST/InputValueDefinitionNode.php | 2 +- src/Utils/ASTDefinitionBuilder.php | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index af4e81d7b..1be9308e7 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -810,11 +810,6 @@ parameters: count: 1 path: src/Utils/ASTDefinitionBuilder.php - - - message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Language\\\\AST\\\\BooleanValueNode\\|GraphQL\\\\Language\\\\AST\\\\EnumValueNode\\|GraphQL\\\\Language\\\\AST\\\\FloatValueNode\\|GraphQL\\\\Language\\\\AST\\\\IntValueNode\\|GraphQL\\\\Language\\\\AST\\\\ListValueNode\\|GraphQL\\\\Language\\\\AST\\\\NullValueNode\\|GraphQL\\\\Language\\\\AST\\\\ObjectValueNode\\|GraphQL\\\\Language\\\\AST\\\\StringValueNode\\|GraphQL\\\\Language\\\\AST\\\\VariableNode given\\.$#" - count: 1 - path: src/Utils/ASTDefinitionBuilder.php - - message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" count: 2 diff --git a/src/Language/AST/InputValueDefinitionNode.php b/src/Language/AST/InputValueDefinitionNode.php index 647de0923..34ec2766a 100644 --- a/src/Language/AST/InputValueDefinitionNode.php +++ b/src/Language/AST/InputValueDefinitionNode.php @@ -15,7 +15,7 @@ class InputValueDefinitionNode extends Node /** @var NamedTypeNode|ListTypeNode|NonNullTypeNode */ public $type; - /** @var VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode */ + /** @var VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode|null */ public $defaultValue; /** @var DirectiveNode[] */ diff --git a/src/Utils/ASTDefinitionBuilder.php b/src/Utils/ASTDefinitionBuilder.php index 851f1f430..d9902b1f2 100644 --- a/src/Utils/ASTDefinitionBuilder.php +++ b/src/Utils/ASTDefinitionBuilder.php @@ -472,7 +472,7 @@ public function buildInputField(InputValueDefinitionNode $value) : array 'astNode' => $value, ]; - if ($value->defaultValue) { + if ($value->defaultValue !== null) { $config['defaultValue'] = $value->defaultValue; } From e1bce3275b3b3229d98e4db28183001990c3c64f Mon Sep 17 00:00:00 2001 From: Max Loeb Date: Mon, 27 Apr 2020 22:46:25 -0700 Subject: [PATCH 170/256] Fix native return typehint errors (#647) --- composer.json | 4 +- phpstan-baseline.neon | 570 ------------------ src/Error/Error.php | 2 +- src/Executor/ReferenceExecutor.php | 2 +- .../Executor/CoroutineExecutor.php | 6 +- src/GraphQL.php | 2 +- src/Language/AST/IntValueNode.php | 2 +- src/Language/AST/NamedTypeNode.php | 2 +- src/Language/AST/NodeList.php | 4 +- src/Language/Parser.php | 18 +- src/Language/Printer.php | 42 +- src/Server/Helper.php | 8 +- src/Type/Definition/QueryPlan.php | 4 +- src/Type/Introspection.php | 44 +- src/Type/SchemaValidationContext.php | 15 +- src/Utils/ASTDefinitionBuilder.php | 6 +- src/Utils/BreakingChangesFinder.php | 4 +- src/Utils/BuildSchema.php | 7 +- src/Utils/SchemaExtender.php | 16 +- src/Utils/SchemaPrinter.php | 20 +- src/Utils/TypeInfo.php | 2 +- src/Utils/Utils.php | 6 +- src/Utils/Value.php | 2 +- src/Validator/DocumentValidator.php | 2 +- src/Validator/Rules/ExecutableDefinitions.php | 3 +- src/Validator/Rules/FieldsOnCorrectType.php | 2 +- .../Rules/FragmentsOnCompositeTypes.php | 4 +- src/Validator/Rules/KnownArgumentNames.php | 2 +- .../Rules/KnownArgumentNamesOnDirectives.php | 2 +- src/Validator/Rules/KnownFragmentNames.php | 2 +- src/Validator/Rules/KnownTypeNames.php | 5 +- .../Rules/LoneAnonymousOperation.php | 4 +- src/Validator/Rules/LoneSchemaDefinition.php | 2 +- src/Validator/Rules/NoFragmentCycles.php | 5 +- src/Validator/Rules/NoUndefinedVariables.php | 6 +- src/Validator/Rules/NoUnusedFragments.php | 7 +- src/Validator/Rules/NoUnusedVariables.php | 6 +- .../Rules/OverlappingFieldsCanBeMerged.php | 8 +- .../Rules/PossibleFragmentSpreads.php | 4 +- .../Rules/ProvidedRequiredArguments.php | 9 +- .../ProvidedRequiredArgumentsOnDirectives.php | 4 +- src/Validator/Rules/QueryComplexity.php | 7 +- src/Validator/Rules/QueryDepth.php | 2 +- src/Validator/Rules/ScalarLeafs.php | 2 +- src/Validator/Rules/UniqueArgumentNames.php | 7 +- .../Rules/UniqueDirectivesPerLocation.php | 2 +- src/Validator/Rules/UniqueFragmentNames.php | 5 +- src/Validator/Rules/UniqueInputFieldNames.php | 7 +- src/Validator/Rules/UniqueOperationNames.php | 5 +- src/Validator/Rules/UniqueVariableNames.php | 4 +- src/Validator/Rules/ValuesOfCorrectType.php | 23 +- .../Rules/VariablesAreInputTypes.php | 2 +- .../Rules/VariablesInAllowedPosition.php | 6 +- src/Validator/ValidationContext.php | 4 +- tests/Type/QueryPlanTest.php | 10 +- tests/Type/ResolveInfoTest.php | 4 +- tests/Type/SchemaTest.php | 4 +- tests/Type/StandardTypesTest.php | 6 +- tests/Type/TypeLoaderTest.php | 24 +- tests/Type/ValidationTest.php | 38 +- tests/Utils/ExtractTypesTest.php | 14 +- tests/Utils/MixedStoreTest.php | 4 +- tests/Utils/SchemaExtenderTest.php | 20 +- .../OverlappingFieldsCanBeMergedTest.php | 6 +- tests/Validator/QueryComplexityTest.php | 4 +- tests/Validator/ValidatorTestCase.php | 10 +- 66 files changed, 269 insertions(+), 816 deletions(-) diff --git a/composer.json b/composer.json index 6588612e4..3289dc143 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "doctrine/coding-standard": "^6.0", "phpbench/phpbench": "^0.14", "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "0.12.11", + "phpstan/phpstan": "0.12.18", "phpstan/phpstan-phpunit": "0.12.6", "phpstan/phpstan-strict-rules": "0.12.2", "phpunit/phpunit": "^7.2|^8.5", @@ -54,7 +54,7 @@ "lint" : "phpcs", "fix" : "phpcbf", "stan": "phpstan analyse --ansi --memory-limit 2048M", - "phpstan-baseline": "phpstan analyse --ansi --error-format baselineNeon > phpstan-baseline.neon", + "phpstan-baseline": "phpstan analyse --ansi --generate-baseline=phpstan-baseline.neon", "check": "composer lint && composer stan && composer test" } } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 1be9308e7..8565ae099 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -50,11 +50,6 @@ parameters: count: 1 path: src/Error/Error.php - - - message: "#^Anonymous function should have native return typehint \"\\?GraphQL\\\\Language\\\\SourceLocation\"\\.$#" - count: 1 - path: src/Error/Error.php - - message: "#^Only booleans are allowed in &&, GraphQL\\\\Language\\\\AST\\\\Location given on the left side\\.$#" count: 1 @@ -160,11 +155,6 @@ parameters: count: 4 path: src/Executor/ReferenceExecutor.php - - - message: "#^Anonymous function should have native return typehint \"\\?GraphQL\\\\Type\\\\Definition\\\\ObjectType\"\\.$#" - count: 1 - path: src/Executor/ReferenceExecutor.php - - message: "#^Only booleans are allowed in &&, GraphQL\\\\Language\\\\AST\\\\BooleanValueNode\\|GraphQL\\\\Language\\\\AST\\\\EnumValueNode\\|GraphQL\\\\Language\\\\AST\\\\FloatValueNode\\|GraphQL\\\\Language\\\\AST\\\\IntValueNode\\|GraphQL\\\\Language\\\\AST\\\\ListValueNode\\|GraphQL\\\\Language\\\\AST\\\\NullValueNode\\|GraphQL\\\\Language\\\\AST\\\\ObjectValueNode\\|GraphQL\\\\Language\\\\AST\\\\StringValueNode\\|GraphQL\\\\Language\\\\AST\\\\VariableNode\\|null given on the right side\\.$#" count: 1 @@ -200,11 +190,6 @@ parameters: count: 2 path: src/Experimental/Executor/CoroutineExecutor.php - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 3 - path: src/Experimental/Executor/CoroutineExecutor.php - - message: "#^Variable property access on object\\.$#" count: 2 @@ -225,11 +210,6 @@ parameters: count: 2 path: src/GraphQL.php - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 1 - path: src/GraphQL.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 1 @@ -255,61 +235,11 @@ parameters: count: 1 path: src/Language/Parser.php - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\AST\\\\VariableDefinitionNode\"\\.$#" - count: 1 - path: src/Language/Parser.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\AST\\\\SelectionNode\"\\.$#" - count: 1 - path: src/Language/Parser.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\AST\\\\ArgumentNode\"\\.$#" - count: 2 - path: src/Language/Parser.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\AST\\\\OperationTypeDefinitionNode\"\\.$#" - count: 1 - path: src/Language/Parser.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\AST\\\\FieldDefinitionNode\"\\.$#" - count: 1 - path: src/Language/Parser.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\AST\\\\InputValueDefinitionNode\"\\.$#" - count: 2 - path: src/Language/Parser.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\AST\\\\EnumValueDefinitionNode\"\\.$#" - count: 1 - path: src/Language/Parser.php - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" count: 1 path: src/Language/Printer.php - - - message: "#^Anonymous function should have native return typehint \"string\"\\.$#" - count: 18 - path: src/Language/Printer.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\AST\\\\NameNode\"\\.$#" - count: 1 - path: src/Language/Printer.php - - - - message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" - count: 3 - path: src/Language/Printer.php - - message: "#^Only booleans are allowed in a ternary operator condition, array\\\\|null given\\.$#" count: 2 @@ -395,21 +325,11 @@ parameters: count: 5 path: src/Server/Helper.php - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Error\\\\Error\"\\.$#" - count: 1 - path: src/Server/Helper.php - - message: "#^Only booleans are allowed in a ternary operator condition, string given\\.$#" count: 1 path: src/Server/Helper.php - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Executor\\\\ExecutionResult\"\\.$#" - count: 1 - path: src/Server/Helper.php - - message: "#^Only booleans are allowed in an if condition, \\(callable\\)\\|null given\\.$#" count: 1 @@ -420,11 +340,6 @@ parameters: count: 1 path: src/Server/Helper.php - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 2 - path: src/Server/Helper.php - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" count: 2 @@ -470,11 +385,6 @@ parameters: count: 1 path: src/Type/Definition/ObjectType.php - - - message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" - count: 2 - path: src/Type/Definition/QueryPlan.php - - message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Language\\\\AST\\\\SelectionSetNode\\|null given\\.$#" count: 1 @@ -495,61 +405,21 @@ parameters: count: 1 path: src/Type/Definition/Type.php - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 3 - path: src/Type/Introspection.php - - - - message: "#^Anonymous function should have native return typehint \"\\?GraphQL\\\\Type\\\\Definition\\\\ObjectType\"\\.$#" - count: 2 - path: src/Type/Introspection.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 3 path: src/Type/Introspection.php - - - message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" - count: 4 - path: src/Type/Introspection.php - - message: "#^Only booleans are allowed in a negated boolean, string\\|null given\\.$#" count: 2 path: src/Type/Introspection.php - - - message: "#^Anonymous function should have native return typehint \"\\?array\"\\.$#" - count: 3 - path: src/Type/Introspection.php - - - - message: "#^Anonymous function should have native return typehint \"\\?GraphQL\\\\Type\\\\Definition\\\\Type\"\\.$#" - count: 2 - path: src/Type/Introspection.php - - - - message: "#^Anonymous function should have native return typehint \"string\"\\.$#" - count: 3 - path: src/Type/Introspection.php - - - - message: "#^Anonymous function should have native return typehint \"\\?string\"\\.$#" - count: 4 - path: src/Type/Introspection.php - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" count: 1 path: src/Type/Introspection.php - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Schema\"\\.$#" - count: 1 - path: src/Type/Introspection.php - - message: "#^Only booleans are allowed in a negated boolean, array\\\\|\\(callable\\) given\\.$#" count: 1 @@ -610,21 +480,11 @@ parameters: count: 1 path: src/Type/SchemaValidationContext.php - - - message: "#^Anonymous function should have native return typehint \"\\?GraphQL\\\\Language\\\\AST\\\\DirectiveDefinitionNode\"\\.$#" - count: 1 - path: src/Type/SchemaValidationContext.php - - message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Error\\\\Error\\|null given\\.$#" count: 1 path: src/Type/SchemaValidationContext.php - - - message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" - count: 6 - path: src/Type/SchemaValidationContext.php - - message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Language\\\\AST\\\\InputValueDefinitionNode given\\.$#" count: 1 @@ -785,21 +645,11 @@ parameters: count: 1 path: src/Utils/ASTDefinitionBuilder.php - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\Type\"\\.$#" - count: 2 - path: src/Utils/ASTDefinitionBuilder.php - - message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Language\\\\AST\\\\NodeList\\\\|null given\\.$#" count: 1 path: src/Utils/ASTDefinitionBuilder.php - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 1 - path: src/Utils/ASTDefinitionBuilder.php - - message: "#^Only booleans are allowed in a ternary operator condition, array\\\\|null given\\.$#" count: 1 @@ -810,11 +660,6 @@ parameters: count: 1 path: src/Utils/ASTDefinitionBuilder.php - - - message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" - count: 2 - path: src/Utils/BreakingChangesFinder.php - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" count: 1 @@ -825,41 +670,11 @@ parameters: count: 3 path: src/Utils/BuildSchema.php - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 1 - path: src/Utils/BuildSchema.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\Type\"\\.$#" - count: 1 - path: src/Utils/BuildSchema.php - - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 1 - path: src/Utils/BuildSchema.php - - message: "#^Only booleans are allowed in &&, array\\\\|null given on the left side\\.$#" count: 1 path: src/Utils/PairSet.php - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 5 - path: src/Utils/SchemaExtender.php - - - - message: "#^Anonymous function should have native return typehint \"string\"\\.$#" - count: 1 - path: src/Utils/SchemaExtender.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\Directive\"\\.$#" - count: 1 - path: src/Utils/SchemaExtender.php - - message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Type\\\\Definition\\\\Type\\|null given\\.$#" count: 1 @@ -875,21 +690,6 @@ parameters: count: 1 path: src/Utils/SchemaExtender.php - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\Type\"\\.$#" - count: 1 - path: src/Utils/SchemaExtender.php - - - - message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" - count: 3 - path: src/Utils/SchemaPrinter.php - - - - message: "#^Anonymous function should have native return typehint \"string\"\\.$#" - count: 7 - path: src/Utils/SchemaPrinter.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 2 @@ -935,11 +735,6 @@ parameters: count: 1 path: src/Utils/TypeInfo.php - - - message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" - count: 1 - path: src/Utils/TypeInfo.php - - message: "#^Only booleans are allowed in &&, GraphQL\\\\Type\\\\Definition\\\\FieldArgument\\|null given on the left side\\.$#" count: 1 @@ -975,26 +770,11 @@ parameters: count: 1 path: src/Utils/Utils.php - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 1 - path: src/Utils/Utils.php - - - - message: "#^Anonymous function should have native return typehint \"string\"\\.$#" - count: 2 - path: src/Utils/Utils.php - - message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Type\\\\Definition\\\\EnumValueDefinition\\|null given\\.$#" count: 1 path: src/Utils/Value.php - - - message: "#^Anonymous function should have native return typehint \"string\"\\.$#" - count: 1 - path: src/Utils/Value.php - - message: "#^Only booleans are allowed in a ternary operator condition, array\\ given\\.$#" count: 2 @@ -1020,21 +800,6 @@ parameters: count: 1 path: src/Validator/DocumentValidator.php - - - message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" - count: 1 - path: src/Validator/DocumentValidator.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\VisitorOperation\"\\.$#" - count: 1 - path: src/Validator/Rules/ExecutableDefinitions.php - - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 1 - path: src/Validator/Rules/FieldsOnCorrectType.php - - message: "#^Only booleans are allowed in a negated boolean, \\(GraphQL\\\\Type\\\\Definition\\\\CompositeType&GraphQL\\\\Type\\\\Definition\\\\Type\\)\\|null given\\.$#" count: 1 @@ -1055,11 +820,6 @@ parameters: count: 1 path: src/Validator/Rules/FieldsOnCorrectType.php - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 2 - path: src/Validator/Rules/FragmentsOnCompositeTypes.php - - message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Language\\\\AST\\\\NamedTypeNode given\\.$#" count: 1 @@ -1070,21 +830,11 @@ parameters: count: 2 path: src/Validator/Rules/FragmentsOnCompositeTypes.php - - - message: "#^Anonymous function should have native return typehint \"string\"\\.$#" - count: 1 - path: src/Validator/Rules/KnownArgumentNames.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 2 path: src/Validator/Rules/KnownArgumentNames.php - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 1 - path: src/Validator/Rules/KnownArgumentNamesOnDirectives.php - - message: "#^Only booleans are allowed in a negated boolean, array\\\\|null given\\.$#" count: 1 @@ -1105,56 +855,21 @@ parameters: count: 1 path: src/Validator/Rules/KnownDirectives.php - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 1 - path: src/Validator/Rules/KnownFragmentNames.php - - message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Language\\\\AST\\\\FragmentDefinitionNode\\|null given\\.$#" count: 1 path: src/Validator/Rules/KnownFragmentNames.php - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\VisitorOperation\"\\.$#" - count: 1 - path: src/Validator/Rules/KnownTypeNames.php - - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 1 - path: src/Validator/Rules/KnownTypeNames.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 1 path: src/Validator/Rules/KnownTypeNames.php - - - message: "#^Anonymous function sometimes return something but return statement at the end is missing\\.$#" - count: 1 - path: src/Validator/Rules/LoneAnonymousOperation.php - - - - message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" - count: 1 - path: src/Validator/Rules/LoneAnonymousOperation.php - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" count: 1 path: src/Validator/Rules/LoneAnonymousOperation.php - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 1 - path: src/Validator/Rules/LoneSchemaDefinition.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\VisitorOperation\"\\.$#" - count: 2 - path: src/Validator/Rules/NoFragmentCycles.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 3 @@ -1165,46 +880,21 @@ parameters: count: 1 path: src/Validator/Rules/NoFragmentCycles.php - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 3 - path: src/Validator/Rules/NoUndefinedVariables.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 1 path: src/Validator/Rules/NoUndefinedVariables.php - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\VisitorOperation\"\\.$#" - count: 2 - path: src/Validator/Rules/NoUnusedFragments.php - - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 1 - path: src/Validator/Rules/NoUnusedFragments.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 1 path: src/Validator/Rules/NoUnusedFragments.php - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 3 - path: src/Validator/Rules/NoUnusedVariables.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 1 path: src/Validator/Rules/NoUnusedVariables.php - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 1 - path: src/Validator/Rules/OverlappingFieldsCanBeMerged.php - - message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Language\\\\AST\\\\NameNode\\|null given\\.$#" count: 1 @@ -1240,21 +930,6 @@ parameters: count: 3 path: src/Validator/Rules/OverlappingFieldsCanBeMerged.php - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 2 - path: src/Validator/Rules/OverlappingFieldsCanBeMerged.php - - - - message: "#^Anonymous function should have native return typehint \"string\"\\.$#" - count: 1 - path: src/Validator/Rules/OverlappingFieldsCanBeMerged.php - - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 2 - path: src/Validator/Rules/PossibleFragmentSpreads.php - - message: "#^Only booleans are allowed in a negated boolean, \\(GraphQL\\\\Type\\\\Definition\\\\CompositeType&GraphQL\\\\Type\\\\Definition\\\\Type\\)\\|null given\\.$#" count: 1 @@ -1265,11 +940,6 @@ parameters: count: 1 path: src/Validator/Rules/PossibleFragmentSpreads.php - - - message: "#^Anonymous function sometimes return something but return statement at the end is missing\\.$#" - count: 2 - path: src/Validator/Rules/ProvidedRequiredArguments.php - - message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\FieldDefinition given\\.$#" count: 1 @@ -1300,11 +970,6 @@ parameters: count: 1 path: src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php - - - message: "#^Anonymous function sometimes return something but return statement at the end is missing\\.$#" - count: 1 - path: src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php - - message: "#^Only booleans are allowed in a negated boolean, array\\|null given\\.$#" count: 1 @@ -1315,16 +980,6 @@ parameters: count: 1 path: src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 2 - path: src/Validator/Rules/QueryComplexity.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\VisitorOperation\"\\.$#" - count: 1 - path: src/Validator/Rules/QueryComplexity.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 3 @@ -1335,11 +990,6 @@ parameters: count: 1 path: src/Validator/Rules/QueryComplexity.php - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 1 - path: src/Validator/Rules/QueryDepth.php - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" count: 2 @@ -1365,11 +1015,6 @@ parameters: count: 1 path: src/Validator/Rules/QuerySecurityRule.php - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 1 - path: src/Validator/Rules/ScalarLeafs.php - - message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\OutputType\\|null given\\.$#" count: 1 @@ -1385,66 +1030,26 @@ parameters: count: 1 path: src/Validator/Rules/ScalarLeafs.php - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 2 - path: src/Validator/Rules/UniqueArgumentNames.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\VisitorOperation\"\\.$#" - count: 1 - path: src/Validator/Rules/UniqueArgumentNames.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 1 path: src/Validator/Rules/UniqueArgumentNames.php - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 1 - path: src/Validator/Rules/UniqueDirectivesPerLocation.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\VisitorOperation\"\\.$#" - count: 2 - path: src/Validator/Rules/UniqueFragmentNames.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 1 path: src/Validator/Rules/UniqueFragmentNames.php - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 2 - path: src/Validator/Rules/UniqueInputFieldNames.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\VisitorOperation\"\\.$#" - count: 1 - path: src/Validator/Rules/UniqueInputFieldNames.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 1 path: src/Validator/Rules/UniqueInputFieldNames.php - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\VisitorOperation\"\\.$#" - count: 2 - path: src/Validator/Rules/UniqueOperationNames.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 1 path: src/Validator/Rules/UniqueOperationNames.php - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 2 - path: src/Validator/Rules/UniqueVariableNames.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 1 @@ -1455,21 +1060,6 @@ parameters: count: 1 path: src/Validator/Rules/ValidationRule.php - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 8 - path: src/Validator/Rules/ValuesOfCorrectType.php - - - - message: "#^Anonymous function sometimes return something but return statement at the end is missing\\.$#" - count: 1 - path: src/Validator/Rules/ValuesOfCorrectType.php - - - - message: "#^Anonymous function should have native return typehint \"string\"\\.$#" - count: 1 - path: src/Validator/Rules/ValuesOfCorrectType.php - - message: "#^Only booleans are allowed in \\|\\|, GraphQL\\\\Type\\\\Definition\\\\EnumType\\|GraphQL\\\\Type\\\\Definition\\\\InputObjectType\\|GraphQL\\\\Type\\\\Definition\\\\ListOfType\\|GraphQL\\\\Type\\\\Definition\\\\NonNull\\|GraphQL\\\\Type\\\\Definition\\\\ScalarType given on the left side\\.$#" count: 1 @@ -1490,21 +1080,11 @@ parameters: count: 1 path: src/Validator/Rules/ValuesOfCorrectType.php - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 1 - path: src/Validator/Rules/VariablesAreInputTypes.php - - message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\Type\\|null given\\.$#" count: 1 path: src/Validator/Rules/VariablesAreInputTypes.php - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 3 - path: src/Validator/Rules/VariablesInAllowedPosition.php - - message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\Type\\|null given\\.$#" count: 1 @@ -1515,16 +1095,6 @@ parameters: count: 1 path: src/Validator/Rules/VariablesInAllowedPosition.php - - - message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" - count: 1 - path: src/Validator/ValidationContext.php - - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 1 - path: src/Validator/ValidationContext.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 3 @@ -2032,11 +1602,6 @@ parameters: - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 5 - path: tests/Type/QueryPlanTest.php - - - - message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" count: 1 path: tests/Type/QueryPlanTest.php @@ -2045,158 +1610,23 @@ parameters: count: 1 path: tests/Type/QueryPlanTest.php - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 2 - path: tests/Type/ResolveInfoTest.php - - - - message: "#^Anonymous function should have native return typehint \"string\"\\.$#" - count: 1 - path: tests/Type/SchemaTest.php - - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 1 - path: tests/Type/SchemaTest.php - - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 3 - path: tests/Type/StandardTypesTest.php - - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 5 - path: tests/Type/TypeLoaderTest.php - - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 5 - path: tests/Type/TypeLoaderTest.php - - message: "#^Variable property access on \\$this\\(GraphQL\\\\Tests\\\\Type\\\\TypeLoaderTest\\)\\.$#" count: 1 path: tests/Type/TypeLoaderTest.php - - - message: "#^Anonymous function should have native return typehint \"stdClass\"\\.$#" - count: 1 - path: tests/Type/TypeLoaderTest.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\InterfaceType\"\\.$#" - count: 1 - path: tests/Type/TypeLoaderTest.php - - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 3 - path: tests/Type/ValidationTest.php - - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 5 - path: tests/Type/ValidationTest.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\ListOfType\"\\.$#" - count: 1 - path: tests/Type/ValidationTest.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\NonNull\"\\.$#" - count: 2 - path: tests/Type/ValidationTest.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\ObjectType\"\\.$#" - count: 1 - path: tests/Type/ValidationTest.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\EnumType\"\\.$#" - count: 1 - path: tests/Type/ValidationTest.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\InputObjectType\"\\.$#" - count: 1 - path: tests/Type/ValidationTest.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\UnionType\"\\.$#" - count: 1 - path: tests/Type/ValidationTest.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\InterfaceType\"\\.$#" - count: 1 - path: tests/Type/ValidationTest.php - - - - message: "#^Anonymous function should have native return typehint \"\\?GraphQL\\\\Type\\\\Definition\\\\ObjectType\"\\.$#" - count: 1 - path: tests/Type/ValidationTest.php - - message: "#^Only booleans are allowed in a negated boolean, stdClass given\\.$#" count: 1 path: tests/Utils/AstFromValueTest.php - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 7 - path: tests/Utils/ExtractTypesTest.php - - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 2 - path: tests/Utils/MixedStoreTest.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 2 path: tests/Utils/MixedStoreTest.php - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 5 - path: tests/Utils/SchemaExtenderTest.php - - - - message: "#^Anonymous function should have native return typehint \"string\"\\.$#" - count: 5 - path: tests/Utils/SchemaExtenderTest.php - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" count: 1 path: tests/Utils/ValueFromAstTest.php - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 3 - path: tests/Validator/OverlappingFieldsCanBeMergedTest.php - - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 1 - path: tests/Validator/QueryComplexityTest.php - - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 1 - path: tests/Validator/QueryComplexityTest.php - - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 3 - path: tests/Validator/ValidatorTestCase.php - - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 2 - path: tests/Validator/ValidatorTestCase.php - diff --git a/src/Error/Error.php b/src/Error/Error.php index dfa98715e..7e6056fc0 100644 --- a/src/Error/Error.php +++ b/src/Error/Error.php @@ -278,7 +278,7 @@ static function ($pos) use ($source) : SourceLocation { } elseif ($nodes) { $locations = array_filter( array_map( - static function ($node) { + static function ($node) : ?SourceLocation { if ($node->loc && $node->loc->source) { return $node->loc->source->getLocation($node->loc->start); } diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 7093fdd58..afcb59434 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -1038,7 +1038,7 @@ private function defaultTypeResolver($value, $contextValue, ResolveInfo $info, A } if (! empty($promisedIsTypeOfResults)) { return $this->exeContext->promiseAdapter->all($promisedIsTypeOfResults) - ->then(static function ($isTypeOfResults) use ($possibleTypes) { + ->then(static function ($isTypeOfResults) use ($possibleTypes) : ?ObjectType { foreach ($isTypeOfResults as $index => $result) { if ($result) { return $possibleTypes[$index]; diff --git a/src/Experimental/Executor/CoroutineExecutor.php b/src/Experimental/Executor/CoroutineExecutor.php index 185f54a74..5a073eb8e 100644 --- a/src/Experimental/Executor/CoroutineExecutor.php +++ b/src/Experimental/Executor/CoroutineExecutor.php @@ -226,7 +226,7 @@ public function doExecute() : Promise $this->run(); if ($this->pending > 0) { - return $this->promiseAdapter->create(function (callable $resolve) { + return $this->promiseAdapter->create(function (callable $resolve) : void { $this->doResolve = $resolve; }); } @@ -313,13 +313,13 @@ private function run() $this->promiseAdapter ->then( $value, - function ($value) use ($strand) { + function ($value) use ($strand) : void { $strand->success = true; $strand->value = $value; $this->queue->enqueue($strand); $this->done(); }, - function (Throwable $throwable) use ($strand) { + function (Throwable $throwable) use ($strand) : void { $strand->success = false; $strand->value = $throwable; $this->queue->enqueue($strand); diff --git a/src/GraphQL.php b/src/GraphQL.php index ef5d7b4be..3b653b6ab 100644 --- a/src/GraphQL.php +++ b/src/GraphQL.php @@ -213,7 +213,7 @@ public static function execute( if ($promiseAdapter instanceof SyncPromiseAdapter) { $result = $promiseAdapter->wait($result)->toArray(); } else { - $result = $result->then(static function (ExecutionResult $r) { + $result = $result->then(static function (ExecutionResult $r) : array { return $r->toArray(); }); } diff --git a/src/Language/AST/IntValueNode.php b/src/Language/AST/IntValueNode.php index 3441b7bc2..0bdab5be0 100644 --- a/src/Language/AST/IntValueNode.php +++ b/src/Language/AST/IntValueNode.php @@ -9,6 +9,6 @@ class IntValueNode extends Node implements ValueNode /** @var string */ public $kind = NodeKind::INT; - /** @var string */ + /** @var mixed */ public $value; } diff --git a/src/Language/AST/NamedTypeNode.php b/src/Language/AST/NamedTypeNode.php index 7a44b2f67..da62aeddf 100644 --- a/src/Language/AST/NamedTypeNode.php +++ b/src/Language/AST/NamedTypeNode.php @@ -9,6 +9,6 @@ class NamedTypeNode extends Node implements TypeNode /** @var string */ public $kind = NodeKind::NAMED_TYPE; - /** @var NameNode */ + /** @var NameNode|string */ public $name; } diff --git a/src/Language/AST/NodeList.php b/src/Language/AST/NodeList.php index 62a776b24..be77b0183 100644 --- a/src/Language/AST/NodeList.php +++ b/src/Language/AST/NodeList.php @@ -83,8 +83,8 @@ public function offsetGet($offset)// : Node } /** - * @param int|string $offset - * @param Node|mixed[] $value + * @param int|string|null $offset + * @param Node|mixed[] $value * * @phpstan-param T|mixed[] $value */ diff --git a/src/Language/Parser.php b/src/Language/Parser.php index b0ccc7b7c..0328fbdb3 100644 --- a/src/Language/Parser.php +++ b/src/Language/Parser.php @@ -550,7 +550,7 @@ private function parseVariableDefinitions() : NodeList return $this->peek(Token::PAREN_L) ? $this->many( Token::PAREN_L, - function () { + function () : VariableDefinitionNode { return $this->parseVariableDefinition(); }, Token::PAREN_R @@ -601,7 +601,7 @@ private function parseSelectionSet() : SelectionSetNode [ 'selections' => $this->many( Token::BRACE_L, - function () { + function () : SelectionNode { return $this->parseSelection(); }, Token::BRACE_R @@ -656,10 +656,10 @@ private function parseField() : FieldNode private function parseArguments(bool $isConst) : NodeList { $parseFn = $isConst - ? function () { + ? function () : ArgumentNode { return $this->parseConstArgument(); } - : function () { + : function () : ArgumentNode { return $this->parseArgument(); }; @@ -1090,7 +1090,7 @@ private function parseSchemaDefinition() : SchemaDefinitionNode $operationTypes = $this->many( Token::BRACE_L, - function () { + function () : OperationTypeDefinitionNode { return $this->parseOperationTypeDefinition(); }, Token::BRACE_R @@ -1207,7 +1207,7 @@ private function parseFieldsDefinition() : NodeList $nodeList = $this->peek(Token::BRACE_L) ? $this->many( Token::BRACE_L, - function () { + function () : FieldDefinitionNode { return $this->parseFieldDefinition(); }, Token::BRACE_R @@ -1250,7 +1250,7 @@ private function parseArgumentsDefinition() : NodeList $nodeList = $this->peek(Token::PAREN_L) ? $this->many( Token::PAREN_L, - function () { + function () : InputValueDefinitionNode { return $this->parseInputValueDefinition(); }, Token::PAREN_R @@ -1382,7 +1382,7 @@ private function parseEnumValuesDefinition() : NodeList $nodeList = $this->peek(Token::BRACE_L) ? $this->many( Token::BRACE_L, - function () { + function () : EnumValueDefinitionNode { return $this->parseEnumValueDefinition(); }, Token::BRACE_R @@ -1440,7 +1440,7 @@ private function parseInputFieldsDefinition() : NodeList $nodeList = $this->peek(Token::BRACE_L) ? $this->many( Token::BRACE_L, - function () { + function () : InputValueDefinitionNode { return $this->parseInputValueDefinition(); }, Token::BRACE_R diff --git a/src/Language/Printer.php b/src/Language/Printer.php index 54b526d55..8e46ac329 100644 --- a/src/Language/Printer.php +++ b/src/Language/Printer.php @@ -99,15 +99,15 @@ public function printAST($ast) $ast, [ 'leave' => [ - NodeKind::NAME => static function (NameNode $node) { + NodeKind::NAME => static function (NameNode $node) : string { return '' . $node->value; }, - NodeKind::VARIABLE => static function (VariableNode $node) { + NodeKind::VARIABLE => static function (VariableNode $node) : string { return '$' . $node->name; }, - NodeKind::DOCUMENT => function (DocumentNode $node) { + NodeKind::DOCUMENT => function (DocumentNode $node) : string { return $this->join($node->definitions, "\n\n") . "\n"; }, @@ -125,7 +125,7 @@ public function printAST($ast) : $this->join([$op, $this->join([$name, $varDefs]), $directives, $selectionSet], ' '); }, - NodeKind::VARIABLE_DEFINITION => function (VariableDefinitionNode $node) { + NodeKind::VARIABLE_DEFINITION => function (VariableDefinitionNode $node) : string { return $node->variable . ': ' . $node->type @@ -152,11 +152,11 @@ public function printAST($ast) ); }, - NodeKind::ARGUMENT => static function (ArgumentNode $node) { + NodeKind::ARGUMENT => static function (ArgumentNode $node) : string { return $node->name . ': ' . $node->value; }, - NodeKind::FRAGMENT_SPREAD => function (FragmentSpreadNode $node) { + NodeKind::FRAGMENT_SPREAD => function (FragmentSpreadNode $node) : string { return '...' . $node->name . $this->wrap(' ', $this->join($node->directives, ' ')); }, @@ -172,7 +172,7 @@ public function printAST($ast) ); }, - NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) { + NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) : string { // Note: fragment variable definitions are experimental and may be changed or removed in the future. return sprintf('fragment %s', $node->name) . $this->wrap('(', $this->join($node->variableDefinitions, ', '), ')') @@ -185,7 +185,7 @@ public function printAST($ast) return $node->value; }, - NodeKind::FLOAT => static function (FloatValueNode $node) { + NodeKind::FLOAT => static function (FloatValueNode $node) : string { return $node->value; }, @@ -201,39 +201,39 @@ public function printAST($ast) return $node->value ? 'true' : 'false'; }, - NodeKind::NULL => static function (NullValueNode $node) { + NodeKind::NULL => static function (NullValueNode $node) : string { return 'null'; }, - NodeKind::ENUM => static function (EnumValueNode $node) { + NodeKind::ENUM => static function (EnumValueNode $node) : string { return $node->value; }, - NodeKind::LST => function (ListValueNode $node) { + NodeKind::LST => function (ListValueNode $node) : string { return '[' . $this->join($node->values, ', ') . ']'; }, - NodeKind::OBJECT => function (ObjectValueNode $node) { + NodeKind::OBJECT => function (ObjectValueNode $node) : string { return '{' . $this->join($node->fields, ', ') . '}'; }, - NodeKind::OBJECT_FIELD => static function (ObjectFieldNode $node) { + NodeKind::OBJECT_FIELD => static function (ObjectFieldNode $node) : string { return $node->name . ': ' . $node->value; }, - NodeKind::DIRECTIVE => function (DirectiveNode $node) { + NodeKind::DIRECTIVE => function (DirectiveNode $node) : string { return '@' . $node->name . $this->wrap('(', $this->join($node->arguments, ', '), ')'); }, - NodeKind::NAMED_TYPE => static function (NamedTypeNode $node) { + NodeKind::NAMED_TYPE => static function (NamedTypeNode $node) : string { return $node->name; }, - NodeKind::LIST_TYPE => static function (ListTypeNode $node) { + NodeKind::LIST_TYPE => static function (ListTypeNode $node) : string { return '[' . $node->type . ']'; }, - NodeKind::NON_NULL_TYPE => static function (NonNullTypeNode $node) { + NodeKind::NON_NULL_TYPE => static function (NonNullTypeNode $node) : string { return $node->type . '!'; }, @@ -248,7 +248,7 @@ public function printAST($ast) ); }, - NodeKind::OPERATION_TYPE_DEFINITION => static function (OperationTypeDefinitionNode $def) { + NodeKind::OPERATION_TYPE_DEFINITION => static function (OperationTypeDefinitionNode $def) : string { return $def->operation . ': ' . $def->type; }, @@ -270,7 +270,7 @@ public function printAST($ast) }), NodeKind::FIELD_DEFINITION => $this->addDescription(function (FieldDefinitionNode $def) { - $noIndent = Utils::every($def->arguments, static function (string $arg) { + $noIndent = Utils::every($def->arguments, static function (string $arg) : bool { return strpos($arg, "\n") === false; }); @@ -437,7 +437,7 @@ function (InterfaceTypeDefinitionNode $def) { }, NodeKind::DIRECTIVE_DEFINITION => $this->addDescription(function (DirectiveDefinitionNode $def) { - $noIndent = Utils::every($def->arguments, static function (string $arg) { + $noIndent = Utils::every($def->arguments, static function (string $arg) : bool { return strpos($arg, "\n") === false; }); @@ -502,7 +502,7 @@ public function join($maybeArray, $separator = '') $separator, Utils::filter( $maybeArray, - static function ($x) { + static function ($x) : bool { return (bool) $x; } ) diff --git a/src/Server/Helper.php b/src/Server/Helper.php index 396589ae2..ce9bf1c5c 100644 --- a/src/Server/Helper.php +++ b/src/Server/Helper.php @@ -266,7 +266,7 @@ private function promiseToExecuteOperation( if (! empty($errors)) { $errors = Utils::map( $errors, - static function (RequestError $err) { + static function (RequestError $err) : Error { return Error::createLocatedError($err, null, null); } ); @@ -315,7 +315,7 @@ static function (RequestError $err) { ); } - $applyErrorHandling = static function (ExecutionResult $result) use ($config) { + $applyErrorHandling = static function (ExecutionResult $result) use ($config) : ExecutionResult { if ($config->getErrorsHandler()) { $result->setErrorsHandler($config->getErrorsHandler()); } @@ -434,7 +434,7 @@ private function resolveContextValue( public function sendResponse($result, $exitWhenDone = false) { if ($result instanceof Promise) { - $result->then(function ($actualResult) use ($exitWhenDone) { + $result->then(function ($actualResult) use ($exitWhenDone) : void { $this->doSendResponse($actualResult, $exitWhenDone); }); } else { @@ -482,7 +482,7 @@ private function resolveHttpStatus($result) if (is_array($result) && isset($result[0])) { Utils::each( $result, - static function ($executionResult, $index) { + static function ($executionResult, $index) : void { if (! $executionResult instanceof ExecutionResult) { throw new InvariantViolation(sprintf( 'Expecting every entry of batched query result to be instance of %s but entry at position %d is %s', diff --git a/src/Type/Definition/QueryPlan.php b/src/Type/Definition/QueryPlan.php index 03788232b..ace572fcb 100644 --- a/src/Type/Definition/QueryPlan.php +++ b/src/Type/Definition/QueryPlan.php @@ -79,7 +79,7 @@ public function getReferencedTypes() : array public function hasType(string $type) : bool { - return count(array_filter($this->getReferencedTypes(), static function (string $referencedType) use ($type) { + return count(array_filter($this->getReferencedTypes(), static function (string $referencedType) use ($type) : bool { return $type === $referencedType; })) > 0; } @@ -94,7 +94,7 @@ public function getReferencedFields() : array public function hasField(string $field) : bool { - return count(array_filter($this->getReferencedFields(), static function (string $referencedField) use ($field) { + return count(array_filter($this->getReferencedFields(), static function (string $referencedField) use ($field) : bool { return $field === $referencedField; })) > 0; } diff --git a/src/Type/Introspection.php b/src/Type/Introspection.php index 5bd928b31..fab7ecd0c 100644 --- a/src/Type/Introspection.php +++ b/src/Type/Introspection.php @@ -228,14 +228,14 @@ public static function _schema() 'types' => [ 'description' => 'A list of all types supported by this server.', 'type' => new NonNull(new ListOfType(new NonNull(self::_type()))), - 'resolve' => static function (Schema $schema) { + 'resolve' => static function (Schema $schema) : array { return array_values($schema->getTypeMap()); }, ], 'queryType' => [ 'description' => 'The type that query operations will be rooted at.', 'type' => new NonNull(self::_type()), - 'resolve' => static function (Schema $schema) { + 'resolve' => static function (Schema $schema) : ?ObjectType { return $schema->getQueryType(); }, ], @@ -244,7 +244,7 @@ public static function _schema() 'If this server supports mutation, the type that ' . 'mutation operations will be rooted at.', 'type' => self::_type(), - 'resolve' => static function (Schema $schema) { + 'resolve' => static function (Schema $schema) : ?ObjectType { return $schema->getMutationType(); }, ], @@ -336,7 +336,7 @@ public static function _type() if (empty($args['includeDeprecated'])) { $fields = array_filter( $fields, - static function (FieldDefinition $field) { + static function (FieldDefinition $field) : bool { return ! $field->deprecationReason; } ); @@ -350,7 +350,7 @@ static function (FieldDefinition $field) { ], 'interfaces' => [ 'type' => Type::listOf(Type::nonNull(self::_type())), - 'resolve' => static function ($type) { + 'resolve' => static function ($type) : ?array { if ($type instanceof ObjectType) { return $type->getInterfaces(); } @@ -360,7 +360,7 @@ static function (FieldDefinition $field) { ], 'possibleTypes' => [ 'type' => Type::listOf(Type::nonNull(self::_type())), - 'resolve' => static function ($type, $args, $context, ResolveInfo $info) { + 'resolve' => static function ($type, $args, $context, ResolveInfo $info) : ?array { if ($type instanceof InterfaceType || $type instanceof UnionType) { return $info->schema->getPossibleTypes($type); } @@ -380,7 +380,7 @@ static function (FieldDefinition $field) { if (empty($args['includeDeprecated'])) { $values = array_filter( $values, - static function ($value) { + static function ($value) : bool { return ! $value->deprecationReason; } ); @@ -394,7 +394,7 @@ static function ($value) { ], 'inputFields' => [ 'type' => Type::listOf(Type::nonNull(self::_inputValue())), - 'resolve' => static function ($type) { + 'resolve' => static function ($type) : ?array { if ($type instanceof InputObjectType) { return array_values($type->getFields()); } @@ -404,7 +404,7 @@ static function ($value) { ], 'ofType' => [ 'type' => self::_type(), - 'resolve' => static function ($type) { + 'resolve' => static function ($type) : ?Type { if ($type instanceof WrappingType) { return $type->getWrappedType(); } @@ -480,19 +480,19 @@ public static function _field() return [ 'name' => [ 'type' => Type::nonNull(Type::string()), - 'resolve' => static function (FieldDefinition $field) { + 'resolve' => static function (FieldDefinition $field) : string { return $field->name; }, ], 'description' => [ 'type' => Type::string(), - 'resolve' => static function (FieldDefinition $field) { + 'resolve' => static function (FieldDefinition $field) : ?string { return $field->description; }, ], 'args' => [ 'type' => Type::nonNull(Type::listOf(Type::nonNull(self::_inputValue()))), - 'resolve' => static function (FieldDefinition $field) { + 'resolve' => static function (FieldDefinition $field) : array { return empty($field->args) ? [] : $field->args; }, ], @@ -504,13 +504,13 @@ public static function _field() ], 'isDeprecated' => [ 'type' => Type::nonNull(Type::boolean()), - 'resolve' => static function (FieldDefinition $field) { + 'resolve' => static function (FieldDefinition $field) : bool { return (bool) $field->deprecationReason; }, ], 'deprecationReason' => [ 'type' => Type::string(), - 'resolve' => static function (FieldDefinition $field) { + 'resolve' => static function (FieldDefinition $field) : ?string { return $field->deprecationReason; }, ], @@ -536,7 +536,7 @@ public static function _inputValue() return [ 'name' => [ 'type' => Type::nonNull(Type::string()), - 'resolve' => static function ($inputValue) { + 'resolve' => static function ($inputValue) : string { /** @var FieldArgument|InputObjectField $inputValue */ $inputValue = $inputValue; @@ -545,7 +545,7 @@ public static function _inputValue() ], 'description' => [ 'type' => Type::string(), - 'resolve' => static function ($inputValue) { + 'resolve' => static function ($inputValue) : ?string { /** @var FieldArgument|InputObjectField $inputValue */ $inputValue = $inputValue; @@ -564,7 +564,7 @@ public static function _inputValue() 'type' => Type::string(), 'description' => 'A GraphQL-formatted string representing the default value for this input value.', - 'resolve' => static function ($inputValue) { + 'resolve' => static function ($inputValue) : ?string { /** @var FieldArgument|InputObjectField $inputValue */ $inputValue = $inputValue; @@ -609,7 +609,7 @@ public static function _enumValue() ], 'isDeprecated' => [ 'type' => Type::nonNull(Type::boolean()), - 'resolve' => static function ($enumValue) { + 'resolve' => static function ($enumValue) : bool { return (bool) $enumValue->deprecationReason; }, ], @@ -661,7 +661,7 @@ public static function _directive() ], 'args' => [ 'type' => Type::nonNull(Type::listOf(Type::nonNull(self::_inputValue()))), - 'resolve' => static function (Directive $directive) { + 'resolve' => static function (Directive $directive) : array { return $directive->args ?: []; }, ], @@ -779,7 +779,7 @@ public static function schemaMetaFieldDef() : FieldDefinition $args, $context, ResolveInfo $info - ) { + ) : Schema { return $info->schema; }, ]); @@ -798,7 +798,7 @@ public static function typeMetaFieldDef() : FieldDefinition 'args' => [ ['name' => 'name', 'type' => Type::nonNull(Type::string())], ], - 'resolve' => static function ($source, $args, $context, ResolveInfo $info) { + 'resolve' => static function ($source, $args, $context, ResolveInfo $info) : Type { return $info->schema->getType($args['name']); }, ]); @@ -820,7 +820,7 @@ public static function typeNameMetaFieldDef() : FieldDefinition $args, $context, ResolveInfo $info - ) { + ) : string { return $info->parentType->name; }, ]); diff --git a/src/Type/SchemaValidationContext.php b/src/Type/SchemaValidationContext.php index dcaee67d0..b37b0990d 100644 --- a/src/Type/SchemaValidationContext.php +++ b/src/Type/SchemaValidationContext.php @@ -5,6 +5,7 @@ namespace GraphQL\Type; use GraphQL\Error\Error; +use GraphQL\Language\AST\DirectiveDefinitionNode; use GraphQL\Language\AST\DirectiveNode; use GraphQL\Language\AST\EnumValueDefinitionNode; use GraphQL\Language\AST\FieldDefinitionNode; @@ -222,7 +223,7 @@ public function validateDirectiveDefinitions() $nodes = Utils::map( $directiveList, - static function (Directive $directive) { + static function (Directive $directive) : DirectiveDefinitionNode { return $directive->astNode; } ); @@ -263,7 +264,7 @@ static function ($directiveNode) { return Utils::filter( $subNodes, - static function ($argNode) use ($argName) { + static function ($argNode) use ($argName) : bool { return $argNode->name->value === $argName; } ); @@ -378,7 +379,7 @@ private function validateDirectivesAtLocation($directives, string $location) } $includes = Utils::some( $schemaDirective->locations, - static function ($schemaLocation) use ($location) { + static function ($schemaLocation) use ($location) : bool { return $schemaLocation === $location; } ); @@ -566,7 +567,7 @@ private function getAllFieldNodes($type, $fieldName) return $typeNode->fields; }); - return Utils::filter($subNodes, static function ($fieldNode) use ($fieldName) { + return Utils::filter($subNodes, static function ($fieldNode) use ($fieldName) : bool { return $fieldNode->name->value === $fieldName; }); } @@ -711,7 +712,7 @@ private function getAllImplementsInterfaceNodes(ObjectType $type, $iface) return $typeNode->interfaces; }); - return Utils::filter($subNodes, static function ($ifaceNode) use ($iface) { + return Utils::filter($subNodes, static function ($ifaceNode) use ($iface) : bool { return $ifaceNode->name->value === $iface->name; }); } @@ -911,7 +912,7 @@ private function getUnionMemberTypeNodes(UnionType $union, $typeName) return $unionNode->types; }); - return Utils::filter($subNodes, static function ($typeNode) use ($typeName) { + return Utils::filter($subNodes, static function ($typeNode) use ($typeName) : bool { return $typeNode->name->value === $typeName; }); } @@ -971,7 +972,7 @@ private function getEnumValueNodes(EnumType $enum, $valueName) return $enumNode->values; }); - return Utils::filter($subNodes, static function ($valueNode) use ($valueName) { + return Utils::filter($subNodes, static function ($valueNode) use ($valueName) : bool { return $valueNode->name->value === $valueName; }); } diff --git a/src/Utils/ASTDefinitionBuilder.php b/src/Utils/ASTDefinitionBuilder.php index d9902b1f2..c032442ed 100644 --- a/src/Utils/ASTDefinitionBuilder.php +++ b/src/Utils/ASTDefinitionBuilder.php @@ -336,7 +336,7 @@ private function makeImplementedInterfaces(ObjectTypeDefinitionNode $def) // validation with validateSchema() will produce more actionable results. return Utils::map( $def->interfaces, - function ($iface) { + function ($iface) : Type { return $this->buildType($iface); } ); @@ -370,7 +370,7 @@ private function makeEnumDef(EnumTypeDefinitionNode $def) static function ($enumValue) { return $enumValue->name->value; }, - function ($enumValue) { + function ($enumValue) : array { return [ 'description' => $this->getDescription($enumValue), 'deprecationReason' => $this->getDeprecationReason($enumValue), @@ -395,7 +395,7 @@ private function makeUnionDef(UnionTypeDefinitionNode $def) ? function () use ($def) { return Utils::map( $def->types, - function ($typeNode) { + function ($typeNode) : Type { return $this->buildType($typeNode); } ); diff --git a/src/Utils/BreakingChangesFinder.php b/src/Utils/BreakingChangesFinder.php index 733ca1889..8ef9253d2 100644 --- a/src/Utils/BreakingChangesFinder.php +++ b/src/Utils/BreakingChangesFinder.php @@ -504,7 +504,7 @@ public static function findArgChanges( $newArgs = $newTypeFields[$fieldName]->args; $newArgDef = Utils::find( $newArgs, - static function ($arg) use ($oldArgDef) { + static function ($arg) use ($oldArgDef) : bool { return $arg->name === $oldArgDef->name; } ); @@ -544,7 +544,7 @@ static function ($arg) use ($oldArgDef) { $oldArgs = $oldTypeFields[$fieldName]->args; $oldArgDef = Utils::find( $oldArgs, - static function ($arg) use ($newTypeFieldArgDef) { + static function ($arg) use ($newTypeFieldArgDef) : bool { return $arg->name === $newTypeFieldArgDef->name; } ); diff --git a/src/Utils/BuildSchema.php b/src/Utils/BuildSchema.php index 57e9c16f4..fb52b1110 100644 --- a/src/Utils/BuildSchema.php +++ b/src/Utils/BuildSchema.php @@ -18,6 +18,7 @@ use GraphQL\Language\Parser; use GraphQL\Language\Source; use GraphQL\Type\Definition\Directive; +use GraphQL\Type\Definition\Type; use GraphQL\Type\Schema; use GraphQL\Validator\DocumentValidator; use function array_map; @@ -145,7 +146,7 @@ public function buildSchema() $DefinitionBuilder = new ASTDefinitionBuilder( $this->nodeMap, $this->options, - static function ($typeName) { + static function ($typeName) : void { throw new Error('Type "' . $typeName . '" not found in document.'); }, $this->typeConfigDecorator @@ -189,12 +190,12 @@ static function (Directive $directive) : string { 'subscription' => isset($operationTypes['subscription']) ? $DefinitionBuilder->buildType($operationTypes['subscription']) : null, - 'typeLoader' => static function ($name) use ($DefinitionBuilder) { + 'typeLoader' => static function ($name) use ($DefinitionBuilder) : Type { return $DefinitionBuilder->buildType($name); }, 'directives' => $directives, 'astNode' => $schemaDef, - 'types' => function () use ($DefinitionBuilder) { + 'types' => function () use ($DefinitionBuilder) : array { $types = []; /** @var ScalarTypeDefinitionNode|ObjectTypeDefinitionNode|InterfaceTypeDefinitionNode|UnionTypeDefinitionNode|EnumTypeDefinitionNode|InputObjectTypeDefinitionNode $def */ foreach ($this->nodeMap as $name => $def) { diff --git a/src/Utils/SchemaExtender.php b/src/Utils/SchemaExtender.php index 3249738d2..e24639ba2 100644 --- a/src/Utils/SchemaExtender.php +++ b/src/Utils/SchemaExtender.php @@ -141,7 +141,7 @@ protected static function extendUnionType(UnionType $type) : UnionType return new UnionType([ 'name' => $type->name, 'description' => $type->description, - 'types' => static function () use ($type) { + 'types' => static function () use ($type) : array { return static::extendPossibleTypes($type); }, 'astNode' => $type->astNode, @@ -166,7 +166,7 @@ protected static function extendInputObjectType(InputObjectType $type) : InputOb return new InputObjectType([ 'name' => $type->name, 'description' => $type->description, - 'fields' => static function () use ($type) { + 'fields' => static function () use ($type) : array { return static::extendInputFieldMap($type); }, 'astNode' => $type->astNode, @@ -315,7 +315,7 @@ protected static function extendArgs(array $args) : array { return Utils::keyValMap( $args, - static function (FieldArgument $arg) { + static function (FieldArgument $arg) : string { return $arg->name; }, static function (FieldArgument $arg) { @@ -382,10 +382,10 @@ protected static function extendObjectType(ObjectType $type) : ObjectType return new ObjectType([ 'name' => $type->name, 'description' => $type->description, - 'interfaces' => static function () use ($type) { + 'interfaces' => static function () use ($type) : array { return static::extendImplementedInterfaces($type); }, - 'fields' => static function () use ($type) { + 'fields' => static function () use ($type) : array { return static::extendFieldMap($type); }, 'astNode' => $type->astNode, @@ -400,7 +400,7 @@ protected static function extendInterfaceType(InterfaceType $type) : InterfaceTy return new InterfaceType([ 'name' => $type->name, 'description' => $type->description, - 'fields' => static function () use ($type) { + 'fields' => static function () use ($type) : array { return static::extendFieldMap($type); }, 'astNode' => $type->astNode, @@ -466,7 +466,7 @@ protected static function extendMaybeNamedType(?NamedType $type = null) */ protected static function getMergedDirectives(Schema $schema, array $directiveDefinitions) : array { - $existingDirectives = array_map(static function (Directive $directive) { + $existingDirectives = array_map(static function (Directive $directive) : Directive { return static::extendDirective($directive); }, $schema->getDirectives()); @@ -619,7 +619,7 @@ static function (string $typeName) use ($schema) { return static::extendNamedType($type); }, array_values($schema->getTypeMap())), // Do the same with new types. - array_map(static function ($type) { + array_map(static function ($type) : Type { return static::$astBuilder->buildType($type); }, array_values($typeDefinitionMap)) ); diff --git a/src/Utils/SchemaPrinter.php b/src/Utils/SchemaPrinter.php index ccbb00734..795c4cc2d 100644 --- a/src/Utils/SchemaPrinter.php +++ b/src/Utils/SchemaPrinter.php @@ -51,10 +51,10 @@ public static function doPrint(Schema $schema, array $options = []) : string { return self::printFilteredSchema( $schema, - static function ($type) { + static function ($type) : bool { return ! Directive::isSpecifiedDirective($type); }, - static function ($type) { + static function ($type) : bool { return ! Type::isBuiltInType($type); }, $options @@ -85,13 +85,13 @@ static function ($directive) use ($directiveFilter) { array_merge( [self::printSchemaDefinition($schema)], array_map( - static function ($directive) use ($options) { + static function ($directive) use ($options) : string { return self::printDirective($directive, $options); }, $directives ), array_map( - static function ($type) use ($options) { + static function ($type) use ($options) : string { return self::printType($type, $options); }, $types @@ -273,7 +273,7 @@ private static function printArgs($options, $args, $indentation = '') : string // If every arg does not have a description, print them on one line. if (Utils::every( $args, - static function ($arg) { + static function ($arg) : bool { return empty($arg->description); } )) { @@ -285,7 +285,7 @@ static function ($arg) { implode( "\n", array_map( - static function ($arg, $i) use ($indentation, $options) { + static function ($arg, $i) use ($indentation, $options) : string { return self::printDescription($options, $arg, ' ' . $indentation, ! $i) . ' ' . $indentation . self::printInputValue($arg); }, @@ -357,7 +357,7 @@ private static function printObject(ObjectType $type, array $options) : string ? ' implements ' . implode( ' & ', array_map( - static function ($i) { + static function ($i) : string { return $i->name; }, $interfaces @@ -379,7 +379,7 @@ private static function printFields($options, $type) : string return implode( "\n", array_map( - static function ($f, $i) use ($options) { + static function ($f, $i) use ($options) : string { return self::printDescription($options, $f, ' ', ! $i) . ' ' . $f->name . self::printArgs($options, $f->args, ' ') . ': ' . (string) $f->getType() . self::printDeprecated($f); @@ -439,7 +439,7 @@ private static function printEnumValues($values, $options) : string return implode( "\n", array_map( - static function ($value, $i) use ($options) { + static function ($value, $i) use ($options) : string { return self::printDescription($options, $value, ' ', ! $i) . ' ' . $value->name . self::printDeprecated($value); }, @@ -463,7 +463,7 @@ private static function printInputObject(InputObjectType $type, array $options) implode( "\n", array_map( - static function ($f, $i) use ($options) { + static function ($f, $i) use ($options) : string { return self::printDescription($options, $f, ' ', ! $i) . ' ' . self::printInputValue($f); }, $fields, diff --git a/src/Utils/TypeInfo.php b/src/Utils/TypeInfo.php index 1468847f9..870180115 100644 --- a/src/Utils/TypeInfo.php +++ b/src/Utils/TypeInfo.php @@ -323,7 +323,7 @@ public function enter(Node $node) /** @var FieldArgument $argDef */ $argDef = Utils::find( $fieldOrDirective->args, - static function ($arg) use ($node) { + static function ($arg) use ($node) : bool { return $arg->name === $node->name->value; } ); diff --git a/src/Utils/Utils.php b/src/Utils/Utils.php index 9f0defa55..7919f262e 100644 --- a/src/Utils/Utils.php +++ b/src/Utils/Utils.php @@ -560,7 +560,7 @@ public static function withErrorHandling(callable $fn, array &$errors) { return static function () use ($fn, &$errors) { // Catch custom errors (to report them in query results) - set_error_handler(static function ($severity, $message, $file, $line) use (&$errors) { + set_error_handler(static function ($severity, $message, $file, $line) use (&$errors) : void { $errors[] = new ErrorException($message, 0, $severity, $file, $line); }); @@ -580,7 +580,7 @@ public static function withErrorHandling(callable $fn, array &$errors) public static function quotedOrList(array $items) { $items = array_map( - static function ($item) { + static function ($item) : string { return sprintf('"%s"', $item); }, $items @@ -609,7 +609,7 @@ public static function orList(array $items) return array_reduce( range(1, $selectedLength - 1), - static function ($list, $index) use ($selected, $selectedLength) { + static function ($list, $index) use ($selected, $selectedLength) : string { return $list . ($selectedLength > 2 ? ', ' : ' ') . ($index === $selectedLength - 1 ? 'or ' : '') . diff --git a/src/Utils/Value.php b/src/Utils/Value.php index 595172a25..2920883a6 100644 --- a/src/Utils/Value.php +++ b/src/Utils/Value.php @@ -90,7 +90,7 @@ public static function coerceValue($value, InputType $type, $blameNode = null, ? $suggestions = Utils::suggestionList( Utils::printSafe($value), array_map( - static function ($enumValue) { + static function ($enumValue) : string { return $enumValue->name; }, $type->getValues() diff --git a/src/Validator/DocumentValidator.php b/src/Validator/DocumentValidator.php index 7df559621..0b5fb0c1b 100644 --- a/src/Validator/DocumentValidator.php +++ b/src/Validator/DocumentValidator.php @@ -264,7 +264,7 @@ public static function isError($value) return is_array($value) ? count(array_filter( $value, - static function ($item) { + static function ($item) : bool { return $item instanceof Throwable; } )) === count($value) diff --git a/src/Validator/Rules/ExecutableDefinitions.php b/src/Validator/Rules/ExecutableDefinitions.php index 632953e8a..3d1887daf 100644 --- a/src/Validator/Rules/ExecutableDefinitions.php +++ b/src/Validator/Rules/ExecutableDefinitions.php @@ -11,6 +11,7 @@ use GraphQL\Language\AST\OperationDefinitionNode; use GraphQL\Language\AST\TypeSystemDefinitionNode; use GraphQL\Language\Visitor; +use GraphQL\Language\VisitorOperation; use GraphQL\Validator\ValidationContext; use function sprintf; @@ -25,7 +26,7 @@ class ExecutableDefinitions extends ValidationRule public function getVisitor(ValidationContext $context) { return [ - NodeKind::DOCUMENT => static function (DocumentNode $node) use ($context) { + NodeKind::DOCUMENT => static function (DocumentNode $node) use ($context) : VisitorOperation { /** @var FragmentDefinitionNode|OperationDefinitionNode|TypeSystemDefinitionNode $definition */ foreach ($node->definitions as $definition) { if ($definition instanceof OperationDefinitionNode || diff --git a/src/Validator/Rules/FieldsOnCorrectType.php b/src/Validator/Rules/FieldsOnCorrectType.php index 105325536..55b66eeb6 100644 --- a/src/Validator/Rules/FieldsOnCorrectType.php +++ b/src/Validator/Rules/FieldsOnCorrectType.php @@ -23,7 +23,7 @@ class FieldsOnCorrectType extends ValidationRule public function getVisitor(ValidationContext $context) { return [ - NodeKind::FIELD => function (FieldNode $node) use ($context) { + NodeKind::FIELD => function (FieldNode $node) use ($context) : void { $type = $context->getParentType(); if (! $type) { return; diff --git a/src/Validator/Rules/FragmentsOnCompositeTypes.php b/src/Validator/Rules/FragmentsOnCompositeTypes.php index c5ca80771..db72a057b 100644 --- a/src/Validator/Rules/FragmentsOnCompositeTypes.php +++ b/src/Validator/Rules/FragmentsOnCompositeTypes.php @@ -19,7 +19,7 @@ class FragmentsOnCompositeTypes extends ValidationRule public function getVisitor(ValidationContext $context) { return [ - NodeKind::INLINE_FRAGMENT => static function (InlineFragmentNode $node) use ($context) { + NodeKind::INLINE_FRAGMENT => static function (InlineFragmentNode $node) use ($context) : void { if (! $node->typeCondition) { return; } @@ -34,7 +34,7 @@ public function getVisitor(ValidationContext $context) [$node->typeCondition] )); }, - NodeKind::FRAGMENT_DEFINITION => static function (FragmentDefinitionNode $node) use ($context) { + NodeKind::FRAGMENT_DEFINITION => static function (FragmentDefinitionNode $node) use ($context) : void { $type = TypeInfo::typeFromAST($context->getSchema(), $node->typeCondition); if (! $type || Type::isCompositeType($type)) { diff --git a/src/Validator/Rules/KnownArgumentNames.php b/src/Validator/Rules/KnownArgumentNames.php index 0a4945ebd..685f4176f 100644 --- a/src/Validator/Rules/KnownArgumentNames.php +++ b/src/Validator/Rules/KnownArgumentNames.php @@ -48,7 +48,7 @@ public function getVisitor(ValidationContext $context) Utils::suggestionList( $node->name->value, array_map( - static function ($arg) { + static function ($arg) : string { return $arg->name; }, $fieldDef->args diff --git a/src/Validator/Rules/KnownArgumentNamesOnDirectives.php b/src/Validator/Rules/KnownArgumentNamesOnDirectives.php index 4087a321a..e636482c6 100644 --- a/src/Validator/Rules/KnownArgumentNamesOnDirectives.php +++ b/src/Validator/Rules/KnownArgumentNamesOnDirectives.php @@ -68,7 +68,7 @@ static function (FieldArgument $arg) : string { } return [ - NodeKind::DIRECTIVE => static function (DirectiveNode $directiveNode) use ($directiveArgs, $context) { + NodeKind::DIRECTIVE => static function (DirectiveNode $directiveNode) use ($directiveArgs, $context) : void { $directiveName = $directiveNode->name->value; $knownArgs = $directiveArgs[$directiveName] ?? null; diff --git a/src/Validator/Rules/KnownFragmentNames.php b/src/Validator/Rules/KnownFragmentNames.php index e26e233e0..052686f28 100644 --- a/src/Validator/Rules/KnownFragmentNames.php +++ b/src/Validator/Rules/KnownFragmentNames.php @@ -15,7 +15,7 @@ class KnownFragmentNames extends ValidationRule public function getVisitor(ValidationContext $context) { return [ - NodeKind::FRAGMENT_SPREAD => static function (FragmentSpreadNode $node) use ($context) { + NodeKind::FRAGMENT_SPREAD => static function (FragmentSpreadNode $node) use ($context) : void { $fragmentName = $node->name->value; $fragment = $context->getFragment($fragmentName); if ($fragment) { diff --git a/src/Validator/Rules/KnownTypeNames.php b/src/Validator/Rules/KnownTypeNames.php index 9abaf0aeb..6f38e237d 100644 --- a/src/Validator/Rules/KnownTypeNames.php +++ b/src/Validator/Rules/KnownTypeNames.php @@ -8,6 +8,7 @@ use GraphQL\Language\AST\NamedTypeNode; use GraphQL\Language\AST\NodeKind; use GraphQL\Language\Visitor; +use GraphQL\Language\VisitorOperation; use GraphQL\Utils\Utils; use GraphQL\Validator\ValidationContext; use function array_keys; @@ -23,7 +24,7 @@ class KnownTypeNames extends ValidationRule { public function getVisitor(ValidationContext $context) { - $skip = static function () { + $skip = static function () : VisitorOperation { return Visitor::skipNode(); }; @@ -35,7 +36,7 @@ public function getVisitor(ValidationContext $context) NodeKind::INTERFACE_TYPE_DEFINITION => $skip, NodeKind::UNION_TYPE_DEFINITION => $skip, NodeKind::INPUT_OBJECT_TYPE_DEFINITION => $skip, - NodeKind::NAMED_TYPE => static function (NamedTypeNode $node) use ($context) { + NodeKind::NAMED_TYPE => static function (NamedTypeNode $node) use ($context) : void { $schema = $context->getSchema(); $typeName = $node->name->value; $type = $schema->getType($typeName); diff --git a/src/Validator/Rules/LoneAnonymousOperation.php b/src/Validator/Rules/LoneAnonymousOperation.php index ebfba6299..f0a68cf6e 100644 --- a/src/Validator/Rules/LoneAnonymousOperation.php +++ b/src/Validator/Rules/LoneAnonymousOperation.php @@ -26,10 +26,10 @@ public function getVisitor(ValidationContext $context) $operationCount = 0; return [ - NodeKind::DOCUMENT => static function (DocumentNode $node) use (&$operationCount) { + NodeKind::DOCUMENT => static function (DocumentNode $node) use (&$operationCount) : void { $tmp = Utils::filter( $node->definitions, - static function (Node $definition) { + static function (Node $definition) : bool { return $definition instanceof OperationDefinitionNode; } ); diff --git a/src/Validator/Rules/LoneSchemaDefinition.php b/src/Validator/Rules/LoneSchemaDefinition.php index bd89f8c46..4ece976b6 100644 --- a/src/Validator/Rules/LoneSchemaDefinition.php +++ b/src/Validator/Rules/LoneSchemaDefinition.php @@ -41,7 +41,7 @@ public function getSDLVisitor(SDLValidationContext $context) $schemaDefinitionsCount = 0; return [ - NodeKind::SCHEMA_DEFINITION => static function (SchemaDefinitionNode $node) use ($alreadyDefined, $context, &$schemaDefinitionsCount) { + NodeKind::SCHEMA_DEFINITION => static function (SchemaDefinitionNode $node) use ($alreadyDefined, $context, &$schemaDefinitionsCount) : void { if ($alreadyDefined !== false) { $context->reportError(new Error(self::canNotDefineSchemaWithinExtensionMessage(), $node)); diff --git a/src/Validator/Rules/NoFragmentCycles.php b/src/Validator/Rules/NoFragmentCycles.php index e01b7631d..a7f938018 100644 --- a/src/Validator/Rules/NoFragmentCycles.php +++ b/src/Validator/Rules/NoFragmentCycles.php @@ -9,6 +9,7 @@ use GraphQL\Language\AST\FragmentSpreadNode; use GraphQL\Language\AST\NodeKind; use GraphQL\Language\Visitor; +use GraphQL\Language\VisitorOperation; use GraphQL\Utils\Utils; use GraphQL\Validator\ValidationContext; use function array_pop; @@ -41,10 +42,10 @@ public function getVisitor(ValidationContext $context) $this->spreadPathIndexByName = []; return [ - NodeKind::OPERATION_DEFINITION => static function () { + NodeKind::OPERATION_DEFINITION => static function () : VisitorOperation { return Visitor::skipNode(); }, - NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) use ($context) { + NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) use ($context) : VisitorOperation { $this->detectCycleRecursive($node, $context); return Visitor::skipNode(); diff --git a/src/Validator/Rules/NoUndefinedVariables.php b/src/Validator/Rules/NoUndefinedVariables.php index f59d03f6a..5dbba94a6 100644 --- a/src/Validator/Rules/NoUndefinedVariables.php +++ b/src/Validator/Rules/NoUndefinedVariables.php @@ -23,10 +23,10 @@ public function getVisitor(ValidationContext $context) return [ NodeKind::OPERATION_DEFINITION => [ - 'enter' => static function () use (&$variableNameDefined) { + 'enter' => static function () use (&$variableNameDefined) : void { $variableNameDefined = []; }, - 'leave' => static function (OperationDefinitionNode $operation) use (&$variableNameDefined, $context) { + 'leave' => static function (OperationDefinitionNode $operation) use (&$variableNameDefined, $context) : void { $usages = $context->getRecursiveVariableUsages($operation); foreach ($usages as $usage) { @@ -49,7 +49,7 @@ public function getVisitor(ValidationContext $context) } }, ], - NodeKind::VARIABLE_DEFINITION => static function (VariableDefinitionNode $def) use (&$variableNameDefined) { + NodeKind::VARIABLE_DEFINITION => static function (VariableDefinitionNode $def) use (&$variableNameDefined) : void { $variableNameDefined[$def->variable->name->value] = true; }, ]; diff --git a/src/Validator/Rules/NoUnusedFragments.php b/src/Validator/Rules/NoUnusedFragments.php index d1cd3668f..6c3f19823 100644 --- a/src/Validator/Rules/NoUnusedFragments.php +++ b/src/Validator/Rules/NoUnusedFragments.php @@ -9,6 +9,7 @@ use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\OperationDefinitionNode; use GraphQL\Language\Visitor; +use GraphQL\Language\VisitorOperation; use GraphQL\Validator\ValidationContext; use function sprintf; @@ -26,18 +27,18 @@ public function getVisitor(ValidationContext $context) $this->fragmentDefs = []; return [ - NodeKind::OPERATION_DEFINITION => function ($node) { + NodeKind::OPERATION_DEFINITION => function ($node) : VisitorOperation { $this->operationDefs[] = $node; return Visitor::skipNode(); }, - NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $def) { + NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $def) : VisitorOperation { $this->fragmentDefs[] = $def; return Visitor::skipNode(); }, NodeKind::DOCUMENT => [ - 'leave' => function () use ($context) { + 'leave' => function () use ($context) : void { $fragmentNameUsed = []; foreach ($this->operationDefs as $operation) { diff --git a/src/Validator/Rules/NoUnusedVariables.php b/src/Validator/Rules/NoUnusedVariables.php index d6a4486d7..592279cd3 100644 --- a/src/Validator/Rules/NoUnusedVariables.php +++ b/src/Validator/Rules/NoUnusedVariables.php @@ -22,10 +22,10 @@ public function getVisitor(ValidationContext $context) return [ NodeKind::OPERATION_DEFINITION => [ - 'enter' => function () { + 'enter' => function () : void { $this->variableDefs = []; }, - 'leave' => function (OperationDefinitionNode $operation) use ($context) { + 'leave' => function (OperationDefinitionNode $operation) use ($context) : void { $variableNameUsed = []; $usages = $context->getRecursiveVariableUsages($operation); $opName = $operation->name !== null @@ -51,7 +51,7 @@ public function getVisitor(ValidationContext $context) } }, ], - NodeKind::VARIABLE_DEFINITION => function ($def) { + NodeKind::VARIABLE_DEFINITION => function ($def) : void { $this->variableDefs[] = $def; }, ]; diff --git a/src/Validator/Rules/OverlappingFieldsCanBeMerged.php b/src/Validator/Rules/OverlappingFieldsCanBeMerged.php index 350318867..790c28cf9 100644 --- a/src/Validator/Rules/OverlappingFieldsCanBeMerged.php +++ b/src/Validator/Rules/OverlappingFieldsCanBeMerged.php @@ -59,7 +59,7 @@ public function getVisitor(ValidationContext $context) $this->cachedFieldsAndFragmentNames = new SplObjectStorage(); return [ - NodeKind::SELECTION_SET => function (SelectionSetNode $selectionSet) use ($context) { + NodeKind::SELECTION_SET => function (SelectionSetNode $selectionSet) use ($context) : void { $conflicts = $this->findConflictsWithinSelectionSet( $context, $context->getParentType(), @@ -845,14 +845,14 @@ static function ($conflict) { ], array_reduce( $conflicts, - static function ($allFields, $conflict) { + static function ($allFields, $conflict) : array { return array_merge($allFields, $conflict[1]); }, [$ast1] ), array_reduce( $conflicts, - static function ($allFields, $conflict) { + static function ($allFields, $conflict) : array { return array_merge($allFields, $conflict[2]); }, [$ast2] @@ -879,7 +879,7 @@ public static function reasonMessage($reason) { if (is_array($reason)) { $tmp = array_map( - static function ($tmp) { + static function ($tmp) : string { [$responseName, $subReason] = $tmp; $reasonMessage = self::reasonMessage($subReason); diff --git a/src/Validator/Rules/PossibleFragmentSpreads.php b/src/Validator/Rules/PossibleFragmentSpreads.php index 26611e6da..184f58bec 100644 --- a/src/Validator/Rules/PossibleFragmentSpreads.php +++ b/src/Validator/Rules/PossibleFragmentSpreads.php @@ -23,7 +23,7 @@ class PossibleFragmentSpreads extends ValidationRule public function getVisitor(ValidationContext $context) { return [ - NodeKind::INLINE_FRAGMENT => function (InlineFragmentNode $node) use ($context) { + NodeKind::INLINE_FRAGMENT => function (InlineFragmentNode $node) use ($context) : void { $fragType = $context->getType(); $parentType = $context->getParentType(); @@ -38,7 +38,7 @@ public function getVisitor(ValidationContext $context) [$node] )); }, - NodeKind::FRAGMENT_SPREAD => function (FragmentSpreadNode $node) use ($context) { + NodeKind::FRAGMENT_SPREAD => function (FragmentSpreadNode $node) use ($context) : void { $fragName = $node->name->value; $fragType = $this->getFragmentType($context, $fragName); $parentType = $context->getParentType(); diff --git a/src/Validator/Rules/ProvidedRequiredArguments.php b/src/Validator/Rules/ProvidedRequiredArguments.php index 2099f3c48..41730ebe2 100644 --- a/src/Validator/Rules/ProvidedRequiredArguments.php +++ b/src/Validator/Rules/ProvidedRequiredArguments.php @@ -9,6 +9,7 @@ use GraphQL\Language\AST\FieldNode; use GraphQL\Language\AST\NodeKind; use GraphQL\Language\Visitor; +use GraphQL\Language\VisitorOperation; use GraphQL\Type\Definition\NonNull; use GraphQL\Validator\ValidationContext; use function sprintf; @@ -19,7 +20,7 @@ public function getVisitor(ValidationContext $context) { return [ NodeKind::FIELD => [ - 'leave' => static function (FieldNode $fieldNode) use ($context) { + 'leave' => static function (FieldNode $fieldNode) use ($context) : ?VisitorOperation { $fieldDef = $context->getFieldDef(); if (! $fieldDef) { @@ -42,10 +43,12 @@ public function getVisitor(ValidationContext $context) [$fieldNode] )); } + + return null; }, ], NodeKind::DIRECTIVE => [ - 'leave' => static function (DirectiveNode $directiveNode) use ($context) { + 'leave' => static function (DirectiveNode $directiveNode) use ($context) : ?VisitorOperation { $directiveDef = $context->getDirective(); if (! $directiveDef) { return Visitor::skipNode(); @@ -71,6 +74,8 @@ public function getVisitor(ValidationContext $context) [$directiveNode] )); } + + return null; }, ], ]; diff --git a/src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php b/src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php index c26c061c6..2eacdc202 100644 --- a/src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php +++ b/src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php @@ -85,7 +85,7 @@ static function (NamedTypeNode $argument) : string { } return [ - NodeKind::DIRECTIVE => static function (DirectiveNode $directiveNode) use ($requiredArgsMap, $context) { + NodeKind::DIRECTIVE => static function (DirectiveNode $directiveNode) use ($requiredArgsMap, $context) : ?string { $directiveName = $directiveNode->name->value; $requiredArgs = $requiredArgsMap[$directiveName] ?? null; if (! $requiredArgs) { @@ -109,6 +109,8 @@ static function (ArgumentNode $arg) : string { new Error(static::missingDirectiveArgMessage($directiveName, $argName), [$directiveNode]) ); } + + return null; }, ]; } diff --git a/src/Validator/Rules/QueryComplexity.php b/src/Validator/Rules/QueryComplexity.php index 7d98c2e25..b084799b3 100644 --- a/src/Validator/Rules/QueryComplexity.php +++ b/src/Validator/Rules/QueryComplexity.php @@ -15,6 +15,7 @@ use GraphQL\Language\AST\OperationDefinitionNode; use GraphQL\Language\AST\SelectionSetNode; use GraphQL\Language\Visitor; +use GraphQL\Language\VisitorOperation; use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\FieldDefinition; use GraphQL\Validator\ValidationContext; @@ -60,7 +61,7 @@ public function getVisitor(ValidationContext $context) return $this->invokeIfNeeded( $context, [ - NodeKind::SELECTION_SET => function (SelectionSetNode $selectionSet) use ($context) { + NodeKind::SELECTION_SET => function (SelectionSetNode $selectionSet) use ($context) : void { $this->fieldNodeAndDefs = $this->collectFieldASTsAndDefs( $context, $context->getParentType(), @@ -69,13 +70,13 @@ public function getVisitor(ValidationContext $context) $this->fieldNodeAndDefs ); }, - NodeKind::VARIABLE_DEFINITION => function ($def) { + NodeKind::VARIABLE_DEFINITION => function ($def) : VisitorOperation { $this->variableDefs[] = $def; return Visitor::skipNode(); }, NodeKind::OPERATION_DEFINITION => [ - 'leave' => function (OperationDefinitionNode $operationDefinition) use ($context, &$complexity) { + 'leave' => function (OperationDefinitionNode $operationDefinition) use ($context, &$complexity) : void { $errors = $context->getErrors(); if (! empty($errors)) { diff --git a/src/Validator/Rules/QueryDepth.php b/src/Validator/Rules/QueryDepth.php index cb01dd235..9b83d061e 100644 --- a/src/Validator/Rules/QueryDepth.php +++ b/src/Validator/Rules/QueryDepth.php @@ -31,7 +31,7 @@ public function getVisitor(ValidationContext $context) $context, [ NodeKind::OPERATION_DEFINITION => [ - 'leave' => function (OperationDefinitionNode $operationDefinition) use ($context) { + 'leave' => function (OperationDefinitionNode $operationDefinition) use ($context) : void { $maxDepth = $this->fieldDepth($operationDefinition); if ($maxDepth <= $this->getMaxQueryDepth()) { diff --git a/src/Validator/Rules/ScalarLeafs.php b/src/Validator/Rules/ScalarLeafs.php index 1bd25152b..f1714239e 100644 --- a/src/Validator/Rules/ScalarLeafs.php +++ b/src/Validator/Rules/ScalarLeafs.php @@ -16,7 +16,7 @@ class ScalarLeafs extends ValidationRule public function getVisitor(ValidationContext $context) { return [ - NodeKind::FIELD => static function (FieldNode $node) use ($context) { + NodeKind::FIELD => static function (FieldNode $node) use ($context) : void { $type = $context->getType(); if (! $type) { return; diff --git a/src/Validator/Rules/UniqueArgumentNames.php b/src/Validator/Rules/UniqueArgumentNames.php index 982fd6346..f3010bc77 100644 --- a/src/Validator/Rules/UniqueArgumentNames.php +++ b/src/Validator/Rules/UniqueArgumentNames.php @@ -9,6 +9,7 @@ use GraphQL\Language\AST\NameNode; use GraphQL\Language\AST\NodeKind; use GraphQL\Language\Visitor; +use GraphQL\Language\VisitorOperation; use GraphQL\Validator\ASTValidationContext; use GraphQL\Validator\SDLValidationContext; use GraphQL\Validator\ValidationContext; @@ -34,13 +35,13 @@ public function getASTVisitor(ASTValidationContext $context) $this->knownArgNames = []; return [ - NodeKind::FIELD => function () { + NodeKind::FIELD => function () : void { $this->knownArgNames = []; }, - NodeKind::DIRECTIVE => function () { + NodeKind::DIRECTIVE => function () : void { $this->knownArgNames = []; }, - NodeKind::ARGUMENT => function (ArgumentNode $node) use ($context) { + NodeKind::ARGUMENT => function (ArgumentNode $node) use ($context) : VisitorOperation { $argName = $node->name->value; if (! empty($this->knownArgNames[$argName])) { $context->reportError(new Error( diff --git a/src/Validator/Rules/UniqueDirectivesPerLocation.php b/src/Validator/Rules/UniqueDirectivesPerLocation.php index 9b784f19c..6860222d5 100644 --- a/src/Validator/Rules/UniqueDirectivesPerLocation.php +++ b/src/Validator/Rules/UniqueDirectivesPerLocation.php @@ -27,7 +27,7 @@ public function getSDLVisitor(SDLValidationContext $context) public function getASTVisitor(ASTValidationContext $context) { return [ - 'enter' => static function (Node $node) use ($context) { + 'enter' => static function (Node $node) use ($context) : void { if (! isset($node->directives)) { return; } diff --git a/src/Validator/Rules/UniqueFragmentNames.php b/src/Validator/Rules/UniqueFragmentNames.php index 475e6b3fa..6333a10c2 100644 --- a/src/Validator/Rules/UniqueFragmentNames.php +++ b/src/Validator/Rules/UniqueFragmentNames.php @@ -9,6 +9,7 @@ use GraphQL\Language\AST\NameNode; use GraphQL\Language\AST\NodeKind; use GraphQL\Language\Visitor; +use GraphQL\Language\VisitorOperation; use GraphQL\Validator\ValidationContext; use function sprintf; @@ -22,10 +23,10 @@ public function getVisitor(ValidationContext $context) $this->knownFragmentNames = []; return [ - NodeKind::OPERATION_DEFINITION => static function () { + NodeKind::OPERATION_DEFINITION => static function () : VisitorOperation { return Visitor::skipNode(); }, - NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) use ($context) { + NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) use ($context) : VisitorOperation { $fragmentName = $node->name->value; if (empty($this->knownFragmentNames[$fragmentName])) { $this->knownFragmentNames[$fragmentName] = $node->name; diff --git a/src/Validator/Rules/UniqueInputFieldNames.php b/src/Validator/Rules/UniqueInputFieldNames.php index da36b9bf8..5eab0260d 100644 --- a/src/Validator/Rules/UniqueInputFieldNames.php +++ b/src/Validator/Rules/UniqueInputFieldNames.php @@ -9,6 +9,7 @@ use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\ObjectFieldNode; use GraphQL\Language\Visitor; +use GraphQL\Language\VisitorOperation; use GraphQL\Validator\ASTValidationContext; use GraphQL\Validator\SDLValidationContext; use GraphQL\Validator\ValidationContext; @@ -40,15 +41,15 @@ public function getASTVisitor(ASTValidationContext $context) return [ NodeKind::OBJECT => [ - 'enter' => function () { + 'enter' => function () : void { $this->knownNameStack[] = $this->knownNames; $this->knownNames = []; }, - 'leave' => function () { + 'leave' => function () : void { $this->knownNames = array_pop($this->knownNameStack); }, ], - NodeKind::OBJECT_FIELD => function (ObjectFieldNode $node) use ($context) { + NodeKind::OBJECT_FIELD => function (ObjectFieldNode $node) use ($context) : VisitorOperation { $fieldName = $node->name->value; if (! empty($this->knownNames[$fieldName])) { diff --git a/src/Validator/Rules/UniqueOperationNames.php b/src/Validator/Rules/UniqueOperationNames.php index 761ce2cba..bc9c19910 100644 --- a/src/Validator/Rules/UniqueOperationNames.php +++ b/src/Validator/Rules/UniqueOperationNames.php @@ -9,6 +9,7 @@ use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\OperationDefinitionNode; use GraphQL\Language\Visitor; +use GraphQL\Language\VisitorOperation; use GraphQL\Validator\ValidationContext; use function sprintf; @@ -22,7 +23,7 @@ public function getVisitor(ValidationContext $context) $this->knownOperationNames = []; return [ - NodeKind::OPERATION_DEFINITION => function (OperationDefinitionNode $node) use ($context) { + NodeKind::OPERATION_DEFINITION => function (OperationDefinitionNode $node) use ($context) : VisitorOperation { $operationName = $node->name; if ($operationName !== null) { @@ -38,7 +39,7 @@ public function getVisitor(ValidationContext $context) return Visitor::skipNode(); }, - NodeKind::FRAGMENT_DEFINITION => static function () { + NodeKind::FRAGMENT_DEFINITION => static function () : VisitorOperation { return Visitor::skipNode(); }, ]; diff --git a/src/Validator/Rules/UniqueVariableNames.php b/src/Validator/Rules/UniqueVariableNames.php index f050a7342..8292e845a 100644 --- a/src/Validator/Rules/UniqueVariableNames.php +++ b/src/Validator/Rules/UniqueVariableNames.php @@ -21,10 +21,10 @@ public function getVisitor(ValidationContext $context) $this->knownVariableNames = []; return [ - NodeKind::OPERATION_DEFINITION => function () { + NodeKind::OPERATION_DEFINITION => function () : void { $this->knownVariableNames = []; }, - NodeKind::VARIABLE_DEFINITION => function (VariableDefinitionNode $node) use ($context) { + NodeKind::VARIABLE_DEFINITION => function (VariableDefinitionNode $node) use ($context) : void { $variableName = $node->variable->name->value; if (empty($this->knownVariableNames[$variableName])) { $this->knownVariableNames[$variableName] = $node->variable->name; diff --git a/src/Validator/Rules/ValuesOfCorrectType.php b/src/Validator/Rules/ValuesOfCorrectType.php index 2e70abff6..53b15423d 100644 --- a/src/Validator/Rules/ValuesOfCorrectType.php +++ b/src/Validator/Rules/ValuesOfCorrectType.php @@ -20,6 +20,7 @@ use GraphQL\Language\AST\VariableNode; use GraphQL\Language\Printer; use GraphQL\Language\Visitor; +use GraphQL\Language\VisitorOperation; use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\EnumValueDefinition; use GraphQL\Type\Definition\InputObjectType; @@ -51,11 +52,11 @@ public function getVisitor(ValidationContext $context) return [ NodeKind::FIELD => [ - 'enter' => static function (FieldNode $node) use (&$fieldName) { + 'enter' => static function (FieldNode $node) use (&$fieldName) : void { $fieldName = $node->name->value; }, ], - NodeKind::NULL => static function (NullValueNode $node) use ($context, &$fieldName) { + NodeKind::NULL => static function (NullValueNode $node) use ($context, &$fieldName) : void { $type = $context->getInputType(); if (! ($type instanceof NonNull)) { return; @@ -68,7 +69,7 @@ public function getVisitor(ValidationContext $context) ) ); }, - NodeKind::LST => function (ListValueNode $node) use ($context, &$fieldName) { + NodeKind::LST => function (ListValueNode $node) use ($context, &$fieldName) : ?VisitorOperation { // Note: TypeInfo will traverse into a list's item type, so look to the // parent input type to check if it is a list. $type = Type::getNullableType($context->getParentInputType()); @@ -77,6 +78,8 @@ public function getVisitor(ValidationContext $context) return Visitor::skipNode(); } + + return null; }, NodeKind::OBJECT => function (ObjectValueNode $node) use ($context, &$fieldName) { // Note: TypeInfo will traverse into a list's item type, so look to the @@ -93,7 +96,7 @@ public function getVisitor(ValidationContext $context) $nodeFields = iterator_to_array($node->fields); $fieldNodeMap = array_combine( array_map( - static function ($field) { + static function ($field) : string { return $field->name->value; }, $nodeFields @@ -114,7 +117,7 @@ static function ($field) { ); } }, - NodeKind::OBJECT_FIELD => static function (ObjectFieldNode $node) use ($context) { + NodeKind::OBJECT_FIELD => static function (ObjectFieldNode $node) use ($context) : void { $parentType = Type::getNamedType($context->getParentInputType()); /** @var ScalarType|EnumType|InputObjectType|ListOfType|NonNull $fieldType */ $fieldType = $context->getInputType(); @@ -137,7 +140,7 @@ static function ($field) { ) ); }, - NodeKind::ENUM => function (EnumValueNode $node) use ($context, &$fieldName) { + NodeKind::ENUM => function (EnumValueNode $node) use ($context, &$fieldName) : void { $type = Type::getNamedType($context->getInputType()); if (! $type instanceof EnumType) { $this->isValidScalar($context, $node, $fieldName); @@ -156,16 +159,16 @@ static function ($field) { ); } }, - NodeKind::INT => function (IntValueNode $node) use ($context, &$fieldName) { + NodeKind::INT => function (IntValueNode $node) use ($context, &$fieldName) : void { $this->isValidScalar($context, $node, $fieldName); }, - NodeKind::FLOAT => function (FloatValueNode $node) use ($context, &$fieldName) { + NodeKind::FLOAT => function (FloatValueNode $node) use ($context, &$fieldName) : void { $this->isValidScalar($context, $node, $fieldName); }, - NodeKind::STRING => function (StringValueNode $node) use ($context, &$fieldName) { + NodeKind::STRING => function (StringValueNode $node) use ($context, &$fieldName) : void { $this->isValidScalar($context, $node, $fieldName); }, - NodeKind::BOOLEAN => function (BooleanValueNode $node) use ($context, &$fieldName) { + NodeKind::BOOLEAN => function (BooleanValueNode $node) use ($context, &$fieldName) : void { $this->isValidScalar($context, $node, $fieldName); }, ]; diff --git a/src/Validator/Rules/VariablesAreInputTypes.php b/src/Validator/Rules/VariablesAreInputTypes.php index 5dc27b433..a1daafff0 100644 --- a/src/Validator/Rules/VariablesAreInputTypes.php +++ b/src/Validator/Rules/VariablesAreInputTypes.php @@ -18,7 +18,7 @@ class VariablesAreInputTypes extends ValidationRule public function getVisitor(ValidationContext $context) { return [ - NodeKind::VARIABLE_DEFINITION => static function (VariableDefinitionNode $node) use ($context) { + NodeKind::VARIABLE_DEFINITION => static function (VariableDefinitionNode $node) use ($context) : void { $type = TypeInfo::typeFromAST($context->getSchema(), $node->type); // If the variable type is not an input type, return an error. diff --git a/src/Validator/Rules/VariablesInAllowedPosition.php b/src/Validator/Rules/VariablesInAllowedPosition.php index a3932fd92..717bd6563 100644 --- a/src/Validator/Rules/VariablesInAllowedPosition.php +++ b/src/Validator/Rules/VariablesInAllowedPosition.php @@ -32,10 +32,10 @@ public function getVisitor(ValidationContext $context) { return [ NodeKind::OPERATION_DEFINITION => [ - 'enter' => function () { + 'enter' => function () : void { $this->varDefMap = []; }, - 'leave' => function (OperationDefinitionNode $operation) use ($context) { + 'leave' => function (OperationDefinitionNode $operation) use ($context) : void { $usages = $context->getRecursiveVariableUsages($operation); foreach ($usages as $usage) { @@ -68,7 +68,7 @@ public function getVisitor(ValidationContext $context) } }, ], - NodeKind::VARIABLE_DEFINITION => function (VariableDefinitionNode $varDefNode) { + NodeKind::VARIABLE_DEFINITION => function (VariableDefinitionNode $varDefNode) : void { $this->varDefMap[$varDefNode->variable->name->value] = $varDefNode; }, ]; diff --git a/src/Validator/ValidationContext.php b/src/Validator/ValidationContext.php index 67f9fbb0c..30ff56cf8 100644 --- a/src/Validator/ValidationContext.php +++ b/src/Validator/ValidationContext.php @@ -105,13 +105,13 @@ private function getVariableUsages(HasSelectionSet $node) Visitor::visitWithTypeInfo( $typeInfo, [ - NodeKind::VARIABLE_DEFINITION => static function () { + NodeKind::VARIABLE_DEFINITION => static function () : bool { return false; }, NodeKind::VARIABLE => static function (VariableNode $variable) use ( &$newUsages, $typeInfo - ) { + ) : void { $newUsages[] = [ 'node' => $variable, 'type' => $typeInfo->getInputType(), diff --git a/tests/Type/QueryPlanTest.php b/tests/Type/QueryPlanTest.php index 0e0e5abfa..20c6f555b 100644 --- a/tests/Type/QueryPlanTest.php +++ b/tests/Type/QueryPlanTest.php @@ -32,7 +32,7 @@ public function testQueryPlan() : void $author = new ObjectType([ 'name' => 'Author', - 'fields' => static function () use ($image, &$article) { + 'fields' => static function () use ($image, &$article) : array { return [ 'id' => ['type' => Type::string()], 'name' => ['type' => Type::string()], @@ -302,7 +302,7 @@ public function testQueryPlanOnInterface() : void { $petType = new InterfaceType([ 'name' => 'Pet', - 'fields' => static function () { + 'fields' => static function () : array { return [ 'name' => ['type' => Type::string()], ]; @@ -312,10 +312,10 @@ public function testQueryPlanOnInterface() : void $dogType = new ObjectType([ 'name' => 'Dog', 'interfaces' => [$petType], - 'isTypeOf' => static function ($obj) { + 'isTypeOf' => static function ($obj) : bool { return $obj instanceof Dog; }, - 'fields' => static function () { + 'fields' => static function () : array { return [ 'name' => ['type' => Type::string()], 'woofs' => ['type' => Type::boolean()], @@ -372,7 +372,7 @@ public function testQueryPlanOnInterface() : void ) use ( &$hasCalled, &$queryPlan -) { + ) : array { $hasCalled = true; $queryPlan = $info->lookAhead(); diff --git a/tests/Type/ResolveInfoTest.php b/tests/Type/ResolveInfoTest.php index d05a44c35..000711f53 100644 --- a/tests/Type/ResolveInfoTest.php +++ b/tests/Type/ResolveInfoTest.php @@ -28,7 +28,7 @@ public function testFieldSelection() : void $author = new ObjectType([ 'name' => 'Author', - 'fields' => static function () use ($image, &$article) { + 'fields' => static function () use ($image, &$article) : array { return [ 'id' => ['type' => Type::string()], 'name' => ['type' => Type::string()], @@ -224,7 +224,7 @@ public function testMergedFragmentsFieldSelection() : void $author = new ObjectType([ 'name' => 'Author', - 'fields' => static function () use ($image, &$article) { + 'fields' => static function () use ($image, &$article) : array { return [ 'id' => ['type' => Type::string()], 'name' => ['type' => Type::string()], diff --git a/tests/Type/SchemaTest.php b/tests/Type/SchemaTest.php index 37075ec94..9b401a49f 100644 --- a/tests/Type/SchemaTest.php +++ b/tests/Type/SchemaTest.php @@ -46,7 +46,7 @@ public function setUp() : void 'fields' => [ 'fieldName' => [ 'type' => Type::string(), - 'resolve' => static function () { + 'resolve' => static function () : string { return ''; }, ], @@ -90,7 +90,7 @@ public function setUp() : void 'fields' => [ 'getObject' => [ 'type' => $this->interfaceType, - 'resolve' => static function () { + 'resolve' => static function () : array { return []; }, ], diff --git a/tests/Type/StandardTypesTest.php b/tests/Type/StandardTypesTest.php index 00e182079..1f1989142 100644 --- a/tests/Type/StandardTypesTest.php +++ b/tests/Type/StandardTypesTest.php @@ -121,11 +121,11 @@ private function createCustomScalarType($name) { return new CustomScalarType([ 'name' => $name, - 'serialize' => static function () { + 'serialize' => static function () : void { }, - 'parseValue' => static function () { + 'parseValue' => static function () : void { }, - 'parseLiteral' => static function () { + 'parseLiteral' => static function () : void { }, ]); } diff --git a/tests/Type/TypeLoaderTest.php b/tests/Type/TypeLoaderTest.php index ab87c70aa..ddf8a2137 100644 --- a/tests/Type/TypeLoaderTest.php +++ b/tests/Type/TypeLoaderTest.php @@ -54,20 +54,20 @@ public function setUp() : void $this->node = new InterfaceType([ 'name' => 'Node', - 'fields' => function () { + 'fields' => function () : array { $this->calls[] = 'Node.fields'; return [ 'id' => Type::string(), ]; }, - 'resolveType' => static function () { + 'resolveType' => static function () : void { }, ]); $this->content = new InterfaceType([ 'name' => 'Content', - 'fields' => function () { + 'fields' => function () : array { $this->calls[] = 'Content.fields'; return [ @@ -75,7 +75,7 @@ public function setUp() : void 'body' => Type::string(), ]; }, - 'resolveType' => static function () { + 'resolveType' => static function () : void { }, ]); @@ -85,7 +85,7 @@ public function setUp() : void $this->node, $this->content, ], - 'fields' => function () { + 'fields' => function () : array { $this->calls[] = 'BlogStory.fields'; return [ @@ -98,7 +98,7 @@ public function setUp() : void $this->query = new ObjectType([ 'name' => 'Query', - 'fields' => function () { + 'fields' => function () : array { $this->calls[] = 'Query.fields'; return [ @@ -110,7 +110,7 @@ public function setUp() : void $this->mutation = new ObjectType([ 'name' => 'Mutation', - 'fields' => function () { + 'fields' => function () : array { $this->calls[] = 'Mutation.fields'; return [ @@ -158,7 +158,7 @@ public function testSchemaAcceptsTypeLoader() : void 'name' => 'Query', 'fields' => ['a' => Type::string()], ]), - 'typeLoader' => static function () { + 'typeLoader' => static function () : void { }, ]); } @@ -259,7 +259,7 @@ public function testFailsOnNonExistentType() : void { $schema = new Schema([ 'query' => $this->query, - 'typeLoader' => static function () { + 'typeLoader' => static function () : void { }, ]); @@ -273,7 +273,7 @@ public function testFailsOnNonType() : void { $schema = new Schema([ 'query' => $this->query, - 'typeLoader' => static function () { + 'typeLoader' => static function () : stdClass { return new stdClass(); }, ]); @@ -288,7 +288,7 @@ public function testFailsOnInvalidLoad() : void { $schema = new Schema([ 'query' => $this->query, - 'typeLoader' => function () { + 'typeLoader' => function () : InterfaceType { return $this->content; }, ]); @@ -303,7 +303,7 @@ public function testPassesThroughAnExceptionInLoader() : void { $schema = new Schema([ 'query' => $this->query, - 'typeLoader' => static function () { + 'typeLoader' => static function () : void { throw new Exception('This is the exception we are looking for'); }, ]); diff --git a/tests/Type/ValidationTest.php b/tests/Type/ValidationTest.php index ca21338c2..f374643e7 100644 --- a/tests/Type/ValidationTest.php +++ b/tests/Type/ValidationTest.php @@ -13,6 +13,8 @@ use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\InterfaceType; +use GraphQL\Type\Definition\ListOfType; +use GraphQL\Type\Definition\NonNull; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ScalarType; use GraphQL\Type\Definition\Type; @@ -65,27 +67,27 @@ public function setUp() : void $this->SomeScalarType = new CustomScalarType([ 'name' => 'SomeScalar', - 'serialize' => static function () { + 'serialize' => static function () : void { }, - 'parseValue' => static function () { + 'parseValue' => static function () : void { }, - 'parseLiteral' => static function () { + 'parseLiteral' => static function () : void { }, ]); $this->SomeInterfaceType = new InterfaceType([ 'name' => 'SomeInterface', - 'fields' => function () { + 'fields' => function () : array { return ['f' => ['type' => $this->SomeObjectType]]; }, ]); $this->SomeObjectType = new ObjectType([ 'name' => 'SomeObject', - 'fields' => function () { + 'fields' => function () : array { return ['f' => ['type' => $this->SomeObjectType]]; }, - 'interfaces' => function () { + 'interfaces' => function () : array { return [$this->SomeInterfaceType]; }, ]); @@ -144,19 +146,19 @@ private function withModifiers($types) $types, Utils::map( $types, - static function ($type) { + static function ($type) : ListOfType { return Type::listOf($type); } ), Utils::map( $types, - static function ($type) { + static function ($type) : NonNull { return Type::nonNull($type); } ), Utils::map( $types, - static function ($type) { + static function ($type) : NonNull { return Type::nonNull(Type::listOf($type)); } ) @@ -173,19 +175,19 @@ public function testRejectsTypesWithoutNames() : void { $this->assertEachCallableThrows( [ - static function () { + static function () : ObjectType { return new ObjectType([]); }, - static function () { + static function () : EnumType { return new EnumType([]); }, - static function () { + static function () : InputObjectType { return new InputObjectType([]); }, - static function () { + static function () : UnionType { return new UnionType([]); }, - static function () { + static function () : InterfaceType { return new InterfaceType([]); }, ], @@ -335,7 +337,7 @@ public function testRejectsASchemaWithoutAQueryType() : void private function formatLocations(Error $error) { - return Utils::map($error->getLocations(), static function (SourceLocation $loc) { + return Utils::map($error->getLocations(), static function (SourceLocation $loc) : array { return ['line' => $loc->line, 'column' => $loc->column]; }); } @@ -348,7 +350,7 @@ private function formatLocations(Error $error) */ private function formatErrors(array $errors, $withLocation = true) { - return Utils::map($errors, function (Error $error) use ($withLocation) { + return Utils::map($errors, function (Error $error) use ($withLocation) : array { if (! $withLocation) { return [ 'message' => $error->getMessage() ]; } @@ -641,7 +643,7 @@ public function testRejectsAnObjectTypeWithMissingFields() : void $manualSchema2 = $this->schemaWithFieldType( new ObjectType([ 'name' => 'IncompleteObject', - 'fields' => static function () { + 'fields' => static function () : array { return []; }, ]) @@ -2355,7 +2357,7 @@ interface AnotherInterface { public function testRejectsDifferentInstancesOfTheSameType() : void { // Invalid: always creates new instance vs returning one from registry - $typeLoader = static function ($name) { + $typeLoader = static function ($name) : ?ObjectType { switch ($name) { case 'Query': return new ObjectType([ diff --git a/tests/Utils/ExtractTypesTest.php b/tests/Utils/ExtractTypesTest.php index 757c8ce8a..be2de6dfe 100644 --- a/tests/Utils/ExtractTypesTest.php +++ b/tests/Utils/ExtractTypesTest.php @@ -65,7 +65,7 @@ public function setUp() : void $this->content = new InterfaceType([ 'name' => 'Content', - 'fields' => function () { + 'fields' => function () : array { return [ 'title' => Type::string(), 'body' => Type::string(), @@ -82,7 +82,7 @@ public function setUp() : void $this->node, $this->content, ], - 'fields' => function () { + 'fields' => function () : array { return [ $this->node->getField('id'), $this->content->getField('title'), @@ -100,7 +100,7 @@ public function setUp() : void $this->node, $this->content, ], - 'fields' => function () { + 'fields' => function () : array { return [ 'id' => $this->node->getField('id'), 'title' => $this->content->getField('title'), @@ -119,7 +119,7 @@ public function setUp() : void $this->node, $this->content, ], - 'fields' => function () { + 'fields' => function () : array { return [ 'id' => $this->node->getField('id'), 'title' => $this->content->getField('title'), @@ -145,7 +145,7 @@ public function setUp() : void 'interfaces' => [ $this->node, ], - 'fields' => function () { + 'fields' => function () : array { return [ 'id' => $this->node->getField('id'), 'author' => $this->user, @@ -162,7 +162,7 @@ public function setUp() : void 'interfaces' => [ $this->node, ], - 'fields' => function () { + 'fields' => function () : array { return [ 'id' => $this->node->getField('id'), 'name' => Type::string(), @@ -175,7 +175,7 @@ public function setUp() : void 'interfaces' => [ $this->node, ], - 'fields' => function () { + 'fields' => function () : array { return [ 'id' => $this->node->getField('id'), 'name' => Type::string(), diff --git a/tests/Utils/MixedStoreTest.php b/tests/Utils/MixedStoreTest.php index fd04d2e0c..e23460daf 100644 --- a/tests/Utils/MixedStoreTest.php +++ b/tests/Utils/MixedStoreTest.php @@ -38,7 +38,7 @@ public function getPossibleValues() 'a', [], new stdClass(), - static function () { + static function () : void { }, new MixedStore(), ]; @@ -124,7 +124,7 @@ public function testAcceptsObjectKeys() : void $this->assertAcceptsKeyValue(new stdClass(), $value); $this->assertAcceptsKeyValue(new MixedStore(), $value); $this->assertAcceptsKeyValue( - static function () { + static function () : void { }, $value ); diff --git a/tests/Utils/SchemaExtenderTest.php b/tests/Utils/SchemaExtenderTest.php index 61cdb18a0..a7d9729bc 100644 --- a/tests/Utils/SchemaExtenderTest.php +++ b/tests/Utils/SchemaExtenderTest.php @@ -68,7 +68,7 @@ public function setUp() : void $SomeInterfaceType = new InterfaceType([ 'name' => 'SomeInterface', - 'fields' => static function () use (&$SomeInterfaceType) { + 'fields' => static function () use (&$SomeInterfaceType) : array { return [ 'name' => [ 'type' => Type::string()], 'some' => [ 'type' => $SomeInterfaceType], @@ -79,7 +79,7 @@ public function setUp() : void $FooType = new ObjectType([ 'name' => 'Foo', 'interfaces' => [$SomeInterfaceType], - 'fields' => static function () use ($SomeInterfaceType, &$FooType) { + 'fields' => static function () use ($SomeInterfaceType, &$FooType) : array { return [ 'name' => [ 'type' => Type::string() ], 'some' => [ 'type' => $SomeInterfaceType ], @@ -184,7 +184,7 @@ public function setUp() : void $testSchemaAst = Parser::parse(SchemaPrinter::doPrint($this->testSchema)); - $this->testSchemaDefinitions = array_map(static function ($node) { + $this->testSchemaDefinitions = array_map(static function ($node) : string { return Printer::doPrint($node); }, iterator_to_array($testSchemaAst->definitions->getIterator())); @@ -1156,19 +1156,19 @@ public function testMayExtendMutationsAndSubscriptions() $mutationSchema = new Schema([ 'query' => new ObjectType([ 'name' => 'Query', - 'fields' => static function () { + 'fields' => static function () : array { return [ 'queryField' => [ 'type' => Type::string() ] ]; }, ]), 'mutation' => new ObjectType([ 'name' => 'Mutation', - 'fields' => static function () { + 'fields' => static function () : array { return [ 'mutationField' => ['type' => Type::string() ] ]; }, ]), 'subscription' => new ObjectType([ 'name' => 'Subscription', - 'fields' => static function () { + 'fields' => static function () : array { return ['subscriptionField' => ['type' => Type::string()]]; }, ]), @@ -1357,7 +1357,7 @@ public function testDoesNotAllowReplacingACustomDirective() */ public function testDoesNotAllowReplacingAnExistingType() { - $existingTypeError = static function ($type) { + $existingTypeError = static function ($type) : string { return 'Type "' . $type . '" already exists in the schema. It cannot also be defined in this type definition.'; }; @@ -1433,7 +1433,7 @@ enum SomeEnum */ public function testDoesNotAllowReplacingAnExistingField() { - $existingFieldError = static function (string $type, string $field) { + $existingFieldError = static function (string $type, string $field) : string { return 'Field "' . $type . '.' . $field . '" already exists in the schema. It cannot also be defined in this type extension.'; }; @@ -1786,7 +1786,7 @@ public function testSchemaExtensionASTAreAvailableFromSchemaObject() '), implode( "\n", - array_map(static function ($node) { + array_map(static function ($node) : string { return Printer::doPrint($node) . "\n"; }, $nodes) ) @@ -1879,7 +1879,7 @@ public function testOriginalResolversArePreserved() 'fields' => [ 'hello' => [ 'type' => Type::string(), - 'resolve' => static function () { + 'resolve' => static function () : string { return 'Hello World!'; }, ], diff --git a/tests/Validator/OverlappingFieldsCanBeMergedTest.php b/tests/Validator/OverlappingFieldsCanBeMergedTest.php index e66ee71f1..9b3879431 100644 --- a/tests/Validator/OverlappingFieldsCanBeMergedTest.php +++ b/tests/Validator/OverlappingFieldsCanBeMergedTest.php @@ -704,7 +704,7 @@ private function getSchema() $SomeBox = new InterfaceType([ 'name' => 'SomeBox', - 'fields' => static function () use (&$SomeBox) { + 'fields' => static function () use (&$SomeBox) : array { return [ 'deepBox' => ['type' => $SomeBox], 'unrelatedField' => ['type' => Type::string()], @@ -715,7 +715,7 @@ private function getSchema() $StringBox = new ObjectType([ 'name' => 'StringBox', 'interfaces' => [$SomeBox], - 'fields' => static function () use (&$StringBox, &$IntBox) { + 'fields' => static function () use (&$StringBox, &$IntBox) : array { return [ 'scalar' => ['type' => Type::string()], 'deepBox' => ['type' => $StringBox], @@ -730,7 +730,7 @@ private function getSchema() $IntBox = new ObjectType([ 'name' => 'IntBox', 'interfaces' => [$SomeBox], - 'fields' => static function () use (&$StringBox, &$IntBox) { + 'fields' => static function () use (&$StringBox, &$IntBox) : array { return [ 'scalar' => ['type' => Type::int()], 'deepBox' => ['type' => $IntBox], diff --git a/tests/Validator/QueryComplexityTest.php b/tests/Validator/QueryComplexityTest.php index d47c9c5b1..0a14fb966 100644 --- a/tests/Validator/QueryComplexityTest.php +++ b/tests/Validator/QueryComplexityTest.php @@ -199,10 +199,10 @@ public function testSkippedWhenThereAreOtherValidationErrors() : void $reportedError = new Error('OtherValidatorError'); $otherRule = new CustomValidationRule( 'otherRule', - static function (ValidationContext $context) use ($reportedError) { + static function (ValidationContext $context) use ($reportedError) : array { return [ NodeKind::OPERATION_DEFINITION => [ - 'leave' => static function () use ($context, $reportedError) { + 'leave' => static function () use ($context, $reportedError) : void { $context->reportError($reportedError); }, ], diff --git a/tests/Validator/ValidatorTestCase.php b/tests/Validator/ValidatorTestCase.php index 641039835..0944f0e5c 100644 --- a/tests/Validator/ValidatorTestCase.php +++ b/tests/Validator/ValidatorTestCase.php @@ -64,7 +64,7 @@ public static function getTestSchema() $Canine = new InterfaceType([ 'name' => 'Canine', - 'fields' => static function () { + 'fields' => static function () : array { return [ 'name' => [ 'type' => Type::string(), @@ -111,7 +111,7 @@ public static function getTestSchema() $Cat = new ObjectType([ 'name' => 'Cat', - 'fields' => static function () use (&$FurColor) { + 'fields' => static function () use (&$FurColor) : array { return [ 'name' => [ 'type' => Type::string(), @@ -142,7 +142,7 @@ public static function getTestSchema() $Human = new ObjectType([ 'name' => 'Human', 'interfaces' => [$Being, $Intelligent], - 'fields' => static function () use (&$Human, $Pet) { + 'fields' => static function () use (&$Human, $Pet) : array { return [ 'name' => [ 'type' => Type::string(), @@ -300,10 +300,10 @@ public static function getTestSchema() 'serialize' => static function ($value) { return $value; }, - 'parseLiteral' => static function ($node) { + 'parseLiteral' => static function ($node) : void { throw new Exception('Invalid scalar is always invalid: ' . $node->value); }, - 'parseValue' => static function ($node) { + 'parseValue' => static function ($node) : void { throw new Exception('Invalid scalar is always invalid: ' . $node); }, ]); From 1f0ec2408010e01879b1f980afac65de2344c631 Mon Sep 17 00:00:00 2001 From: Max Loeb Date: Wed, 29 Apr 2020 00:32:40 -0700 Subject: [PATCH 171/256] Fix native return type hints part II (#656) --- phpstan-baseline.neon | 425 ------------------ .../Rules/LoneAnonymousOperation.php | 2 +- tests/Executor/AbstractPromiseTest.php | 38 +- tests/Executor/AbstractTest.php | 29 +- tests/Executor/DeferredFieldsTest.php | 20 +- tests/Executor/ExecutorLazySchemaTest.php | 32 +- tests/Executor/ExecutorSchemaTest.php | 8 +- tests/Executor/ExecutorTest.php | 102 ++--- tests/Executor/LazyInterfaceTest.php | 6 +- tests/Executor/ListsTest.php | 90 ++-- tests/Executor/MutationsTest.php | 10 +- tests/Executor/NonNullTest.php | 46 +- .../Promise/ReactPromiseAdapterTest.php | 18 +- .../Promise/SyncPromiseAdapterTest.php | 26 +- tests/Executor/Promise/SyncPromiseTest.php | 28 +- tests/Executor/ResolveTest.php | 2 +- tests/Executor/SyncTest.php | 2 +- tests/Executor/TestClasses/Adder.php | 2 +- tests/Executor/TestClasses/Root.php | 4 +- tests/Executor/UnionInterfaceTest.php | 11 +- tests/Executor/VariablesTest.php | 2 +- tests/Experimental/Executor/CollectorTest.php | 2 +- tests/GraphQLTest.php | 3 +- tests/Language/ParserTest.php | 2 +- tests/Language/SchemaParserTest.php | 40 +- tests/Language/VisitorTest.php | 141 +++--- tests/Regression/Issue396Test.php | 6 +- tests/Server/QueryExecutionTest.php | 32 +- tests/Server/RequestParsingTest.php | 8 +- tests/Server/ServerConfigTest.php | 22 +- tests/Server/ServerTestCase.php | 4 +- tests/StarWarsSchema.php | 12 +- tests/Type/DefinitionTest.php | 68 +-- tests/Type/EnumTypeTest.php | 2 +- tests/Type/QueryPlanTest.php | 4 +- 35 files changed, 438 insertions(+), 811 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 8565ae099..8598378fa 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -865,11 +865,6 @@ parameters: count: 1 path: src/Validator/Rules/KnownTypeNames.php - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 1 - path: src/Validator/Rules/LoneAnonymousOperation.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 3 @@ -1115,71 +1110,6 @@ parameters: count: 1 path: src/Validator/ValidationContext.php - - - message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" - count: 5 - path: tests/Executor/AbstractPromiseTest.php - - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 7 - path: tests/Executor/AbstractPromiseTest.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Deferred\"\\.$#" - count: 2 - path: tests/Executor/AbstractPromiseTest.php - - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 2 - path: tests/Executor/AbstractPromiseTest.php - - - - message: "#^Anonymous function should have native return typehint \"\\?GraphQL\\\\Deferred\"\\.$#" - count: 1 - path: tests/Executor/AbstractPromiseTest.php - - - - message: "#^Anonymous function should have native return typehint \"\\?GraphQL\\\\Type\\\\Definition\\\\ObjectType\"\\.$#" - count: 1 - path: tests/Executor/AbstractPromiseTest.php - - - - message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" - count: 4 - path: tests/Executor/AbstractTest.php - - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 7 - path: tests/Executor/AbstractTest.php - - - - message: "#^Anonymous function sometimes return something but return statement at the end is missing\\.$#" - count: 1 - path: tests/Executor/AbstractTest.php - - - - message: "#^Anonymous function should have native return typehint \"string\"\\.$#" - count: 1 - path: tests/Executor/AbstractTest.php - - - - message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" - count: 4 - path: tests/Executor/DeferredFieldsTest.php - - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 4 - path: tests/Executor/DeferredFieldsTest.php - - - - message: "#^Anonymous function should have native return typehint \"string\"\\.$#" - count: 2 - path: tests/Executor/DeferredFieldsTest.php - - message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Schema given\\.$#" count: 1 @@ -1190,286 +1120,36 @@ parameters: count: 1 path: tests/Executor/DirectivesTest.php - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 13 - path: tests/Executor/ExecutorLazySchemaTest.php - - - - message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" - count: 2 - path: tests/Executor/ExecutorLazySchemaTest.php - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" count: 7 path: tests/Executor/ExecutorLazySchemaTest.php - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 1 - path: tests/Executor/ExecutorLazySchemaTest.php - - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 3 - path: tests/Executor/ExecutorSchemaTest.php - - - - message: "#^Anonymous function should have native return typehint \"\\?GraphQL\\\\Deferred\"\\.$#" - count: 1 - path: tests/Executor/ExecutorTest.php - - - - message: "#^Anonymous function should have native return typehint \"string\"\\.$#" - count: 19 - path: tests/Executor/ExecutorTest.php - - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 6 - path: tests/Executor/ExecutorTest.php - - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 12 - path: tests/Executor/ExecutorTest.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Error\\\\UserError\"\\.$#" - count: 1 - path: tests/Executor/ExecutorTest.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Deferred\"\\.$#" - count: 7 - path: tests/Executor/ExecutorTest.php - - - - message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" - count: 1 - path: tests/Executor/ExecutorTest.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\ObjectType\"\\.$#" - count: 1 - path: tests/Executor/ExecutorTest.php - - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 1 - path: tests/Executor/LazyInterfaceTest.php - - message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\InterfaceType given\\.$#" count: 1 path: tests/Executor/LazyInterfaceTest.php - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\ObjectType\"\\.$#" - count: 1 - path: tests/Executor/LazyInterfaceTest.php - - message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\ObjectType given\\.$#" count: 1 path: tests/Executor/LazyInterfaceTest.php - - - message: "#^Anonymous function should have native return typehint \"string\"\\.$#" - count: 1 - path: tests/Executor/LazyInterfaceTest.php - - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 9 - path: tests/Executor/ListsTest.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Deferred\"\\.$#" - count: 4 - path: tests/Executor/ListsTest.php - - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 8 - path: tests/Executor/ListsTest.php - - - - message: "#^Anonymous function should have native return typehint \"int\"\\.$#" - count: 24 - path: tests/Executor/ListsTest.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Tests\\\\Executor\\\\TestClasses\\\\NumberHolder\"\\.$#" - count: 1 - path: tests/Executor/MutationsTest.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Deferred\"\\.$#" - count: 2 - path: tests/Executor/MutationsTest.php - - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 1 - path: tests/Executor/MutationsTest.php - - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 4 - path: tests/Executor/NonNullTest.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Deferred\"\\.$#" - count: 2 - path: tests/Executor/NonNullTest.php - - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 9 - path: tests/Executor/NonNullTest.php - - - - message: "#^Anonymous function should have native return typehint \"\\?GraphQL\\\\Deferred\"\\.$#" - count: 2 - path: tests/Executor/NonNullTest.php - - - - message: "#^Anonymous function sometimes return something but return statement at the end is missing\\.$#" - count: 1 - path: tests/Executor/NonNullTest.php - - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 9 - path: tests/Executor/Promise/ReactPromiseAdapterTest.php - - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 7 - path: tests/Executor/Promise/SyncPromiseAdapterTest.php - - - - message: "#^Anonymous function should have native return typehint \"string\"\\.$#" - count: 1 - path: tests/Executor/Promise/SyncPromiseAdapterTest.php - - - - message: "#^Anonymous function should have native return typehint \"int\"\\.$#" - count: 4 - path: tests/Executor/Promise/SyncPromiseAdapterTest.php - - - - message: "#^Anonymous function should have native return typehint \"string\"\\.$#" - count: 3 - path: tests/Executor/Promise/SyncPromiseTest.php - - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 10 - path: tests/Executor/Promise/SyncPromiseTest.php - - - - message: "#^Anonymous function should have native return typehint \"int\"\\.$#" - count: 1 - path: tests/Executor/Promise/SyncPromiseTest.php - - - - message: "#^Anonymous function should have native return typehint \"string\"\\.$#" - count: 1 - path: tests/Executor/ResolveTest.php - - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 1 - path: tests/Executor/SyncTest.php - - - - message: "#^Anonymous function should have native return typehint \"float\"\\.$#" - count: 1 - path: tests/Executor/TestClasses/Adder.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Tests\\\\Executor\\\\TestClasses\\\\NumberHolder\"\\.$#" - count: 1 - path: tests/Executor/TestClasses/Root.php - - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 1 - path: tests/Executor/TestClasses/Root.php - - - - message: "#^Anonymous function should have native return typehint \"bool\"\\.$#" - count: 3 - path: tests/Executor/UnionInterfaceTest.php - - - - message: "#^Anonymous function sometimes return something but return statement at the end is missing\\.$#" - count: 1 - path: tests/Executor/UnionInterfaceTest.php - - message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Schema given\\.$#" count: 1 path: tests/Executor/ValuesTest.php - - - message: "#^Anonymous function should have native return typehint \"\\?string\"\\.$#" - count: 1 - path: tests/Executor/VariablesTest.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 6 path: tests/Experimental/Executor/CollectorTest.php - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 1 - path: tests/Experimental/Executor/CollectorTest.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Executor\\\\Promise\\\\Promise\"\\.$#" - count: 1 - path: tests/GraphQLTest.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 1 path: tests/Language/LexerTest.php - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 1 - path: tests/Language/ParserTest.php - - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 20 - path: tests/Language/SchemaParserTest.php - - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 31 - path: tests/Language/VisitorTest.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\AST\\\\OperationDefinitionNode\"\\.$#" - count: 2 - path: tests/Language/VisitorTest.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\AST\\\\DocumentNode\"\\.$#" - count: 1 - path: tests/Language/VisitorTest.php - - - - message: "#^Anonymous function sometimes return something but return statement at the end is missing\\.$#" - count: 18 - path: tests/Language/VisitorTest.php - - message: "#^Only booleans are allowed in a ternary operator condition, \\(GraphQL\\\\Type\\\\Definition\\\\CompositeType&GraphQL\\\\Type\\\\Definition\\\\Type\\)\\|null given\\.$#" count: 4 @@ -1485,131 +1165,26 @@ parameters: count: 4 path: tests/Language/VisitorTest.php - - - message: "#^Anonymous function should return GraphQL\\\\Type\\\\Definition\\\\Type but return statement is missing\\.$#" - count: 1 - path: tests/Regression/Issue396Test.php - - - - message: "#^Anonymous function should return GraphQL\\\\Type\\\\Definition\\\\Type\\|null but return statement is missing\\.$#" - count: 1 - path: tests/Regression/Issue396Test.php - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" count: 1 path: tests/Server/Psr7/PsrStreamStub.php - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 7 - path: tests/Server/QueryExecutionTest.php - - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 4 - path: tests/Server/QueryExecutionTest.php - - - - message: "#^Anonymous function should have native return typehint \"string\"\\.$#" - count: 3 - path: tests/Server/QueryExecutionTest.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Language\\\\AST\\\\DocumentNode\"\\.$#" - count: 1 - path: tests/Server/QueryExecutionTest.php - - - - message: "#^Anonymous function should have native return typehint \"stdClass\"\\.$#" - count: 1 - path: tests/Server/QueryExecutionTest.php - - - - message: "#^Anonymous function should have native return typehint \"string\"\\.$#" - count: 1 - path: tests/Server/RequestParsingTest.php - - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 3 - path: tests/Server/RequestParsingTest.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 1 path: tests/Server/RequestValidationTest.php - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 10 - path: tests/Server/ServerConfigTest.php - - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 1 - path: tests/Server/ServerConfigTest.php - - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 2 - path: tests/Server/ServerTestCase.php - - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 4 - path: tests/StarWarsSchema.php - - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 2 - path: tests/StarWarsSchema.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 7 path: tests/StarWarsValidationTest.php - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 16 - path: tests/Type/DefinitionTest.php - - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 16 - path: tests/Type/DefinitionTest.php - - - - message: "#^Anonymous function should have native return typehint \"stdClass\"\\.$#" - count: 1 - path: tests/Type/DefinitionTest.php - - - - message: "#^Anonymous function should have native return typehint \"int\"\\.$#" - count: 1 - path: tests/Type/DefinitionTest.php - - - - message: "#^Anonymous function should have native return typehint \"void\"\\.$#" - count: 1 - path: tests/Type/EnumTypeTest.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 4 path: tests/Type/EnumTypeTest.php - - - message: "#^Anonymous function should have native return typehint \"array\"\\.$#" - count: 1 - path: tests/Type/QueryPlanTest.php - - - - message: "#^Anonymous function should have native return typehint \"GraphQL\\\\Type\\\\Definition\\\\ObjectType\"\\.$#" - count: 1 - path: tests/Type/QueryPlanTest.php - - message: "#^Variable property access on \\$this\\(GraphQL\\\\Tests\\\\Type\\\\TypeLoaderTest\\)\\.$#" count: 1 diff --git a/src/Validator/Rules/LoneAnonymousOperation.php b/src/Validator/Rules/LoneAnonymousOperation.php index f0a68cf6e..facc895a3 100644 --- a/src/Validator/Rules/LoneAnonymousOperation.php +++ b/src/Validator/Rules/LoneAnonymousOperation.php @@ -39,7 +39,7 @@ static function (Node $definition) : bool { NodeKind::OPERATION_DEFINITION => static function (OperationDefinitionNode $node) use ( &$operationCount, $context - ) { + ) : void { if ($node->name !== null || $operationCount <= 1) { return; } diff --git a/tests/Executor/AbstractPromiseTest.php b/tests/Executor/AbstractPromiseTest.php index c5fd7935a..04013e476 100644 --- a/tests/Executor/AbstractPromiseTest.php +++ b/tests/Executor/AbstractPromiseTest.php @@ -41,7 +41,7 @@ public function testIsTypeOfUsedToResolveRuntimeTypeForInterface() : void 'name' => 'Dog', 'interfaces' => [$petType], 'isTypeOf' => static function ($obj) { - return new Deferred(static function () use ($obj) { + return new Deferred(static function () use ($obj) : bool { return $obj instanceof Dog; }); }, @@ -55,7 +55,7 @@ public function testIsTypeOfUsedToResolveRuntimeTypeForInterface() : void 'name' => 'Cat', 'interfaces' => [$petType], 'isTypeOf' => static function ($obj) { - return new Deferred(static function () use ($obj) { + return new Deferred(static function () use ($obj) : bool { return $obj instanceof Cat; }); }, @@ -71,7 +71,7 @@ public function testIsTypeOfUsedToResolveRuntimeTypeForInterface() : void 'fields' => [ 'pets' => [ 'type' => Type::listOf($petType), - 'resolve' => static function () { + 'resolve' => static function () : array { return [ new Dog('Odie', true), new Cat('Garfield', false), @@ -124,8 +124,8 @@ public function testIsTypeOfCanBeRejected() : void $DogType = new ObjectType([ 'name' => 'Dog', 'interfaces' => [$PetType], - 'isTypeOf' => static function () { - return new Deferred(static function () { + 'isTypeOf' => static function () : Deferred { + return new Deferred(static function () : void { throw new UserError('We are testing this error'); }); }, @@ -139,7 +139,7 @@ public function testIsTypeOfCanBeRejected() : void 'name' => 'Cat', 'interfaces' => [$PetType], 'isTypeOf' => static function ($obj) { - return new Deferred(static function () use ($obj) { + return new Deferred(static function () use ($obj) : bool { return $obj instanceof Cat; }); }, @@ -155,7 +155,7 @@ public function testIsTypeOfCanBeRejected() : void 'fields' => [ 'pets' => [ 'type' => Type::listOf($PetType), - 'resolve' => static function () { + 'resolve' => static function () : array { return [ new Dog('Odie', true), new Cat('Garfield', false), @@ -210,7 +210,7 @@ public function testIsTypeOfUsedToResolveRuntimeTypeForUnion() : void $dogType = new ObjectType([ 'name' => 'Dog', 'isTypeOf' => static function ($obj) { - return new Deferred(static function () use ($obj) { + return new Deferred(static function () use ($obj) : bool { return $obj instanceof Dog; }); }, @@ -223,7 +223,7 @@ public function testIsTypeOfUsedToResolveRuntimeTypeForUnion() : void $catType = new ObjectType([ 'name' => 'Cat', 'isTypeOf' => static function ($obj) { - return new Deferred(static function () use ($obj) { + return new Deferred(static function () use ($obj) : bool { return $obj instanceof Cat; }); }, @@ -244,7 +244,7 @@ public function testIsTypeOfUsedToResolveRuntimeTypeForUnion() : void 'fields' => [ 'pets' => [ 'type' => Type::listOf($petType), - 'resolve' => static function () { + 'resolve' => static function () : array { return [new Dog('Odie', true), new Cat('Garfield', false)]; }, ], @@ -286,8 +286,8 @@ public function testResolveTypeOnInterfaceYieldsUsefulError() : void { $PetType = new InterfaceType([ 'name' => 'Pet', - 'resolveType' => static function ($obj) use (&$DogType, &$CatType, &$HumanType) { - return new Deferred(static function () use ($obj, $DogType, $CatType, $HumanType) { + 'resolveType' => static function ($obj) use (&$DogType, &$CatType, &$HumanType) : Deferred { + return new Deferred(static function () use ($obj, $DogType, $CatType, $HumanType) : ?Type { if ($obj instanceof Dog) { return $DogType; } @@ -338,7 +338,7 @@ public function testResolveTypeOnInterfaceYieldsUsefulError() : void 'pets' => [ 'type' => Type::listOf($PetType), 'resolve' => static function () { - return new Deferred(static function () { + return new Deferred(static function () : array { return [ new Dog('Odie', true), new Cat('Garfield', false), @@ -417,7 +417,7 @@ public function testResolveTypeOnUnionYieldsUsefulError() : void $PetType = new UnionType([ 'name' => 'Pet', 'resolveType' => static function ($obj) use ($DogType, $CatType, $HumanType) { - return new Deferred(static function () use ($obj, $DogType, $CatType, $HumanType) { + return new Deferred(static function () use ($obj, $DogType, $CatType, $HumanType) : ?Type { if ($obj instanceof Dog) { return $DogType; } @@ -440,7 +440,7 @@ public function testResolveTypeOnUnionYieldsUsefulError() : void 'fields' => [ 'pets' => [ 'type' => Type::listOf($PetType), - 'resolve' => static function () { + 'resolve' => static function () : array { return [ new Dog('Odie', true), new Cat('Garfield', false), @@ -535,7 +535,7 @@ public function testResolveTypeAllowsResolvingWithTypeName() : void 'fields' => [ 'pets' => [ 'type' => Type::listOf($PetType), - 'resolve' => static function () { + 'resolve' => static function () : array { return [ new Dog('Odie', true), new Cat('Garfield', false), @@ -579,8 +579,8 @@ public function testResolveTypeCanBeCaught() : void { $PetType = new InterfaceType([ 'name' => 'Pet', - 'resolveType' => static function () { - return new Deferred(static function () { + 'resolveType' => static function () : Deferred { + return new Deferred(static function () : void { throw new UserError('We are testing this error'); }); }, @@ -613,7 +613,7 @@ public function testResolveTypeCanBeCaught() : void 'fields' => [ 'pets' => [ 'type' => Type::listOf($PetType), - 'resolve' => static function () { + 'resolve' => static function () : array { return [ new Dog('Odie', true), new Cat('Garfield', false), diff --git a/tests/Executor/AbstractTest.php b/tests/Executor/AbstractTest.php index 60f5b793f..fe25b4656 100644 --- a/tests/Executor/AbstractTest.php +++ b/tests/Executor/AbstractTest.php @@ -4,6 +4,7 @@ namespace GraphQL\Tests\Executor; +use GraphQL\Error\InvariantViolation; use GraphQL\Executor\ExecutionResult; use GraphQL\Executor\Executor; use GraphQL\GraphQL; @@ -43,7 +44,7 @@ public function testIsTypeOfUsedToResolveRuntimeTypeForInterface() : void $dogType = new ObjectType([ 'name' => 'Dog', 'interfaces' => [$petType], - 'isTypeOf' => static function ($obj) { + 'isTypeOf' => static function ($obj) : bool { return $obj instanceof Dog; }, 'fields' => [ @@ -55,7 +56,7 @@ public function testIsTypeOfUsedToResolveRuntimeTypeForInterface() : void $catType = new ObjectType([ 'name' => 'Cat', 'interfaces' => [$petType], - 'isTypeOf' => static function ($obj) { + 'isTypeOf' => static function ($obj) : bool { return $obj instanceof Cat; }, 'fields' => [ @@ -70,7 +71,7 @@ public function testIsTypeOfUsedToResolveRuntimeTypeForInterface() : void 'fields' => [ 'pets' => [ 'type' => Type::listOf($petType), - 'resolve' => static function () { + 'resolve' => static function () : array { return [new Dog('Odie', true), new Cat('Garfield', false)]; }, ], @@ -109,7 +110,7 @@ public function testIsTypeOfUsedToResolveRuntimeTypeForUnion() : void { $dogType = new ObjectType([ 'name' => 'Dog', - 'isTypeOf' => static function ($obj) { + 'isTypeOf' => static function ($obj) : bool { return $obj instanceof Dog; }, 'fields' => [ @@ -120,7 +121,7 @@ public function testIsTypeOfUsedToResolveRuntimeTypeForUnion() : void $catType = new ObjectType([ 'name' => 'Cat', - 'isTypeOf' => static function ($obj) { + 'isTypeOf' => static function ($obj) : bool { return $obj instanceof Cat; }, 'fields' => [ @@ -140,7 +141,7 @@ public function testIsTypeOfUsedToResolveRuntimeTypeForUnion() : void 'fields' => [ 'pets' => [ 'type' => Type::listOf($petType), - 'resolve' => static function () { + 'resolve' => static function () : array { return [new Dog('Odie', true), new Cat('Garfield', false)]; }, ], @@ -230,7 +231,7 @@ public function testResolveTypeOnInterfaceYieldsUsefulError() : void 'fields' => [ 'pets' => [ 'type' => Type::listOf($PetType), - 'resolve' => static function () { + 'resolve' => static function () : array { return [ new Dog('Odie', true), new Cat('Garfield', false), @@ -305,7 +306,7 @@ public function testResolveTypeOnUnionYieldsUsefulError() : void $PetType = new UnionType([ 'name' => 'Pet', - 'resolveType' => static function ($obj) use ($DogType, $CatType, $HumanType) { + 'resolveType' => static function ($obj) use ($DogType, $CatType, $HumanType) : Type { if ($obj instanceof Dog) { return $DogType; } @@ -315,6 +316,8 @@ public function testResolveTypeOnUnionYieldsUsefulError() : void if ($obj instanceof Human) { return $HumanType; } + + throw new InvariantViolation('Invalid type'); }, 'types' => [$DogType, $CatType], ]); @@ -325,7 +328,7 @@ public function testResolveTypeOnUnionYieldsUsefulError() : void 'fields' => [ 'pets' => [ 'type' => Type::listOf($PetType), - 'resolve' => static function () { + 'resolve' => static function () : array { return [ new Dog('Odie', true), new Cat('Garfield', false), @@ -383,7 +386,7 @@ public function testReturningInvalidValueFromResolveTypeYieldsUsefulError() : vo $fooInterface = new InterfaceType([ 'name' => 'FooInterface', 'fields' => ['bar' => ['type' => Type::string()]], - 'resolveType' => static function () { + 'resolveType' => static function () : array { return []; }, ]); @@ -400,7 +403,7 @@ public function testReturningInvalidValueFromResolveTypeYieldsUsefulError() : vo 'fields' => [ 'foo' => [ 'type' => $fooInterface, - 'resolve' => static function () { + 'resolve' => static function () : string { return 'dummy'; }, ], @@ -476,7 +479,7 @@ public function testResolveTypeAllowsResolvingWithTypeName() : void 'fields' => [ 'pets' => [ 'type' => Type::listOf($PetType), - 'resolve' => static function () { + 'resolve' => static function () : array { return [ new Dog('Odie', true), new Cat('Garfield', false), @@ -523,7 +526,7 @@ public function testHintsOnConflictingTypeInstancesInResolveType() : void 'fields' => [ 'a' => Type::string(), ], - 'interfaces' => static function () use ($iface) { + 'interfaces' => static function () use ($iface) : array { return [$iface]; }, ]); diff --git a/tests/Executor/DeferredFieldsTest.php b/tests/Executor/DeferredFieldsTest.php index a897614a0..2f301c5af 100644 --- a/tests/Executor/DeferredFieldsTest.php +++ b/tests/Executor/DeferredFieldsTest.php @@ -144,7 +144,7 @@ public function setUp() : void return Utils::filter( $this->storyDataSource, - static function ($story) use ($category) { + static function ($story) use ($category) : bool { return in_array($category['id'], $story['categoryIds'], true); } ); @@ -192,7 +192,7 @@ static function ($story) use ($category) { return Utils::filter( $this->storyDataSource, - static function ($story) { + static function ($story) : bool { return $story['id'] % 2 === 1; } ); @@ -200,7 +200,7 @@ static function ($story) { ], 'featuredCategory' => [ 'type' => $this->categoryType, - 'resolve' => function ($rootValue, $args, $context, ResolveInfo $info) { + 'resolve' => function ($rootValue, $args, $context, ResolveInfo $info) : array { $this->paths[] = $info->path; return $this->categoryDataSource[0]; @@ -208,7 +208,7 @@ static function ($story) { ], 'categories' => [ 'type' => Type::listOf($this->categoryType), - 'resolve' => function ($rootValue, $args, $context, ResolveInfo $info) { + 'resolve' => function ($rootValue, $args, $context, ResolveInfo $info) : array { $this->paths[] = $info->path; return $this->categoryDataSource; @@ -403,7 +403,7 @@ public function testComplexRecursiveDeferredFields() : void return [ 'sync' => [ 'type' => Type::string(), - 'resolve' => function ($complexType, $args, $context, ResolveInfo $info) { + 'resolve' => function ($complexType, $args, $context, ResolveInfo $info) : string { $this->paths[] = $info->path; return 'sync'; @@ -414,7 +414,7 @@ public function testComplexRecursiveDeferredFields() : void 'resolve' => function ($complexType, $args, $context, ResolveInfo $info) { $this->paths[] = $info->path; - return new Deferred(function () use ($info) { + return new Deferred(function () use ($info) : string { $this->paths[] = ['!dfd for: ', $info->path]; return 'deferred'; @@ -423,7 +423,7 @@ public function testComplexRecursiveDeferredFields() : void ], 'nest' => [ 'type' => $complexType, - 'resolve' => function ($complexType, $args, $context, ResolveInfo $info) { + 'resolve' => function ($complexType, $args, $context, ResolveInfo $info) : array { $this->paths[] = $info->path; return []; @@ -434,7 +434,7 @@ public function testComplexRecursiveDeferredFields() : void 'resolve' => function ($complexType, $args, $context, ResolveInfo $info) { $this->paths[] = $info->path; - return new Deferred(function () use ($info) { + return new Deferred(function () use ($info) : array { $this->paths[] = ['!dfd nest for: ', $info->path]; return []; @@ -635,7 +635,7 @@ private function findStoryById($id) { return Utils::find( $this->storyDataSource, - static function ($story) use ($id) { + static function ($story) use ($id) : bool { return $story['id'] === $id; } ); @@ -645,7 +645,7 @@ private function findUserById($id) { return Utils::find( $this->userDataSource, - static function ($user) use ($id) { + static function ($user) use ($id) : bool { return $user['id'] === $id; } ); diff --git a/tests/Executor/ExecutorLazySchemaTest.php b/tests/Executor/ExecutorLazySchemaTest.php index 245b95df4..9d9878531 100644 --- a/tests/Executor/ExecutorLazySchemaTest.php +++ b/tests/Executor/ExecutorLazySchemaTest.php @@ -64,7 +64,7 @@ public function testWarnsAboutSlowIsTypeOfForLazySchema() : void // isTypeOf used to resolve runtime type for Interface $petType = new InterfaceType([ 'name' => 'Pet', - 'fields' => static function () { + 'fields' => static function () : array { return [ 'name' => ['type' => Type::string()], ]; @@ -75,10 +75,10 @@ public function testWarnsAboutSlowIsTypeOfForLazySchema() : void $dogType = new ObjectType([ 'name' => 'Dog', 'interfaces' => [$petType], - 'isTypeOf' => static function ($obj) { + 'isTypeOf' => static function ($obj) : bool { return $obj instanceof Dog; }, - 'fields' => static function () { + 'fields' => static function () : array { return [ 'name' => ['type' => Type::string()], 'woofs' => ['type' => Type::boolean()], @@ -89,10 +89,10 @@ public function testWarnsAboutSlowIsTypeOfForLazySchema() : void $catType = new ObjectType([ 'name' => 'Cat', 'interfaces' => [$petType], - 'isTypeOf' => static function ($obj) { + 'isTypeOf' => static function ($obj) : bool { return $obj instanceof Cat; }, - 'fields' => static function () { + 'fields' => static function () : array { return [ 'name' => ['type' => Type::string()], 'meows' => ['type' => Type::boolean()], @@ -106,7 +106,7 @@ public function testWarnsAboutSlowIsTypeOfForLazySchema() : void 'fields' => [ 'pets' => [ 'type' => Type::listOf($petType), - 'resolve' => static function () { + 'resolve' => static function () : array { return [new Dog('Odie', true), new Cat('Garfield', false)]; }, ], @@ -171,7 +171,7 @@ public function testHintsOnConflictingTypeInstancesInDefinitions() : void case 'Test': return new ObjectType([ 'name' => 'Test', - 'fields' => static function () { + 'fields' => static function () : array { return [ 'test' => Type::string(), ]; @@ -184,7 +184,7 @@ public function testHintsOnConflictingTypeInstancesInDefinitions() : void $query = new ObjectType([ 'name' => 'Query', - 'fields' => static function () use ($typeLoader) { + 'fields' => static function () use ($typeLoader) : array { return [ 'test' => $typeLoader('Test'), ]; @@ -259,7 +259,7 @@ public function loadType($name, $isExecutorCall = false) case 'Query': return $this->queryType ?: $this->queryType = new ObjectType([ 'name' => 'Query', - 'fields' => function () { + 'fields' => function () : array { $this->calls[] = 'Query.fields'; return [ @@ -271,7 +271,7 @@ public function loadType($name, $isExecutorCall = false) case 'SomeObject': return $this->someObjectType ?: $this->someObjectType = new ObjectType([ 'name' => 'SomeObject', - 'fields' => function () { + 'fields' => function () : array { $this->calls[] = 'SomeObject.fields'; return [ @@ -279,7 +279,7 @@ public function loadType($name, $isExecutorCall = false) 'object' => ['type' => $this->someObjectType], ]; }, - 'interfaces' => function () { + 'interfaces' => function () : array { $this->calls[] = 'SomeObject.interfaces'; return [ @@ -290,7 +290,7 @@ public function loadType($name, $isExecutorCall = false) case 'OtherObject': return $this->otherObjectType ?: $this->otherObjectType = new ObjectType([ 'name' => 'OtherObject', - 'fields' => function () { + 'fields' => function () : array { $this->calls[] = 'OtherObject.fields'; return [ @@ -302,7 +302,7 @@ public function loadType($name, $isExecutorCall = false) case 'DeeperObject': return $this->deeperObjectType ?: $this->deeperObjectType = new ObjectType([ 'name' => 'DeeperObject', - 'fields' => function () { + 'fields' => function () : array { return [ 'scalar' => ['type' => $this->loadType('SomeScalar')], ]; @@ -317,7 +317,7 @@ public function loadType($name, $isExecutorCall = false) 'parseValue' => static function ($value) { return $value; }, - 'parseLiteral' => static function () { + 'parseLiteral' => static function () : void { }, ]); case 'SomeUnion': @@ -328,7 +328,7 @@ public function loadType($name, $isExecutorCall = false) return $this->loadType('DeeperObject'); }, - 'types' => function () { + 'types' => function () : array { $this->calls[] = 'SomeUnion.types'; return [$this->loadType('DeeperObject')]; @@ -342,7 +342,7 @@ public function loadType($name, $isExecutorCall = false) return $this->loadType('SomeObject'); }, - 'fields' => function () { + 'fields' => function () : array { $this->calls[] = 'SomeInterface.fields'; return [ diff --git a/tests/Executor/ExecutorSchemaTest.php b/tests/Executor/ExecutorSchemaTest.php index 494d49301..4a35291a2 100644 --- a/tests/Executor/ExecutorSchemaTest.php +++ b/tests/Executor/ExecutorSchemaTest.php @@ -82,7 +82,7 @@ public function testExecutesUsingASchema() : void ], 'feed' => [ 'type' => Type::listOf($BlogArticle), - 'resolve' => function () { + 'resolve' => function () : array { return [ $this->article(1), $this->article(2), @@ -213,7 +213,7 @@ public function testExecutesUsingASchema() : void private function article($id) { $johnSmith = null; - $article = static function ($id) use (&$johnSmith) { + $article = static function ($id) use (&$johnSmith) : array { return [ 'id' => $id, 'isPublished' => 'true', @@ -226,7 +226,7 @@ private function article($id) ]; }; - $getPic = static function ($uid, $width, $height) { + $getPic = static function ($uid, $width, $height) : array { return [ 'url' => sprintf('cdn://%s', $uid), 'width' => $width, @@ -237,7 +237,7 @@ private function article($id) $johnSmith = [ 'id' => 123, 'name' => 'John Smith', - 'pic' => static function ($width, $height) use ($getPic) { + 'pic' => static function ($width, $height) use ($getPic) : array { return $getPic(123, $width, $height); }, 'recentArticle' => $article(1), diff --git a/tests/Executor/ExecutorTest.php b/tests/Executor/ExecutorTest.php index 28b36f9ed..33ae342b7 100644 --- a/tests/Executor/ExecutorTest.php +++ b/tests/Executor/ExecutorTest.php @@ -45,33 +45,33 @@ public function testExecutesArbitraryCode() : void $deepData = null; $data = null; - $promiseData = static function () use (&$data) { + $promiseData = static function () use (&$data) : Deferred { return new Deferred(static function () use (&$data) { return $data; }); }; $data = [ - 'a' => static function () { + 'a' => static function () : string { return 'Apple'; }, - 'b' => static function () { + 'b' => static function () : string { return 'Banana'; }, - 'c' => static function () { + 'c' => static function () : string { return 'Cookie'; }, - 'd' => static function () { + 'd' => static function () : string { return 'Donut'; }, - 'e' => static function () { + 'e' => static function () : string { return 'Egg'; }, 'f' => 'Fish', - 'pic' => static function ($size = 50) { + 'pic' => static function ($size = 50) : string { return 'Pic of size: ' . $size; }, - 'promise' => static function () use ($promiseData) { + 'promise' => static function () use ($promiseData) : Deferred { return $promiseData(); }, 'deep' => static function () use (&$deepData) { @@ -81,16 +81,16 @@ public function testExecutesArbitraryCode() : void // Required for that & reference above $deepData = [ - 'a' => static function () { + 'a' => static function () : string { return 'Already Been Done'; }, - 'b' => static function () { + 'b' => static function () : string { return 'Boring'; }, - 'c' => static function () { + 'c' => static function () : array { return ['Contrived', null, 'Confusing']; }, - 'deeper' => static function () use (&$data) { + 'deeper' => static function () use (&$data) : array { return [$data, null, $data]; }, ]; @@ -216,25 +216,25 @@ public function testMergesParallelFragments() : void return [ 'a' => [ 'type' => Type::string(), - 'resolve' => static function () { + 'resolve' => static function () : string { return 'Apple'; }, ], 'b' => [ 'type' => Type::string(), - 'resolve' => static function () { + 'resolve' => static function () : string { return 'Banana'; }, ], 'c' => [ 'type' => Type::string(), - 'resolve' => static function () { + 'resolve' => static function () : string { return 'Cherry'; }, ], 'deep' => [ 'type' => $Type, - 'resolve' => static function () { + 'resolve' => static function () : array { return []; }, ], @@ -277,7 +277,7 @@ public function testProvidesInfoAboutCurrentExecutionState() : void 'fields' => [ 'test' => [ 'type' => Type::string(), - 'resolve' => static function ($test, $args, $ctx, $_info) use (&$info) { + 'resolve' => static function ($test, $args, $ctx, $_info) use (&$info) : void { $info = $_info; }, ], @@ -324,7 +324,7 @@ public function testThreadsContextCorrectly() : void 'fields' => [ 'a' => [ 'type' => Type::string(), - 'resolve' => static function ($context) use (&$gotHere) { + 'resolve' => static function ($context) use (&$gotHere) : void { self::assertEquals('thing', $context['contextThing']); $gotHere = true; }, @@ -361,7 +361,7 @@ public function testCorrectlyThreadsArguments() : void 'stringArg' => ['type' => Type::string()], ], 'type' => Type::string(), - 'resolve' => static function ($_, $args) use (&$gotHere) { + 'resolve' => static function ($_, $args) use (&$gotHere) : void { self::assertEquals(123, $args['numArg']); self::assertEquals('foo', $args['stringArg']); $gotHere = true; @@ -396,21 +396,21 @@ public function testNullsOutErrorSubtrees() : void }'; $data = [ - 'sync' => static function () { + 'sync' => static function () : string { return 'sync'; }, - 'syncError' => static function () { + 'syncError' => static function () : void { throw new UserError('Error getting syncError'); }, - 'syncRawError' => static function () { + 'syncRawError' => static function () : void { throw new UserError('Error getting syncRawError'); }, // inherited from JS reference implementation, but make no sense in this PHP impl // leaving it just to simplify migrations from newer js versions - 'syncReturnError' => static function () { + 'syncReturnError' => static function () : UserError { return new UserError('Error getting syncReturnError'); }, - 'syncReturnErrorList' => static function () { + 'syncReturnErrorList' => static function () : array { return [ 'sync0', new UserError('Error getting syncReturnErrorList1'), @@ -418,45 +418,45 @@ public function testNullsOutErrorSubtrees() : void new UserError('Error getting syncReturnErrorList3'), ]; }, - 'async' => static function () { - return new Deferred(static function () { + 'async' => static function () : Deferred { + return new Deferred(static function () : string { return 'async'; }); }, - 'asyncReject' => static function () { - return new Deferred(static function () { + 'asyncReject' => static function () : Deferred { + return new Deferred(static function () : void { throw new UserError('Error getting asyncReject'); }); }, - 'asyncRawReject' => static function () { - return new Deferred(static function () { + 'asyncRawReject' => static function () : Deferred { + return new Deferred(static function () : void { throw new UserError('Error getting asyncRawReject'); }); }, - 'asyncEmptyReject' => static function () { - return new Deferred(static function () { + 'asyncEmptyReject' => static function () : Deferred { + return new Deferred(static function () : void { throw new UserError(); }); }, - 'asyncError' => static function () { - return new Deferred(static function () { + 'asyncError' => static function () : Deferred { + return new Deferred(static function () : void { throw new UserError('Error getting asyncError'); }); }, // inherited from JS reference implementation, but make no sense in this PHP impl // leaving it just to simplify migrations from newer js versions - 'asyncRawError' => static function () { - return new Deferred(static function () { + 'asyncRawError' => static function () : Deferred { + return new Deferred(static function () : void { throw new UserError('Error getting asyncRawError'); }); }, - 'asyncReturnError' => static function () { - return new Deferred(static function () { + 'asyncReturnError' => static function () : Deferred { + return new Deferred(static function () : void { throw new UserError('Error getting asyncReturnError'); }); }, - 'asyncReturnErrorWithExtensions' => static function () { - return new Deferred(static function () { + 'asyncReturnErrorWithExtensions' => static function () : Deferred { + return new Deferred(static function () : void { $error = new Error( 'Error getting asyncReturnErrorWithExtensions', null, @@ -824,23 +824,23 @@ public function testCorrectFieldOrderingDespiteExecutionOrder() : void e }'; $data = [ - 'a' => static function () { + 'a' => static function () : string { return 'a'; }, 'b' => static function () { - return new Deferred(static function () { + return new Deferred(static function () : string { return 'b'; }); }, - 'c' => static function () { + 'c' => static function () : string { return 'c'; }, - 'd' => static function () { - return new Deferred(static function () { + 'd' => static function () : Deferred { + return new Deferred(static function () : string { return 'd'; }); }, - 'e' => static function () { + 'e' => static function () : string { return 'e'; }, ]; @@ -973,7 +973,7 @@ public function testFailsWhenAnIsTypeOfCheckIsNotMet() : void { $SpecialType = new ObjectType([ 'name' => 'SpecialType', - 'isTypeOf' => static function ($obj) { + 'isTypeOf' => static function ($obj) : bool { return $obj instanceof Special; }, 'fields' => [ @@ -1068,7 +1068,7 @@ public function testUsesACustomFieldResolver() : void ]); // For the purposes of test, just return the name of the field! - $customResolver = static function ($source, $args, $context, ResolveInfo $info) { + $customResolver = static function ($source, $args, $context, ResolveInfo $info) : string { return $info->fieldName; }; @@ -1155,7 +1155,7 @@ public function testSerializesToEmptyObjectVsEmptyArray() : void 'fields' => [ 'id' => Type::id(), ], - 'interfaces' => static function () use (&$iface) { + 'interfaces' => static function () use (&$iface) : array { return [$iface]; }, ]); @@ -1165,7 +1165,7 @@ public function testSerializesToEmptyObjectVsEmptyArray() : void 'fields' => [ 'id' => Type::id(), ], - 'interfaces' => static function () use (&$iface) { + 'interfaces' => static function () use (&$iface) : array { return [$iface]; }, ]); @@ -1175,7 +1175,7 @@ public function testSerializesToEmptyObjectVsEmptyArray() : void 'fields' => [ 'id' => Type::id(), ], - 'resolveType' => static function ($v) use ($a, $b) { + 'resolveType' => static function ($v) use ($a, $b) : ObjectType { return $v['type'] === 'A' ? $a : $b; }, ]); diff --git a/tests/Executor/LazyInterfaceTest.php b/tests/Executor/LazyInterfaceTest.php index b1602ecec..7917b483d 100644 --- a/tests/Executor/LazyInterfaceTest.php +++ b/tests/Executor/LazyInterfaceTest.php @@ -58,7 +58,7 @@ protected function setUp() : void return [ 'lazyInterface' => [ 'type' => $this->getLazyInterfaceType(), - 'resolve' => static function () { + 'resolve' => static function () : array { return []; }, ], @@ -82,7 +82,7 @@ protected function getLazyInterfaceType() 'fields' => [ 'a' => Type::string(), ], - 'resolveType' => function () { + 'resolveType' => function () : ObjectType { return $this->getTestObjectType(); }, ]); @@ -104,7 +104,7 @@ protected function getTestObjectType() 'fields' => [ 'name' => [ 'type' => Type::string(), - 'resolve' => static function () { + 'resolve' => static function () : string { return 'testname'; }, ], diff --git a/tests/Executor/ListsTest.php b/tests/Executor/ListsTest.php index 036a908f2..103841ab8 100644 --- a/tests/Executor/ListsTest.php +++ b/tests/Executor/ListsTest.php @@ -62,7 +62,7 @@ private function check($testType, $testData, $expected, $debug = false) 'test' => ['type' => $testType], 'nest' => [ 'type' => $dataType, - 'resolve' => static function () use ($data) { + 'resolve' => static function () use ($data) : array { return $data; }, ], @@ -85,7 +85,7 @@ public function testHandlesNullableListsWithPromiseArray() : void { // Contains values $this->checkHandlesNullableLists( - new Deferred(static function () { + new Deferred(static function () : array { return [1, 2]; }), ['data' => ['nest' => ['test' => [1, 2]]]] @@ -93,7 +93,7 @@ public function testHandlesNullableListsWithPromiseArray() : void // Contains null $this->checkHandlesNullableLists( - new Deferred(static function () { + new Deferred(static function () : array { return [1, null, 2]; }), ['data' => ['nest' => ['test' => [1, null, 2]]]] @@ -109,8 +109,8 @@ public function testHandlesNullableListsWithPromiseArray() : void // Rejected $this->checkHandlesNullableLists( - static function () { - return new Deferred(static function () { + static function () : Deferred { + return new Deferred(static function () : void { throw new UserError('bad'); }); }, @@ -135,10 +135,10 @@ public function testHandlesNullableListsWithArrayPromise() : void // Contains values $this->checkHandlesNullableLists( [ - new Deferred(static function () { + new Deferred(static function () : int { return 1; }), - new Deferred(static function () { + new Deferred(static function () : int { return 2; }), ], @@ -148,13 +148,13 @@ public function testHandlesNullableListsWithArrayPromise() : void // Contains null $this->checkHandlesNullableLists( [ - new Deferred(static function () { + new Deferred(static function () : int { return 1; }), new Deferred(static function () { return null; }), - new Deferred(static function () { + new Deferred(static function () : int { return 2; }), ], @@ -173,13 +173,13 @@ public function testHandlesNullableListsWithArrayPromise() : void $this->checkHandlesNullableLists( static function () { return [ - new Deferred(static function () { + new Deferred(static function () : int { return 1; }), - new Deferred(static function () { + new Deferred(static function () : void { throw new UserError('bad'); }), - new Deferred(static function () { + new Deferred(static function () : int { return 2; }), ]; @@ -243,7 +243,7 @@ public function testHandlesNonNullableListsWithPromiseArray() : void { // Contains values $this->checkHandlesNonNullableLists( - new Deferred(static function () { + new Deferred(static function () : array { return [1, 2]; }), ['data' => ['nest' => ['test' => [1, 2]]]] @@ -251,7 +251,7 @@ public function testHandlesNonNullableListsWithPromiseArray() : void // Contains null $this->checkHandlesNonNullableLists( - new Deferred(static function () { + new Deferred(static function () : array { return [1, null, 2]; }), ['data' => ['nest' => ['test' => [1, null, 2]]]] @@ -274,8 +274,8 @@ public function testHandlesNonNullableListsWithPromiseArray() : void // Rejected $this->checkHandlesNonNullableLists( - static function () { - return new Deferred(static function () { + static function () : Deferred { + return new Deferred(static function () : void { throw new UserError('bad'); }); }, @@ -300,10 +300,10 @@ public function testHandlesNonNullableListsWithArrayPromise() : void // Contains values $this->checkHandlesNonNullableLists( [ - new Deferred(static function () { + new Deferred(static function () : int { return 1; }), - new Deferred(static function () { + new Deferred(static function () : int { return 2; }), ], @@ -313,13 +313,13 @@ public function testHandlesNonNullableListsWithArrayPromise() : void // Contains null $this->checkHandlesNonNullableLists( [ - new Deferred(static function () { + new Deferred(static function () : int { return 1; }), new Deferred(static function () { return null; }), - new Deferred(static function () { + new Deferred(static function () : int { return 2; }), ], @@ -330,13 +330,13 @@ public function testHandlesNonNullableListsWithArrayPromise() : void $this->checkHandlesNonNullableLists( static function () { return [ - new Deferred(static function () { + new Deferred(static function () : int { return 1; }), - new Deferred(static function () { + new Deferred(static function () : void { throw new UserError('bad'); }), - new Deferred(static function () { + new Deferred(static function () : int { return 2; }), ]; @@ -400,7 +400,7 @@ public function testHandlesListOfNonNullsWithPromiseArray() : void { // Contains values $this->checkHandlesListOfNonNulls( - new Deferred(static function () { + new Deferred(static function () : array { return [1, 2]; }), ['data' => ['nest' => ['test' => [1, 2]]]] @@ -408,7 +408,7 @@ public function testHandlesListOfNonNullsWithPromiseArray() : void // Contains null $this->checkHandlesListOfNonNulls( - new Deferred(static function () { + new Deferred(static function () : array { return [1, null, 2]; }), [ @@ -433,8 +433,8 @@ public function testHandlesListOfNonNullsWithPromiseArray() : void // Rejected $this->checkHandlesListOfNonNulls( - static function () { - return new Deferred(static function () { + static function () : Deferred { + return new Deferred(static function () : void { throw new UserError('bad'); }); }, @@ -459,10 +459,10 @@ public function testHandlesListOfNonNullsWithArrayPromise() : void // Contains values $this->checkHandlesListOfNonNulls( [ - new Deferred(static function () { + new Deferred(static function () : int { return 1; }), - new Deferred(static function () { + new Deferred(static function () : int { return 2; }), ], @@ -472,13 +472,13 @@ public function testHandlesListOfNonNullsWithArrayPromise() : void // Contains null $this->checkHandlesListOfNonNulls( [ - new Deferred(static function () { + new Deferred(static function () : int { return 1; }), new Deferred(static function () { return null; }), - new Deferred(static function () { + new Deferred(static function () : int { return 2; }), ], @@ -489,13 +489,13 @@ public function testHandlesListOfNonNullsWithArrayPromise() : void $this->checkHandlesListOfNonNulls( static function () { return [ - new Deferred(static function () { + new Deferred(static function () : int { return 1; }), - new Deferred(static function () { + new Deferred(static function () : void { throw new UserError('bad'); }), - new Deferred(static function () { + new Deferred(static function () : int { return 2; }), ]; @@ -568,7 +568,7 @@ public function testHandlesNonNullListOfNonNullsWithPromiseArray() : void { // Contains values $this->checkHandlesNonNullListOfNonNulls( - new Deferred(static function () { + new Deferred(static function () : array { return [1, 2]; }), ['data' => ['nest' => ['test' => [1, 2]]]] @@ -576,7 +576,7 @@ public function testHandlesNonNullListOfNonNullsWithPromiseArray() : void // Contains null $this->checkHandlesNonNullListOfNonNulls( - new Deferred(static function () { + new Deferred(static function () : array { return [1, null, 2]; }), [ @@ -610,8 +610,8 @@ public function testHandlesNonNullListOfNonNullsWithPromiseArray() : void // Rejected $this->checkHandlesNonNullListOfNonNulls( - static function () { - return new Deferred(static function () { + static function () : Deferred { + return new Deferred(static function () : void { throw new UserError('bad'); }); }, @@ -636,10 +636,10 @@ public function testHandlesNonNullListOfNonNullsWithArrayPromise() : void // Contains values $this->checkHandlesNonNullListOfNonNulls( [ - new Deferred(static function () { + new Deferred(static function () : int { return 1; }), - new Deferred(static function () { + new Deferred(static function () : int { return 2; }), @@ -650,13 +650,13 @@ public function testHandlesNonNullListOfNonNullsWithArrayPromise() : void // Contains null $this->checkHandlesNonNullListOfNonNulls( [ - new Deferred(static function () { + new Deferred(static function () : int { return 1; }), new Deferred(static function () { return null; }), - new Deferred(static function () { + new Deferred(static function () : int { return 2; }), ], @@ -676,13 +676,13 @@ public function testHandlesNonNullListOfNonNullsWithArrayPromise() : void $this->checkHandlesNonNullListOfNonNulls( static function () { return [ - new Deferred(static function () { + new Deferred(static function () : int { return 1; }), - new Deferred(static function () { + new Deferred(static function () : void { throw new UserError('bad'); }), - new Deferred(static function () { + new Deferred(static function () : int { return 2; }), ]; diff --git a/tests/Executor/MutationsTest.php b/tests/Executor/MutationsTest.php index 2f45a186a..7b61ae472 100644 --- a/tests/Executor/MutationsTest.php +++ b/tests/Executor/MutationsTest.php @@ -4,8 +4,10 @@ namespace GraphQL\Tests\Executor; +use GraphQL\Deferred; use GraphQL\Executor\Executor; use GraphQL\Language\Parser; +use GraphQL\Tests\Executor\TestClasses\NumberHolder; use GraphQL\Tests\Executor\TestClasses\Root; use GraphQL\Tests\PHPUnit\ArraySubsetAsserts; use GraphQL\Type\Definition\ObjectType; @@ -76,28 +78,28 @@ private function schema() : Schema 'immediatelyChangeTheNumber' => [ 'type' => $numberHolderType, 'args' => ['newNumber' => ['type' => Type::int()]], - 'resolve' => static function (Root $obj, $args) { + 'resolve' => static function (Root $obj, $args) : NumberHolder { return $obj->immediatelyChangeTheNumber($args['newNumber']); }, ], 'promiseToChangeTheNumber' => [ 'type' => $numberHolderType, 'args' => ['newNumber' => ['type' => Type::int()]], - 'resolve' => static function (Root $obj, $args) { + 'resolve' => static function (Root $obj, $args) : Deferred { return $obj->promiseToChangeTheNumber($args['newNumber']); }, ], 'failToChangeTheNumber' => [ 'type' => $numberHolderType, 'args' => ['newNumber' => ['type' => Type::int()]], - 'resolve' => static function (Root $obj, $args) { + 'resolve' => static function (Root $obj, $args) : void { $obj->failToChangeTheNumber(); }, ], 'promiseAndFailToChangeTheNumber' => [ 'type' => $numberHolderType, 'args' => ['newNumber' => ['type' => Type::int()]], - 'resolve' => static function (Root $obj, $args) { + 'resolve' => static function (Root $obj, $args) : Deferred { return $obj->promiseAndFailToChangeTheNumber(); }, ], diff --git a/tests/Executor/NonNullTest.php b/tests/Executor/NonNullTest.php index a0f36ea25..a057bd665 100644 --- a/tests/Executor/NonNullTest.php +++ b/tests/Executor/NonNullTest.php @@ -57,35 +57,35 @@ public function setUp() : void $this->promiseNonNullError = new UserError('promiseNonNull'); $this->throwingData = [ - 'sync' => function () { + 'sync' => function () : void { throw $this->syncError; }, - 'syncNonNull' => function () { + 'syncNonNull' => function () : void { throw $this->syncNonNullError; }, - 'promise' => function () { - return new Deferred(function () { + 'promise' => function () : Deferred { + return new Deferred(function () : void { throw $this->promiseError; }); }, - 'promiseNonNull' => function () { - return new Deferred(function () { + 'promiseNonNull' => function () : Deferred { + return new Deferred(function () : void { throw $this->promiseNonNullError; }); }, - 'syncNest' => function () { + 'syncNest' => function () : array { return $this->throwingData; }, - 'syncNonNullNest' => function () { + 'syncNonNullNest' => function () : array { return $this->throwingData; }, - 'promiseNest' => function () { - return new Deferred(function () { + 'promiseNest' => function () : Deferred { + return new Deferred(function () : array { return $this->throwingData; }); }, - 'promiseNonNullNest' => function () { - return new Deferred(function () { + 'promiseNonNullNest' => function () : Deferred { + return new Deferred(function () : array { return $this->throwingData; }); }, @@ -98,29 +98,29 @@ public function setUp() : void 'syncNonNull' => static function () { return null; }, - 'promise' => static function () { + 'promise' => static function () : Deferred { return new Deferred(static function () { return null; }); }, - 'promiseNonNull' => static function () { + 'promiseNonNull' => static function () : Deferred { return new Deferred(static function () { return null; }); }, - 'syncNest' => function () { + 'syncNest' => function () : array { return $this->nullingData; }, - 'syncNonNullNest' => function () { + 'syncNonNullNest' => function () : array { return $this->nullingData; }, - 'promiseNest' => function () { - return new Deferred(function () { + 'promiseNest' => function () : Deferred { + return new Deferred(function () : array { return $this->nullingData; }); }, - 'promiseNonNullNest' => function () { - return new Deferred(function () { + 'promiseNonNullNest' => function () : Deferred { + return new Deferred(function () : array { return $this->nullingData; }); }, @@ -128,7 +128,7 @@ public function setUp() : void $dataType = new ObjectType([ 'name' => 'DataType', - 'fields' => static function () use (&$dataType) { + 'fields' => static function () use (&$dataType) : array { return [ 'sync' => ['type' => Type::string()], 'syncNonNull' => ['type' => Type::nonNull(Type::string())], @@ -155,10 +155,12 @@ public function setUp() : void 'type' => Type::nonNull(Type::string()), ], ], - 'resolve' => static function ($value, $args) { + 'resolve' => static function ($value, $args) : ?string { if (is_string($args['cannotBeNull'])) { return 'Passed: ' . $args['cannotBeNull']; } + + return null; }, ], ], diff --git a/tests/Executor/Promise/ReactPromiseAdapterTest.php b/tests/Executor/Promise/ReactPromiseAdapterTest.php index 58e821ffe..4c3fbb80e 100644 --- a/tests/Executor/Promise/ReactPromiseAdapterTest.php +++ b/tests/Executor/Promise/ReactPromiseAdapterTest.php @@ -34,13 +34,13 @@ public function testIsThenableReturnsTrueWhenAReactPromiseIsGiven() : void $reactAdapter = new ReactPromiseAdapter(); self::assertTrue( - $reactAdapter->isThenable(new ReactPromise(static function () { + $reactAdapter->isThenable(new ReactPromise(static function () : void { })) ); self::assertTrue($reactAdapter->isThenable(new FulfilledPromise())); self::assertTrue($reactAdapter->isThenable(new RejectedPromise())); self::assertTrue( - $reactAdapter->isThenable(new LazyPromise(static function () { + $reactAdapter->isThenable(new LazyPromise(static function () : void { })) ); self::assertFalse($reactAdapter->isThenable(false)); @@ -74,7 +74,7 @@ public function testThen() : void $resultPromise = $reactAdapter->then( $promise, - static function ($value) use (&$result) { + static function ($value) use (&$result) : void { $result = $value; } ); @@ -87,7 +87,7 @@ static function ($value) use (&$result) { public function testCreate() : void { $reactAdapter = new ReactPromiseAdapter(); - $resolvedPromise = $reactAdapter->create(static function ($resolve) { + $resolvedPromise = $reactAdapter->create(static function ($resolve) : void { $resolve(1); }); @@ -96,7 +96,7 @@ public function testCreate() : void $result = null; - $resolvedPromise->then(static function ($value) use (&$result) { + $resolvedPromise->then(static function ($value) use (&$result) : void { $result = $value; }); @@ -113,7 +113,7 @@ public function testCreateFulfilled() : void $result = null; - $fulfilledPromise->then(static function ($value) use (&$result) { + $fulfilledPromise->then(static function ($value) use (&$result) : void { $result = $value; }); @@ -132,7 +132,7 @@ public function testCreateRejected() : void $rejectedPromise->then( null, - static function ($error) use (&$exception) { + static function ($error) use (&$exception) : void { $exception = $error; } ); @@ -153,7 +153,7 @@ public function testAll() : void $result = null; - $allPromise->then(static function ($values) use (&$result) { + $allPromise->then(static function ($values) use (&$result) : void { $result = $values; }); @@ -167,7 +167,7 @@ public function testAllShouldPreserveTheOrderOfTheArrayWhenResolvingAsyncPromise $promises = [new FulfilledPromise(1), $deferred->promise(), new FulfilledPromise(3)]; $result = null; - $reactAdapter->all($promises)->then(static function ($values) use (&$result) { + $reactAdapter->all($promises)->then(static function ($values) use (&$result) : void { $result = $values; }); diff --git a/tests/Executor/Promise/SyncPromiseAdapterTest.php b/tests/Executor/Promise/SyncPromiseAdapterTest.php index a7c7fa219..47ec89c68 100644 --- a/tests/Executor/Promise/SyncPromiseAdapterTest.php +++ b/tests/Executor/Promise/SyncPromiseAdapterTest.php @@ -28,7 +28,7 @@ public function testIsThenable() : void { self::assertEquals( true, - $this->promises->isThenable(new Deferred(static function () { + $this->promises->isThenable(new Deferred(static function () : void { })) ); self::assertEquals(false, $this->promises->isThenable(false)); @@ -43,7 +43,7 @@ public function testIsThenable() : void public function testConvert() : void { - $dfd = new Deferred(static function () { + $dfd = new Deferred(static function () : void { }); $result = $this->promises->convertThenable($dfd); @@ -57,7 +57,7 @@ public function testConvert() : void public function testThen() : void { - $dfd = new Deferred(static function () { + $dfd = new Deferred(static function () : void { }); $promise = $this->promises->convertThenable($dfd); @@ -69,13 +69,13 @@ public function testThen() : void public function testCreatePromise() : void { - $promise = $this->promises->create(static function ($resolve, $reject) { + $promise = $this->promises->create(static function ($resolve, $reject) : void { }); self::assertInstanceOf('GraphQL\Executor\Promise\Promise', $promise); self::assertInstanceOf('GraphQL\Executor\Promise\Adapter\SyncPromise', $promise->adoptedPromise); - $promise = $this->promises->create(static function ($resolve, $reject) { + $promise = $this->promises->create(static function ($resolve, $reject) : void { $resolve('A'); }); @@ -93,11 +93,11 @@ private static function assertValidPromise($promise, $expectedNextReason, $expec $onRejectedCalled = false; $promise->then( - static function ($nextValue) use (&$actualNextValue, &$onFulfilledCalled) { + static function ($nextValue) use (&$actualNextValue, &$onFulfilledCalled) : void { $onFulfilledCalled = true; $actualNextValue = $nextValue; }, - static function (Throwable $reason) use (&$actualNextReason, &$onRejectedCalled) { + static function (Throwable $reason) use (&$actualNextReason, &$onRejectedCalled) : void { $onRejectedCalled = true; $actualNextReason = $reason->getMessage(); } @@ -141,7 +141,7 @@ public function testCreatePromiseAll() : void $promise1 = new SyncPromise(); $promise2 = new SyncPromise(); $promise3 = $promise2->then( - static function ($value) { + static function ($value) : string { return $value . '-value3'; } ); @@ -173,12 +173,12 @@ public function testWait() : void { $called = []; - $deferred1 = new Deferred(static function () use (&$called) { + $deferred1 = new Deferred(static function () use (&$called) : int { $called[] = 1; return 1; }); - $deferred2 = new Deferred(static function () use (&$called) { + $deferred2 = new Deferred(static function () use (&$called) : int { $called[] = 2; return 2; @@ -187,8 +187,8 @@ public function testWait() : void $p1 = $this->promises->convertThenable($deferred1); $p2 = $this->promises->convertThenable($deferred2); - $p3 = $p2->then(function () use (&$called) { - $dfd = new Deferred(static function () use (&$called) { + $p3 = $p2->then(function () use (&$called) : Promise { + $dfd = new Deferred(static function () use (&$called) : int { $called[] = 3; return 3; @@ -198,7 +198,7 @@ public function testWait() : void }); $p4 = $p3->then(static function () use (&$called) { - return new Deferred(static function () use (&$called) { + return new Deferred(static function () use (&$called) : int { $called[] = 4; return 4; diff --git a/tests/Executor/Promise/SyncPromiseTest.php b/tests/Executor/Promise/SyncPromiseTest.php index 732f95e8b..b7e117000 100644 --- a/tests/Executor/Promise/SyncPromiseTest.php +++ b/tests/Executor/Promise/SyncPromiseTest.php @@ -23,11 +23,11 @@ public function getFulfilledPromiseResolveData() return $value; }; - $onFulfilledReturnsOtherValue = static function ($value) { + $onFulfilledReturnsOtherValue = static function ($value) : string { return 'other-' . $value; }; - $onFulfilledThrows = static function ($value) { + $onFulfilledThrows = static function ($value) : void { throw new Exception('onFulfilled throws this!'); }; @@ -101,7 +101,7 @@ public function testFulfilledPromise( $nextPromise = $promise->then( null, - static function () { + static function () : void { } ); self::assertSame($promise, $nextPromise); @@ -109,7 +109,7 @@ static function () { $onRejectedCalled = false; $nextPromise = $promise->then( $onFulfilled, - static function () use (&$onRejectedCalled) { + static function () use (&$onRejectedCalled) : void { $onRejectedCalled = true; } ); @@ -149,11 +149,11 @@ private static function assertValidPromise( $onRejectedCalled = false; $promise->then( - static function ($nextValue) use (&$actualNextValue, &$onFulfilledCalled) { + static function ($nextValue) use (&$actualNextValue, &$onFulfilledCalled) : void { $onFulfilledCalled = true; $actualNextValue = $nextValue; }, - static function (Throwable $reason) use (&$actualNextReason, &$onRejectedCalled) { + static function (Throwable $reason) use (&$actualNextReason, &$onRejectedCalled) : void { $onRejectedCalled = true; $actualNextReason = $reason->getMessage(); } @@ -178,15 +178,15 @@ public function getRejectedPromiseData() return null; }; - $onRejectedReturnsSomeValue = static function ($reason) { + $onRejectedReturnsSomeValue = static function ($reason) : string { return 'some-value'; }; - $onRejectedThrowsSameReason = static function ($reason) { + $onRejectedThrowsSameReason = static function ($reason) : void { throw $reason; }; - $onRejectedThrowsOtherReason = static function ($value) { + $onRejectedThrowsOtherReason = static function ($value) : void { throw new Exception('onRejected throws other!'); }; @@ -273,7 +273,7 @@ public function testRejectedPromise( } $nextPromise = $promise->then( - static function () { + static function () : void { }, null ); @@ -281,7 +281,7 @@ static function () { $onFulfilledCalled = false; $nextPromise = $promise->then( - static function () use (&$onFulfilledCalled) { + static function () use (&$onFulfilledCalled) : void { $onFulfilledCalled = true; }, $onRejected @@ -358,7 +358,7 @@ public function testPendingPromise() : void $promise = new SyncPromise(); $promise2 = $promise->then( null, - static function () { + static function () : string { return 'value'; } ); @@ -384,13 +384,13 @@ public function testPendingPromiseThen() : void // Make sure that it queues derivative promises until resolution: $onFulfilledCount = 0; $onRejectedCount = 0; - $onFulfilled = static function ($value) use (&$onFulfilledCount) { + $onFulfilled = static function ($value) use (&$onFulfilledCount) : int { $onFulfilledCount++; return $onFulfilledCount; }; - $onRejected = static function ($reason) use (&$onRejectedCount) { + $onRejected = static function ($reason) use (&$onRejectedCount) : void { $onRejectedCount++; throw $reason; }; diff --git a/tests/Executor/ResolveTest.php b/tests/Executor/ResolveTest.php index 8fe4ab7ca..c4ca4e62d 100644 --- a/tests/Executor/ResolveTest.php +++ b/tests/Executor/ResolveTest.php @@ -51,7 +51,7 @@ public function testDefaultFunctionCallsClosures() : void $_secret = 'secretValue' . uniqid(); $source = [ - 'test' => static function () use ($_secret) { + 'test' => static function () use ($_secret) : string { return $_secret; }, ]; diff --git a/tests/Executor/SyncTest.php b/tests/Executor/SyncTest.php index 6128dfd64..f05df29e8 100644 --- a/tests/Executor/SyncTest.php +++ b/tests/Executor/SyncTest.php @@ -202,7 +202,7 @@ public function testDoesNotReturnAPromiseForValidationErrors() : void $expected = [ 'errors' => Utils::map( $validationErrors, - static function ($e) { + static function ($e) : array { return FormattedError::createFromException($e); } ), diff --git a/tests/Executor/TestClasses/Adder.php b/tests/Executor/TestClasses/Adder.php index 562aba6e0..e28814cda 100644 --- a/tests/Executor/TestClasses/Adder.php +++ b/tests/Executor/TestClasses/Adder.php @@ -16,7 +16,7 @@ public function __construct(float $num) { $this->num = $num; - $this->test = function ($objectValue, $args, $context) { + $this->test = function ($objectValue, $args, $context) : float { return $this->num + $args['addend1'] + $context['addend2']; }; } diff --git a/tests/Executor/TestClasses/Root.php b/tests/Executor/TestClasses/Root.php index 6263bd9e6..d77d9459c 100644 --- a/tests/Executor/TestClasses/Root.php +++ b/tests/Executor/TestClasses/Root.php @@ -19,7 +19,7 @@ public function __construct(float $originalNumber) public function promiseToChangeTheNumber($newNumber) : Deferred { - return new Deferred(function () use ($newNumber) { + return new Deferred(function () use ($newNumber) : NumberHolder { return $this->immediatelyChangeTheNumber($newNumber); }); } @@ -38,7 +38,7 @@ public function failToChangeTheNumber() : void public function promiseAndFailToChangeTheNumber() : Deferred { - return new Deferred(function () { + return new Deferred(function () : void { $this->failToChangeTheNumber(); }); } diff --git a/tests/Executor/UnionInterfaceTest.php b/tests/Executor/UnionInterfaceTest.php index 766a966ee..56982ae97 100644 --- a/tests/Executor/UnionInterfaceTest.php +++ b/tests/Executor/UnionInterfaceTest.php @@ -4,6 +4,7 @@ namespace GraphQL\Tests\Executor; +use GraphQL\Error\InvariantViolation; use GraphQL\Executor\Executor; use GraphQL\GraphQL; use GraphQL\Language\Parser; @@ -51,7 +52,7 @@ public function setUp() : void 'name' => ['type' => Type::string()], 'woofs' => ['type' => Type::boolean()], ], - 'isTypeOf' => static function ($value) { + 'isTypeOf' => static function ($value) : bool { return $value instanceof Dog; }, ]); @@ -63,7 +64,7 @@ public function setUp() : void 'name' => ['type' => Type::string()], 'meows' => ['type' => Type::boolean()], ], - 'isTypeOf' => static function ($value) { + 'isTypeOf' => static function ($value) : bool { return $value instanceof Cat; }, ]); @@ -71,13 +72,15 @@ public function setUp() : void $PetType = new UnionType([ 'name' => 'Pet', 'types' => [$DogType, $CatType], - 'resolveType' => static function ($value) use ($DogType, $CatType) { + 'resolveType' => static function ($value) use ($DogType, $CatType) : ObjectType { if ($value instanceof Dog) { return $DogType; } if ($value instanceof Cat) { return $CatType; } + + throw new InvariantViolation('Unknown type'); }, ]); @@ -89,7 +92,7 @@ public function setUp() : void 'pets' => ['type' => Type::listOf($PetType)], 'friends' => ['type' => Type::listOf($NamedType)], ], - 'isTypeOf' => static function ($value) { + 'isTypeOf' => static function ($value) : bool { return $value instanceof Person; }, ]); diff --git a/tests/Executor/VariablesTest.php b/tests/Executor/VariablesTest.php index 7676985d3..e7a97de70 100644 --- a/tests/Executor/VariablesTest.php +++ b/tests/Executor/VariablesTest.php @@ -187,7 +187,7 @@ private function fieldWithInputArg($inputArg) return [ 'type' => Type::string(), 'args' => ['input' => $inputArg], - 'resolve' => static function ($_, $args) { + 'resolve' => static function ($_, $args) : ?string { if (isset($args['input'])) { return Utils::printSafeJson($args['input']); } diff --git a/tests/Experimental/Executor/CollectorTest.php b/tests/Experimental/Executor/CollectorTest.php index b081e9cc6..3a5852fa0 100644 --- a/tests/Experimental/Executor/CollectorTest.php +++ b/tests/Experimental/Executor/CollectorTest.php @@ -71,7 +71,7 @@ public function addError($error) foreach ($collector->collectFields($collector->rootType, $collector->operation->selectionSet) as $shared) { $execution = new stdClass(); if (! empty($shared->fieldNodes)) { - $execution->fieldNodes = array_map(static function (Node $node) { + $execution->fieldNodes = array_map(static function (Node $node) : array { return $node->toArray(true); }, $shared->fieldNodes); } diff --git a/tests/GraphQLTest.php b/tests/GraphQLTest.php index 6d28e0970..32396856c 100644 --- a/tests/GraphQLTest.php +++ b/tests/GraphQLTest.php @@ -5,6 +5,7 @@ namespace GraphQL\Tests; use GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter; +use GraphQL\Executor\Promise\Promise; use GraphQL\GraphQL; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; @@ -30,7 +31,7 @@ public function testPromiseToExecute() : void 'type' => Type::nonNull(Type::string()), ], ], - 'resolve' => static function ($rootValue, $args) use ($promiseAdapter) { + 'resolve' => static function ($rootValue, $args) use ($promiseAdapter) : Promise { return $promiseAdapter->createFulfilled(sprintf('Hi %s!', $args['name'])); }, ], diff --git a/tests/Language/ParserTest.php b/tests/Language/ParserTest.php index c61a5870d..229bcc473 100644 --- a/tests/Language/ParserTest.php +++ b/tests/Language/ParserTest.php @@ -457,7 +457,7 @@ public function testParseCreatesAstFromNamelessQueryWithoutVariables() : void '); $result = Parser::parse($source); - $loc = static function ($start, $end) { + $loc = static function ($start, $end) : array { return [ 'start' => $start, 'end' => $end, diff --git a/tests/Language/SchemaParserTest.php b/tests/Language/SchemaParserTest.php index ca2677a97..267db2246 100644 --- a/tests/Language/SchemaParserTest.php +++ b/tests/Language/SchemaParserTest.php @@ -27,7 +27,7 @@ public function testSimpleType() : void world: String }'; $doc = Parser::parse($body); - $loc = static function ($start, $end) { + $loc = static function ($start, $end) : array { return TestUtils::locArray($start, $end); }; @@ -102,7 +102,7 @@ public function testParsesTypeWithDescriptionString() : void world: String }'; $doc = Parser::parse($body); - $loc = static function ($start, $end) { + $loc = static function ($start, $end) : array { return TestUtils::locArray($start, $end); }; @@ -149,7 +149,7 @@ public function testParsesTypeWithDescriptionMultiLineString() : void world: String }'; $doc = Parser::parse($body); - $loc = static function ($start, $end) { + $loc = static function ($start, $end) : array { return TestUtils::locArray($start, $end); }; @@ -193,7 +193,7 @@ public function testSimpleExtension() : void } '; $doc = Parser::parse($body); - $loc = static function ($start, $end) { + $loc = static function ($start, $end) : array { return TestUtils::locArray($start, $end); }; @@ -227,7 +227,7 @@ public function testExtensionWithoutFields() : void { $body = 'extend type Hello implements Greeting'; $doc = Parser::parse($body); - $loc = static function ($start, $end) { + $loc = static function ($start, $end) : array { return TestUtils::locArray($start, $end); }; @@ -360,7 +360,7 @@ public function testSimpleNonNullType() : void }'; $doc = Parser::parse($body); - $loc = static function ($start, $end) { + $loc = static function ($start, $end) : array { return TestUtils::locArray($start, $end); }; @@ -400,7 +400,7 @@ public function testSimpleTypeInheritingInterface() : void { $body = 'type Hello implements World { field: String }'; $doc = Parser::parse($body); - $loc = static function ($start, $end) { + $loc = static function ($start, $end) : array { return TestUtils::locArray($start, $end); }; @@ -438,7 +438,7 @@ public function testSimpleTypeInheritingMultipleInterfaces() : void { $body = 'type Hello implements Wo & rld { field: String }'; $doc = Parser::parse($body); - $loc = static function ($start, $end) { + $loc = static function ($start, $end) : array { return TestUtils::locArray($start, $end); }; @@ -477,7 +477,7 @@ public function testSimpleTypeInheritingMultipleInterfacesWithLeadingAmpersand() { $body = 'type Hello implements & Wo & rld { field: String }'; $doc = Parser::parse($body); - $loc = static function ($start, $end) { + $loc = static function ($start, $end) : array { return TestUtils::locArray($start, $end); }; @@ -515,7 +515,7 @@ public function testSingleValueEnum() : void { $body = 'enum Hello { WORLD }'; $doc = Parser::parse($body); - $loc = static function ($start, $end) { + $loc = static function ($start, $end) : array { return TestUtils::locArray($start, $end); }; @@ -555,7 +555,7 @@ public function testDoubleValueEnum() : void { $body = 'enum Hello { WO, RLD }'; $doc = Parser::parse($body); - $loc = static function ($start, $end) { + $loc = static function ($start, $end) : array { return TestUtils::locArray($start, $end); }; @@ -590,7 +590,7 @@ interface Hello { world: String }'; $doc = Parser::parse($body); - $loc = static function ($start, $end) { + $loc = static function ($start, $end) : array { return TestUtils::locArray($start, $end); }; @@ -627,7 +627,7 @@ public function testSimpleFieldWithArg() : void world(flag: Boolean): String }'; $doc = Parser::parse($body); - $loc = static function ($start, $end) { + $loc = static function ($start, $end) : array { return TestUtils::locArray($start, $end); }; @@ -687,7 +687,7 @@ public function testSimpleFieldWithArgWithDefaultValue() : void world(flag: Boolean = true): String }'; $doc = Parser::parse($body); - $loc = static function ($start, $end) { + $loc = static function ($start, $end) : array { return TestUtils::locArray($start, $end); }; @@ -733,7 +733,7 @@ public function testSimpleFieldWithListArg() : void world(things: [String]): String }'; $doc = Parser::parse($body); - $loc = static function ($start, $end) { + $loc = static function ($start, $end) : array { return TestUtils::locArray($start, $end); }; @@ -786,7 +786,7 @@ public function testSimpleFieldWithTwoArgs() : void world(argOne: Boolean, argTwo: Int): String }'; $doc = Parser::parse($body); - $loc = static function ($start, $end) { + $loc = static function ($start, $end) : array { return TestUtils::locArray($start, $end); }; @@ -836,7 +836,7 @@ public function testSimpleUnion() : void { $body = 'union Hello = World'; $doc = Parser::parse($body); - $loc = static function ($start, $end) { + $loc = static function ($start, $end) : array { return TestUtils::locArray($start, $end); }; @@ -865,7 +865,7 @@ public function testUnionWithTwoTypes() : void { $body = 'union Hello = Wo | Rld'; $doc = Parser::parse($body); - $loc = static function ($start, $end) { + $loc = static function ($start, $end) : array { return TestUtils::locArray($start, $end); }; @@ -971,7 +971,7 @@ public function testScalar() : void { $body = 'scalar Hello'; $doc = Parser::parse($body); - $loc = static function ($start, $end) { + $loc = static function ($start, $end) : array { return TestUtils::locArray($start, $end); }; @@ -1001,7 +1001,7 @@ public function testSimpleInputObject() : void world: String }'; $doc = Parser::parse($body); - $loc = static function ($start, $end) { + $loc = static function ($start, $end) : array { return TestUtils::locArray($start, $end); }; diff --git a/tests/Language/VisitorTest.php b/tests/Language/VisitorTest.php index 0e0787832..04cd7476c 100644 --- a/tests/Language/VisitorTest.php +++ b/tests/Language/VisitorTest.php @@ -16,6 +16,7 @@ use GraphQL\Language\Parser; use GraphQL\Language\Printer; use GraphQL\Language\Visitor; +use GraphQL\Language\VisitorOperation; use GraphQL\Tests\Validator\ValidatorTestCase; use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\InputObjectType; @@ -49,11 +50,11 @@ public function testValidatesPathArgument() : void Visitor::visit( $ast, [ - 'enter' => function ($node, $key, $parent, $path) use ($ast, &$visited) { + 'enter' => function ($node, $key, $parent, $path) use ($ast, &$visited) : void { $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['enter', $path]; }, - 'leave' => function ($node, $key, $parent, $path) use ($ast, &$visited) { + 'leave' => function ($node, $key, $parent, $path) use ($ast, &$visited) : void { $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['leave', $path]; }, @@ -85,7 +86,7 @@ public function testValidatesAncestorsArgument() $visitedNodes = []; Visitor::visit($ast, [ - 'enter' => static function ($node, $key, $parent, $path, $ancestors) use (&$visitedNodes) { + 'enter' => static function ($node, $key, $parent, $path, $ancestors) use (&$visitedNodes) : void { $inArray = is_numeric($key); if ($inArray) { $visitedNodes[] = $parent; @@ -95,7 +96,7 @@ public function testValidatesAncestorsArgument() $expectedAncestors = array_slice($visitedNodes, 0, -2); self::assertEquals($expectedAncestors, $ancestors); }, - 'leave' => static function ($node, $key, $parent, $path, $ancestors) use (&$visitedNodes) { + 'leave' => static function ($node, $key, $parent, $path, $ancestors) use (&$visitedNodes) : void { $expectedAncestors = array_slice($visitedNodes, 0, -2); self::assertEquals($expectedAncestors, $ancestors); @@ -181,7 +182,7 @@ public function testAllowsEditingNodeOnEnterAndOnLeave() : void $ast, [ NodeKind::OPERATION_DEFINITION => [ - 'enter' => function (OperationDefinitionNode $node) use (&$selectionSet, $ast) { + 'enter' => function (OperationDefinitionNode $node) use (&$selectionSet, $ast) : OperationDefinitionNode { $this->checkVisitorFnArgs($ast, func_get_args()); $selectionSet = $node->selectionSet; @@ -193,7 +194,7 @@ public function testAllowsEditingNodeOnEnterAndOnLeave() : void return $newNode; }, - 'leave' => function (OperationDefinitionNode $node) use (&$selectionSet, $ast) { + 'leave' => function (OperationDefinitionNode $node) use (&$selectionSet, $ast) : OperationDefinitionNode { $this->checkVisitorFnArgs($ast, func_get_args(), true); $newNode = clone $node; $newNode->selectionSet = $selectionSet; @@ -224,7 +225,7 @@ public function testAllowsEditingRootNodeOnEnterAndLeave() : void $ast, [ NodeKind::DOCUMENT => [ - 'enter' => function (DocumentNode $node) use ($ast) { + 'enter' => function (DocumentNode $node) use ($ast) : DocumentNode { $this->checkVisitorFnArgs($ast, func_get_args()); /** @var NodeList $definitionNodeList */ $definitionNodeList = new NodeList([]); @@ -234,7 +235,7 @@ public function testAllowsEditingRootNodeOnEnterAndLeave() : void return $tmp; }, - 'leave' => function (DocumentNode $node) use ($definitions, $ast) { + 'leave' => function (DocumentNode $node) use ($definitions, $ast) : void { $this->checkVisitorFnArgs($ast, func_get_args(), true); $node->definitions = $definitions; $node->didLeave = true; @@ -258,11 +259,13 @@ public function testAllowsForEditingOnEnter() : void $editedAst = Visitor::visit( $ast, [ - 'enter' => function ($node) use ($ast) { + 'enter' => function ($node) use ($ast) : ?VisitorOperation { $this->checkVisitorFnArgs($ast, func_get_args()); if ($node instanceof FieldNode && $node->name->value === 'b') { return Visitor::removeNode(); } + + return null; }, ] ); @@ -283,11 +286,13 @@ public function testAllowsForEditingOnLeave() : void $editedAst = Visitor::visit( $ast, [ - 'leave' => function ($node) use ($ast) { + 'leave' => function ($node) use ($ast) : ?VisitorOperation { $this->checkVisitorFnArgs($ast, func_get_args(), true); if ($node instanceof FieldNode && $node->name->value === 'b') { return Visitor::removeNode(); } + + return null; }, ] ); @@ -316,7 +321,7 @@ public function testVisitsEditedNode() : void Visitor::visit( $ast, [ - 'enter' => function ($node) use ($addedField, &$didVisitAddedField, $ast) { + 'enter' => function ($node) use ($addedField, &$didVisitAddedField, $ast) : ?FieldNode { $this->checkVisitorFnArgs($ast, func_get_args(), true); if ($node instanceof FieldNode && $node->name->value === 'a') { return new FieldNode([ @@ -330,6 +335,8 @@ public function testVisitsEditedNode() : void } $didVisitAddedField = true; + + return null; }, ] ); @@ -345,14 +352,16 @@ public function testAllowsSkippingASubTree() : void Visitor::visit( $ast, [ - 'enter' => function (Node $node) use (&$visited, $ast) { + 'enter' => function (Node $node) use (&$visited, $ast) : ?VisitorOperation { $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['enter', $node->kind, $node->value ?? null]; if ($node instanceof FieldNode && $node->name->value === 'b') { return Visitor::skipNode(); } + + return null; }, - 'leave' => function (Node $node) use (&$visited, $ast) { + 'leave' => function (Node $node) use (&$visited, $ast) : void { $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['leave', $node->kind, $node->value ?? null]; }, @@ -388,14 +397,16 @@ public function testAllowsEarlyExitWhileVisiting() : void Visitor::visit( $ast, [ - 'enter' => function (Node $node) use (&$visited, $ast) { + 'enter' => function (Node $node) use (&$visited, $ast) : ?VisitorOperation { $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['enter', $node->kind, $node->value ?? null]; if ($node instanceof NameNode && $node->value === 'x') { return Visitor::stop(); } + + return null; }, - 'leave' => function (Node $node) use (&$visited, $ast) { + 'leave' => function (Node $node) use (&$visited, $ast) : void { $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['leave', $node->kind, $node->value ?? null]; }, @@ -429,17 +440,19 @@ public function testAllowsEarlyExitWhileLeaving() : void Visitor::visit( $ast, [ - 'enter' => function ($node) use (&$visited, $ast) { + 'enter' => function ($node) use (&$visited, $ast) : void { $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['enter', $node->kind, $node->value ?? null]; }, - 'leave' => function ($node) use (&$visited, $ast) { + 'leave' => function ($node) use (&$visited, $ast) : ?VisitorOperation { $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['leave', $node->kind, $node->value ?? null]; if ($node instanceof NameNode && $node->value === 'x') { return Visitor::stop(); } + + return null; }, ] ); @@ -473,16 +486,16 @@ public function testAllowsANamedFunctionsVisitorAPI() : void Visitor::visit( $ast, [ - NodeKind::NAME => function (NameNode $node) use (&$visited, $ast) { + NodeKind::NAME => function (NameNode $node) use (&$visited, $ast) : void { $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['enter', $node->kind, $node->value]; }, NodeKind::SELECTION_SET => [ - 'enter' => function (SelectionSetNode $node) use (&$visited, $ast) { + 'enter' => function (SelectionSetNode $node) use (&$visited, $ast) : void { $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['enter', $node->kind, null]; }, - 'leave' => function (SelectionSetNode $node) use (&$visited, $ast) { + 'leave' => function (SelectionSetNode $node) use (&$visited, $ast) : void { $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['leave', $node->kind, null]; }, @@ -518,11 +531,11 @@ public function testExperimentalVisitsVariablesDefinedInFragments() : void Visitor::visit( $ast, [ - 'enter' => function ($node) use (&$visited, $ast) { + 'enter' => function ($node) use (&$visited, $ast) : void { $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['enter', $node->kind, $node->value ?? null]; }, - 'leave' => function ($node) use (&$visited, $ast) { + 'leave' => function ($node) use (&$visited, $ast) : void { $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['leave', $node->kind, $node->value ?? null]; }, @@ -572,12 +585,12 @@ public function testVisitsKitchenSink() : void Visitor::visit( $ast, [ - 'enter' => function (Node $node, $key, $parent) use (&$visited, $ast) { + 'enter' => function (Node $node, $key, $parent) use (&$visited, $ast) : void { $this->checkVisitorFnArgs($ast, func_get_args()); $r = ['enter', $node->kind, $key, $parent instanceof Node ? $parent->kind : null]; $visited[] = $r; }, - 'leave' => function (Node $node, $key, $parent) use (&$visited, $ast) { + 'leave' => function (Node $node, $key, $parent) use (&$visited, $ast) : void { $this->checkVisitorFnArgs($ast, func_get_args()); $r = ['leave', $node->kind, $key, $parent instanceof Node ? $parent->kind : null]; $visited[] = $r; @@ -914,16 +927,18 @@ public function testAllowsSkippingSubTree() : void $ast, Visitor::visitInParallel([ [ - 'enter' => function ($node) use (&$visited, $ast) { + 'enter' => function ($node) use (&$visited, $ast) : ?VisitorOperation { $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['enter', $node->kind, $node->value ?? null]; if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'b') { return Visitor::skipNode(); } + + return null; }, - 'leave' => function ($node) use (&$visited, $ast) { + 'leave' => function ($node) use (&$visited, $ast) : void { $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['leave', $node->kind, $node->value ?? null]; }, @@ -962,27 +977,31 @@ public function testAllowsSkippingDifferentSubTrees() : void $ast, Visitor::visitInParallel([ [ - 'enter' => function ($node) use (&$visited, $ast) { + 'enter' => function ($node) use (&$visited, $ast) : ?VisitorOperation { $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['no-a', 'enter', $node->kind, $node->value ?? null]; if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'a') { return Visitor::skipNode(); } + + return null; }, - 'leave' => function ($node) use (&$visited, $ast) { + 'leave' => function ($node) use (&$visited, $ast) : void { $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['no-a', 'leave', $node->kind, $node->value ?? null]; }, ], [ - 'enter' => function ($node) use (&$visited, $ast) { + 'enter' => function ($node) use (&$visited, $ast) : ?VisitorOperation { $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['no-b', 'enter', $node->kind, $node->value ?? null]; if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'b') { return Visitor::skipNode(); } + + return null; }, - 'leave' => function ($node) use (&$visited, $ast) { + 'leave' => function ($node) use (&$visited, $ast) : void { $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['no-b', 'leave', $node->kind, $node->value ?? null]; }, @@ -1039,15 +1058,17 @@ public function testAllowsEarlyExitWhileVisiting2() : void Visitor::visit( $ast, Visitor::visitInParallel([[ - 'enter' => function ($node) use (&$visited, $ast) { + 'enter' => function ($node) use (&$visited, $ast) : ?VisitorOperation { $this->checkVisitorFnArgs($ast, func_get_args()); $value = $node->value ?? null; $visited[] = ['enter', $node->kind, $value]; if ($node->kind === 'Name' && $value === 'x') { return Visitor::stop(); } + + return null; }, - 'leave' => function ($node) use (&$visited, $ast) { + 'leave' => function ($node) use (&$visited, $ast) : void { $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['leave', $node->kind, $node->value ?? null]; }, @@ -1084,29 +1105,33 @@ public function testAllowsEarlyExitFromDifferentPoints() : void $ast, Visitor::visitInParallel([ [ - 'enter' => function ($node) use (&$visited, $ast) { + 'enter' => function ($node) use (&$visited, $ast) : ?VisitorOperation { $this->checkVisitorFnArgs($ast, func_get_args()); $value = $node->value ?? null; $visited[] = ['break-a', 'enter', $node->kind, $value]; if ($node->kind === 'Name' && $value === 'a') { return Visitor::stop(); } + + return null; }, - 'leave' => function ($node) use (&$visited, $ast) { + 'leave' => function ($node) use (&$visited, $ast) : void { $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['break-a', 'leave', $node->kind, $node->value ?? null]; }, ], [ - 'enter' => function ($node) use (&$visited, $ast) { + 'enter' => function ($node) use (&$visited, $ast) : ?VisitorOperation { $this->checkVisitorFnArgs($ast, func_get_args()); $value = $node->value ?? null; $visited[] = ['break-b', 'enter', $node->kind, $value]; if ($node->kind === 'Name' && $value === 'b') { return Visitor::stop(); } + + return null; }, - 'leave' => function ($node) use (&$visited, $ast) { + 'leave' => function ($node) use (&$visited, $ast) : void { $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['break-b', 'leave', $node->kind, $node->value ?? null]; }, @@ -1149,17 +1174,19 @@ public function testAllowsEarlyExitWhileLeaving2() : void Visitor::visit( $ast, Visitor::visitInParallel([[ - 'enter' => function ($node) use (&$visited, $ast) { + 'enter' => function ($node) use (&$visited, $ast) : void { $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['enter', $node->kind, $node->value ?? null]; }, - 'leave' => function ($node) use (&$visited, $ast) { + 'leave' => function ($node) use (&$visited, $ast) : ?VisitorOperation { $this->checkVisitorFnArgs($ast, func_get_args()); $value = $node->value ?? null; $visited[] = ['leave', $node->kind, $value]; if ($node->kind === 'Name' && $value === 'x') { return Visitor::stop(); } + + return null; }, ], ]) @@ -1195,29 +1222,33 @@ public function testAllowsEarlyExitFromLeavingDifferentPoints() : void $ast, Visitor::visitInParallel([ [ - 'enter' => function ($node) use (&$visited, $ast) { + 'enter' => function ($node) use (&$visited, $ast) : void { $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['break-a', 'enter', $node->kind, $node->value ?? null]; }, - 'leave' => function ($node) use (&$visited, $ast) { + 'leave' => function ($node) use (&$visited, $ast) : ?VisitorOperation { $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['break-a', 'leave', $node->kind, $node->value ?? null]; if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'a') { return Visitor::stop(); } + + return null; }, ], [ - 'enter' => function ($node) use (&$visited, $ast) { + 'enter' => function ($node) use (&$visited, $ast) : void { $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['break-b', 'enter', $node->kind, $node->value ?? null]; }, - 'leave' => function ($node) use (&$visited, $ast) { + 'leave' => function ($node) use (&$visited, $ast) : ?VisitorOperation { $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['break-b', 'leave', $node->kind, $node->value ?? null]; if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'b') { return Visitor::stop(); } + + return null; }, ], ]) @@ -1275,19 +1306,21 @@ public function testAllowsForEditingOnEnter2() : void $ast, Visitor::visitInParallel([ [ - 'enter' => function ($node) use ($ast) { + 'enter' => function ($node) use ($ast) : ?VisitorOperation { $this->checkVisitorFnArgs($ast, func_get_args()); if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'b') { return Visitor::removeNode(); } + + return null; }, ], [ - 'enter' => function ($node) use (&$visited, $ast) { + 'enter' => function ($node) use (&$visited, $ast) : void { $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['enter', $node->kind, $node->value ?? null]; }, - 'leave' => function ($node) use (&$visited, $ast) { + 'leave' => function ($node) use (&$visited, $ast) : void { $this->checkVisitorFnArgs($ast, func_get_args(), true); $visited[] = ['leave', $node->kind, $node->value ?? null]; }, @@ -1345,19 +1378,21 @@ public function testAllowsForEditingOnLeave2() : void $ast, Visitor::visitInParallel([ [ - 'leave' => function ($node) use ($ast) { + 'leave' => function ($node) use ($ast) : ?VisitorOperation { $this->checkVisitorFnArgs($ast, func_get_args(), true); if ($node->kind === 'Field' && isset($node->name->value) && $node->name->value === 'b') { return Visitor::removeNode(); } + + return null; }, ], [ - 'enter' => function ($node) use (&$visited, $ast) { + 'enter' => function ($node) use (&$visited, $ast) : void { $this->checkVisitorFnArgs($ast, func_get_args()); $visited[] = ['enter', $node->kind, $node->value ?? null]; }, - 'leave' => function ($node) use (&$visited, $ast) { + 'leave' => function ($node) use (&$visited, $ast) : void { $this->checkVisitorFnArgs($ast, func_get_args(), true); $visited[] = ['leave', $node->kind, $node->value ?? null]; }, @@ -1427,7 +1462,7 @@ public function testMaintainsTypeInfoDuringVisit() : void Visitor::visitWithTypeInfo( $typeInfo, [ - 'enter' => function ($node) use ($typeInfo, &$visited, $ast) { + 'enter' => function ($node) use ($typeInfo, &$visited, $ast) : void { $this->checkVisitorFnArgs($ast, func_get_args()); $parentType = $typeInfo->getParentType(); $type = $typeInfo->getType(); @@ -1442,7 +1477,7 @@ public function testMaintainsTypeInfoDuringVisit() : void $inputType ? (string) $inputType : null, ]; }, - 'leave' => function ($node) use ($typeInfo, &$visited, $ast) { + 'leave' => function ($node) use ($typeInfo, &$visited, $ast) : void { $this->checkVisitorFnArgs($ast, func_get_args()); $parentType = $typeInfo->getParentType(); $type = $typeInfo->getType(); @@ -1521,7 +1556,7 @@ public function testMaintainsTypeInfoDuringEdit() : void Visitor::visitWithTypeInfo( $typeInfo, [ - 'enter' => function ($node) use ($typeInfo, &$visited, $ast) { + 'enter' => function ($node) use ($typeInfo, &$visited, $ast) : ?FieldNode { $this->checkVisitorFnArgs($ast, func_get_args(), true); $parentType = $typeInfo->getParentType(); $type = $typeInfo->getType(); @@ -1555,8 +1590,10 @@ public function testMaintainsTypeInfoDuringEdit() : void ]), ]); } + + return null; }, - 'leave' => function ($node) use ($typeInfo, &$visited, $ast) { + 'leave' => function ($node) use ($typeInfo, &$visited, $ast) : void { $this->checkVisitorFnArgs($ast, func_get_args(), true); $parentType = $typeInfo->getParentType(); $type = $typeInfo->getType(); diff --git a/tests/Regression/Issue396Test.php b/tests/Regression/Issue396Test.php index 06cf5e819..33e494b07 100644 --- a/tests/Regression/Issue396Test.php +++ b/tests/Regression/Issue396Test.php @@ -30,7 +30,7 @@ public function testUnionResolveType() $unionResult = new UnionType([ 'name' => 'UnionResult', 'types' => [$a, $b, $c], - 'resolveType' => static function ($result, $value, ResolveInfo $info) use ($a, $b, $c, &$log) : Type { + 'resolveType' => static function ($result, $value, ResolveInfo $info) use ($a, $b, $c, &$log) : ?Type { $log[] = [$result, $info->path]; if (stristr($result['name'], 'A')) { return $a; @@ -41,6 +41,8 @@ public function testUnionResolveType() if (stristr($result['name'], 'C')) { return $c; } + + return null; }, ]); @@ -108,6 +110,8 @@ public function testInterfaceResolveType() if (stristr($result['name'], 'C')) { return $c; } + + return null; }, ]); diff --git a/tests/Server/QueryExecutionTest.php b/tests/Server/QueryExecutionTest.php index b1fc4db24..fcfca7c5f 100644 --- a/tests/Server/QueryExecutionTest.php +++ b/tests/Server/QueryExecutionTest.php @@ -174,7 +174,7 @@ public function testPassesCustomValidationRules() : void $called = false; $rules = [ - new CustomValidationRule('SomeRule', static function () use (&$called) { + new CustomValidationRule('SomeRule', static function () use (&$called) : array { $called = true; return []; @@ -194,7 +194,7 @@ public function testAllowsValidationRulesAsClosure() : void $called = false; $params = $doc = $operationType = null; - $this->config->setValidationRules(static function ($p, $d, $o) use (&$called, &$params, &$doc, &$operationType) { + $this->config->setValidationRules(static function ($p, $d, $o) use (&$called, &$params, &$doc, &$operationType) : array { $called = true; $params = $p; $doc = $d; @@ -218,7 +218,7 @@ public function testAllowsDifferentValidationRulesDependingOnOperation() : void $called1 = false; $called2 = false; - $this->config->setValidationRules(static function (OperationParams $params) use ($q1, &$called1, &$called2) { + $this->config->setValidationRules(static function (OperationParams $params) use ($q1, &$called1, &$called2) : array { if ($params->query === $q1) { $called1 = true; @@ -228,7 +228,7 @@ public function testAllowsDifferentValidationRulesDependingOnOperation() : void $called2 = true; return [ - new CustomValidationRule('MyRule', static function (ValidationContext $context) { + new CustomValidationRule('MyRule', static function (ValidationContext $context) : void { $context->reportError(new Error('This is the error we are looking for!')); }), ]; @@ -357,7 +357,7 @@ public function testMutationsAreNotAllowedInReadonlyMode() : void public function testAllowsPersistentQueries() : void { $called = false; - $this->config->setPersistentQueryLoader(static function ($queryId, OperationParams $params) use (&$called) { + $this->config->setPersistentQueryLoader(static function ($queryId, OperationParams $params) use (&$called) : string { $called = true; self::assertEquals('some-id', $queryId); @@ -374,7 +374,7 @@ public function testAllowsPersistentQueries() : void // Make sure it allows returning document node: $called = false; - $this->config->setPersistentQueryLoader(static function ($queryId, OperationParams $params) use (&$called) { + $this->config->setPersistentQueryLoader(static function ($queryId, OperationParams $params) use (&$called) : DocumentNode { $called = true; self::assertEquals('some-id', $queryId); @@ -392,7 +392,7 @@ public function testProhibitsInvalidPersistedQueryLoader() : void 'Persistent query loader must return query string or instance of GraphQL\Language\AST\DocumentNode ' . 'but got: {"err":"err"}' ); - $this->config->setPersistentQueryLoader(static function () { + $this->config->setPersistentQueryLoader(static function () : array { return ['err' => 'err']; }); $this->executePersistedQuery('some-id'); @@ -400,7 +400,7 @@ public function testProhibitsInvalidPersistedQueryLoader() : void public function testPersistedQueriesAreStillValidatedByDefault() : void { - $this->config->setPersistentQueryLoader(static function () { + $this->config->setPersistentQueryLoader(static function () : string { return '{invalid}'; }); $result = $this->executePersistedQuery('some-id'); @@ -426,7 +426,7 @@ public function testAllowSkippingValidationForPersistedQueries() : void return '{invalid2}'; }) - ->setValidationRules(static function (OperationParams $params) { + ->setValidationRules(static function (OperationParams $params) : array { if ($params->queryId === 'some-id') { return []; } @@ -457,7 +457,7 @@ public function testProhibitsUnexpectedValidationRules() : void { $this->expectException(InvariantViolation::class); $this->expectExceptionMessage('Expecting validation rules to be array or callable returning array, but got: instance of stdClass'); - $this->config->setValidationRules(static function (OperationParams $params) { + $this->config->setValidationRules(static function (OperationParams $params) : stdClass { return new stdClass(); }); $this->executeQuery('{f1}'); @@ -523,10 +523,10 @@ public function testDeferredsAreSharedAmongAllBatchedQueries() : void ->setQueryBatching(true) ->setRootValue('1') ->setContext([ - 'buffer' => static function ($num) use (&$calls) { + 'buffer' => static function ($num) use (&$calls) : void { $calls[] = sprintf('buffer: %d', $num); }, - 'load' => static function ($num) use (&$calls) { + 'load' => static function ($num) use (&$calls) : string { $calls[] = sprintf('load: %d', $num); return sprintf('loaded: %d', $num); @@ -588,7 +588,7 @@ public function testAllowsContextAsClosure() : void $called = false; $params = $doc = $operationType = null; - $this->config->setContext(static function ($p, $d, $o) use (&$called, &$params, &$doc, &$operationType) { + $this->config->setContext(static function ($p, $d, $o) use (&$called, &$params, &$doc, &$operationType) : void { $called = true; $params = $p; $doc = $d; @@ -608,7 +608,7 @@ public function testAllowsRootValueAsClosure() : void $called = false; $params = $doc = $operationType = null; - $this->config->setRootValue(static function ($p, $d, $o) use (&$called, &$params, &$doc, &$operationType) { + $this->config->setRootValue(static function ($p, $d, $o) use (&$called, &$params, &$doc, &$operationType) : void { $called = true; $params = $p; $doc = $d; @@ -627,7 +627,7 @@ public function testAppliesErrorFormatter() : void { $called = false; $error = null; - $this->config->setErrorFormatter(static function ($e) use (&$called, &$error) { + $this->config->setErrorFormatter(static function ($e) use (&$called, &$error) : array { $called = true; $error = $e; @@ -664,7 +664,7 @@ public function testAppliesErrorsHandler() : void $called = false; $errors = null; $formatter = null; - $this->config->setErrorsHandler(static function ($e, $f) use (&$called, &$errors, &$formatter) { + $this->config->setErrorsHandler(static function ($e, $f) use (&$called, &$errors, &$formatter) : array { $called = true; $errors = $e; $formatter = $f; diff --git a/tests/Server/RequestParsingTest.php b/tests/Server/RequestParsingTest.php index 8db287daa..a926b1263 100644 --- a/tests/Server/RequestParsingTest.php +++ b/tests/Server/RequestParsingTest.php @@ -43,7 +43,7 @@ private function parseRawRequest($contentType, $content, string $method = 'POST' $helper = new Helper(); - return $helper->parseHttpRequest(static function () use ($content) { + return $helper->parseHttpRequest(static function () use ($content) : string { return $content; }); } @@ -138,7 +138,7 @@ private function parseRawFormUrlencodedRequest($postValue) $helper = new Helper(); - return $helper->parseHttpRequest(static function () { + return $helper->parseHttpRequest(static function () : void { throw new InvariantViolation("Shouldn't read from php://input for urlencoded request"); }); } @@ -194,7 +194,7 @@ private function parseRawGetRequest($getValue) $helper = new Helper(); - return $helper->parseHttpRequest(static function () { + return $helper->parseHttpRequest(static function () : void { throw new InvariantViolation("Shouldn't read from php://input for urlencoded request"); }); } @@ -250,7 +250,7 @@ private function parseRawMultipartFormDataRequest($postValue) $helper = new Helper(); - return $helper->parseHttpRequest(static function () { + return $helper->parseHttpRequest(static function () : void { throw new InvariantViolation("Shouldn't read from php://input for multipart/form-data request"); }); } diff --git a/tests/Server/ServerConfigTest.php b/tests/Server/ServerConfigTest.php index a596ab300..6311fab92 100644 --- a/tests/Server/ServerConfigTest.php +++ b/tests/Server/ServerConfigTest.php @@ -74,7 +74,7 @@ public function testAllowsSettingErrorFormatter() : void { $config = ServerConfig::create(); - $formatter = static function () { + $formatter = static function () : void { }; $config->setErrorFormatter($formatter); self::assertSame($formatter, $config->getErrorFormatter()); @@ -88,7 +88,7 @@ public function testAllowsSettingErrorsHandler() : void { $config = ServerConfig::create(); - $handler = static function () { + $handler = static function () : void { }; $config->setErrorsHandler($handler); self::assertSame($handler, $config->getErrorsHandler()); @@ -119,14 +119,14 @@ public function testAllowsSettingValidationRules() : void $config->setValidationRules($rules); self::assertSame($rules, $config->getValidationRules()); - $rules = [static function () { + $rules = [static function () : void { }, ]; $config->setValidationRules($rules); self::assertSame($rules, $config->getValidationRules()); - $rules = static function () { - return [static function () { + $rules = static function () : array { + return [static function () : void { }, ]; }; @@ -138,7 +138,7 @@ public function testAllowsSettingDefaultFieldResolver() : void { $config = ServerConfig::create(); - $resolver = static function () { + $resolver = static function () : void { }; $config->setFieldResolver($resolver); self::assertSame($resolver, $config->getFieldResolver()); @@ -152,7 +152,7 @@ public function testAllowsSettingPersistedQueryLoader() : void { $config = ServerConfig::create(); - $loader = static function () { + $loader = static function () : void { }; $config->setPersistentQueryLoader($loader); self::assertSame($loader, $config->getPersistentQueryLoader()); @@ -181,15 +181,15 @@ public function testAcceptsArray() : void ]), 'context' => new stdClass(), 'rootValue' => new stdClass(), - 'errorFormatter' => static function () { + 'errorFormatter' => static function () : void { }, 'promiseAdapter' => new SyncPromiseAdapter(), - 'validationRules' => [static function () { + 'validationRules' => [static function () : void { }, ], - 'fieldResolver' => static function () { + 'fieldResolver' => static function () : void { }, - 'persistentQueryLoader' => static function () { + 'persistentQueryLoader' => static function () : void { }, 'debug' => true, 'queryBatching' => true, diff --git a/tests/Server/ServerTestCase.php b/tests/Server/ServerTestCase.php index 62001988d..45db91fde 100644 --- a/tests/Server/ServerTestCase.php +++ b/tests/Server/ServerTestCase.php @@ -47,13 +47,13 @@ protected function buildSchema() ], 'fieldWithSafeException' => [ 'type' => Type::string(), - 'resolve' => static function () { + 'resolve' => static function () : void { throw new UserError('This is the exception we want'); }, ], 'fieldWithUnsafeException' => [ 'type' => Type::string(), - 'resolve' => static function () { + 'resolve' => static function () : void { throw new Unsafe('This exception should not be shown to the user'); }, ], diff --git a/tests/StarWarsSchema.php b/tests/StarWarsSchema.php index 86221e698..4304914c8 100644 --- a/tests/StarWarsSchema.php +++ b/tests/StarWarsSchema.php @@ -109,7 +109,7 @@ public static function build() : Schema $characterInterface = new InterfaceType([ 'name' => 'Character', 'description' => 'A character in the Star Wars Trilogy', - 'fields' => static function () use (&$characterInterface, $episodeEnum) { + 'fields' => static function () use (&$characterInterface, $episodeEnum) : array { return [ 'id' => [ 'type' => Type::nonNull(Type::string()), @@ -165,12 +165,12 @@ public static function build() : Schema 'friends' => [ 'type' => Type::listOf($characterInterface), 'description' => 'The friends of the human, or an empty list if they have none.', - 'resolve' => static function ($human, $args, $context, ResolveInfo $info) { + 'resolve' => static function ($human, $args, $context, ResolveInfo $info) : array { $fieldSelection = $info->getFieldSelection(); $fieldSelection['id'] = true; return array_map( - static function ($friend) use ($fieldSelection) { + static function ($friend) use ($fieldSelection) : array { return array_intersect_key($friend, $fieldSelection); }, StarWarsData::getFriends($human) @@ -188,7 +188,7 @@ static function ($friend) use ($fieldSelection) { 'secretBackstory' => [ 'type' => Type::string(), 'description' => 'Where are they from and how they came to be who they are.', - 'resolve' => static function () { + 'resolve' => static function () : void { // This is to demonstrate error reporting throw new Exception('secretBackstory is secret.'); }, @@ -236,7 +236,7 @@ static function ($friend) use ($fieldSelection) { 'secretBackstory' => [ 'type' => Type::string(), 'description' => 'Construction date and the name of the designer.', - 'resolve' => static function () { + 'resolve' => static function () : void { // This is to demonstrate error reporting throw new Exception('secretBackstory is secret.'); }, @@ -273,7 +273,7 @@ static function ($friend) use ($fieldSelection) { 'type' => $episodeEnum, ], ], - 'resolve' => static function ($rootValue, $args) { + 'resolve' => static function ($rootValue, $args) : array { return StarWarsData::getHero($args['episode'] ?? null); }, ], diff --git a/tests/Type/DefinitionTest.php b/tests/Type/DefinitionTest.php index 7a6ec9c05..3a750be6a 100644 --- a/tests/Type/DefinitionTest.php +++ b/tests/Type/DefinitionTest.php @@ -82,11 +82,11 @@ public function setUp() : void $this->scalarType = new CustomScalarType([ 'name' => 'Scalar', - 'serialize' => static function () { + 'serialize' => static function () : void { }, - 'parseValue' => static function () { + 'parseValue' => static function () : void { }, - 'parseLiteral' => static function () { + 'parseLiteral' => static function () : void { }, ]); @@ -101,7 +101,7 @@ public function setUp() : void $this->blogAuthor = new ObjectType([ 'name' => 'Author', - 'fields' => function () { + 'fields' => function () : array { return [ 'id' => ['type' => Type::string()], 'name' => ['type' => Type::string()], @@ -405,7 +405,7 @@ public function testIncludesInterfacesThunkSubtypesInTheTypeMap() : void 'fields' => [ 'f' => ['type' => Type::int()], ], - 'interfaces' => static function () use (&$someInterface) { + 'interfaces' => static function () use (&$someInterface) : array { return [$someInterface]; }, ]); @@ -542,7 +542,7 @@ public function testAllowsThunkForUnionTypes() : void { $union = new UnionType([ 'name' => 'ThunkUnion', - 'types' => function () { + 'types' => function () : array { return [$this->objectType]; }, ]); @@ -567,7 +567,7 @@ public function testAllowsRecursiveDefinitions() : void $user = new ObjectType([ 'name' => 'User', - 'fields' => static function () use (&$blog, &$called) { + 'fields' => static function () use (&$blog, &$called) : array { self::assertNotNull($blog, 'Blog type is expected to be defined at this point, but it is null'); $called = true; @@ -576,20 +576,20 @@ public function testAllowsRecursiveDefinitions() : void 'blogs' => ['type' => Type::nonNull(Type::listOf(Type::nonNull($blog)))], ]; }, - 'interfaces' => static function () use ($node) { + 'interfaces' => static function () use ($node) : array { return [$node]; }, ]); $blog = new ObjectType([ 'name' => 'Blog', - 'fields' => static function () use ($user) { + 'fields' => static function () use ($user) : array { return [ 'id' => ['type' => Type::nonNull(Type::id())], 'owner' => ['type' => Type::nonNull($user)], ]; }, - 'interfaces' => static function () use ($node) { + 'interfaces' => static function () use ($node) : array { return [$node]; }, ]); @@ -626,7 +626,7 @@ public function testInputObjectTypeAllowsRecursiveDefinitions() : void $called = false; $inputObject = new InputObjectType([ 'name' => 'InputObject', - 'fields' => static function () use (&$inputObject, &$called) { + 'fields' => static function () use (&$inputObject, &$called) : array { $called = true; return [ @@ -663,7 +663,7 @@ public function testInterfaceTypeAllowsRecursiveDefinitions() : void $called = false; $interface = new InterfaceType([ 'name' => 'SomeInterface', - 'fields' => static function () use (&$interface, &$called) { + 'fields' => static function () use (&$interface, &$called) : array { $called = true; return [ @@ -693,7 +693,7 @@ public function testAllowsShorthandFieldDefinition() : void { $interface = new InterfaceType([ 'name' => 'SomeInterface', - 'fields' => static function () use (&$interface) { + 'fields' => static function () use (&$interface) : array { return [ 'value' => Type::string(), 'nested' => $interface, @@ -749,11 +749,11 @@ public function testAllowsOverridingInternalTypes() : void { $idType = new CustomScalarType([ 'name' => 'ID', - 'serialize' => static function () { + 'serialize' => static function () : void { }, - 'parseValue' => static function () { + 'parseValue' => static function () : void { }, - 'parseLiteral' => static function () { + 'parseLiteral' => static function () : void { }, ]); @@ -774,7 +774,7 @@ public function testAcceptsAnObjectTypeWithAFieldFunction() : void { $objType = new ObjectType([ 'name' => 'SomeObject', - 'fields' => static function () { + 'fields' => static function () : array { return [ 'f' => ['type' => Type::string()], ]; @@ -824,7 +824,7 @@ public function testRejectsAnObjectTypeWithAFieldFunctionThatReturnsIncorrectTyp { $objType = new ObjectType([ 'name' => 'SomeObject', - 'fields' => static function () { + 'fields' => static function () : array { return [['field' => Type::string()]]; }, ]); @@ -905,7 +905,7 @@ public function testAcceptsAnObjectTypeWithInterfacesAsAFunctionReturningAnArray { $objType = new ObjectType([ 'name' => 'SomeObject', - 'interfaces' => function () { + 'interfaces' => function () : array { return [$this->interfaceType]; }, 'fields' => ['f' => ['type' => Type::string()]], @@ -937,7 +937,7 @@ public function testRejectsAnObjectTypeWithInterfacesAsAFunctionReturningAnIncor { $objType = new ObjectType([ 'name' => 'SomeObject', - 'interfaces' => static function () { + 'interfaces' => static function () : stdClass { return new stdClass(); }, 'fields' => ['f' => ['type' => Type::string()]], @@ -958,7 +958,7 @@ public function testAcceptsALambdaAsAnObjectFieldResolver() : void { $this->expectNotToPerformAssertions(); // should not throw: - $this->schemaWithObjectWithFieldResolver(static function () { + $this->schemaWithObjectWithFieldResolver(static function () : void { }); } @@ -1237,11 +1237,11 @@ public function testAcceptsAScalarTypeDefiningParseValueAndParseLiteral() : void $this->schemaWithFieldType( new CustomScalarType([ 'name' => 'SomeScalar', - 'serialize' => static function () { + 'serialize' => static function () : void { }, - 'parseValue' => static function () { + 'parseValue' => static function () : void { }, - 'parseLiteral' => static function () { + 'parseLiteral' => static function () : void { }, ]) ); @@ -1259,9 +1259,9 @@ public function testRejectsAScalarTypeDefiningParseValueButNotParseLiteral() : v $this->schemaWithFieldType( new CustomScalarType([ 'name' => 'SomeScalar', - 'serialize' => static function () { + 'serialize' => static function () : void { }, - 'parseValue' => static function () { + 'parseValue' => static function () : void { }, ]) ); @@ -1279,9 +1279,9 @@ public function testRejectsAScalarTypeDefiningParseLiteralButNotParseValue() : v $this->schemaWithFieldType( new CustomScalarType([ 'name' => 'SomeScalar', - 'serialize' => static function () { + 'serialize' => static function () : void { }, - 'parseLiteral' => static function () { + 'parseLiteral' => static function () : void { }, ]) ); @@ -1299,7 +1299,7 @@ public function testRejectsAScalarTypeDefiningParseValueAndParseLiteralWithAnInc $this->schemaWithFieldType( new CustomScalarType([ 'name' => 'SomeScalar', - 'serialize' => static function () { + 'serialize' => static function () : void { }, 'parseValue' => new stdClass(), 'parseLiteral' => new stdClass(), @@ -1368,7 +1368,7 @@ public function testAcceptsAUnionTypeWithFunctionReturningAnArrayOfTypes() : voi $this->schemaWithFieldType( new UnionType([ 'name' => 'SomeUnion', - 'types' => function () { + 'types' => function () : array { return [$this->objectType]; }, ]) @@ -1430,7 +1430,7 @@ public function testAcceptsAnInputObjectTypeWithAFieldFunction() : void { $inputObjType = new InputObjectType([ 'name' => 'SomeInputObject', - 'fields' => static function () { + 'fields' => static function () : array { return [ 'f' => ['type' => Type::string()], ]; @@ -1464,7 +1464,7 @@ public function testRejectsAnInputObjectTypeWithFieldsFunctionThatReturnsIncorre { $inputObjType = new InputObjectType([ 'name' => 'SomeInputObject', - 'fields' => static function () { + 'fields' => static function () : array { return []; }, ]); @@ -1486,7 +1486,7 @@ public function testRejectsAnInputObjectTypeWithResolvers() : void 'fields' => [ 'f' => [ 'type' => Type::string(), - 'resolve' => static function () { + 'resolve' => static function () : int { return 0; }, ], @@ -1600,7 +1600,7 @@ public function testRejectsASchemaWhichRedefinesABuiltInType() : void { $FakeString = new CustomScalarType([ 'name' => 'String', - 'serialize' => static function () { + 'serialize' => static function () : void { }, ]); diff --git a/tests/Type/EnumTypeTest.php b/tests/Type/EnumTypeTest.php index 3573613b2..18abe98bf 100644 --- a/tests/Type/EnumTypeTest.php +++ b/tests/Type/EnumTypeTest.php @@ -54,7 +54,7 @@ public function setUp() : void ]); $Complex1 = [ - 'someRandomFunction' => static function () { + 'someRandomFunction' => static function () : void { }, ]; $Complex2 = new ArrayObject(['someRandomValue' => 123]); diff --git a/tests/Type/QueryPlanTest.php b/tests/Type/QueryPlanTest.php index 20c6f555b..25fabc010 100644 --- a/tests/Type/QueryPlanTest.php +++ b/tests/Type/QueryPlanTest.php @@ -424,7 +424,7 @@ public function testMergedFragmentsQueryPlan() : void $author = new ObjectType([ 'name' => 'Author', - 'fields' => static function () use ($image, &$article) { + 'fields' => static function () use ($image, &$article) : array { return [ 'id' => ['type' => Type::string()], 'name' => ['type' => Type::string()], @@ -868,7 +868,7 @@ public function testQueryPlanOnUnionGroupingImplementorFields() : void $item = new UnionType([ 'name' => 'Item', 'types' => [$car, $building], - 'resolveType' => static function () use ($car) { + 'resolveType' => static function () use ($car) : ObjectType { return $car; }, ]); From 7a7add0f1dbbc0c2dbae8e3bb2d06335d7277d86 Mon Sep 17 00:00:00 2001 From: Max Loeb Date: Tue, 19 May 2020 04:07:52 -0700 Subject: [PATCH 172/256] Fix short ternary stan errors (#645) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * elvis has left the building * restore param * fix param * conservative string handling * lock sniffer * lint * update stan, invert ternary * update baseline command * regenerate baseline * reverse ternary, add check * reverse ternary, add check * Update tests/Executor/DirectivesTest.php Co-Authored-By: Šimon Podlipský * remove method * update stan * try again * remove typehint * use default baseline filename * fix new error Co-authored-by: Šimon Podlipský --- composer.json | 2 +- phpstan-baseline.neon | 177 +----------------- src/Error/Error.php | 14 +- src/Error/FormattedError.php | 6 +- src/Error/Warning.php | 4 +- src/Executor/ExecutionContext.php | 2 +- src/Executor/ExecutionResult.php | 2 +- src/Executor/Executor.php | 4 +- src/Executor/Promise/Adapter/SyncPromise.php | 2 +- src/Executor/ReferenceExecutor.php | 10 +- .../Executor/CoroutineExecutor.php | 4 +- src/GraphQL.php | 2 +- src/Language/Lexer.php | 4 +- src/Language/Parser.php | 2 +- src/Language/Printer.php | 2 +- src/Language/Source.php | 4 +- src/Language/Visitor.php | 4 +- src/Server/Helper.php | 8 +- src/Server/OperationParams.php | 2 +- src/Type/Definition/FieldDefinition.php | 2 +- src/Type/Definition/ObjectType.php | 2 +- src/Type/Introspection.php | 4 +- src/Type/Schema.php | 2 +- src/Type/SchemaValidationContext.php | 2 +- src/Utils/BreakingChangesFinder.php | 2 +- src/Utils/TypeInfo.php | 2 +- src/Utils/Utils.php | 2 +- src/Validator/DocumentValidator.php | 2 +- .../Rules/OverlappingFieldsCanBeMerged.php | 2 +- .../Rules/ProvidedRequiredArguments.php | 4 +- .../ProvidedRequiredArgumentsOnDirectives.php | 2 +- src/Validator/Rules/QueryComplexity.php | 2 +- src/Validator/Rules/QuerySecurityRule.php | 4 +- src/Validator/Rules/ValidationRule.php | 2 +- tests/Executor/DirectivesTest.php | 20 +- tests/Executor/ExecutorLazySchemaTest.php | 14 +- tests/Server/Psr7/PsrStreamStub.php | 11 +- tests/Utils/ValueFromAstTest.php | 2 +- 38 files changed, 78 insertions(+), 260 deletions(-) diff --git a/composer.json b/composer.json index 3289dc143..54d22f9a9 100644 --- a/composer.json +++ b/composer.json @@ -54,7 +54,7 @@ "lint" : "phpcs", "fix" : "phpcbf", "stan": "phpstan analyse --ansi --memory-limit 2048M", - "phpstan-baseline": "phpstan analyse --ansi --generate-baseline=phpstan-baseline.neon", + "phpstan-baseline": "phpstan analyse --ansi --generate-baseline", "check": "composer lint && composer stan && composer test" } } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 8598378fa..f57b1c63d 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -5,11 +5,6 @@ parameters: count: 1 path: src/Error/Error.php - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 6 - path: src/Error/Error.php - - message: "#^Only booleans are allowed in &&, Throwable\\|null given on the left side\\.$#" count: 1 @@ -80,11 +75,6 @@ parameters: count: 1 path: src/Error/FormattedError.php - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 3 - path: src/Error/FormattedError.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 5 @@ -110,46 +100,16 @@ parameters: count: 1 path: src/Error/FormattedError.php - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 2 - path: src/Error/Warning.php - - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 1 - path: src/Executor/ExecutionContext.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 2 path: src/Executor/ExecutionResult.php - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 1 - path: src/Executor/ExecutionResult.php - - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 2 - path: src/Executor/Executor.php - - message: "#^Variable property access on object\\.$#" count: 2 path: src/Executor/Executor.php - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 1 - path: src/Executor/Promise/Adapter/SyncPromise.php - - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 5 - path: src/Executor/ReferenceExecutor.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 4 @@ -185,11 +145,6 @@ parameters: count: 3 path: src/Experimental/Executor/CoroutineExecutor.php - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 2 - path: src/Experimental/Executor/CoroutineExecutor.php - - message: "#^Variable property access on object\\.$#" count: 2 @@ -200,11 +155,6 @@ parameters: count: 1 path: src/Experimental/Executor/CoroutineExecutor.php - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 1 - path: src/GraphQL.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 2 @@ -220,41 +170,16 @@ parameters: count: 1 path: src/Language/AST/Node.php - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 2 - path: src/Language/Lexer.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 3 path: src/Language/Parser.php - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 1 - path: src/Language/Parser.php - - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 1 - path: src/Language/Printer.php - - message: "#^Only booleans are allowed in a ternary operator condition, array\\\\|null given\\.$#" count: 2 path: src/Language/Printer.php - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 2 - path: src/Language/Source.php - - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 2 - path: src/Language/Visitor.php - - message: "#^Only booleans are allowed in a negated boolean, array\\ given\\.$#" count: 1 @@ -295,11 +220,6 @@ parameters: count: 2 path: src/Server/Helper.php - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 4 - path: src/Server/Helper.php - - message: "#^Only booleans are allowed in an if condition, int given\\.$#" count: 1 @@ -340,11 +260,6 @@ parameters: count: 1 path: src/Server/Helper.php - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 2 - path: src/Server/OperationParams.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 2 @@ -360,11 +275,6 @@ parameters: count: 1 path: src/Type/Definition/EnumType.php - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 1 - path: src/Type/Definition/FieldDefinition.php - - message: "#^Variable property access on \\$this\\(GraphQL\\\\Type\\\\Definition\\\\InputObjectField\\)\\.$#" count: 1 @@ -380,11 +290,6 @@ parameters: count: 1 path: src/Type/Definition/InputObjectType.php - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 1 - path: src/Type/Definition/ObjectType.php - - message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Language\\\\AST\\\\SelectionSetNode\\|null given\\.$#" count: 1 @@ -416,7 +321,7 @@ parameters: path: src/Type/Introspection.php - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" + message: "#^Anonymous function should have native return typehint \"array\"\\.$#" count: 1 path: src/Type/Introspection.php @@ -430,11 +335,6 @@ parameters: count: 1 path: src/Type/Schema.php - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 1 - path: src/Type/Schema.php - - message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\Type\\|null given\\.$#" count: 1 @@ -515,11 +415,6 @@ parameters: count: 1 path: src/Type/SchemaValidationContext.php - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 1 - path: src/Type/SchemaValidationContext.php - - message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Language\\\\AST\\\\InterfaceTypeDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\InterfaceTypeExtensionNode\\|GraphQL\\\\Language\\\\AST\\\\ObjectTypeDefinitionNode\\|GraphQL\\\\Language\\\\AST\\\\ObjectTypeExtensionNode given\\.$#" count: 1 @@ -660,11 +555,6 @@ parameters: count: 1 path: src/Utils/ASTDefinitionBuilder.php - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 1 - path: src/Utils/BreakingChangesFinder.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 3 @@ -725,11 +615,6 @@ parameters: count: 1 path: src/Utils/TypeInfo.php - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 1 - path: src/Utils/TypeInfo.php - - message: "#^Only booleans are allowed in an if condition, GraphQL\\\\Type\\\\Definition\\\\Directive\\|GraphQL\\\\Type\\\\Definition\\\\FieldDefinition\\|null given\\.$#" count: 1 @@ -750,11 +635,6 @@ parameters: count: 1 path: src/Utils/TypeInfo.php - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 1 - path: src/Utils/Utils.php - - message: "#^Variable property access on object\\.$#" count: 1 @@ -795,11 +675,6 @@ parameters: count: 1 path: src/Utils/Value.php - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 1 - path: src/Validator/DocumentValidator.php - - message: "#^Only booleans are allowed in a negated boolean, \\(GraphQL\\\\Type\\\\Definition\\\\CompositeType&GraphQL\\\\Type\\\\Definition\\\\Type\\)\\|null given\\.$#" count: 1 @@ -905,11 +780,6 @@ parameters: count: 2 path: src/Validator/Rules/OverlappingFieldsCanBeMerged.php - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 2 - path: src/Validator/Rules/OverlappingFieldsCanBeMerged.php - - message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Language\\\\AST\\\\ArgumentNode\\|null given\\.$#" count: 1 @@ -940,11 +810,6 @@ parameters: count: 1 path: src/Validator/Rules/ProvidedRequiredArguments.php - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 2 - path: src/Validator/Rules/ProvidedRequiredArguments.php - - message: "#^Only booleans are allowed in \\|\\|, GraphQL\\\\Language\\\\AST\\\\ArgumentNode\\|null given on the left side\\.$#" count: 1 @@ -970,26 +835,11 @@ parameters: count: 1 path: src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 1 - path: src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 3 path: src/Validator/Rules/QueryComplexity.php - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 1 - path: src/Validator/Rules/QueryComplexity.php - - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 2 - path: src/Validator/Rules/QuerySecurityRule.php - - message: "#^Only booleans are allowed in &&, GraphQL\\\\Type\\\\Definition\\\\Type\\|null given on the left side\\.$#" count: 1 @@ -1050,11 +900,6 @@ parameters: count: 1 path: src/Validator/Rules/UniqueVariableNames.php - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 1 - path: src/Validator/Rules/ValidationRule.php - - message: "#^Only booleans are allowed in \\|\\|, GraphQL\\\\Type\\\\Definition\\\\EnumType\\|GraphQL\\\\Type\\\\Definition\\\\InputObjectType\\|GraphQL\\\\Type\\\\Definition\\\\ListOfType\\|GraphQL\\\\Type\\\\Definition\\\\NonNull\\|GraphQL\\\\Type\\\\Definition\\\\ScalarType given on the left side\\.$#" count: 1 @@ -1115,16 +960,6 @@ parameters: count: 1 path: tests/Executor/DirectivesTest.php - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 1 - path: tests/Executor/DirectivesTest.php - - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 7 - path: tests/Executor/ExecutorLazySchemaTest.php - - message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Type\\\\Definition\\\\InterfaceType given\\.$#" count: 1 @@ -1165,11 +1000,6 @@ parameters: count: 4 path: tests/Language/VisitorTest.php - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 1 - path: tests/Server/Psr7/PsrStreamStub.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 1 @@ -1200,8 +1030,3 @@ parameters: count: 2 path: tests/Utils/MixedStoreTest.php - - - message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#" - count: 1 - path: tests/Utils/ValueFromAstTest.php - diff --git a/src/Error/Error.php b/src/Error/Error.php index 7e6056fc0..4509c140e 100644 --- a/src/Error/Error.php +++ b/src/Error/Error.php @@ -15,6 +15,7 @@ use function array_filter; use function array_map; use function array_values; +use function count; use function is_array; use function iterator_to_array; @@ -114,7 +115,7 @@ public function __construct( $this->source = $source; $this->positions = $positions; $this->path = $path; - $this->extensions = $extensions ?: ( + $this->extensions = count($extensions) > 0 ? $extensions : ( $previous && $previous instanceof self ? $previous->extensions : [] @@ -122,7 +123,8 @@ public function __construct( if ($previous instanceof ClientAware) { $this->isClientSafe = $previous->isClientSafe(); - $this->category = $previous->getCategory() ?: self::CATEGORY_INTERNAL; + $cat = $previous->getCategory(); + $this->category = $cat === '' || $cat === null ? self::CATEGORY_INTERNAL: $cat; } elseif ($previous) { $this->isClientSafe = false; $this->category = self::CATEGORY_INTERNAL; @@ -150,8 +152,8 @@ public static function createLocatedError($error, $nodes = null, $path = null) return $error; } - $nodes = $nodes ?: $error->nodes; - $path = $path ?: $error->path; + $nodes = $nodes ?? $error->nodes; + $path = $path ?? $error->path; } $source = $positions = $originalError = null; @@ -160,7 +162,7 @@ public static function createLocatedError($error, $nodes = null, $path = null) if ($error instanceof self) { $message = $error->getMessage(); $originalError = $error; - $nodes = $error->nodes ?: $nodes; + $nodes = $error->nodes ?? $nodes; $source = $error->source; $positions = $error->positions; $extensions = $error->extensions; @@ -172,7 +174,7 @@ public static function createLocatedError($error, $nodes = null, $path = null) } return new static( - $message ?: 'An unknown error occurred.', + $message === '' || $message === null ? 'An unknown error occurred.' : $message, $nodes, $source, $positions, diff --git a/src/Error/FormattedError.php b/src/Error/FormattedError.php index 46c781e26..64b5ea266 100644 --- a/src/Error/FormattedError.php +++ b/src/Error/FormattedError.php @@ -181,7 +181,7 @@ public static function createFromException($e, $debug = false, $internalErrorMes Utils::getVariableType($e) ); - $internalErrorMessage = $internalErrorMessage ?: self::$internalErrorMessage; + $internalErrorMessage = $internalErrorMessage === '' || $internalErrorMessage === null ? self::$internalErrorMessage : $internalErrorMessage; if ($e instanceof ClientAware) { $formattedError = [ @@ -284,7 +284,7 @@ public static function addDebugEntries(array $formattedError, $e, $debug) $isTrivial = $e instanceof Error && ! $e->getPrevious(); if (! $isTrivial) { - $debugging = $e->getPrevious() ?: $e; + $debugging = $e->getPrevious() ?? $e; $formattedError['trace'] = static::toSafeTrace($debugging); } } @@ -302,7 +302,7 @@ public static function addDebugEntries(array $formattedError, $e, $debug) */ public static function prepareFormatter(?callable $formatter = null, $debug) { - $formatter = $formatter ?: static function ($e) : array { + $formatter = $formatter ?? static function ($e) : array { return FormattedError::createFromException($e); }; if ($debug) { diff --git a/src/Error/Warning.php b/src/Error/Warning.php index f376ed9cd..3efd1cfeb 100644 --- a/src/Error/Warning.php +++ b/src/Error/Warning.php @@ -96,7 +96,7 @@ public static function enable($enable = true) : void public static function warnOnce(string $errorMessage, int $warningId, ?int $messageLevel = null) : void { - $messageLevel = $messageLevel ?: E_USER_WARNING; + $messageLevel = $messageLevel ?? E_USER_WARNING; if (self::$warningHandler !== null) { $fn = self::$warningHandler; @@ -109,7 +109,7 @@ public static function warnOnce(string $errorMessage, int $warningId, ?int $mess public static function warn(string $errorMessage, int $warningId, ?int $messageLevel = null) : void { - $messageLevel = $messageLevel ?: E_USER_WARNING; + $messageLevel = $messageLevel ?? E_USER_WARNING; if (self::$warningHandler !== null) { $fn = self::$warningHandler; diff --git a/src/Executor/ExecutionContext.php b/src/Executor/ExecutionContext.php index c510c30cf..4a57f20c4 100644 --- a/src/Executor/ExecutionContext.php +++ b/src/Executor/ExecutionContext.php @@ -64,7 +64,7 @@ public function __construct( $this->contextValue = $contextValue; $this->operation = $operation; $this->variableValues = $variableValues; - $this->errors = $errors ?: []; + $this->errors = $errors ?? []; $this->fieldResolver = $fieldResolver; $this->promiseAdapter = $promiseAdapter; } diff --git a/src/Executor/ExecutionResult.php b/src/Executor/ExecutionResult.php index 1fc47c8db..1331c64b0 100644 --- a/src/Executor/ExecutionResult.php +++ b/src/Executor/ExecutionResult.php @@ -139,7 +139,7 @@ public function toArray($debug = false) $result = []; if (! empty($this->errors)) { - $errorsHandler = $this->errorsHandler ?: static function (array $errors, callable $formatter) : array { + $errorsHandler = $this->errorsHandler ?? static function (array $errors, callable $formatter) : array { return array_map($formatter, $errors); }; diff --git a/src/Executor/Executor.php b/src/Executor/Executor.php index ee9618748..babe2f9e0 100644 --- a/src/Executor/Executor.php +++ b/src/Executor/Executor.php @@ -44,7 +44,7 @@ public static function setDefaultFieldResolver(callable $fieldResolver) public static function getPromiseAdapter() : PromiseAdapter { - return self::$defaultPromiseAdapter ?: (self::$defaultPromiseAdapter = new SyncPromiseAdapter()); + return self::$defaultPromiseAdapter ?? (self::$defaultPromiseAdapter = new SyncPromiseAdapter()); } public static function setPromiseAdapter(?PromiseAdapter $defaultPromiseAdapter = null) @@ -149,7 +149,7 @@ public static function promiseToExecute( $contextValue, $variableValues, $operationName, - $fieldResolver ?: self::$defaultFieldResolver + $fieldResolver ?? self::$defaultFieldResolver ); return $executor->doExecute(); diff --git a/src/Executor/Promise/Adapter/SyncPromise.php b/src/Executor/Promise/Adapter/SyncPromise.php index 29d41c36d..d0b94b18d 100644 --- a/src/Executor/Promise/Adapter/SyncPromise.php +++ b/src/Executor/Promise/Adapter/SyncPromise.php @@ -167,7 +167,7 @@ private function enqueueWaitingPromises() : void public static function getQueue() : SplQueue { - return self::$queue ?: self::$queue = new SplQueue(); + return self::$queue ?? self::$queue = new SplQueue(); } /** diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index afcb59434..1f6a33f42 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -167,8 +167,8 @@ private static function buildExecutionContext( if ($operation !== null) { [$coercionErrors, $coercedVariableValues] = Values::getVariableValues( $schema, - $operation->variableDefinitions ?: [], - $rawVariableValues ?: [] + $operation->variableDefinitions ?? [], + $rawVariableValues ?? [] ); if (empty($coercionErrors)) { $variableValues = $coercedVariableValues; @@ -582,9 +582,9 @@ private function resolveField(ObjectType $parentType, $rootValue, $fieldNodes, $ private function getFieldDef(Schema $schema, ObjectType $parentType, string $fieldName) : ?FieldDefinition { static $schemaMetaFieldDef, $typeMetaFieldDef, $typeNameMetaFieldDef; - $schemaMetaFieldDef = $schemaMetaFieldDef ?: Introspection::schemaMetaFieldDef(); - $typeMetaFieldDef = $typeMetaFieldDef ?: Introspection::typeMetaFieldDef(); - $typeNameMetaFieldDef = $typeNameMetaFieldDef ?: Introspection::typeNameMetaFieldDef(); + $schemaMetaFieldDef = $schemaMetaFieldDef ?? Introspection::schemaMetaFieldDef(); + $typeMetaFieldDef = $typeMetaFieldDef ?? Introspection::typeMetaFieldDef(); + $typeNameMetaFieldDef = $typeNameMetaFieldDef ?? Introspection::typeNameMetaFieldDef(); if ($fieldName === $schemaMetaFieldDef->name && $schema->getQueryType() === $parentType) { return $schemaMetaFieldDef; } diff --git a/src/Experimental/Executor/CoroutineExecutor.php b/src/Experimental/Executor/CoroutineExecutor.php index 5a073eb8e..cebef18c8 100644 --- a/src/Experimental/Executor/CoroutineExecutor.php +++ b/src/Experimental/Executor/CoroutineExecutor.php @@ -187,8 +187,8 @@ public function doExecute() : Promise [$errors, $coercedVariableValues] = Values::getVariableValues( $this->schema, - $this->collector->operation->variableDefinitions ?: [], - $this->rawVariableValues ?: [] + $this->collector->operation->variableDefinitions ?? [], + $this->rawVariableValues ?? [] ); if (! empty($errors)) { diff --git a/src/GraphQL.php b/src/GraphQL.php index 3b653b6ab..01e14d8d5 100644 --- a/src/GraphQL.php +++ b/src/GraphQL.php @@ -131,7 +131,7 @@ public static function promiseToExecute( if ($source instanceof DocumentNode) { $documentNode = $source; } else { - $documentNode = Parser::parse(new Source($source ?: '', 'GraphQL')); + $documentNode = Parser::parse(new Source($source ?? '', 'GraphQL')); } // FIXME diff --git a/src/Language/Lexer.php b/src/Language/Lexer.php index 14bdc580e..15563bb23 100644 --- a/src/Language/Lexer.php +++ b/src/Language/Lexer.php @@ -121,7 +121,7 @@ public function lookahead() $token = $this->token; if ($token->kind !== Token::EOF) { do { - $token = $token->next ?: ($token->next = $this->readToken($token)); + $token = $token->next ?? ($token->next = $this->readToken($token)); } while ($token->kind === Token::COMMENT); } @@ -793,7 +793,7 @@ private function readChars($charCount, $advance = false, $byteStreamPosition = n { $result = ''; $totalBytes = 0; - $byteOffset = $byteStreamPosition ?: $this->byteStreamPosition; + $byteOffset = $byteStreamPosition ?? $this->byteStreamPosition; for ($i = 0; $i < $charCount; $i++) { [$char, $code, $bytes] = $this->readChar(false, $byteOffset); diff --git a/src/Language/Parser.php b/src/Language/Parser.php index 0328fbdb3..1050ecd2c 100644 --- a/src/Language/Parser.php +++ b/src/Language/Parser.php @@ -351,7 +351,7 @@ private function expectKeyword(string $value) : Token private function unexpected(?Token $atToken = null) : SyntaxError { - $token = $atToken ?: $this->lexer->token; + $token = $atToken ?? $this->lexer->token; return new SyntaxError($this->lexer->source, $token->start, 'Unexpected ' . $token->getDescription()); } diff --git a/src/Language/Printer.php b/src/Language/Printer.php index 8e46ac329..7aa13b748 100644 --- a/src/Language/Printer.php +++ b/src/Language/Printer.php @@ -84,7 +84,7 @@ class Printer public static function doPrint($ast) { static $instance; - $instance = $instance ?: new static(); + $instance = $instance ?? new static(); return $instance->printAST($ast); } diff --git a/src/Language/Source.php b/src/Language/Source.php index 7e940305e..bd8794076 100644 --- a/src/Language/Source.php +++ b/src/Language/Source.php @@ -46,8 +46,8 @@ public function __construct($body, $name = null, ?SourceLocation $location = nul $this->body = $body; $this->length = mb_strlen($body, 'UTF-8'); - $this->name = $name ?: 'GraphQL request'; - $this->locationOffset = $location ?: new SourceLocation(1, 1); + $this->name = $name === '' || $name === null ? 'GraphQL request' : $name; + $this->locationOffset = $location ?? new SourceLocation(1, 1); Utils::invariant( $this->locationOffset->line > 0, diff --git a/src/Language/Visitor.php b/src/Language/Visitor.php index dc52a2347..cc3576fa8 100644 --- a/src/Language/Visitor.php +++ b/src/Language/Visitor.php @@ -186,7 +186,7 @@ class Visitor */ public static function visit($root, $visitor, $keyMap = null) { - $visitorKeys = $keyMap ?: self::$visitorKeys; + $visitorKeys = $keyMap ?? self::$visitorKeys; $stack = null; $inArray = $root instanceof NodeList || is_array($root); @@ -328,7 +328,7 @@ public static function visit($root, $visitor, $keyMap = null) ]; $inArray = $node instanceof NodeList || is_array($node); - $keys = ($inArray ? $node : $visitorKeys[$node->kind]) ?: []; + $keys = ($inArray ? $node : $visitorKeys[$node->kind]) ?? []; $index = -1; $edits = []; if ($parent !== null) { diff --git a/src/Server/Helper.php b/src/Server/Helper.php index ce9bf1c5c..e950fc869 100644 --- a/src/Server/Helper.php +++ b/src/Server/Helper.php @@ -76,12 +76,12 @@ public function parseHttpRequest(?callable $readRawBodyFn = null) $rawBody = $readRawBodyFn ? $readRawBodyFn() : $this->readRawBody(); - $bodyParams = ['query' => $rawBody ?: '']; + $bodyParams = ['query' => $rawBody ?? '']; } elseif (stripos($contentType, 'application/json') !== false) { $rawBody = $readRawBodyFn ? $readRawBodyFn() : $this->readRawBody(); - $bodyParams = json_decode($rawBody ?: '', true); + $bodyParams = json_decode($rawBody ?? '', true); if (json_last_error()) { throw new RequestError('Could not parse JSON: ' . json_last_error_msg()); @@ -202,7 +202,7 @@ public function validateOperationParams(OperationParams $params) */ public function executeOperation(ServerConfig $config, OperationParams $op) { - $promiseAdapter = $config->getPromiseAdapter() ?: Executor::getPromiseAdapter(); + $promiseAdapter = $config->getPromiseAdapter() ?? Executor::getPromiseAdapter(); $result = $this->promiseToExecuteOperation($promiseAdapter, $config, $op); if ($promiseAdapter instanceof SyncPromiseAdapter) { @@ -224,7 +224,7 @@ public function executeOperation(ServerConfig $config, OperationParams $op) */ public function executeBatch(ServerConfig $config, array $operations) { - $promiseAdapter = $config->getPromiseAdapter() ?: Executor::getPromiseAdapter(); + $promiseAdapter = $config->getPromiseAdapter() ?? Executor::getPromiseAdapter(); $result = []; foreach ($operations as $operation) { diff --git a/src/Server/OperationParams.php b/src/Server/OperationParams.php index a976ec072..67c38d60e 100644 --- a/src/Server/OperationParams.php +++ b/src/Server/OperationParams.php @@ -102,7 +102,7 @@ public static function create(array $params, bool $readonly = false) : Operation } $instance->query = $params['query']; - $instance->queryId = $params['queryid'] ?: $params['documentid'] ?: $params['id']; + $instance->queryId = $params['queryid'] ?? $params['documentid'] ?? $params['id']; $instance->operation = $params['operationname']; $instance->variables = $params['variables']; $instance->extensions = $params['extensions']; diff --git a/src/Type/Definition/FieldDefinition.php b/src/Type/Definition/FieldDefinition.php index 7bf0800b6..39392cd53 100644 --- a/src/Type/Definition/FieldDefinition.php +++ b/src/Type/Definition/FieldDefinition.php @@ -164,7 +164,7 @@ public static function defaultComplexity($childrenComplexity) */ public function getArg($name) { - foreach ($this->args ?: [] as $arg) { + foreach ($this->args ?? [] as $arg) { /** @var FieldArgument $arg */ if ($arg->name === $name) { return $arg; diff --git a/src/Type/Definition/ObjectType.php b/src/Type/Definition/ObjectType.php index 5e031df2f..ce6097a09 100644 --- a/src/Type/Definition/ObjectType.php +++ b/src/Type/Definition/ObjectType.php @@ -195,7 +195,7 @@ public function getInterfaces() ); } - $this->interfaces = $interfaces ?: []; + $this->interfaces = $interfaces ?? []; } return $this->interfaces; diff --git a/src/Type/Introspection.php b/src/Type/Introspection.php index fab7ecd0c..0178a13ec 100644 --- a/src/Type/Introspection.php +++ b/src/Type/Introspection.php @@ -661,8 +661,8 @@ public static function _directive() ], 'args' => [ 'type' => Type::nonNull(Type::listOf(Type::nonNull(self::_inputValue()))), - 'resolve' => static function (Directive $directive) : array { - return $directive->args ?: []; + 'resolve' => static function (Directive $directive) { + return $directive->args ?? []; }, ], ], diff --git a/src/Type/Schema.php b/src/Type/Schema.php index 610d5e944..e69b018db 100644 --- a/src/Type/Schema.php +++ b/src/Type/Schema.php @@ -158,7 +158,7 @@ public function __construct($config) */ private function resolveAdditionalTypes() { - $types = $this->config->types ?: []; + $types = $this->config->types ?? []; if (is_callable($types)) { $types = $types(); diff --git a/src/Type/SchemaValidationContext.php b/src/Type/SchemaValidationContext.php index b37b0990d..3ac18ede6 100644 --- a/src/Type/SchemaValidationContext.php +++ b/src/Type/SchemaValidationContext.php @@ -528,7 +528,7 @@ private function getAllNodes($obj) ? ($extensionNodes ? array_merge([$astNode], $extensionNodes) : [$astNode]) - : ($extensionNodes ?: []); + : ($extensionNodes ?? []); } /** diff --git a/src/Utils/BreakingChangesFinder.php b/src/Utils/BreakingChangesFinder.php index 8ef9253d2..3eec2b071 100644 --- a/src/Utils/BreakingChangesFinder.php +++ b/src/Utils/BreakingChangesFinder.php @@ -691,7 +691,7 @@ public static function findRemovedArgsForDirectives(Directive $oldDirective, Dir private static function getArgumentMapForDirective(Directive $directive) { return Utils::keyMap( - $directive->args ?: [], + $directive->args ?? [], static function ($arg) { return $arg->name; } diff --git a/src/Utils/TypeInfo.php b/src/Utils/TypeInfo.php index 870180115..ef1f02301 100644 --- a/src/Utils/TypeInfo.php +++ b/src/Utils/TypeInfo.php @@ -317,7 +317,7 @@ public function enter(Node $node) break; case $node instanceof ArgumentNode: - $fieldOrDirective = $this->getDirective() ?: $this->getFieldDef(); + $fieldOrDirective = $this->getDirective() ?? $this->getFieldDef(); $argDef = $argType = null; if ($fieldOrDirective) { /** @var FieldArgument $argDef */ diff --git a/src/Utils/Utils.php b/src/Utils/Utils.php index 7919f262e..0e6c18641 100644 --- a/src/Utils/Utils.php +++ b/src/Utils/Utils.php @@ -58,7 +58,7 @@ public static function undefined() { static $undefined; - return $undefined ?: $undefined = new stdClass(); + return $undefined ?? $undefined = new stdClass(); } /** diff --git a/src/Validator/DocumentValidator.php b/src/Validator/DocumentValidator.php index 0b5fb0c1b..fa94dc4b1 100644 --- a/src/Validator/DocumentValidator.php +++ b/src/Validator/DocumentValidator.php @@ -110,7 +110,7 @@ public static function validate( return []; } - $typeInfo = $typeInfo ?: new TypeInfo($schema); + $typeInfo = $typeInfo ?? new TypeInfo($schema); return static::visitUsingRules($schema, $typeInfo, $ast, $rules); } diff --git a/src/Validator/Rules/OverlappingFieldsCanBeMerged.php b/src/Validator/Rules/OverlappingFieldsCanBeMerged.php index 790c28cf9..1817d0144 100644 --- a/src/Validator/Rules/OverlappingFieldsCanBeMerged.php +++ b/src/Validator/Rules/OverlappingFieldsCanBeMerged.php @@ -380,7 +380,7 @@ private function findConflict( ]; } - if (! $this->sameArguments($ast1->arguments ?: [], $ast2->arguments ?: [])) { + if (! $this->sameArguments($ast1->arguments ?? [], $ast2->arguments ?? [])) { return [ [$responseName, 'they have differing arguments'], [$ast1], diff --git a/src/Validator/Rules/ProvidedRequiredArguments.php b/src/Validator/Rules/ProvidedRequiredArguments.php index 41730ebe2..930163023 100644 --- a/src/Validator/Rules/ProvidedRequiredArguments.php +++ b/src/Validator/Rules/ProvidedRequiredArguments.php @@ -26,7 +26,7 @@ public function getVisitor(ValidationContext $context) if (! $fieldDef) { return Visitor::skipNode(); } - $argNodes = $fieldNode->arguments ?: []; + $argNodes = $fieldNode->arguments ?? []; $argNodeMap = []; foreach ($argNodes as $argNode) { @@ -53,7 +53,7 @@ public function getVisitor(ValidationContext $context) if (! $directiveDef) { return Visitor::skipNode(); } - $argNodes = $directiveNode->arguments ?: []; + $argNodes = $directiveNode->arguments ?? []; $argNodeMap = []; foreach ($argNodes as $argNode) { $argNodeMap[$argNode->name->value] = $argNodes; diff --git a/src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php b/src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php index 2eacdc202..3c7c84b08 100644 --- a/src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php +++ b/src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php @@ -92,7 +92,7 @@ static function (NamedTypeNode $argument) : string { return null; } - $argNodes = $directiveNode->arguments ?: []; + $argNodes = $directiveNode->arguments ?? []; $argNodeMap = Utils::keyMap( $argNodes, static function (ArgumentNode $arg) : string { diff --git a/src/Validator/Rules/QueryComplexity.php b/src/Validator/Rules/QueryComplexity.php index b084799b3..78d3e11d0 100644 --- a/src/Validator/Rules/QueryComplexity.php +++ b/src/Validator/Rules/QueryComplexity.php @@ -232,7 +232,7 @@ public function getRawVariableValues() */ public function setRawVariableValues(?array $rawVariableValues = null) { - $this->rawVariableValues = $rawVariableValues ?: []; + $this->rawVariableValues = $rawVariableValues ?? []; } private function buildFieldArguments(FieldNode $node) diff --git a/src/Validator/Rules/QuerySecurityRule.php b/src/Validator/Rules/QuerySecurityRule.php index c6e5b460d..1ff2f508a 100644 --- a/src/Validator/Rules/QuerySecurityRule.php +++ b/src/Validator/Rules/QuerySecurityRule.php @@ -109,8 +109,8 @@ protected function collectFieldASTsAndDefs( ?ArrayObject $visitedFragmentNames = null, ?ArrayObject $astAndDefs = null ) { - $_visitedFragmentNames = $visitedFragmentNames ?: new ArrayObject(); - $_astAndDefs = $astAndDefs ?: new ArrayObject(); + $_visitedFragmentNames = $visitedFragmentNames ?? new ArrayObject(); + $_astAndDefs = $astAndDefs ?? new ArrayObject(); foreach ($selectionSet->selections as $selection) { switch (true) { diff --git a/src/Validator/Rules/ValidationRule.php b/src/Validator/Rules/ValidationRule.php index cf38cd138..461aafeeb 100644 --- a/src/Validator/Rules/ValidationRule.php +++ b/src/Validator/Rules/ValidationRule.php @@ -15,7 +15,7 @@ abstract class ValidationRule public function getName() { - return $this->name ?: static::class; + return $this->name === '' || $this->name === null ? static::class : $this->name; } public function __invoke(ValidationContext $context) diff --git a/tests/Executor/DirectivesTest.php b/tests/Executor/DirectivesTest.php index 84aa491c8..fda71bb43 100644 --- a/tests/Executor/DirectivesTest.php +++ b/tests/Executor/DirectivesTest.php @@ -20,8 +20,11 @@ class DirectivesTest extends TestCase /** @var Schema */ private static $schema; - /** @var string[] */ - private static $data; + /** @var array */ + private static $data = [ + 'a' => 'a', + 'b' => 'b', + ]; /** * @see it('basic query works') @@ -38,7 +41,7 @@ public function testWorksWithoutDirectives() : void */ private function executeTestQuery($doc) : array { - return Executor::execute(self::getSchema(), Parser::parse($doc), self::getData())->toArray(); + return Executor::execute(self::getSchema(), Parser::parse($doc), self::$data)->toArray(); } private static function getSchema() : Schema @@ -58,17 +61,6 @@ private static function getSchema() : Schema return self::$schema; } - /** - * @return string[] - */ - private static function getData() : array - { - return self::$data ?: (self::$data = [ - 'a' => 'a', - 'b' => 'b', - ]); - } - public function testWorksOnScalars() : void { // if true includes scalar diff --git a/tests/Executor/ExecutorLazySchemaTest.php b/tests/Executor/ExecutorLazySchemaTest.php index 9d9878531..43f94bee9 100644 --- a/tests/Executor/ExecutorLazySchemaTest.php +++ b/tests/Executor/ExecutorLazySchemaTest.php @@ -257,7 +257,7 @@ public function loadType($name, $isExecutorCall = false) switch ($name) { case 'Query': - return $this->queryType ?: $this->queryType = new ObjectType([ + return $this->queryType ?? $this->queryType = new ObjectType([ 'name' => 'Query', 'fields' => function () : array { $this->calls[] = 'Query.fields'; @@ -269,7 +269,7 @@ public function loadType($name, $isExecutorCall = false) }, ]); case 'SomeObject': - return $this->someObjectType ?: $this->someObjectType = new ObjectType([ + return $this->someObjectType ?? $this->someObjectType = new ObjectType([ 'name' => 'SomeObject', 'fields' => function () : array { $this->calls[] = 'SomeObject.fields'; @@ -288,7 +288,7 @@ public function loadType($name, $isExecutorCall = false) }, ]); case 'OtherObject': - return $this->otherObjectType ?: $this->otherObjectType = new ObjectType([ + return $this->otherObjectType ?? $this->otherObjectType = new ObjectType([ 'name' => 'OtherObject', 'fields' => function () : array { $this->calls[] = 'OtherObject.fields'; @@ -300,7 +300,7 @@ public function loadType($name, $isExecutorCall = false) }, ]); case 'DeeperObject': - return $this->deeperObjectType ?: $this->deeperObjectType = new ObjectType([ + return $this->deeperObjectType ?? $this->deeperObjectType = new ObjectType([ 'name' => 'DeeperObject', 'fields' => function () : array { return [ @@ -309,7 +309,7 @@ public function loadType($name, $isExecutorCall = false) }, ]); case 'SomeScalar': - return $this->someScalarType ?: $this->someScalarType = new CustomScalarType([ + return $this->someScalarType ?? $this->someScalarType = new CustomScalarType([ 'name' => 'SomeScalar', 'serialize' => static function ($value) { return $value; @@ -321,7 +321,7 @@ public function loadType($name, $isExecutorCall = false) }, ]); case 'SomeUnion': - return $this->someUnionType ?: $this->someUnionType = new UnionType([ + return $this->someUnionType ?? $this->someUnionType = new UnionType([ 'name' => 'SomeUnion', 'resolveType' => function () { $this->calls[] = 'SomeUnion.resolveType'; @@ -335,7 +335,7 @@ public function loadType($name, $isExecutorCall = false) }, ]); case 'SomeInterface': - return $this->someInterfaceType ?: $this->someInterfaceType = new InterfaceType([ + return $this->someInterfaceType ?? $this->someInterfaceType = new InterfaceType([ 'name' => 'SomeInterface', 'resolveType' => function () { $this->calls[] = 'SomeInterface.resolveType'; diff --git a/tests/Server/Psr7/PsrStreamStub.php b/tests/Server/Psr7/PsrStreamStub.php index 736ada980..1865dfedd 100644 --- a/tests/Server/Psr7/PsrStreamStub.php +++ b/tests/Server/Psr7/PsrStreamStub.php @@ -13,6 +13,7 @@ */ class PsrStreamStub implements StreamInterface { + /** @var string */ public $content; /** @@ -27,9 +28,8 @@ class PsrStreamStub implements StreamInterface * string casting operations. * * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring - * @return string */ - public function __toString() + public function __toString() : string { return $this->content; } @@ -59,11 +59,10 @@ public function detach() /** * Get the size of the stream if known. * - * @return int|null Returns the size in bytes if known, or null if unknown. */ - public function getSize() + public function getSize() : ?int { - return strlen($this->content ?: ''); + return $this->content === null ? null : strlen($this->content); } /** @@ -185,7 +184,7 @@ public function read($length) * @throws \RuntimeException if unable to read or an error occurs while * reading. */ - public function getContents() + public function getContents() : string { return $this->content; } diff --git a/tests/Utils/ValueFromAstTest.php b/tests/Utils/ValueFromAstTest.php index ee3f6a843..310df55f1 100644 --- a/tests/Utils/ValueFromAstTest.php +++ b/tests/Utils/ValueFromAstTest.php @@ -183,7 +183,7 @@ public function testCoercesInputObjectsAccordingToInputCoercionRules() : void private function inputObj() { - return $this->inputObj ?: $this->inputObj = new InputObjectType([ + return $this->inputObj ?? $this->inputObj = new InputObjectType([ 'name' => 'TestInput', 'fields' => [ 'int' => ['type' => Type::int(), 'defaultValue' => 42], From 10e62cfd22ab3f9334be10855e9a5c25088b4fa8 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Fri, 13 Dec 2019 15:22:22 +0100 Subject: [PATCH 173/256] Implement PSR-7 RequestInterface support - ServerRequestInterface is not needed, parent RequestInterface is sufficient - Replaced custom PSR-7 test stubs with nyholm/psr-7 implementation - Fixed reading body contents --- composer.json | 1 + docs/executing-queries.md | 4 +- docs/reference.md | 6 +- src/Server/Helper.php | 18 +- src/Server/StandardServer.php | 6 +- tests/Server/Psr7/PsrRequestStub.php | 610 -------------------------- tests/Server/Psr7/PsrResponseStub.php | 287 ------------ tests/Server/Psr7/PsrStreamStub.php | 208 --------- tests/Server/PsrResponseTest.php | 15 +- tests/Server/RequestParsingTest.php | 81 ++-- tests/Server/StandardServerTest.php | 22 +- 11 files changed, 76 insertions(+), 1182 deletions(-) delete mode 100644 tests/Server/Psr7/PsrRequestStub.php delete mode 100644 tests/Server/Psr7/PsrResponseStub.php delete mode 100644 tests/Server/Psr7/PsrStreamStub.php diff --git a/composer.json b/composer.json index 54d22f9a9..b5761829f 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,7 @@ "require-dev": { "amphp/amp": "^2.3", "doctrine/coding-standard": "^6.0", + "nyholm/psr7": "^1.2", "phpbench/phpbench": "^0.14", "phpstan/extension-installer": "^1.0", "phpstan/phpstan": "0.12.18", diff --git a/docs/executing-queries.md b/docs/executing-queries.md index d3e8821d7..3c6403d43 100644 --- a/docs/executing-queries.md +++ b/docs/executing-queries.md @@ -66,11 +66,11 @@ Server also supports [PSR-7 request/response interfaces](http://www.php-fig.org/ getMethod() === 'GET') { $bodyParams = []; @@ -533,13 +535,13 @@ public function parsePsrRequest(ServerRequestInterface $request) } if (stripos($contentType[0], 'application/graphql') !== false) { - $bodyParams = ['query' => $request->getBody()->getContents()]; + $bodyParams = ['query' => (string) $request->getBody()]; } elseif (stripos($contentType[0], 'application/json') !== false) { - $bodyParams = $request->getParsedBody(); + $bodyParams = json_decode((string) $request->getBody(), true); if ($bodyParams === null) { throw new InvariantViolation( - 'PSR-7 request is expected to provide parsed body for "application/json" requests but got null' + 'Did not receive valid JSON array in PSR-7 request body with Content-Type "application/json"' ); } @@ -550,7 +552,7 @@ public function parsePsrRequest(ServerRequestInterface $request) ); } } else { - $bodyParams = $request->getParsedBody(); + parse_str((string) $request->getBody(), $bodyParams); if (! is_array($bodyParams)) { throw new RequestError('Unexpected content type: ' . Utils::printSafeJson($contentType[0])); @@ -558,10 +560,12 @@ public function parsePsrRequest(ServerRequestInterface $request) } } + parse_str(html_entity_decode($request->getUri()->getQuery()), $queryParams); + return $this->parseRequestParams( $request->getMethod(), $bodyParams, - $request->getQueryParams() + $queryParams ); } diff --git a/src/Server/StandardServer.php b/src/Server/StandardServer.php index 7b7e627b2..b3f21fa9d 100644 --- a/src/Server/StandardServer.php +++ b/src/Server/StandardServer.php @@ -9,8 +9,8 @@ use GraphQL\Executor\ExecutionResult; use GraphQL\Executor\Promise\Promise; use GraphQL\Utils\Utils; +use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\StreamInterface; use Throwable; use function is_array; @@ -146,7 +146,7 @@ public function executeRequest($parsedBody = null) * @api */ public function processPsrRequest( - ServerRequestInterface $request, + RequestInterface $request, ResponseInterface $response, StreamInterface $writableBodyStream ) { @@ -163,7 +163,7 @@ public function processPsrRequest( * * @api */ - public function executePsrRequest(ServerRequestInterface $request) + public function executePsrRequest(RequestInterface $request) { $parsedBody = $this->helper->parsePsrRequest($request); diff --git a/tests/Server/Psr7/PsrRequestStub.php b/tests/Server/Psr7/PsrRequestStub.php deleted file mode 100644 index 23892c04b..000000000 --- a/tests/Server/Psr7/PsrRequestStub.php +++ /dev/null @@ -1,610 +0,0 @@ -getHeaders() as $name => $values) { - * echo $name . ": " . implode(", ", $values); - * } - * - * // Emit headers iteratively: - * foreach ($message->getHeaders() as $name => $values) { - * foreach ($values as $value) { - * header(sprintf('%s: %s', $name, $value), false); - * } - * } - * - * While header names are not case-sensitive, getHeaders() will preserve the - * exact case in which headers were originally specified. - * - * @return string[][] Returns an associative array of the message's headers. Each - * key MUST be a header name, and each value MUST be an array of strings - * for that header. - */ - public function getHeaders() - { - throw new \Exception('Not implemented'); - } - - /** - * Checks if a header exists by the given case-insensitive name. - * - * @param string $name Case-insensitive header field name. - * @return bool Returns true if any header names match the given header - * name using a case-insensitive string comparison. Returns false if - * no matching header name is found in the message. - */ - public function hasHeader($name) - { - throw new \Exception('Not implemented'); - } - - /** - * Retrieves a message header value by the given case-insensitive name. - * - * This method returns an array of all the header values of the given - * case-insensitive header name. - * - * If the header does not appear in the message, this method MUST return an - * empty array. - * - * @param string $name Case-insensitive header field name. - * @return string[] An array of string values as provided for the given - * header. If the header does not appear in the message, this method MUST - * return an empty array. - */ - public function getHeader($name) - { - $name = strtolower($name); - - return $this->headers[$name] ?? []; - } - - /** - * Retrieves a comma-separated string of the values for a single header. - * - * This method returns all of the header values of the given - * case-insensitive header name as a string concatenated together using - * a comma. - * - * NOTE: Not all header values may be appropriately represented using - * comma concatenation. For such headers, use getHeader() instead - * and supply your own delimiter when concatenating. - * - * If the header does not appear in the message, this method MUST return - * an empty string. - * - * @param string $name Case-insensitive header field name. - * @return string A string of values as provided for the given header - * concatenated together using a comma. If the header does not appear in - * the message, this method MUST return an empty string. - */ - public function getHeaderLine($name) - { - throw new \Exception('Not implemented'); - } - - /** - * Return an instance with the provided value replacing the specified header. - * - * While header names are case-insensitive, the casing of the header will - * be preserved by this function, and returned from getHeaders(). - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * new and/or updated header and value. - * - * @param string $name Case-insensitive header field name. - * @param string|string[] $value Header value(s). - * @return static - * @throws \InvalidArgumentException for invalid header names or values. - */ - public function withHeader($name, $value) - { - throw new \Exception('Not implemented'); - } - - /** - * Return an instance with the specified header appended with the given value. - * - * Existing values for the specified header will be maintained. The new - * value(s) will be appended to the existing list. If the header did not - * exist previously, it will be added. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * new header and/or value. - * - * @param string $name Case-insensitive header field name to add. - * @param string|string[] $value Header value(s). - * @return static - * @throws \InvalidArgumentException for invalid header names or values. - */ - public function withAddedHeader($name, $value) - { - throw new \Exception('Not implemented'); - } - - /** - * Return an instance without the specified header. - * - * Header resolution MUST be done without case-sensitivity. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that removes - * the named header. - * - * @param string $name Case-insensitive header field name to remove. - * @return static - */ - public function withoutHeader($name) - { - throw new \Exception('Not implemented'); - } - - /** - * Gets the body of the message. - * - * @return StreamInterface Returns the body as a stream. - */ - public function getBody() - { - return $this->body; - } - - /** - * Return an instance with the specified message body. - * - * The body MUST be a StreamInterface object. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return a new instance that has the - * new body stream. - * - * @param StreamInterface $body Body. - * @return static - * @throws \InvalidArgumentException When the body is not valid. - */ - public function withBody(StreamInterface $body) - { - throw new \Exception('Not implemented'); - } - - /** - * Retrieves the message's request target. - * - * Retrieves the message's request-target either as it will appear (for - * clients), as it appeared at request (for servers), or as it was - * specified for the instance (see withRequestTarget()). - * - * In most cases, this will be the origin-form of the composed URI, - * unless a value was provided to the concrete implementation (see - * withRequestTarget() below). - * - * If no URI is available, and no request-target has been specifically - * provided, this method MUST return the string "/". - * - * @return string - */ - public function getRequestTarget() - { - throw new \Exception('Not implemented'); - } - - /** - * Return an instance with the specific request-target. - * - * If the request needs a non-origin-form request-target — e.g., for - * specifying an absolute-form, authority-form, or asterisk-form — - * this method may be used to create an instance with the specified - * request-target, verbatim. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * changed request target. - * - * @link http://tools.ietf.org/html/rfc7230#section-5.3 (for the various - * request-target forms allowed in request messages) - * @param mixed $requestTarget - * @return static - */ - public function withRequestTarget($requestTarget) - { - throw new \Exception('Not implemented'); - } - - /** - * Retrieves the HTTP method of the request. - * - * @return string Returns the request method. - */ - public function getMethod() - { - return $this->method; - } - - /** - * Return an instance with the provided HTTP method. - * - * While HTTP method names are typically all uppercase characters, HTTP - * method names are case-sensitive and thus implementations SHOULD NOT - * modify the given string. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * changed request method. - * - * @param string $method Case-sensitive method. - * @return static - * @throws \InvalidArgumentException for invalid HTTP methods. - */ - public function withMethod($method) - { - throw new \Exception('Not implemented'); - } - - /** - * Retrieves the URI instance. - * - * This method MUST return a UriInterface instance. - * - * @link http://tools.ietf.org/html/rfc3986#section-4.3 - * @return UriInterface Returns a UriInterface instance - * representing the URI of the request. - */ - public function getUri() - { - throw new \Exception('Not implemented'); - } - - /** - * Returns an instance with the provided URI. - * - * This method MUST update the Host header of the returned request by - * default if the URI contains a host component. If the URI does not - * contain a host component, any pre-existing Host header MUST be carried - * over to the returned request. - * - * You can opt-in to preserving the original state of the Host header by - * setting `$preserveHost` to `true`. When `$preserveHost` is set to - * `true`, this method interacts with the Host header in the following ways: - * - * - If the Host header is missing or empty, and the new URI contains - * a host component, this method MUST update the Host header in the returned - * request. - * - If the Host header is missing or empty, and the new URI does not contain a - * host component, this method MUST NOT update the Host header in the returned - * request. - * - If a Host header is present and non-empty, this method MUST NOT update - * the Host header in the returned request. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * new UriInterface instance. - * - * @link http://tools.ietf.org/html/rfc3986#section-4.3 - * @param UriInterface $uri New request URI to use. - * @param bool $preserveHost Preserve the original state of the Host header. - * @return static - */ - public function withUri(UriInterface $uri, $preserveHost = false) - { - throw new \Exception('Not implemented'); - } - - /** - * Retrieve server parameters. - * - * Retrieves data related to the incoming request environment, - * typically derived from PHP's $_SERVER superglobal. The data IS NOT - * REQUIRED to originate from $_SERVER. - * - * @return array - */ - public function getServerParams() - { - throw new \Exception('Not implemented'); - } - - /** - * Retrieve cookies. - * - * Retrieves cookies sent by the client to the server. - * - * The data MUST be compatible with the structure of the $_COOKIE - * superglobal. - * - * @return array - */ - public function getCookieParams() - { - throw new \Exception('Not implemented'); - } - - /** - * Return an instance with the specified cookies. - * - * The data IS NOT REQUIRED to come from the $_COOKIE superglobal, but MUST - * be compatible with the structure of $_COOKIE. Typically, this data will - * be injected at instantiation. - * - * This method MUST NOT update the related Cookie header of the request - * instance, nor related values in the server params. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * updated cookie values. - * - * @param array $cookies Array of key/value pairs representing cookies. - * @return static - */ - public function withCookieParams(array $cookies) - { - throw new \Exception('Not implemented'); - } - - /** - * Retrieve query string arguments. - * - * Retrieves the deserialized query string arguments, if any. - * - * Note: the query params might not be in sync with the URI or server - * params. If you need to ensure you are only getting the original - * values, you may need to parse the query string from `getUri()->getQuery()` - * or from the `QUERY_STRING` server param. - * - * @return array - */ - public function getQueryParams() - { - return $this->queryParams; - } - - /** - * Return an instance with the specified query string arguments. - * - * These values SHOULD remain immutable over the course of the incoming - * request. They MAY be injected during instantiation, such as from PHP's - * $_GET superglobal, or MAY be derived from some other value such as the - * URI. In cases where the arguments are parsed from the URI, the data - * MUST be compatible with what PHP's parse_str() would return for - * purposes of how duplicate query parameters are handled, and how nested - * sets are handled. - * - * Setting query string arguments MUST NOT change the URI stored by the - * request, nor the values in the server params. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * updated query string arguments. - * - * @param array $query Array of query string arguments, typically from - * $_GET. - * @return static - */ - public function withQueryParams(array $query) - { - throw new \Exception('Not implemented'); - } - - /** - * Retrieve normalized file upload data. - * - * This method returns upload metadata in a normalized tree, with each leaf - * an instance of Psr\Http\Message\UploadedFileInterface. - * - * These values MAY be prepared from $_FILES or the message body during - * instantiation, or MAY be injected via withUploadedFiles(). - * - * @return array An array tree of UploadedFileInterface instances; an empty - * array MUST be returned if no data is present. - */ - public function getUploadedFiles() - { - throw new \Exception('Not implemented'); - } - - /** - * Create a new instance with the specified uploaded files. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * updated body parameters. - * - * @param array $uploadedFiles An array tree of UploadedFileInterface instances. - * @return static - * @throws \InvalidArgumentException if an invalid structure is provided. - */ - public function withUploadedFiles(array $uploadedFiles) - { - throw new \Exception('Not implemented'); - } - - /** - * Retrieve any parameters provided in the request body. - * - * If the request Content-Type is either application/x-www-form-urlencoded - * or multipart/form-data, and the request method is POST, this method MUST - * return the contents of $_POST. - * - * Otherwise, this method may return any results of deserializing - * the request body content; as parsing returns structured content, the - * potential types MUST be arrays or objects only. A null value indicates - * the absence of body content. - * - * @return null|array|object The deserialized body parameters, if any. - * These will typically be an array or object. - */ - public function getParsedBody() - { - return $this->parsedBody; - } - - /** - * Return an instance with the specified body parameters. - * - * These MAY be injected during instantiation. - * - * If the request Content-Type is either application/x-www-form-urlencoded - * or multipart/form-data, and the request method is POST, use this method - * ONLY to inject the contents of $_POST. - * - * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of - * deserializing the request body content. Deserialization/parsing returns - * structured data, and, as such, this method ONLY accepts arrays or objects, - * or a null value if nothing was available to parse. - * - * As an example, if content negotiation determines that the request data - * is a JSON payload, this method could be used to create a request - * instance with the deserialized parameters. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * updated body parameters. - * - * @param null|array|object $data The deserialized body data. This will - * typically be in an array or object. - * @return static - * @throws \InvalidArgumentException if an unsupported argument type is - * provided. - */ - public function withParsedBody($data) - { - throw new \Exception('Not implemented'); - } - - /** - * Retrieve attributes derived from the request. - * - * The request "attributes" may be used to allow injection of any - * parameters derived from the request: e.g., the results of path - * match operations; the results of decrypting cookies; the results of - * deserializing non-form-encoded message bodies; etc. Attributes - * will be application and request specific, and CAN be mutable. - * - * @return array Attributes derived from the request. - */ - public function getAttributes() - { - throw new \Exception('Not implemented'); - } - - /** - * Retrieve a single derived request attribute. - * - * Retrieves a single derived request attribute as described in - * getAttributes(). If the attribute has not been previously set, returns - * the default value as provided. - * - * This method obviates the need for a hasAttribute() method, as it allows - * specifying a default value to return if the attribute is not found. - * - * @see getAttributes() - * @param string $name The attribute name. - * @param mixed $default Default value to return if the attribute does not exist. - * @return mixed - */ - public function getAttribute($name, $default = null) - { - throw new \Exception('Not implemented'); - } - - /** - * Return an instance with the specified derived request attribute. - * - * This method allows setting a single derived request attribute as - * described in getAttributes(). - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * updated attribute. - * - * @see getAttributes() - * @param string $name The attribute name. - * @param mixed $value The value of the attribute. - * @return static - */ - public function withAttribute($name, $value) - { - throw new \Exception('Not implemented'); - } - - /** - * Return an instance that removes the specified derived request attribute. - * - * This method allows removing a single derived request attribute as - * described in getAttributes(). - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that removes - * the attribute. - * - * @see getAttributes() - * @param string $name The attribute name. - * @return static - */ - public function withoutAttribute($name) - { - throw new \Exception('Not implemented'); - } -} diff --git a/tests/Server/Psr7/PsrResponseStub.php b/tests/Server/Psr7/PsrResponseStub.php deleted file mode 100644 index bac2ffb23..000000000 --- a/tests/Server/Psr7/PsrResponseStub.php +++ /dev/null @@ -1,287 +0,0 @@ -getHeaders() as $name => $values) { - * echo $name . ": " . implode(", ", $values); - * } - * - * // Emit headers iteratively: - * foreach ($message->getHeaders() as $name => $values) { - * foreach ($values as $value) { - * header(sprintf('%s: %s', $name, $value), false); - * } - * } - * - * While header names are not case-sensitive, getHeaders() will preserve the - * exact case in which headers were originally specified. - * - * @return string[][] Returns an associative array of the message's headers. Each - * key MUST be a header name, and each value MUST be an array of strings - * for that header. - */ - public function getHeaders() - { - throw new \Exception('Not implemented'); - } - - /** - * Checks if a header exists by the given case-insensitive name. - * - * @param string $name Case-insensitive header field name. - * @return bool Returns true if any header names match the given header - * name using a case-insensitive string comparison. Returns false if - * no matching header name is found in the message. - */ - public function hasHeader($name) - { - throw new \Exception('Not implemented'); - } - - /** - * Retrieves a message header value by the given case-insensitive name. - * - * This method returns an array of all the header values of the given - * case-insensitive header name. - * - * If the header does not appear in the message, this method MUST return an - * empty array. - * - * @param string $name Case-insensitive header field name. - * @return string[] An array of string values as provided for the given - * header. If the header does not appear in the message, this method MUST - * return an empty array. - */ - public function getHeader($name) - { - throw new \Exception('Not implemented'); - } - - /** - * Retrieves a comma-separated string of the values for a single header. - * - * This method returns all of the header values of the given - * case-insensitive header name as a string concatenated together using - * a comma. - * - * NOTE: Not all header values may be appropriately represented using - * comma concatenation. For such headers, use getHeader() instead - * and supply your own delimiter when concatenating. - * - * If the header does not appear in the message, this method MUST return - * an empty string. - * - * @param string $name Case-insensitive header field name. - * @return string A string of values as provided for the given header - * concatenated together using a comma. If the header does not appear in - * the message, this method MUST return an empty string. - */ - public function getHeaderLine($name) - { - throw new \Exception('Not implemented'); - } - - /** - * Return an instance with the provided value replacing the specified header. - * - * While header names are case-insensitive, the casing of the header will - * be preserved by this function, and returned from getHeaders(). - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * new and/or updated header and value. - * - * @param string $name Case-insensitive header field name. - * @param string|string[] $value Header value(s). - * @return static - * @throws \InvalidArgumentException for invalid header names or values. - */ - public function withHeader($name, $value) - { - $tmp = clone $this; - $tmp->headers[$name][] = $value; - - return $tmp; - } - - /** - * Return an instance with the specified header appended with the given value. - * - * Existing values for the specified header will be maintained. The new - * value(s) will be appended to the existing list. If the header did not - * exist previously, it will be added. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * new header and/or value. - * - * @param string $name Case-insensitive header field name to add. - * @param string|string[] $value Header value(s). - * @return static - * @throws \InvalidArgumentException for invalid header names or values. - */ - public function withAddedHeader($name, $value) - { - throw new \Exception('Not implemented'); - } - - /** - * Return an instance without the specified header. - * - * Header resolution MUST be done without case-sensitivity. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that removes - * the named header. - * - * @param string $name Case-insensitive header field name to remove. - * @return static - */ - public function withoutHeader($name) - { - throw new \Exception('Not implemented'); - } - - /** - * Gets the body of the message. - * - * @return StreamInterface Returns the body as a stream. - */ - public function getBody() - { - throw new \Exception('Not implemented'); - } - - /** - * Return an instance with the specified message body. - * - * The body MUST be a StreamInterface object. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return a new instance that has the - * new body stream. - * - * @param StreamInterface $body Body. - * @return static - * @throws \InvalidArgumentException When the body is not valid. - */ - public function withBody(StreamInterface $body) - { - $tmp = clone $this; - $tmp->body = $body; - - return $tmp; - } - - /** - * Gets the response status code. - * - * The status code is a 3-digit integer result code of the server's attempt - * to understand and satisfy the request. - * - * @return int Status code. - */ - public function getStatusCode() - { - throw new \Exception('Not implemented'); - } - - /** - * Return an instance with the specified status code and, optionally, reason phrase. - * - * If no reason phrase is specified, implementations MAY choose to default - * to the RFC 7231 or IANA recommended reason phrase for the response's - * status code. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * updated status and reason phrase. - * - * @link http://tools.ietf.org/html/rfc7231#section-6 - * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml - * @param int $code The 3-digit integer result code to set. - * @param string $reasonPhrase The reason phrase to use with the - * provided status code; if none is provided, implementations MAY - * use the defaults as suggested in the HTTP specification. - * @return static - * @throws \InvalidArgumentException For invalid status code arguments. - */ - public function withStatus($code, $reasonPhrase = '') - { - $tmp = clone $this; - $tmp->statusCode = $code; - - return $tmp; - } - - /** - * Gets the response reason phrase associated with the status code. - * - * Because a reason phrase is not a required element in a response - * status line, the reason phrase value MAY be null. Implementations MAY - * choose to return the default RFC 7231 recommended reason phrase (or those - * listed in the IANA HTTP Status Code Registry) for the response's - * status code. - * - * @link http://tools.ietf.org/html/rfc7231#section-6 - * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml - * @return string Reason phrase; must return an empty string if none present. - */ - public function getReasonPhrase() - { - throw new \Exception('Not implemented'); - } -} diff --git a/tests/Server/Psr7/PsrStreamStub.php b/tests/Server/Psr7/PsrStreamStub.php deleted file mode 100644 index 1865dfedd..000000000 --- a/tests/Server/Psr7/PsrStreamStub.php +++ /dev/null @@ -1,208 +0,0 @@ -content; - } - - /** - * Closes the stream and any underlying resources. - * - * @return void - */ - public function close() - { - throw new \Exception('Not implemented'); - } - - /** - * Separates any underlying resources from the stream. - * - * After the stream has been detached, the stream is in an unusable state. - * - * @return resource|null Underlying PHP stream, if any - */ - public function detach() - { - throw new \Exception('Not implemented'); - } - - /** - * Get the size of the stream if known. - * - */ - public function getSize() : ?int - { - return $this->content === null ? null : strlen($this->content); - } - - /** - * Returns the current position of the file read/write pointer - * - * @return int Position of the file pointer - * @throws \RuntimeException on error. - */ - public function tell() - { - throw new \Exception('Not implemented'); - } - - /** - * Returns true if the stream is at the end of the stream. - * - * @return bool - */ - public function eof() - { - throw new \Exception('Not implemented'); - } - - /** - * Returns whether or not the stream is seekable. - * - * @return bool - */ - public function isSeekable() - { - throw new \Exception('Not implemented'); - } - - /** - * Seek to a position in the stream. - * - * @link http://www.php.net/manual/en/function.fseek.php - * @param int $offset Stream offset - * @param int $whence Specifies how the cursor position will be calculated - * based on the seek offset. Valid values are identical to the built-in - * PHP $whence values for `fseek()`. SEEK_SET: Set position equal to - * offset bytes SEEK_CUR: Set position to current location plus offset - * SEEK_END: Set position to end-of-stream plus offset. - * @throws \RuntimeException on failure. - */ - public function seek($offset, $whence = SEEK_SET) - { - throw new \Exception('Not implemented'); - } - - /** - * Seek to the beginning of the stream. - * - * If the stream is not seekable, this method will raise an exception; - * otherwise, it will perform a seek(0). - * - * @see seek() - * @link http://www.php.net/manual/en/function.fseek.php - * @throws \RuntimeException on failure. - */ - public function rewind() - { - throw new \Exception('Not implemented'); - } - - /** - * Returns whether or not the stream is writable. - * - * @return bool - */ - public function isWritable() - { - return true; - } - - /** - * Write data to the stream. - * - * @param string $string The string that is to be written. - * @return int Returns the number of bytes written to the stream. - * @throws \RuntimeException on failure. - */ - public function write($string) - { - $this->content = $string; - - return strlen($string); - } - - /** - * Returns whether or not the stream is readable. - * - * @return bool - */ - public function isReadable() - { - throw new \Exception('Not implemented'); - } - - /** - * Read data from the stream. - * - * @param int $length Read up to $length bytes from the object and return - * them. Fewer than $length bytes may be returned if underlying stream - * call returns fewer bytes. - * @return string Returns the data read from the stream, or an empty string - * if no bytes are available. - * @throws \RuntimeException if an error occurs. - */ - public function read($length) - { - throw new \Exception('Not implemented'); - } - - /** - * Returns the remaining contents in a string - * - * @return string - * @throws \RuntimeException if unable to read or an error occurs while - * reading. - */ - public function getContents() : string - { - return $this->content; - } - - /** - * Get stream metadata as an associative array or retrieve a specific key. - * - * The keys returned are identical to the keys returned from PHP's - * stream_get_meta_data() function. - * - * @link http://php.net/manual/en/function.stream-get-meta-data.php - * @param string $key Specific metadata to retrieve. - * @return array|mixed|null Returns an associative array if no key is - * provided. Returns a specific key value if a key is provided and the - * value is found, or null if the key is not found. - */ - public function getMetadata($key = null) - { - throw new \Exception('Not implemented'); - } -} diff --git a/tests/Server/PsrResponseTest.php b/tests/Server/PsrResponseTest.php index 5a2eab525..6edbf3014 100644 --- a/tests/Server/PsrResponseTest.php +++ b/tests/Server/PsrResponseTest.php @@ -6,24 +6,23 @@ use GraphQL\Executor\ExecutionResult; use GraphQL\Server\Helper; -use GraphQL\Tests\Server\Psr7\PsrResponseStub; -use GraphQL\Tests\Server\Psr7\PsrStreamStub; +use Nyholm\Psr7\Response; +use Nyholm\Psr7\Stream; use PHPUnit\Framework\TestCase; use function json_encode; -class PsrResponseTest extends TestCase +final class PsrResponseTest extends TestCase { public function testConvertsResultToPsrResponse() : void { $result = new ExecutionResult(['key' => 'value']); - $stream = new PsrStreamStub(); - $psrResponse = new PsrResponseStub(); + $stream = Stream::create(); + $psrResponse = new Response(); $helper = new Helper(); - /** @var PsrResponseStub $resp */ $resp = $helper->toPsrResponse($result, $psrResponse, $stream); - self::assertSame(json_encode($result), $resp->body->content); - self::assertSame(['Content-Type' => ['application/json']], $resp->headers); + self::assertSame(json_encode($result), (string) $resp->getBody()); + self::assertSame(['Content-Type' => ['application/json']], $resp->getHeaders()); } } diff --git a/tests/Server/RequestParsingTest.php b/tests/Server/RequestParsingTest.php index a926b1263..16df93aca 100644 --- a/tests/Server/RequestParsingTest.php +++ b/tests/Server/RequestParsingTest.php @@ -8,10 +8,12 @@ use GraphQL\Server\Helper; use GraphQL\Server\OperationParams; use GraphQL\Server\RequestError; -use GraphQL\Tests\Server\Psr7\PsrRequestStub; -use GraphQL\Tests\Server\Psr7\PsrStreamStub; +use InvalidArgumentException; +use Nyholm\Psr7\Request; +use Nyholm\Psr7\Stream; +use Nyholm\Psr7\Uri; use PHPUnit\Framework\TestCase; -use function json_decode; +use function http_build_query; use function json_encode; class RequestParsingTest extends TestCase @@ -56,22 +58,12 @@ private function parseRawRequest($contentType, $content, string $method = 'POST' */ private function parsePsrRequest($contentType, $content, string $method = 'POST') { - $psrRequestBody = new PsrStreamStub(); - $psrRequestBody->content = $content; - - $psrRequest = new PsrRequestStub(); - $psrRequest->headers['content-type'] = [$contentType]; - $psrRequest->method = $method; - $psrRequest->body = $psrRequestBody; - - if ($contentType === 'application/json') { - $parsedBody = json_decode($content, true); - $parsedBody = $parsedBody === false ? null : $parsedBody; - } else { - $parsedBody = null; - } - - $psrRequest->parsedBody = $parsedBody; + $psrRequest = new Request( + $method, + '', + ['Content-Type' => $contentType], + Stream::create($content) + ); $helper = new Helper(); @@ -106,7 +98,7 @@ private static function assertValidOperationParams( public function testParsesUrlencodedRequest() : void { $query = '{my query}'; - $variables = ['test' => 1, 'test2' => 2]; + $variables = ['test' => '1', 'test2' => '2']; $operation = 'op'; $post = [ @@ -150,20 +142,22 @@ private function parseRawFormUrlencodedRequest($postValue) */ private function parsePsrFormUrlEncodedRequest($postValue) { - $psrRequest = new PsrRequestStub(); - $psrRequest->headers['content-type'] = ['application/x-www-form-urlencoded']; - $psrRequest->method = 'POST'; - $psrRequest->parsedBody = $postValue; - $helper = new Helper(); - return $helper->parsePsrRequest($psrRequest); + return $helper->parsePsrRequest( + new Request( + 'POST', + '', + ['Content-Type' => 'application/x-www-form-urlencoded'], + http_build_query($postValue) + ) + ); } public function testParsesGetRequest() : void { $query = '{my query}'; - $variables = ['test' => 1, 'test2' => 2]; + $variables = ['test' => '1', 'test2' => '2']; $operation = 'op'; $get = [ @@ -204,21 +198,19 @@ private function parseRawGetRequest($getValue) * * @return OperationParams[]|OperationParams */ - private function parsePsrGetRequest($getValue) + private function parsePsrGetRequest(array $getValue) { - $psrRequest = new PsrRequestStub(); - $psrRequest->method = 'GET'; - $psrRequest->queryParams = $getValue; - $helper = new Helper(); - return $helper->parsePsrRequest($psrRequest); + return $helper->parsePsrRequest( + new Request('GET', (new Uri())->withQuery(http_build_query($getValue))) + ); } public function testParsesMultipartFormdataRequest() : void { $query = '{my query}'; - $variables = ['test' => 1, 'test2' => 2]; + $variables = ['test' => '1', 'test2' => '2']; $operation = 'op'; $post = [ @@ -262,14 +254,16 @@ private function parseRawMultipartFormDataRequest($postValue) */ private function parsePsrMultipartFormDataRequest($postValue) { - $psrRequest = new PsrRequestStub(); - $psrRequest->headers['content-type'] = ['multipart/form-data; boundary=----FormBoundary']; - $psrRequest->method = 'POST'; - $psrRequest->parsedBody = $postValue; - $helper = new Helper(); - return $helper->parsePsrRequest($psrRequest); + return $helper->parsePsrRequest( + new Request( + 'POST', + '', + ['Content-Type' => 'multipart/form-data; boundary=----FormBoundary'], + http_build_query($postValue) + ) + ); } public function testParsesJSONRequest() : void @@ -415,7 +409,7 @@ public function testFailsParsingInvalidRawJsonRequestPsr() : void $body = 'not really{} a json'; $this->expectException(InvariantViolation::class); - $this->expectExceptionMessage('PSR-7 request is expected to provide parsed body for "application/json" requests but got null'); + $this->expectExceptionMessage('Did not receive valid JSON array in PSR-7 request body with Content-Type "application/json"'); $this->parsePsrRequest('application/json', $body); } @@ -427,7 +421,7 @@ public function testFailsParsingNonPreParsedPsrRequest() : void } catch (InvariantViolation $e) { // Expecting parsing exception to be thrown somewhere else: self::assertEquals( - 'PSR-7 request is expected to provide parsed body for "application/json" requests but got null', + 'Did not receive valid JSON array in PSR-7 request body with Content-Type "application/json"', $e->getMessage() ); } @@ -483,8 +477,7 @@ public function testFailsWithMissingContentTypeRaw() : void public function testFailsWithMissingContentTypePsr() : void { - $this->expectException(RequestError::class); - $this->expectExceptionMessage('Missing "Content-Type" header'); + $this->expectException(InvalidArgumentException::class); $this->parsePsrRequest(null, 'test'); } diff --git a/tests/Server/StandardServerTest.php b/tests/Server/StandardServerTest.php index 516bf687d..57c65a2b4 100644 --- a/tests/Server/StandardServerTest.php +++ b/tests/Server/StandardServerTest.php @@ -9,7 +9,9 @@ use GraphQL\Server\ServerConfig; use GraphQL\Server\StandardServer; use GraphQL\Tests\PHPUnit\ArraySubsetAsserts; -use GraphQL\Tests\Server\Psr7\PsrRequestStub; +use Nyholm\Psr7\Request; +use Nyholm\Psr7\Stream; +use Psr\Http\Message\RequestInterface; use function json_encode; class StandardServerTest extends ServerTestCase @@ -61,18 +63,18 @@ public function testSimplePsrRequestExecution() : void 'data' => ['f1' => 'f1'], ]; - $request = $this->preparePsrRequest('application/json', $body); + $request = $this->preparePsrRequest('application/json', json_encode($body)); $this->assertPsrRequestEquals($expected, $request); } - private function preparePsrRequest($contentType, $parsedBody) + private function preparePsrRequest($contentType, $body) : RequestInterface { - $psrRequest = new PsrRequestStub(); - $psrRequest->headers['content-type'] = [$contentType]; - $psrRequest->method = 'POST'; - $psrRequest->parsedBody = $parsedBody; - - return $psrRequest; + return new Request( + 'POST', + '', + ['Content-Type' => $contentType], + $body + ); } private function assertPsrRequestEquals($expected, $request) @@ -103,7 +105,7 @@ public function testMultipleOperationPsrRequestExecution() : void 'data' => ['f1' => 'f1'], ]; - $request = $this->preparePsrRequest('application/json', $body); + $request = $this->preparePsrRequest('application/json', json_encode($body)); $this->assertPsrRequestEquals($expected, $request); } } From 3887d8f798b85db5ed8bee79e45d5f1928e4104d Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Thu, 4 Jun 2020 11:39:04 +0200 Subject: [PATCH 174/256] Bump php action in order to use composer v1 --- .github/workflows/ci-build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index d47e79063..5c784fa54 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -24,7 +24,7 @@ jobs: - uses: actions/checkout@v2 - name: Install PHP - uses: shivammathur/setup-php@1.7.0 + uses: shivammathur/setup-php@2.2.2 with: php-version: ${{ matrix.php }} coverage: none @@ -58,7 +58,7 @@ jobs: - uses: actions/checkout@v2 - name: Install PHP - uses: shivammathur/setup-php@1.7.0 + uses: shivammathur/setup-php@2.2.2 with: php-version: 7.1 coverage: none @@ -89,7 +89,7 @@ jobs: - uses: actions/checkout@v2 - name: Install PHP - uses: shivammathur/setup-php@1.7.0 + uses: shivammathur/setup-php@2.2.2 with: php-version: 7.1 coverage: none @@ -122,7 +122,7 @@ jobs: ref: ${{ github.ref }} - name: Install PHP - uses: shivammathur/setup-php@1.7.0 + uses: shivammathur/setup-php@2.2.2 with: php-version: 7.2 coverage: pcov From 91b55bb221b0e9c02a4a6eed3da54d23a2e511ba Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Mon, 8 Jun 2020 19:46:32 +0200 Subject: [PATCH 175/256] Support repeatable directives (#643) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Support repeatable directives See https://github.com/graphql/graphql-js/pull/1965 * Fix introspection See https://github.com/graphql/graphql-js/pull/2416 * Fix codestyle * Canonical ordering * Update baseline * Make DirectiveTest.php final Co-Authored-By: Šimon Podlipský * Fix baseline Co-authored-by: Šimon Podlipský --- phpstan-baseline.neon | 5 -- src/Language/AST/DirectiveDefinitionNode.php | 9 +- src/Language/Parser.php | 57 ++++++------ src/Language/Printer.php | 1 + src/Type/Definition/Directive.php | 30 +++++-- src/Type/Introspection.php | 86 +++++++++++-------- src/Utils/ASTDefinitionBuilder.php | 3 +- src/Utils/BuildClientSchema.php | 3 +- src/Utils/SchemaPrinter.php | 70 ++++++++------- .../Rules/UniqueDirectivesPerLocation.php | 35 +++++++- tests/Language/SchemaParserTest.php | 81 +++++++++++++++++ tests/Language/SchemaPrinterTest.php | 2 + tests/Language/schema-kitchen-sink.graphql | 4 + tests/Type/DirectiveTest.php | 15 ++++ tests/Type/IntrospectionTest.php | 70 ++++++++++----- tests/Utils/BuildClientSchemaTest.php | 10 ++- tests/Utils/BuildSchemaTest.php | 2 + tests/Utils/SchemaExtenderTest.php | 4 +- tests/Utils/SchemaPrinterTest.php | 40 +++++++-- .../UniqueDirectivesPerLocationTest.php | 74 ++++++++++------ tests/Validator/ValidatorTestCase.php | 29 +++++-- 21 files changed, 446 insertions(+), 184 deletions(-) create mode 100644 tests/Type/DirectiveTest.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index f57b1c63d..e47bdc0d7 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -265,11 +265,6 @@ parameters: count: 2 path: src/Server/OperationParams.php - - - message: "#^Variable property access on \\$this\\(GraphQL\\\\Type\\\\Definition\\\\Directive\\)\\.$#" - count: 1 - path: src/Type/Definition/Directive.php - - message: "#^Only booleans are allowed in a negated boolean, ArrayObject\\ given\\.$#" count: 1 diff --git a/src/Language/AST/DirectiveDefinitionNode.php b/src/Language/AST/DirectiveDefinitionNode.php index 825c7f789..26aa23e3c 100644 --- a/src/Language/AST/DirectiveDefinitionNode.php +++ b/src/Language/AST/DirectiveDefinitionNode.php @@ -12,12 +12,15 @@ class DirectiveDefinitionNode extends Node implements TypeSystemDefinitionNode /** @var NameNode */ public $name; + /** @var StringValueNode|null */ + public $description; + /** @var ArgumentNode[] */ public $arguments; + /** @var bool */ + public $repeatable; + /** @var NameNode[] */ public $locations; - - /** @var StringValueNode|null */ - public $description; } diff --git a/src/Language/Parser.php b/src/Language/Parser.php index 1050ecd2c..d18a0f93d 100644 --- a/src/Language/Parser.php +++ b/src/Language/Parser.php @@ -327,26 +327,39 @@ private function expect(string $kind) : Token } /** - * If the next token is a keyword with the given value, return that token after - * advancing the parser. Otherwise, do not change the parser state and return - * false. + * If the next token is a keyword with the given value, advance the lexer. + * Otherwise, throw an error. * * @throws SyntaxError */ - private function expectKeyword(string $value) : Token + private function expectKeyword(string $value) : void { $token = $this->lexer->token; + if ($token->kind !== Token::NAME || $token->value !== $value) { + throw new SyntaxError( + $this->lexer->source, + $token->start, + 'Expected "' . $value . '", found ' . $token->getDescription() + ); + } + + $this->lexer->advance(); + } + /** + * If the next token is a given keyword, return "true" after advancing + * the lexer. Otherwise, do not change the parser state and return "false". + */ + private function expectOptionalKeyword(string $value) : bool + { + $token = $this->lexer->token; if ($token->kind === Token::NAME && $token->value === $value) { $this->lexer->advance(); - return $token; + return true; } - throw new SyntaxError( - $this->lexer->source, - $token->start, - 'Expected "' . $value . '", found ' . $token->getDescription() - ); + + return false; } private function unexpected(?Token $atToken = null) : SyntaxError @@ -716,7 +729,8 @@ private function parseFragment() : SelectionNode $start = $this->lexer->token; $this->expect(Token::SPREAD); - if ($this->peek(Token::NAME) && $this->lexer->token->value !== 'on') { + $hasTypeCondition = $this->expectOptionalKeyword('on'); + if (! $hasTypeCondition && $this->peek(Token::NAME)) { return new FragmentSpreadNode([ 'name' => $this->parseFragmentName(), 'directives' => $this->parseDirectives(false), @@ -724,14 +738,8 @@ private function parseFragment() : SelectionNode ]); } - $typeCondition = null; - if ($this->lexer->token->value === 'on') { - $this->lexer->advance(); - $typeCondition = $this->parseNamedType(); - } - return new InlineFragmentNode([ - 'typeCondition' => $typeCondition, + 'typeCondition' => $hasTypeCondition ? $this->parseNamedType() : null, 'directives' => $this->parseDirectives(false), 'selectionSet' => $this->parseSelectionSet(), 'loc' => $this->loc($start), @@ -1172,8 +1180,7 @@ private function parseObjectTypeDefinition() : ObjectTypeDefinitionNode private function parseImplementsInterfaces() : array { $types = []; - if ($this->lexer->token->value === 'implements') { - $this->lexer->advance(); + if ($this->expectOptionalKeyword('implements')) { // Optional leading ampersand $this->skip(Token::AMP); do { @@ -1668,7 +1675,7 @@ private function parseInputObjectTypeExtension() : InputObjectTypeExtensionNode /** * DirectiveDefinition : - * - directive @ Name ArgumentsDefinition? on DirectiveLocations + * - Description? directive @ Name ArgumentsDefinition? `repeatable`? on DirectiveLocations * * @throws SyntaxError */ @@ -1678,17 +1685,19 @@ private function parseDirectiveDefinition() : DirectiveDefinitionNode $description = $this->parseDescription(); $this->expectKeyword('directive'); $this->expect(Token::AT); - $name = $this->parseName(); - $args = $this->parseArgumentsDefinition(); + $name = $this->parseName(); + $args = $this->parseArgumentsDefinition(); + $repeatable = $this->expectOptionalKeyword('repeatable'); $this->expectKeyword('on'); $locations = $this->parseDirectiveLocations(); return new DirectiveDefinitionNode([ 'name' => $name, + 'description' => $description, 'arguments' => $args, + 'repeatable' => $repeatable, 'locations' => $locations, 'loc' => $this->loc($start), - 'description' => $description, ]); } diff --git a/src/Language/Printer.php b/src/Language/Printer.php index 7aa13b748..8aa27d235 100644 --- a/src/Language/Printer.php +++ b/src/Language/Printer.php @@ -446,6 +446,7 @@ function (InterfaceTypeDefinitionNode $def) { . ($noIndent ? $this->wrap('(', $this->join($def->arguments, ', '), ')') : $this->wrap("(\n", $this->indent($this->join($def->arguments, "\n")), "\n")) + . ($def->repeatable ? ' repeatable' : '') . ' on ' . $this->join($def->locations, ' | '); }), ], diff --git a/src/Type/Definition/Directive.php b/src/Type/Definition/Directive.php index 75bc03407..c27025c50 100644 --- a/src/Type/Definition/Directive.php +++ b/src/Type/Definition/Directive.php @@ -4,9 +4,9 @@ namespace GraphQL\Type\Definition; +use GraphQL\Error\InvariantViolation; use GraphQL\Language\AST\DirectiveDefinitionNode; use GraphQL\Language\DirectiveLocation; -use GraphQL\Utils\Utils; use function array_key_exists; use function is_array; @@ -31,12 +31,15 @@ class Directive /** @var string|null */ public $description; - /** @var string[] */ - public $locations; - /** @var FieldArgument[] */ public $args = []; + /** @var bool */ + public $isRepeatable; + + /** @var string[] */ + public $locations; + /** @var DirectiveDefinitionNode|null */ public $astNode; @@ -48,6 +51,13 @@ class Directive */ public function __construct(array $config) { + if (! isset($config['name'])) { + throw new InvariantViolation('Directive must be named.'); + } + $this->name = $config['name']; + + $this->description = $config['description'] ?? null; + if (isset($config['args'])) { $args = []; foreach ($config['args'] as $name => $arg) { @@ -58,14 +68,16 @@ public function __construct(array $config) } } $this->args = $args; - unset($config['args']); } - foreach ($config as $key => $value) { - $this->{$key} = $value; + + if (! isset($config['locations']) || ! is_array($config['locations'])) { + throw new InvariantViolation('Must provide locations for directive.'); } + $this->locations = $config['locations']; + + $this->isRepeatable = $config['isRepeatable'] ?? false; + $this->astNode = $config['astNode'] ?? null; - Utils::invariant($this->name, 'Directive must be named.'); - Utils::invariant(is_array($this->locations), 'Must provide locations for directive.'); $this->config = $config; } diff --git a/src/Type/Introspection.php b/src/Type/Introspection.php index 0178a13ec..7f0b0f582 100644 --- a/src/Type/Introspection.php +++ b/src/Type/Introspection.php @@ -27,6 +27,7 @@ use GraphQL\Utils\Utils; use function array_filter; use function array_key_exists; +use function array_merge; use function array_values; use function is_bool; use function method_exists; @@ -43,28 +44,28 @@ class Introspection private static $map = []; /** - * Options: - * - descriptions - * Whether to include descriptions in the introspection result. - * Default: true - * - * @param bool[]|bool $options + * @param array $options + * Available options: + * - descriptions + * Whether to include descriptions in the introspection result. + * Default: true + * - directiveIsRepeatable + * Whether to include `isRepeatable` flag on directives. + * Default: false * * @return string + * + * @api */ - public static function getIntrospectionQuery($options = []) + public static function getIntrospectionQuery(array $options = []) { - if (is_bool($options)) { - trigger_error( - 'Calling Introspection::getIntrospectionQuery(boolean) is deprecated. ' . - 'Please use Introspection::getIntrospectionQuery(["descriptions" => boolean]).', - E_USER_DEPRECATED - ); - $descriptions = $options; - } else { - $descriptions = ! array_key_exists('descriptions', $options) || $options['descriptions'] === true; - } - $descriptionField = $descriptions ? 'description' : ''; + $optionsWithDefaults = array_merge([ + 'descriptions' => true, + 'directiveIsRepeatable' => false, + ], $options); + + $descriptions = $optionsWithDefaults['descriptions'] ? 'description' : ''; + $directiveIsRepeatable = $optionsWithDefaults['directiveIsRepeatable'] ? 'isRepeatable' : ''; return << $options + * Available options: + * - descriptions + * Whether to include `isRepeatable` flag on directives. + * Default: true + * - directiveIsRepeatable + * Whether to include descriptions in the introspection result. + * Default: true * * @return array>|null + * + * @api */ public static function fromSchema(Schema $schema, array $options = []) : ?array { + $optionsWithDefaults = array_merge(['directiveIsRepeatable' => true], $options); + $result = GraphQL::executeQuery( $schema, - self::getIntrospectionQuery($options) + self::getIntrospectionQuery($optionsWithDefaults) ); return $result->data; @@ -651,6 +659,18 @@ public static function _directive() return $obj->description; }, ], + 'args' => [ + 'type' => Type::nonNull(Type::listOf(Type::nonNull(self::_inputValue()))), + 'resolve' => static function (Directive $directive) { + return $directive->args ?? []; + }, + ], + 'isRepeatable' => [ + 'type' => Type::nonNull(Type::boolean()), + 'resolve' => static function (Directive $directive) : bool { + return $directive->isRepeatable; + }, + ], 'locations' => [ 'type' => Type::nonNull(Type::listOf(Type::nonNull( self::_directiveLocation() @@ -659,12 +679,6 @@ public static function _directive() return $obj->locations; }, ], - 'args' => [ - 'type' => Type::nonNull(Type::listOf(Type::nonNull(self::_inputValue()))), - 'resolve' => static function (Directive $directive) { - return $directive->args ?? []; - }, - ], ], ]); } diff --git a/src/Utils/ASTDefinitionBuilder.php b/src/Utils/ASTDefinitionBuilder.php index c032442ed..05a3b5ad5 100644 --- a/src/Utils/ASTDefinitionBuilder.php +++ b/src/Utils/ASTDefinitionBuilder.php @@ -79,13 +79,14 @@ public function buildDirective(DirectiveDefinitionNode $directiveNode) return new Directive([ 'name' => $directiveNode->name->value, 'description' => $this->getDescription($directiveNode), + 'args' => $directiveNode->arguments ? FieldArgument::createMap($this->makeInputValues($directiveNode->arguments)) : null, + 'isRepeatable' => $directiveNode->repeatable, 'locations' => Utils::map( $directiveNode->locations, static function ($node) { return $node->value; } ), - 'args' => $directiveNode->arguments ? FieldArgument::createMap($this->makeInputValues($directiveNode->arguments)) : null, 'astNode' => $directiveNode, ]); } diff --git a/src/Utils/BuildClientSchema.php b/src/Utils/BuildClientSchema.php index 57cef3284..a013c1ac1 100644 --- a/src/Utils/BuildClientSchema.php +++ b/src/Utils/BuildClientSchema.php @@ -472,8 +472,9 @@ public function buildDirective(array $directive) : Directive return new Directive([ 'name' => $directive['name'], 'description' => $directive['description'], - 'locations' => $directive['locations'], 'args' => $this->buildInputValueDefMap($directive['args']), + 'isRepeatable' => $directive['isRepeatable'], + 'locations' => $directive['locations'], ]); } } diff --git a/src/Utils/SchemaPrinter.php b/src/Utils/SchemaPrinter.php index 795c4cc2d..cc68d26c8 100644 --- a/src/Utils/SchemaPrinter.php +++ b/src/Utils/SchemaPrinter.php @@ -38,13 +38,11 @@ class SchemaPrinter { /** - * Accepts options as a second argument: - * + * @param array $options + * Available options: * - commentDescriptions: * Provide true to use preceding comments as the description. * - * @param bool[] $options - * * @api */ public static function doPrint(Schema $schema, array $options = []) : string @@ -62,16 +60,11 @@ static function ($type) : bool { } /** - * @param bool[] $options + * @param array $options */ - private static function printFilteredSchema(Schema $schema, $directiveFilter, $typeFilter, $options) : string + private static function printFilteredSchema(Schema $schema, callable $directiveFilter, callable $typeFilter, array $options) : string { - $directives = array_filter( - $schema->getDirectives(), - static function ($directive) use ($directiveFilter) { - return $directiveFilter($directive); - } - ); + $directives = array_filter($schema->getDirectives(), $directiveFilter); $types = $schema->getTypeMap(); ksort($types); @@ -85,7 +78,7 @@ static function ($directive) use ($directiveFilter) { array_merge( [self::printSchemaDefinition($schema)], array_map( - static function ($directive) use ($options) : string { + static function (Directive $directive) use ($options) : string { return self::printDirective($directive, $options); }, $directives @@ -157,14 +150,22 @@ private static function isSchemaOfCommonNames(Schema $schema) : bool return $subscriptionType === null || $subscriptionType->name === 'Subscription'; } - private static function printDirective($directive, $options) : string + /** + * @param array $options + */ + private static function printDirective(Directive $directive, array $options) : string { - return self::printDescription($options, $directive) . - 'directive @' . $directive->name . self::printArgs($options, $directive->args) . - ' on ' . implode(' | ', $directive->locations); + return self::printDescription($options, $directive) + . 'directive @' . $directive->name + . self::printArgs($options, $directive->args) + . ($directive->isRepeatable ? ' repeatable' : '') + . ' on ' . implode(' | ', $directive->locations); } - private static function printDescription($options, $def, $indentation = '', $firstInBlock = true) : string + /** + * @param array $options + */ + private static function printDescription(array $options, $def, $indentation = '', $firstInBlock = true) : string { if (! $def->description) { return ''; @@ -264,7 +265,10 @@ private static function escapeQuote($line) : string return str_replace('"""', '\\"""', $line); } - private static function printArgs($options, $args, $indentation = '') : string + /** + * @param array $options + */ + private static function printArgs(array $options, $args, $indentation = '') : string { if (! $args) { return ''; @@ -308,7 +312,7 @@ private static function printInputValue($arg) : string } /** - * @param bool[] $options + * @param array $options */ public static function printType(Type $type, array $options = []) : string { @@ -340,7 +344,7 @@ public static function printType(Type $type, array $options = []) : string } /** - * @param bool[] $options + * @param array $options */ private static function printScalar(ScalarType $type, array $options) : string { @@ -348,7 +352,7 @@ private static function printScalar(ScalarType $type, array $options) : string } /** - * @param bool[] $options + * @param array $options */ private static function printObject(ObjectType $type, array $options) : string { @@ -357,8 +361,8 @@ private static function printObject(ObjectType $type, array $options) : string ? ' implements ' . implode( ' & ', array_map( - static function ($i) : string { - return $i->name; + static function (InterfaceType $interface) : string { + return $interface->name; }, $interfaces ) @@ -370,9 +374,9 @@ static function ($i) : string { } /** - * @param bool[] $options + * @param array $options */ - private static function printFields($options, $type) : string + private static function printFields(array $options, $type) : string { $fields = array_values($type->getFields()); @@ -405,7 +409,7 @@ private static function printDeprecated($fieldOrEnumVal) : string } /** - * @param bool[] $options + * @param array $options */ private static function printInterface(InterfaceType $type, array $options) : string { @@ -414,7 +418,7 @@ private static function printInterface(InterfaceType $type, array $options) : st } /** - * @param bool[] $options + * @param array $options */ private static function printUnion(UnionType $type, array $options) : string { @@ -423,7 +427,7 @@ private static function printUnion(UnionType $type, array $options) : string } /** - * @param bool[] $options + * @param array $options */ private static function printEnum(EnumType $type, array $options) : string { @@ -432,9 +436,9 @@ private static function printEnum(EnumType $type, array $options) : string } /** - * @param bool[] $options + * @param array $options */ - private static function printEnumValues($values, $options) : string + private static function printEnumValues($values, array $options) : string { return implode( "\n", @@ -450,7 +454,7 @@ static function ($value, $i) use ($options) : string { } /** - * @param bool[] $options + * @param array $options */ private static function printInputObject(InputObjectType $type, array $options) : string { @@ -474,7 +478,7 @@ static function ($f, $i) use ($options) : string { } /** - * @param bool[] $options + * @param array $options * * @api */ diff --git a/src/Validator/Rules/UniqueDirectivesPerLocation.php b/src/Validator/Rules/UniqueDirectivesPerLocation.php index 6860222d5..02844af73 100644 --- a/src/Validator/Rules/UniqueDirectivesPerLocation.php +++ b/src/Validator/Rules/UniqueDirectivesPerLocation.php @@ -5,13 +5,21 @@ namespace GraphQL\Validator\Rules; use GraphQL\Error\Error; +use GraphQL\Language\AST\DirectiveDefinitionNode; use GraphQL\Language\AST\DirectiveNode; use GraphQL\Language\AST\Node; +use GraphQL\Type\Definition\Directive; use GraphQL\Validator\ASTValidationContext; use GraphQL\Validator\SDLValidationContext; use GraphQL\Validator\ValidationContext; use function sprintf; +/** + * Unique directive names per location + * + * A GraphQL document is only valid if all non-repeatable directives at + * a given location are uniquely named. + */ class UniqueDirectivesPerLocation extends ValidationRule { public function getVisitor(ValidationContext $context) @@ -26,16 +34,41 @@ public function getSDLVisitor(SDLValidationContext $context) public function getASTVisitor(ASTValidationContext $context) { + $uniqueDirectiveMap = []; + + $schema = $context->getSchema(); + $definedDirectives = $schema !== null + ? $schema->getDirectives() + : Directive::getInternalDirectives(); + foreach ($definedDirectives as $directive) { + $uniqueDirectiveMap[$directive->name] = ! $directive->isRepeatable; + } + + $astDefinitions = $context->getDocument()->definitions; + foreach ($astDefinitions as $definition) { + if (! ($definition instanceof DirectiveDefinitionNode)) { + continue; + } + + $uniqueDirectiveMap[$definition->name->value] = $definition->repeatable; + } + return [ - 'enter' => static function (Node $node) use ($context) : void { + 'enter' => static function (Node $node) use ($uniqueDirectiveMap, $context) : void { if (! isset($node->directives)) { return; } $knownDirectives = []; + /** @var DirectiveNode $directive */ foreach ($node->directives as $directive) { $directiveName = $directive->name->value; + + if (! isset($uniqueDirectiveMap[$directiveName])) { + continue; + } + if (isset($knownDirectives[$directiveName])) { $context->reportError(new Error( self::duplicateDirectiveMessage($directiveName), diff --git a/tests/Language/SchemaParserTest.php b/tests/Language/SchemaParserTest.php index 267db2246..cea96e12b 100644 --- a/tests/Language/SchemaParserTest.php +++ b/tests/Language/SchemaParserTest.php @@ -6,6 +6,7 @@ use GraphQL\Error\SyntaxError; use GraphQL\Language\AST\NodeKind; +use GraphQL\Language\DirectiveLocation; use GraphQL\Language\Parser; use GraphQL\Language\SourceLocation; use GraphQL\Tests\PHPUnit\ArraySubsetAsserts; @@ -1045,6 +1046,86 @@ public function testSimpleInputObjectWithArgsShouldFail() : void ); } + /** + * @see it('Directive definition', () => { + */ + public function testDirectiveDefinition() : void + { + $body = 'directive @foo on OBJECT | INTERFACE'; + $doc = Parser::parse($body); + $loc = static function ($start, $end) : array { + return TestUtils::locArray($start, $end); + }; + + $expected = [ + 'kind' => NodeKind::DOCUMENT, + 'definitions' => [ + [ + 'kind' => NodeKind::DIRECTIVE_DEFINITION, + 'name' => $this->nameNode('foo', $loc(11, 14)), + 'description' => null, + 'arguments' => [], + 'repeatable' => false, + 'locations' => [ + [ + 'kind' => NodeKind::NAME, + 'value' => DirectiveLocation::OBJECT, + 'loc' => $loc(18, 24), + ], + [ + 'kind' => NodeKind::NAME, + 'value' => DirectiveLocation::IFACE, + 'loc' => $loc(27, 36), + ], + ], + 'loc' => $loc(0, 36), + ], + ], + 'loc' => $loc(0, 36), + ]; + self::assertEquals($expected, TestUtils::nodeToArray($doc)); + } + + /** + * @see it('Repeatable directive definition', () => { + */ + public function testRepeatableDirectiveDefinition() : void + { + $body = 'directive @foo repeatable on OBJECT | INTERFACE'; + $doc = Parser::parse($body); + $loc = static function ($start, $end) : array { + return TestUtils::locArray($start, $end); + }; + + $expected = [ + 'kind' => NodeKind::DOCUMENT, + 'definitions' => [ + [ + 'kind' => NodeKind::DIRECTIVE_DEFINITION, + 'name' => $this->nameNode('foo', $loc(11, 14)), + 'description' => null, + 'arguments' => [], + 'repeatable' => true, + 'locations' => [ + [ + 'kind' => NodeKind::NAME, + 'value' => DirectiveLocation::OBJECT, + 'loc' => $loc(29, 35), + ], + [ + 'kind' => NodeKind::NAME, + 'value' => DirectiveLocation::IFACE, + 'loc' => $loc(38, 47), + ], + ], + 'loc' => $loc(0, 47), + ], + ], + 'loc' => $loc(0, 47), + ]; + self::assertEquals($expected, TestUtils::nodeToArray($doc)); + } + /** * @see it('Directive with incorrect locations') */ diff --git a/tests/Language/SchemaPrinterTest.php b/tests/Language/SchemaPrinterTest.php index b5769835a..05a326189 100644 --- a/tests/Language/SchemaPrinterTest.php +++ b/tests/Language/SchemaPrinterTest.php @@ -176,6 +176,8 @@ enum UndefinedEnum directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT directive @include2(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT + +directive @myRepeatableDir(name: String!) repeatable on OBJECT | INTERFACE '; self::assertEquals($expected, $printed); } diff --git a/tests/Language/schema-kitchen-sink.graphql b/tests/Language/schema-kitchen-sink.graphql index 3ad830cbf..b912f06d5 100644 --- a/tests/Language/schema-kitchen-sink.graphql +++ b/tests/Language/schema-kitchen-sink.graphql @@ -123,3 +123,7 @@ directive @include2(if: Boolean!) on | FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT + +directive @myRepeatableDir(name: String!) repeatable on + | OBJECT + | INTERFACE diff --git a/tests/Type/DirectiveTest.php b/tests/Type/DirectiveTest.php new file mode 100644 index 000000000..c98265379 --- /dev/null +++ b/tests/Type/DirectiveTest.php @@ -0,0 +1,15 @@ + { + */ +final class DirectiveTest extends TestCase +{ + // TODO implement https://github.com/graphql/graphql-js/blob/master/src/type/__tests__/directive-test.js +} diff --git a/tests/Type/IntrospectionTest.php b/tests/Type/IntrospectionTest.php index fac1c9934..de8363172 100644 --- a/tests/Type/IntrospectionTest.php +++ b/tests/Type/IntrospectionTest.php @@ -35,7 +35,10 @@ public function testExecutesAnIntrospectionQuery() : void ]), ]); - $request = Introspection::getIntrospectionQuery(['descriptions' => false]); + $request = Introspection::getIntrospectionQuery([ + 'descriptions' => false, + 'directiveIsRepeatable' => true, + ]); $expected = [ 'data' => [ @@ -794,7 +797,7 @@ public function testExecutesAnIntrospectionQuery() : void ], 2 => [ - 'name' => 'locations', + 'name' => 'args', 'args' => [], 'type' => @@ -811,8 +814,8 @@ public function testExecutesAnIntrospectionQuery() : void 'name' => null, 'ofType' => [ - 'kind' => 'ENUM', - 'name' => '__DirectiveLocation', + 'kind' => 'OBJECT', + 'name' => '__InputValue', ], ], ], @@ -822,7 +825,25 @@ public function testExecutesAnIntrospectionQuery() : void ], 3 => [ - 'name' => 'args', + 'name' => 'isRepeatable', + 'args' => + [], + 'type' => + [ + 'kind' => 'NON_NULL', + 'name' => null, + 'ofType' => [ + 'kind' => 'SCALAR', + 'name' => 'Boolean', + 'ofType' => null, + ], + ], + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 4 => + [ + 'name' => 'locations', 'args' => [], 'type' => @@ -839,8 +860,8 @@ public function testExecutesAnIntrospectionQuery() : void 'name' => null, 'ofType' => [ - 'kind' => 'OBJECT', - 'name' => '__InputValue', + 'kind' => 'ENUM', + 'name' => '__DirectiveLocation', ], ], ], @@ -975,12 +996,7 @@ public function testExecutesAnIntrospectionQuery() : void 0 => [ 'name' => 'include', - 'locations' => - [ - 0 => 'FIELD', - 1 => 'FRAGMENT_SPREAD', - 2 => 'INLINE_FRAGMENT', - ], + 'isRepeatable' => false, 'args' => [ 0 => @@ -999,16 +1015,17 @@ public function testExecutesAnIntrospectionQuery() : void ], ], ], - ], - 1 => - [ - 'name' => 'skip', 'locations' => [ 0 => 'FIELD', 1 => 'FRAGMENT_SPREAD', 2 => 'INLINE_FRAGMENT', ], + ], + 1 => + [ + 'name' => 'skip', + 'isRepeatable' => false, 'args' => [ 0 => @@ -1027,15 +1044,17 @@ public function testExecutesAnIntrospectionQuery() : void ], ], ], + 'locations' => + [ + 0 => 'FIELD', + 1 => 'FRAGMENT_SPREAD', + 2 => 'INLINE_FRAGMENT', + ], ], 2 => [ 'name' => 'deprecated', - 'locations' => - [ - 0 => 'FIELD_DEFINITION', - 1 => 'ENUM_VALUE', - ], + 'isRepeatable' => false, 'args' => [ 0 => @@ -1050,6 +1069,11 @@ public function testExecutesAnIntrospectionQuery() : void ], ], ], + 'locations' => + [ + 0 => 'FIELD_DEFINITION', + 1 => 'ENUM_VALUE', + ], ], ], ], @@ -1608,7 +1632,7 @@ public function testExecutesAnIntrospectionQueryWithoutCallingGlobalFieldResolve ]); $schema = new Schema([ 'query' => $QueryRoot ]); - $source = Introspection::getIntrospectionQuery(); + $source = Introspection::getIntrospectionQuery(['directiveIsRepeatable' => true]); $calledForFields = []; /* istanbul ignore next */ diff --git a/tests/Utils/BuildClientSchemaTest.php b/tests/Utils/BuildClientSchemaTest.php index 814f69dd7..0d82fd4f3 100644 --- a/tests/Utils/BuildClientSchemaTest.php +++ b/tests/Utils/BuildClientSchemaTest.php @@ -23,10 +23,12 @@ class BuildClientSchemaTest extends TestCase { protected static function assertCycleIntrospection(string $sdl) : void { + $options = ['directiveIsRepeatable' => true]; + $serverSchema = BuildSchema::build($sdl); - $initialIntrospection = Introspection::fromSchema($serverSchema); + $initialIntrospection = Introspection::fromSchema($serverSchema, $options); $clientSchema = BuildClientSchema::build($initialIntrospection); - $secondIntrospection = Introspection::fromSchema($clientSchema); + $secondIntrospection = Introspection::fromSchema($clientSchema, $options); self::assertSame($initialIntrospection, $secondIntrospection); } @@ -489,8 +491,8 @@ public function testBuildsASchemaWithCustomDirectives() : void { self::assertCycleIntrospection(' """This is a custom directive""" - directive @customDirective on FIELD - + directive @customDirective repeatable on FIELD + type Query { string: String } diff --git a/tests/Utils/BuildSchemaTest.php b/tests/Utils/BuildSchemaTest.php index 3990093fa..7da00b10a 100644 --- a/tests/Utils/BuildSchemaTest.php +++ b/tests/Utils/BuildSchemaTest.php @@ -106,6 +106,8 @@ public function testWithDirectives() : void $body = ' directive @foo(arg: Int) on FIELD +directive @repeatableFoo(arg: Int) repeatable on FIELD + type Query { str: String } diff --git a/tests/Utils/SchemaExtenderTest.php b/tests/Utils/SchemaExtenderTest.php index a7d9729bc..ea5fbc476 100644 --- a/tests/Utils/SchemaExtenderTest.php +++ b/tests/Utils/SchemaExtenderTest.php @@ -522,7 +522,7 @@ interfaceField: String type TestType implements TestInterface { interfaceField: String } - directive @test(arg: Int) on FIELD | SCALAR + directive @test(arg: Int) repeatable on FIELD | SCALAR '); $extendedTwiceSchema = SchemaExtender::extend($extendedSchema, $ast); @@ -1263,7 +1263,7 @@ public function testSetsCorrectDescriptionUsingLegacyComments() public function testMayExtendDirectivesWithNewComplexDirective() { $extendedSchema = $this->extendTestSchema(' - directive @profile(enable: Boolean! tag: String) on QUERY | FIELD + directive @profile(enable: Boolean! tag: String) repeatable on QUERY | FIELD '); $extendedDirective = $extendedSchema->getDirective('profile'); diff --git a/tests/Utils/SchemaPrinterTest.php b/tests/Utils/SchemaPrinterTest.php index ab54cdf1b..be8c4d7db 100644 --- a/tests/Utils/SchemaPrinterTest.php +++ b/tests/Utils/SchemaPrinterTest.php @@ -706,26 +706,50 @@ public function testPrintsCustomDirectives() : void $query = new ObjectType([ 'name' => 'Query', 'fields' => [ - 'field' => ['type' => Type::string()], + 'field' => [ + 'type' => Type::string(), + ], ], ]); - $customDirectives = new Directive([ - 'name' => 'customDirective', + $simpleDirective = new Directive([ + 'name' => 'simpleDirective', 'locations' => [ DirectiveLocation::FIELD, ], ]); + $complexDirective = new Directive([ + 'name' => 'complexDirective', + 'description' => 'Complex Directive', + 'args' => [ + 'stringArg' => [ + 'type' => Type::string(), + ], + 'intArg' => [ + 'type' => Type::int(), + 'defaultValue' => -1, + ], + ], + 'isRepeatable' => true, + 'locations' => [ + DirectiveLocation::FIELD, + DirectiveLocation::QUERY, + ], + ]); + $schema = new Schema([ 'query' => $query, - 'directives' => [$customDirectives], + 'directives' => [$simpleDirective, $complexDirective], ]); $output = $this->printForTest($schema); self::assertEquals( ' -directive @customDirective on FIELD +directive @simpleDirective on FIELD + +"""Complex Directive""" +directive @complexDirective(stringArg: String, intArg: Int = -1) repeatable on FIELD | QUERY type Query { field: String @@ -871,8 +895,9 @@ public function testPrintIntrospectionSchema() : void type __Directive { name: String! description: String - locations: [__DirectiveLocation!]! args: [__InputValue!]! + isRepeatable: Boolean! + locations: [__DirectiveLocation!]! } """ @@ -1111,8 +1136,9 @@ public function testPrintIntrospectionSchemaWithCommentDescriptions() : void type __Directive { name: String! description: String - locations: [__DirectiveLocation!]! args: [__InputValue!]! + isRepeatable: Boolean! + locations: [__DirectiveLocation!]! } # A Directive can be adjacent to many parts of the GraphQL language, a diff --git a/tests/Validator/UniqueDirectivesPerLocationTest.php b/tests/Validator/UniqueDirectivesPerLocationTest.php index c71d02c65..5e7d656c6 100644 --- a/tests/Validator/UniqueDirectivesPerLocationTest.php +++ b/tests/Validator/UniqueDirectivesPerLocationTest.php @@ -89,6 +89,39 @@ public function testSameDirectivesInSimilarLocations() : void ); } + /** + * @see it('repeatable directives in same location', () => { + */ + public function testRepeatableDirectivesInSameLocation() : void + { + $this->expectPassesRule( + new UniqueDirectivesPerLocation(), + ' + fragment Test on Type @repeatable @repeatable { + field @repeatable @repeatable + } + ' + ); + } + + /** + * @see it('unknown directives must be ignored', () => { + */ + public function testUnknownDirectivesMustBeIgnored() : void + { + $this->expectPassesRule( + new UniqueDirectivesPerLocation(), + ' + type Test @unknown @unknown { + field: String! @unknown @unknown + } + extend type Test @unknown { + anotherField: String! + } + ' + ); + } + /** * @see it('duplicate directives in one location') */ @@ -180,38 +213,25 @@ public function testDuplicateDirectivesOnSDLDefinitions() { $this->expectSDLErrors( ' - schema @directive @directive { query: Dummy } - extend schema @directive @directive - - scalar TestScalar @directive @directive - extend scalar TestScalar @directive @directive - - type TestObject @directive @directive - extend type TestObject @directive @directive - - interface TestInterface @directive @directive - extend interface TestInterface @directive @directive + directive @nonRepeatable on + SCHEMA | SCALAR | OBJECT | INTERFACE | UNION | INPUT_OBJECT - union TestUnion @directive @directive - extend union TestUnion @directive @directive + schema @nonRepeatable @nonRepeatable { query: Dummy } - input TestInput @directive @directive - extend input TestInput @directive @directive + scalar TestScalar @nonRepeatable @nonRepeatable + type TestObject @nonRepeatable @nonRepeatable + interface TestInterface @nonRepeatable @nonRepeatable + union TestUnion @nonRepeatable @nonRepeatable + input TestInput @nonRepeatable @nonRepeatable ', null, [ - $this->duplicateDirective('directive', 2, 14, 2, 25), - $this->duplicateDirective('directive', 3, 21, 3, 32), - $this->duplicateDirective('directive', 5, 25, 5, 36), - $this->duplicateDirective('directive', 6, 32, 6, 43), - $this->duplicateDirective('directive', 8, 23, 8, 34), - $this->duplicateDirective('directive', 9, 30, 9, 41), - $this->duplicateDirective('directive', 11, 31, 11, 42), - $this->duplicateDirective('directive', 12, 38, 12, 49), - $this->duplicateDirective('directive', 14, 23, 14, 34), - $this->duplicateDirective('directive', 15, 30, 15, 41), - $this->duplicateDirective('directive', 17, 23, 17, 34), - $this->duplicateDirective('directive', 18, 30, 18, 41), + $this->duplicateDirective('nonRepeatable', 5, 14, 5, 29), + $this->duplicateDirective('nonRepeatable', 7, 25, 7, 40), + $this->duplicateDirective('nonRepeatable', 8, 23, 8, 38), + $this->duplicateDirective('nonRepeatable', 9, 31, 9, 46), + $this->duplicateDirective('nonRepeatable', 10, 23, 10, 38), + $this->duplicateDirective('nonRepeatable', 11, 23, 11, 38), ] ); } diff --git a/tests/Validator/ValidatorTestCase.php b/tests/Validator/ValidatorTestCase.php index 0944f0e5c..6af9b580c 100644 --- a/tests/Validator/ValidatorTestCase.php +++ b/tests/Validator/ValidatorTestCase.php @@ -5,6 +5,7 @@ namespace GraphQL\Tests\Validator; use Exception; +use GraphQL\Language\DirectiveLocation; use GraphQL\Language\Parser; use GraphQL\Type\Definition\CustomScalarType; use GraphQL\Type\Definition\Directive; @@ -354,37 +355,49 @@ public static function getTestSchema() 'directives' => [ Directive::includeDirective(), Directive::skipDirective(), + new Directive([ + 'name' => 'directive', + 'locations' => [DirectiveLocation::FIELD], + ]), + new Directive([ + 'name' => 'directiveA', + 'locations' => [DirectiveLocation::FIELD], + ]), + new Directive([ + 'name' => 'directiveB', + 'locations' => [DirectiveLocation::FIELD], + ]), new Directive([ 'name' => 'onQuery', - 'locations' => ['QUERY'], + 'locations' => [DirectiveLocation::QUERY], ]), new Directive([ 'name' => 'onMutation', - 'locations' => ['MUTATION'], + 'locations' => [DirectiveLocation::MUTATION], ]), new Directive([ 'name' => 'onSubscription', - 'locations' => ['SUBSCRIPTION'], + 'locations' => [DirectiveLocation::SUBSCRIPTION], ]), new Directive([ 'name' => 'onField', - 'locations' => ['FIELD'], + 'locations' => [DirectiveLocation::FIELD], ]), new Directive([ 'name' => 'onFragmentDefinition', - 'locations' => ['FRAGMENT_DEFINITION'], + 'locations' => [DirectiveLocation::FRAGMENT_DEFINITION], ]), new Directive([ 'name' => 'onFragmentSpread', - 'locations' => ['FRAGMENT_SPREAD'], + 'locations' => [DirectiveLocation::FRAGMENT_SPREAD], ]), new Directive([ 'name' => 'onInlineFragment', - 'locations' => ['INLINE_FRAGMENT'], + 'locations' => [DirectiveLocation::INLINE_FRAGMENT], ]), new Directive([ 'name' => 'onVariableDefinition', - 'locations' => ['VARIABLE_DEFINITION'], + 'locations' => [DirectiveLocation::VARIABLE_DEFINITION], ]), ], ]); From 815fea369bb090019acbd6ab65445dde7524f070 Mon Sep 17 00:00:00 2001 From: Max Loeb Date: Mon, 8 Jun 2020 10:49:30 -0700 Subject: [PATCH 176/256] Fix phpunit deprecation (#646) * fix phpunit deprecation * switch on phpunit version * lock version * check for method --- tests/Utils/BuildClientSchemaTest.php | 30 +++++++++++++++++++-------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/tests/Utils/BuildClientSchemaTest.php b/tests/Utils/BuildClientSchemaTest.php index 0d82fd4f3..5592f4b14 100644 --- a/tests/Utils/BuildClientSchemaTest.php +++ b/tests/Utils/BuildClientSchemaTest.php @@ -14,7 +14,10 @@ use GraphQL\Utils\BuildSchema; use GraphQL\Utils\SchemaPrinter; use PHPUnit\Framework\TestCase; +use PHPUnit\Runner\Version; use function array_filter; +use function method_exists; +use function version_compare; /** * @see BuildClientSchema @@ -625,6 +628,15 @@ enum SomeEnum { FOO } '); } + protected function _expectExceptionMessage(string $message) : void + { + if (version_compare(Version::id(), '8.4', '<')) { + $this->expectExceptionMessageRegExp($message); + } elseif (method_exists($this, 'expectExceptionMessageMatches')) { + $this->expectExceptionMessageMatches($message); + } + } + /** * it('throws when introspection is missing __schema property', () => { */ @@ -715,7 +727,7 @@ public function testThrowsWhenMissingKind() : void unset($queryTypeIntrospection['kind']); - $this->expectExceptionMessageRegExp( + $this->_expectExceptionMessage( '/Invalid or incomplete introspection result. Ensure that a full introspection query is used in order to build a client schema: {"name":"Query",.*}\./' ); BuildClientSchema::build($introspection); @@ -740,7 +752,7 @@ public function testThrowsWhenMissingInterfaces() : void unset($queryTypeIntrospection['interfaces']); - $this->expectExceptionMessageRegExp( + $this->_expectExceptionMessage( '/Introspection result missing interfaces: {"kind":"OBJECT","name":"Query",.*}\./' ); BuildClientSchema::build($introspection); @@ -792,7 +804,7 @@ public function testThrowsWhenMissingFields() : void unset($queryTypeIntrospection['fields']); - $this->expectExceptionMessageRegExp( + $this->_expectExceptionMessage( '/Introspection result missing fields: {"kind":"OBJECT","name":"Query",.*}\./' ); BuildClientSchema::build($introspection); @@ -818,7 +830,7 @@ public function testThrowsWhenMissingFieldArgs() : void unset($firstField['args']); - $this->expectExceptionMessageRegExp( + $this->_expectExceptionMessage( '/Introspection result missing field args: {"name":"foo",.*}\./' ); BuildClientSchema::build($introspection); @@ -895,7 +907,7 @@ public function testThrowsWhenMissingPossibleTypes() : void unset($someUnionIntrospection['possibleTypes']); - $this->expectExceptionMessageRegExp( + $this->_expectExceptionMessage( '/Introspection result missing possibleTypes: {"kind":"UNION","name":"SomeUnion",.*}\./' ); BuildClientSchema::build($introspection); @@ -920,7 +932,7 @@ public function testThrowsWhenMissingEnumValues() : void unset($someEnumIntrospection['enumValues']); - $this->expectExceptionMessageRegExp( + $this->_expectExceptionMessage( '/Introspection result missing enumValues: {"kind":"ENUM","name":"SomeEnum",.*}\./' ); BuildClientSchema::build($introspection); @@ -945,7 +957,7 @@ public function testThrowsWhenMissingInputFields() : void unset($someInputObjectIntrospection['inputFields']); - $this->expectExceptionMessageRegExp( + $this->_expectExceptionMessage( '/Introspection result missing inputFields: {"kind":"INPUT_OBJECT","name":"SomeInputObject",.*}\./' ); BuildClientSchema::build($introspection); @@ -964,7 +976,7 @@ public function testThrowsWhenMissingDirectiveLocations() : void unset($someDirectiveIntrospection['locations']); - $this->expectExceptionMessageRegExp( + $this->_expectExceptionMessage( '/Introspection result missing directive locations: {"name":"SomeDirective",.*}\./' ); BuildClientSchema::build($introspection); @@ -983,7 +995,7 @@ public function testThrowsWhenMissingDirectiveArgs() : void unset($someDirectiveIntrospection['args']); - $this->expectExceptionMessageRegExp( + $this->_expectExceptionMessage( '/Introspection result missing directive args: {"name":"SomeDirective",.*}\./' ); BuildClientSchema::build($introspection); From 203f8175805898564eeb2f09b10733a60593040e Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Tue, 9 Jun 2020 01:47:43 +0700 Subject: [PATCH 177/256] Show GitHub CI badge instead of Travis --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f17ac4b61..86aced09a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # graphql-php -[![Build Status](https://travis-ci.org/webonyx/graphql-php.svg?branch=master)](https://travis-ci.org/webonyx/graphql-php) +![CI](https://github.com/webonyx/graphql-php/workflows/CI/badge.svg) [![Coverage Status](https://coveralls.io/repos/github/webonyx/graphql-php/badge.svg?branch=master)](https://coveralls.io/github/webonyx/graphql-php?branch=master) [![Latest Stable Version](https://poser.pugx.org/webonyx/graphql-php/version)](https://packagist.org/packages/webonyx/graphql-php) [![License](https://poser.pugx.org/webonyx/graphql-php/license)](https://packagist.org/packages/webonyx/graphql-php) From a747575e5a5c8e72a1d5a71e9c3eeb580159b9f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Podlipsk=C3=BD?= Date: Mon, 8 Jun 2020 21:05:45 +0200 Subject: [PATCH 178/256] Drop Error::$message (#670) * Drop Error::$message * Update UPGRADE.md Co-authored-by: Benedikt Franke Co-authored-by: Benedikt Franke --- UPGRADE.md | 2 ++ src/Error/Error.php | 7 ------- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/UPGRADE.md b/UPGRADE.md index ce5c93ea9..0a7e12639 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,5 +1,7 @@ ## Master +- Dropped `GraphQL\Error\Error::$message`, use `->getMessage()` instead. + ### Breaking (major): dropped deprecations - dropped deprecated `GraphQL\Schema`. Use `GraphQL\Type\Schema`. diff --git a/src/Error/Error.php b/src/Error/Error.php index 4509c140e..35bedb6da 100644 --- a/src/Error/Error.php +++ b/src/Error/Error.php @@ -38,13 +38,6 @@ class Error extends Exception implements JsonSerializable, ClientAware const CATEGORY_GRAPHQL = 'graphql'; const CATEGORY_INTERNAL = 'internal'; - /** - * A message describing the Error for debugging purposes. - * - * @var string - */ - public $message; - /** @var SourceLocation[] */ private $locations; From e31c89ad6ef448f0849716e48d722ca8ccbd1f85 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Fri, 19 Jun 2020 14:51:00 +0700 Subject: [PATCH 179/256] Deprecate experimental executor (#397) --- src/GraphQL.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/GraphQL.php b/src/GraphQL.php index 01e14d8d5..271ee1ddb 100644 --- a/src/GraphQL.php +++ b/src/GraphQL.php @@ -333,6 +333,10 @@ public static function setPromiseAdapter(?PromiseAdapter $promiseAdapter = null) */ public static function useExperimentalExecutor() { + trigger_error( + 'Experimental Executor is deprecated and will be removed in the next major version', + E_USER_DEPRECATED + ); Executor::setImplementationFactory([CoroutineExecutor::class, 'create']); } From fdc5808e2ea438ece5495c31bad6c6174dd5df4c Mon Sep 17 00:00:00 2001 From: Max Loeb Date: Fri, 19 Jun 2020 02:25:41 -0700 Subject: [PATCH 180/256] Lazy loading of types (#557) * lazy load types * fix introspection resolve * tests, linting, stanning * fix annotation * remove annotation, debug stuff * cleanup * fix annotations * start on stanning * rename to Schema::resolveType * remove some more useless resolve calls * remove another unneeded resolve call * remove unused * workaround for Scrutinizer * ws * make assertType 0 cost * use getType in assertValid * add lazy load to blog sample * fix scrutinizer * use InvariantViolation * resolve abstract type * remove whoopsie * remove annotation * lint * linting and stanning * fix completeAbstractValue * fix tests * lint * cast * fix stan issues * use trait * remove assert stuff * lock sniffer * stanning * remove unused * remove unused * remove unused * remove assert * remove cast * seal class * add typehint * rename, change visibility * add typehint * stanning * expand type * expand type * remove unused * reorganize * simplify * use isset * touch * revert * disable specific rule * cast type * broaden type * annotate return type * remove redundant code * skip method call * move execution of lazy type to typeloader * remove pointless test --- .../01-blog/Blog/Type/Scalar/EmailType.php | 19 +- examples/01-blog/Blog/Types.php | 139 +++---- examples/01-blog/graphql.php | 6 +- src/Executor/ReferenceExecutor.php | 13 +- src/Type/Definition/FieldArgument.php | 6 +- src/Type/Definition/FieldDefinition.php | 10 +- src/Type/Definition/InputObjectField.php | 8 +- src/Type/Definition/ListOfType.php | 24 +- src/Type/Definition/NonNull.php | 20 +- src/Type/Definition/ObjectType.php | 11 +- src/Type/Definition/ResolveInfo.php | 2 +- src/Type/Definition/Type.php | 24 +- src/Type/Definition/UnionType.php | 11 +- src/Type/Introspection.php | 2 +- src/Type/Schema.php | 27 +- src/Type/SchemaConfig.php | 4 +- src/Utils/SchemaExtender.php | 2 +- src/Utils/TypeInfo.php | 3 +- tests/Type/LazyTypeLoaderTest.php | 381 ++++++++++++++++++ tests/Type/TypeLoaderTest.php | 4 +- tests/Type/ValidationTest.php | 22 - 21 files changed, 553 insertions(+), 185 deletions(-) create mode 100644 tests/Type/LazyTypeLoaderTest.php diff --git a/examples/01-blog/Blog/Type/Scalar/EmailType.php b/examples/01-blog/Blog/Type/Scalar/EmailType.php index fd78ea796..ec304c168 100644 --- a/examples/01-blog/Blog/Type/Scalar/EmailType.php +++ b/examples/01-blog/Blog/Type/Scalar/EmailType.php @@ -6,15 +6,14 @@ use GraphQL\Type\Definition\CustomScalarType; use GraphQL\Utils\Utils; -class EmailType +class EmailType extends CustomScalarType { - public static function create() + public function __construct(array $config = []) { - return new CustomScalarType([ - 'name' => 'Email', - 'serialize' => [__CLASS__, 'serialize'], - 'parseValue' => [__CLASS__, 'parseValue'], - 'parseLiteral' => [__CLASS__, 'parseLiteral'], + parent::__construct([ + 'serialize' => [__CLASS__, 's_serialize'], + 'parseValue' => [__CLASS__, 's_parseValue'], + 'parseLiteral' => [__CLASS__, 's_parseLiteral'], ]); } @@ -24,7 +23,7 @@ public static function create() * @param string $value * @return string */ - public static function serialize($value) + public static function s_serialize($value) { // Assuming internal representation of email is always correct: return $value; @@ -40,7 +39,7 @@ public static function serialize($value) * @param mixed $value * @return mixed */ - public static function parseValue($value) + public static function s_parseValue($value) { if (!filter_var($value, FILTER_VALIDATE_EMAIL)) { throw new \UnexpectedValueException("Cannot represent value as email: " . Utils::printSafe($value)); @@ -55,7 +54,7 @@ public static function parseValue($value) * @return string * @throws Error */ - public static function parseLiteral($valueNode) + public static function s_parseLiteral($valueNode) { // Note: throwing GraphQL\Error\Error vs \UnexpectedValueException to benefit from GraphQL // error location in query: diff --git a/examples/01-blog/Blog/Types.php b/examples/01-blog/Blog/Types.php index a8bb93aa5..9dddaec3b 100644 --- a/examples/01-blog/Blog/Types.php +++ b/examples/01-blog/Blog/Types.php @@ -1,13 +1,13 @@ Types::query() + 'query' => new QueryType(), + 'typeLoader' => function($name) { + return Types::byTypeName($name, true); + } ]); $result = GraphQL::executeQuery( diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 1f6a33f42..02c7eb152 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -45,6 +45,7 @@ use function array_values; use function get_class; use function is_array; +use function is_callable; use function is_string; use function sprintf; @@ -528,7 +529,6 @@ private function resolveField(ObjectType $parentType, $rootValue, $fieldNodes, $ // The resolve function's optional 3rd argument is a context value that // is provided to every resolve function within an execution. It is commonly // used to represent an authenticated user, or request-specific caches. - $context = $exeContext->contextValue; // The resolve function's optional 4th argument is a collection of // information about the current execution state. $info = new ResolveInfo( @@ -938,10 +938,15 @@ private function completeLeafValue(LeafType $returnType, &$result) */ private function completeAbstractValue(AbstractType $returnType, $fieldNodes, ResolveInfo $info, $path, &$result) { - $exeContext = $this->exeContext; - $runtimeType = $returnType->resolveType($result, $exeContext->contextValue, $info); - if ($runtimeType === null) { + $exeContext = $this->exeContext; + $typeCandidate = $returnType->resolveType($result, $exeContext->contextValue, $info); + + if ($typeCandidate === null) { $runtimeType = self::defaultTypeResolver($result, $exeContext->contextValue, $info, $returnType); + } elseif (is_callable($typeCandidate)) { + $runtimeType = Schema::resolveType($typeCandidate); + } else { + $runtimeType = $typeCandidate; } $promise = $this->getPromise($runtimeType); if ($promise !== null) { diff --git a/src/Type/Definition/FieldArgument.php b/src/Type/Definition/FieldArgument.php index 895504c94..c8cc49c14 100644 --- a/src/Type/Definition/FieldArgument.php +++ b/src/Type/Definition/FieldArgument.php @@ -6,6 +6,7 @@ use GraphQL\Error\InvariantViolation; use GraphQL\Language\AST\InputValueDefinitionNode; +use GraphQL\Type\Schema; use GraphQL\Utils\Utils; use function array_key_exists; use function is_array; @@ -77,12 +78,9 @@ public static function createMap(array $config) : array return $map; } - /** - * @return InputType&Type - */ public function getType() : Type { - return $this->type; + return Schema::resolveType($this->type); } public function defaultValueExists() : bool diff --git a/src/Type/Definition/FieldDefinition.php b/src/Type/Definition/FieldDefinition.php index 39392cd53..2fb1c52bb 100644 --- a/src/Type/Definition/FieldDefinition.php +++ b/src/Type/Definition/FieldDefinition.php @@ -7,6 +7,7 @@ use GraphQL\Error\Error; use GraphQL\Error\InvariantViolation; use GraphQL\Language\AST\FieldDefinitionNode; +use GraphQL\Type\Schema; use GraphQL\Utils\Utils; use function is_array; use function is_callable; @@ -58,7 +59,7 @@ class FieldDefinition */ public $config; - /** @var OutputType&Type */ + /** @var callable|(OutputType&Type) */ public $type; /** @var callable|string */ @@ -174,12 +175,9 @@ public function getArg($name) return null; } - /** - * @return OutputType&Type - */ public function getType() : Type { - return $this->type; + return Schema::resolveType($this->type); } /** @@ -217,7 +215,7 @@ public function assertValid(Type $parentType) ) ); - $type = $this->type; + $type = $this->getType(); if ($type instanceof WrappingType) { $type = $type->getWrappedType(true); } diff --git a/src/Type/Definition/InputObjectField.php b/src/Type/Definition/InputObjectField.php index 0284a72b5..0eae4a2b3 100644 --- a/src/Type/Definition/InputObjectField.php +++ b/src/Type/Definition/InputObjectField.php @@ -7,6 +7,7 @@ use GraphQL\Error\Error; use GraphQL\Error\InvariantViolation; use GraphQL\Language\AST\InputValueDefinitionNode; +use GraphQL\Type\Schema; use GraphQL\Utils\Utils; use function array_key_exists; use function sprintf; @@ -55,7 +56,12 @@ public function __construct(array $opts) */ public function getType() : Type { - return $this->type; + /** + * TODO: Replace this cast with native assert + * + * @var Type&InputType + */ + return Schema::resolveType($this->type); } public function defaultValueExists() : bool diff --git a/src/Type/Definition/ListOfType.php b/src/Type/Definition/ListOfType.php index f9ca85b32..3f0945527 100644 --- a/src/Type/Definition/ListOfType.php +++ b/src/Type/Definition/ListOfType.php @@ -4,24 +4,38 @@ namespace GraphQL\Type\Definition; +use GraphQL\Type\Schema; +use function is_callable; + class ListOfType extends Type implements WrappingType, OutputType, NullableType, InputType { - /** @var Type */ + /** @var callable():Type|Type */ public $ofType; - public function __construct(Type $type) + /** + * @param callable():Type|Type $type + */ + public function __construct($type) { - $this->ofType = $type; + $this->ofType = is_callable($type) ? $type : Type::assertType($type); } public function toString() : string { - return '[' . $this->ofType->toString() . ']'; + return '[' . $this->getOfType()->toString() . ']'; + } + + public function getOfType() + { + return Schema::resolveType($this->ofType); } + /** + * @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|(Type&WrappingType) + */ public function getWrappedType(bool $recurse = false) : Type { - $type = $this->ofType; + $type = $this->getOfType(); return $recurse && $type instanceof WrappingType ? $type->getWrappedType($recurse) diff --git a/src/Type/Definition/NonNull.php b/src/Type/Definition/NonNull.php index 716dac3d1..dd00fa2ac 100644 --- a/src/Type/Definition/NonNull.php +++ b/src/Type/Definition/NonNull.php @@ -4,12 +4,21 @@ namespace GraphQL\Type\Definition; +use GraphQL\Error\InvariantViolation; +use GraphQL\Type\Schema; +use function is_callable; + class NonNull extends Type implements WrappingType, OutputType, InputType { - /** @var NullableType&Type */ + /** @var callable|(NullableType&Type) */ private $ofType; - public function __construct(NullableType $type) + /** + * code sniffer doesn't understand this syntax. Pr with a fix here: waiting on https://github.com/squizlabs/PHP_CodeSniffer/pull/2919 + * phpcs:disable Squiz.Commenting.FunctionComment.SpacingAfterParamType + * @param (NullableType&Type)|callable $type + */ + public function __construct($type) { /** @var Type&NullableType $nullableType*/ $nullableType = $type; @@ -21,9 +30,14 @@ public function toString() : string return $this->getWrappedType()->toString() . '!'; } + public function getOfType() + { + return Schema::resolveType($this->ofType); + } + public function getWrappedType(bool $recurse = false) : Type { - $type = $this->ofType; + $type = $this->getOfType(); return $recurse && $type instanceof WrappingType ? $type->getWrappedType($recurse) diff --git a/src/Type/Definition/ObjectType.php b/src/Type/Definition/ObjectType.php index ce6097a09..b6febf026 100644 --- a/src/Type/Definition/ObjectType.php +++ b/src/Type/Definition/ObjectType.php @@ -5,10 +5,13 @@ namespace GraphQL\Type\Definition; use Exception; +use GraphQL\Deferred; use GraphQL\Error\InvariantViolation; use GraphQL\Language\AST\ObjectTypeDefinitionNode; use GraphQL\Language\AST\ObjectTypeExtensionNode; +use GraphQL\Type\Schema; use GraphQL\Utils\Utils; +use function array_map; use function call_user_func; use function is_array; use function is_callable; @@ -171,6 +174,7 @@ private function getInterfaceMap() if ($this->interfaceMap === null) { $this->interfaceMap = []; foreach ($this->getInterfaces() as $interface) { + $interface = Schema::resolveType($interface); $this->interfaceMap[$interface->name] = $interface; } } @@ -195,7 +199,10 @@ public function getInterfaces() ); } - $this->interfaces = $interfaces ?? []; + /** @var InterfaceType[] $interfaces */ + $interfaces = array_map([Schema::class, 'resolveType'], $interfaces ?? []); + + $this->interfaces = $interfaces; } return $this->interfaces; @@ -205,7 +212,7 @@ public function getInterfaces() * @param mixed $value * @param mixed[]|null $context * - * @return bool|null + * @return bool|Deferred|null */ public function isTypeOf($value, $context, ResolveInfo $info) { diff --git a/src/Type/Definition/ResolveInfo.php b/src/Type/Definition/ResolveInfo.php index 7049a1940..922725654 100644 --- a/src/Type/Definition/ResolveInfo.php +++ b/src/Type/Definition/ResolveInfo.php @@ -40,7 +40,7 @@ class ResolveInfo * Expected return type of the field being resolved. * * @api - * @var OutputType&Type + * @var Type */ public $returnType; diff --git a/src/Type/Definition/Type.php b/src/Type/Definition/Type.php index a779189e9..f79a49945 100644 --- a/src/Type/Definition/Type.php +++ b/src/Type/Definition/Type.php @@ -13,6 +13,7 @@ use ReflectionClass; use function array_keys; use function array_merge; +use function assert; use function implode; use function in_array; use function preg_replace; @@ -121,9 +122,11 @@ public static function listOf(Type $wrappedType) : ListOfType } /** + * @param callable|NullableType $wrappedType + * * @api */ - public static function nonNull(NullableType $wrappedType) : NonNull + public static function nonNull($wrappedType) : NonNull { return new NonNull($wrappedType); } @@ -280,29 +283,14 @@ public static function isAbstractType($type) : bool /** * @param mixed $type - * - * @return mixed */ - public static function assertType($type) + public static function assertType($type) : Type { - Utils::invariant( - self::isType($type), - 'Expected ' . Utils::printSafe($type) . ' to be a GraphQL type.' - ); + assert($type instanceof Type, new InvariantViolation('Expected ' . Utils::printSafe($type) . ' to be a GraphQL type.')); return $type; } - /** - * @param Type $type - * - * @api - */ - public static function isType($type) : bool - { - return $type instanceof Type; - } - /** * @api */ diff --git a/src/Type/Definition/UnionType.php b/src/Type/Definition/UnionType.php index f414bdbaa..345642158 100644 --- a/src/Type/Definition/UnionType.php +++ b/src/Type/Definition/UnionType.php @@ -7,7 +7,9 @@ use GraphQL\Error\InvariantViolation; use GraphQL\Language\AST\UnionTypeDefinitionNode; use GraphQL\Language\AST\UnionTypeExtensionNode; +use GraphQL\Type\Schema; use GraphQL\Utils\Utils; +use function array_map; use function call_user_func; use function is_array; use function is_callable; @@ -41,7 +43,7 @@ public function __construct(array $config) /** * Optionally provide a custom type resolver function. If one is not provided, - * the default implemenation will call `isTypeOf` on each implementing + * the default implementation will call `isTypeOf` on each implementing * Object type. */ $this->name = $config['name']; @@ -90,7 +92,12 @@ public function getTypes() ); } - $this->types = $types; + $rawTypes = $types; + foreach ($rawTypes as $i => $rawType) { + $rawTypes[$i] = Schema::resolveType($rawType); + } + + $this->types = $rawTypes; } return $this->types; diff --git a/src/Type/Introspection.php b/src/Type/Introspection.php index 7f0b0f582..9a87d2c2a 100644 --- a/src/Type/Introspection.php +++ b/src/Type/Introspection.php @@ -506,7 +506,7 @@ public static function _field() ], 'type' => [ 'type' => Type::nonNull(self::_type()), - 'resolve' => static function (FieldDefinition $field) { + 'resolve' => static function (FieldDefinition $field) : Type { return $field->getType(); }, ], diff --git a/src/Type/Schema.php b/src/Type/Schema.php index e69b018db..eeeb3f0e8 100644 --- a/src/Type/Schema.php +++ b/src/Type/Schema.php @@ -57,7 +57,7 @@ class Schema */ private $resolvedTypes = []; - /** @var array>|null */ + /** @var array> */ private $possibleTypeMap; /** @@ -172,6 +172,7 @@ private function resolveAdditionalTypes() } foreach ($types as $index => $type) { + $type = self::resolveType($type); if (! $type instanceof Type) { throw new InvariantViolation(sprintf( 'Each entry of schema types must be instance of GraphQL\Type\Definition\Type but entry at %s is %s', @@ -315,10 +316,11 @@ public function getType(string $name) : ?Type { if (! isset($this->resolvedTypes[$name])) { $type = $this->loadType($name); + if (! $type) { return null; } - $this->resolvedTypes[$name] = $type; + $this->resolvedTypes[$name] = self::resolveType($type); } return $this->resolvedTypes[$name]; @@ -342,12 +344,13 @@ private function loadType(string $typeName) : ?Type if (! $type instanceof Type) { throw new InvariantViolation( sprintf( - 'Type loader is expected to return valid type "%s", but it returned %s', + 'Type loader is expected to return a callable or valid type "%s", but it returned %s', $typeName, Utils::printSafe($type) ) ); } + if ($type->name !== $typeName) { throw new InvariantViolation( sprintf('Type loader is expected to return type "%s", but it returned "%s"', $typeName, $type->name) @@ -359,12 +362,24 @@ private function loadType(string $typeName) : ?Type private function defaultTypeLoader(string $typeName) : ?Type { - // Default type loader simply fallbacks to collecting all types + // Default type loader simply falls back to collecting all types $typeMap = $this->getTypeMap(); return $typeMap[$typeName] ?? null; } + /** + * @param Type|callable():Type $type + */ + public static function resolveType($type) : Type + { + if (is_callable($type)) { + return $type(); + } + + return $type; + } + /** * Returns all possible concrete types for given abstract type * (implementations for interfaces and members of union type for unions) @@ -385,11 +400,11 @@ public function getPossibleTypes(Type $abstractType) : array } /** - * @return array> + * @return array> */ private function getPossibleTypeMap() { - if ($this->possibleTypeMap === null) { + if (! isset($this->possibleTypeMap)) { $this->possibleTypeMap = []; foreach ($this->getTypeMap() as $type) { if ($type instanceof ObjectType) { diff --git a/src/Type/SchemaConfig.php b/src/Type/SchemaConfig.php index e6b0fb725..e90058a56 100644 --- a/src/Type/SchemaConfig.php +++ b/src/Type/SchemaConfig.php @@ -42,7 +42,7 @@ class SchemaConfig /** @var Directive[]|null */ public $directives; - /** @var callable|null */ + /** @var callable(string $name):Type|null */ public $typeLoader; /** @var SchemaDefinitionNode|null */ @@ -253,7 +253,7 @@ public function setDirectives(array $directives) } /** - * @return callable|null + * @return callable(string $name):Type|null * * @api */ diff --git a/src/Utils/SchemaExtender.php b/src/Utils/SchemaExtender.php index e24639ba2..5668f94d1 100644 --- a/src/Utils/SchemaExtender.php +++ b/src/Utils/SchemaExtender.php @@ -296,7 +296,7 @@ protected static function extendImplementedInterfaces(ObjectType $type) : array protected static function extendType($typeDef) { if ($typeDef instanceof ListOfType) { - return Type::listOf(static::extendType($typeDef->ofType)); + return Type::listOf(static::extendType($typeDef->getOfType())); } if ($typeDef instanceof NonNull) { diff --git a/src/Utils/TypeInfo.php b/src/Utils/TypeInfo.php index ef1f02301..e6a14761b 100644 --- a/src/Utils/TypeInfo.php +++ b/src/Utils/TypeInfo.php @@ -159,6 +159,7 @@ public static function extractTypes($type, ?array $typeMap = null) if ($type instanceof WrappingType) { return self::extractTypes($type->getWrappedType(true), $typeMap); } + if (! $type instanceof Type) { // Preserve these invalid types in map (at numeric index) to make them // detectable during $schema->validate() @@ -198,7 +199,7 @@ public static function extractTypes($type, ?array $typeMap = null) foreach ($type->getFields() as $fieldName => $field) { if (! empty($field->args)) { $fieldArgTypes = array_map( - static function (FieldArgument $arg) { + static function (FieldArgument $arg) : Type { return $arg->getType(); }, $field->args diff --git a/tests/Type/LazyTypeLoaderTest.php b/tests/Type/LazyTypeLoaderTest.php new file mode 100644 index 000000000..49934200e --- /dev/null +++ b/tests/Type/LazyTypeLoaderTest.php @@ -0,0 +1,381 @@ +loadedTypes[$name])) { + $type = null; + switch ($name) { + case 'Node': + $type = new InterfaceType([ + 'name' => 'Node', + 'fields' => function () : array { + $this->calls[] = 'Node.fields'; + + return [ + 'id' => Type::string(), + ]; + }, + 'resolveType' => static function () : void { + }, + ]); + break; + + case 'Content': + $type = new InterfaceType([ + 'name' => 'Content', + 'fields' => function () : array { + $this->calls[] = 'Content.fields'; + + return [ + 'title' => Type::string(), + 'body' => Type::string(), + ]; + }, + 'resolveType' => static function () : void { + }, + ]); + break; + + case 'BlogStory': + $type = new ObjectType([ + 'name' => 'BlogStory', + 'interfaces' => [ + $this->node, + $this->content, + ], + 'fields' => function () : array { + $this->calls[] = 'BlogStory.fields'; + + return [ + 'id' => Type::string(), + 'title' => Type::string(), + 'body' => Type::string(), + ]; + }, + ]); + break; + + case 'PostStoryMutation': + $type = new ObjectType([ + 'name' => 'PostStoryMutation', + 'fields' => [ + 'story' => $this->blogStory, + ], + ]); + break; + + case 'PostStoryMutationInput': + $type = new InputObjectType([ + 'name' => 'PostStoryMutationInput', + 'fields' => [ + 'title' => Type::string(), + 'body' => Type::string(), + 'author' => Type::id(), + 'category' => Type::id(), + ], + ]); + break; + } + $this->loadedTypes[$name] = $type; + } + + return $this->loadedTypes[$name]; + }; + } + + public function setUp() : void + { + $this->calls = []; + + $this->node = $this->lazyLoad('Node'); + $this->blogStory = $this->lazyLoad('BlogStory'); + $this->content = $this->lazyLoad('Content'); + $this->postStoryMutation = $this->lazyLoad('PostStoryMutation'); + $this->postStoryMutationInput = $this->lazyLoad('PostStoryMutationInput'); + $this->query = new ObjectType([ + 'name' => 'Query', + 'fields' => function () : array { + $this->calls[] = 'Query.fields'; + + return [ + 'latestContent' => $this->lazyLoad('Content'), + 'node' => $this->lazyLoad('Node'), + ]; + }, + ]); + + $this->mutation = new ObjectType([ + 'name' => 'Mutation', + 'fields' => function () : array { + $this->calls[] = 'Mutation.fields'; + + return [ + 'postStory' => [ + 'type' => $this->postStoryMutation, + 'args' => [ + 'input' => Type::nonNull($this->postStoryMutationInput), + 'clientRequestId' => Type::string(), + ], + ], + ]; + }, + ]); + + $this->typeLoader = function (string $name) : Type { + $this->calls[] = $name; + $prop = lcfirst($name); + + switch ($prop) { + case 'node': + return ($this->node)(); + case 'blogStory': + return ($this->blogStory)(); + case 'content': + return ($this->content)(); + case 'postStoryMutation': + return ($this->postStoryMutation)(); + case 'postStoryMutationInput': + return ($this->postStoryMutationInput)(); + } + + throw new InvalidArgument('Unknown type'); + }; + } + + public function testSchemaAcceptsTypeLoader() : void + { + $this->expectNotToPerformAssertions(); + new Schema([ + 'query' => new ObjectType([ + 'name' => 'Query', + 'fields' => ['a' => Type::string()], + ]), + 'typeLoader' => static function () : void { + }, + ]); + } + + public function testSchemaRejectsNonCallableTypeLoader() : void + { + $this->expectException(InvariantViolation::class); + $this->expectExceptionMessage('Schema type loader must be callable if provided but got: []'); + + new Schema([ + 'query' => new ObjectType([ + 'name' => 'Query', + 'fields' => ['a' => Type::string()], + ]), + 'typeLoader' => [], + ]); + } + + public function testWorksWithoutTypeLoader() : void + { + $schema = new Schema([ + 'query' => $this->query, + 'mutation' => $this->mutation, + 'types' => [Schema::resolveType($this->blogStory)], + ]); + + $expected = [ + 'Query.fields', + 'Content.fields', + 'Node.fields', + 'Mutation.fields', + 'BlogStory.fields', + ]; + self::assertEquals($expected, $this->calls); + + self::assertSame($this->query, $schema->getType('Query')); + self::assertSame($this->mutation, $schema->getType('Mutation')); + self::assertSame(Schema::resolveType($this->node), $schema->getType('Node')); + self::assertSame(Schema::resolveType($this->content), $schema->getType('Content')); + self::assertSame(Schema::resolveType($this->blogStory), $schema->getType('BlogStory')); + self::assertSame(Schema::resolveType($this->postStoryMutation), $schema->getType('PostStoryMutation')); + self::assertSame(Schema::resolveType($this->postStoryMutationInput), $schema->getType('PostStoryMutationInput')); + + $expectedTypeMap = [ + 'Query' => $this->query, + 'Mutation' => $this->mutation, + 'Node' => Schema::resolveType($this->node), + 'String' => Type::string(), + 'Content' => Schema::resolveType($this->content), + 'BlogStory' => Schema::resolveType($this->blogStory), + 'PostStoryMutationInput' => Schema::resolveType($this->postStoryMutationInput), + ]; + + self::assertArraySubset($expectedTypeMap, $schema->getTypeMap()); + } + + public function testWorksWithTypeLoader() : void + { + $schema = new Schema([ + 'query' => $this->query, + 'mutation' => $this->mutation, + 'typeLoader' => $this->typeLoader, + ]); + self::assertEquals([], $this->calls); + + $node = $schema->getType('Node'); + self::assertSame(Schema::resolveType($this->node), $node); + self::assertEquals(['Node'], $this->calls); + + $content = $schema->getType('Content'); + self::assertSame(Schema::resolveType($this->content), $content); + self::assertEquals(['Node', 'Content'], $this->calls); + + $input = $schema->getType('PostStoryMutationInput'); + self::assertSame(Schema::resolveType($this->postStoryMutationInput), $input); + self::assertEquals(['Node', 'Content', 'PostStoryMutationInput'], $this->calls); + + $result = $schema->isPossibleType( + Schema::resolveType($this->node), + Schema::resolveType($this->blogStory) + ); + self::assertTrue($result); + self::assertEquals(['Node', 'Content', 'PostStoryMutationInput'], $this->calls); + } + + public function testOnlyCallsLoaderOnce() : void + { + $schema = new Schema([ + 'query' => $this->query, + 'typeLoader' => $this->typeLoader, + ]); + + $schema->getType('Node'); + self::assertEquals(['Node'], $this->calls); + + $schema->getType('Node'); + self::assertEquals(['Node'], $this->calls); + } + + public function testFailsOnNonExistentType() : void + { + $schema = new Schema([ + 'query' => $this->query, + 'typeLoader' => static function () : void { + }, + ]); + + $this->expectException(InvariantViolation::class); + $this->expectExceptionMessage('Type loader is expected to return a callable or valid type "NonExistingType", but it returned null'); + + $schema->getType('NonExistingType'); + } + + public function testFailsOnNonType() : void + { + $schema = new Schema([ + 'query' => $this->query, + 'typeLoader' => static function () : stdClass { + return new stdClass(); + }, + ]); + + $this->expectException(InvariantViolation::class); + $this->expectExceptionMessage('Type loader is expected to return a callable or valid type "Node", but it returned instance of stdClass'); + + $schema->getType('Node'); + } + + public function testPassesThroughAnExceptionInLoader() : void + { + $schema = new Schema([ + 'query' => $this->query, + 'typeLoader' => static function () : void { + throw new Exception('This is the exception we are looking for'); + }, + ]); + + $this->expectException(Throwable::class); + $this->expectExceptionMessage('This is the exception we are looking for'); + + $schema->getType('Node'); + } + + public function testReturnsIdenticalResults() : void + { + $withoutLoader = new Schema([ + 'query' => $this->query, + 'mutation' => $this->mutation, + ]); + + $withLoader = new Schema([ + 'query' => $this->query, + 'mutation' => $this->mutation, + 'typeLoader' => $this->typeLoader, + ]); + + self::assertSame($withoutLoader->getQueryType(), $withLoader->getQueryType()); + self::assertSame($withoutLoader->getMutationType(), $withLoader->getMutationType()); + self::assertSame($withoutLoader->getType('BlogStory'), $withLoader->getType('BlogStory')); + self::assertSame($withoutLoader->getDirectives(), $withLoader->getDirectives()); + } + + public function testSkipsLoaderForInternalTypes() : void + { + $schema = new Schema([ + 'query' => $this->query, + 'mutation' => $this->mutation, + 'typeLoader' => $this->typeLoader, + ]); + + $type = $schema->getType('ID'); + self::assertSame(Type::id(), $type); + self::assertEquals([], $this->calls); + } +} diff --git a/tests/Type/TypeLoaderTest.php b/tests/Type/TypeLoaderTest.php index ddf8a2137..516b4cb72 100644 --- a/tests/Type/TypeLoaderTest.php +++ b/tests/Type/TypeLoaderTest.php @@ -264,7 +264,7 @@ public function testFailsOnNonExistentType() : void ]); $this->expectException(InvariantViolation::class); - $this->expectExceptionMessage('Type loader is expected to return valid type "NonExistingType", but it returned null'); + $this->expectExceptionMessage('Type loader is expected to return a callable or valid type "NonExistingType", but it returned null'); $schema->getType('NonExistingType'); } @@ -279,7 +279,7 @@ public function testFailsOnNonType() : void ]); $this->expectException(InvariantViolation::class); - $this->expectExceptionMessage('Type loader is expected to return valid type "Node", but it returned instance of stdClass'); + $this->expectExceptionMessage('Type loader is expected to return a callable or valid type "Node", but it returned instance of stdClass'); $schema->getType('Node'); } diff --git a/tests/Type/ValidationTest.php b/tests/Type/ValidationTest.php index f374643e7..b696abb67 100644 --- a/tests/Type/ValidationTest.php +++ b/tests/Type/ValidationTest.php @@ -1348,28 +1348,6 @@ public function testRejectsWithReleventLocationsForANonOutputTypeAsAnObjectField ); } - // DESCRIBE: Type System: Interface fields must have output types - - /** - * @see it('rejects an Object implementing a non-type values') - */ - public function testRejectsAnObjectImplementingANonTypeValues() : void - { - $schema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'BadObject', - 'interfaces' => [null], - 'fields' => ['a' => Type::string()], - ]), - ]); - $expected = ['message' => 'Type BadObject must only implement Interface types, it cannot implement null.']; - - $this->assertMatchesValidationMessage( - $schema->validate(), - [$expected] - ); - } - /** * @see it('rejects an Object implementing a non-Interface type') */ From beca6acad44fe0b7c23b33b0fe078b5cc69bd24d Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Fri, 19 Jun 2020 17:01:30 +0700 Subject: [PATCH 181/256] Use marker interfaces where appropriate --- src/Utils/BuildSchema.php | 7 +------ src/Validator/Rules/ExecutableDefinitions.php | 9 +++------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/Utils/BuildSchema.php b/src/Utils/BuildSchema.php index fb52b1110..8ffed96bd 100644 --- a/src/Utils/BuildSchema.php +++ b/src/Utils/BuildSchema.php @@ -116,12 +116,7 @@ public function buildSchema() case $definition instanceof SchemaDefinitionNode: $schemaDef = $definition; break; - case $definition instanceof ScalarTypeDefinitionNode: - case $definition instanceof ObjectTypeDefinitionNode: - case $definition instanceof InterfaceTypeDefinitionNode: - case $definition instanceof EnumTypeDefinitionNode: - case $definition instanceof UnionTypeDefinitionNode: - case $definition instanceof InputObjectTypeDefinitionNode: + case $definition instanceof TypeDefinitionNode: $typeName = $definition->name->value; if (! empty($this->nodeMap[$typeName])) { throw new Error(sprintf('Type "%s" was defined more than once.', $typeName)); diff --git a/src/Validator/Rules/ExecutableDefinitions.php b/src/Validator/Rules/ExecutableDefinitions.php index 3d1887daf..5966df7ef 100644 --- a/src/Validator/Rules/ExecutableDefinitions.php +++ b/src/Validator/Rules/ExecutableDefinitions.php @@ -6,9 +6,8 @@ use GraphQL\Error\Error; use GraphQL\Language\AST\DocumentNode; -use GraphQL\Language\AST\FragmentDefinitionNode; +use GraphQL\Language\AST\ExecutableDefinitionNode; use GraphQL\Language\AST\NodeKind; -use GraphQL\Language\AST\OperationDefinitionNode; use GraphQL\Language\AST\TypeSystemDefinitionNode; use GraphQL\Language\Visitor; use GraphQL\Language\VisitorOperation; @@ -27,11 +26,9 @@ public function getVisitor(ValidationContext $context) { return [ NodeKind::DOCUMENT => static function (DocumentNode $node) use ($context) : VisitorOperation { - /** @var FragmentDefinitionNode|OperationDefinitionNode|TypeSystemDefinitionNode $definition */ + /** @var ExecutableDefinitionNode|TypeSystemDefinitionNode $definition */ foreach ($node->definitions as $definition) { - if ($definition instanceof OperationDefinitionNode || - $definition instanceof FragmentDefinitionNode - ) { + if ($definition instanceof ExecutableDefinitionNode) { continue; } From 8ca6592d610991c443de173f0fcee0e1c231e62f Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Fri, 19 Jun 2020 17:21:30 +0700 Subject: [PATCH 182/256] Add `isRequired` convenience method for input fields and arguments --- src/Type/Definition/FieldArgument.php | 5 +++++ src/Type/Definition/InputObjectField.php | 5 +++++ src/Validator/Rules/ProvidedRequiredArguments.php | 2 +- src/Validator/Rules/ValuesOfCorrectType.php | 2 +- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Type/Definition/FieldArgument.php b/src/Type/Definition/FieldArgument.php index c8cc49c14..c88c66930 100644 --- a/src/Type/Definition/FieldArgument.php +++ b/src/Type/Definition/FieldArgument.php @@ -88,6 +88,11 @@ public function defaultValueExists() : bool return array_key_exists('defaultValue', $this->config); } + public function isRequired() : bool + { + return $this->getType() instanceof NonNull && ! $this->defaultValueExists(); + } + public function assertValid(FieldDefinition $parentField, Type $parentType) { try { diff --git a/src/Type/Definition/InputObjectField.php b/src/Type/Definition/InputObjectField.php index 0eae4a2b3..6af6fc8ae 100644 --- a/src/Type/Definition/InputObjectField.php +++ b/src/Type/Definition/InputObjectField.php @@ -69,6 +69,11 @@ public function defaultValueExists() : bool return array_key_exists('defaultValue', $this->config); } + public function isRequired() : bool + { + return $this->getType() instanceof NonNull && ! $this->defaultValueExists(); + } + /** * @throws InvariantViolation */ diff --git a/src/Validator/Rules/ProvidedRequiredArguments.php b/src/Validator/Rules/ProvidedRequiredArguments.php index 930163023..f179a09d3 100644 --- a/src/Validator/Rules/ProvidedRequiredArguments.php +++ b/src/Validator/Rules/ProvidedRequiredArguments.php @@ -34,7 +34,7 @@ public function getVisitor(ValidationContext $context) } foreach ($fieldDef->args as $argDef) { $argNode = $argNodeMap[$argDef->name] ?? null; - if ($argNode || (! ($argDef->getType() instanceof NonNull)) || $argDef->defaultValueExists()) { + if ($argNode || ! $argDef->isRequired()) { continue; } diff --git a/src/Validator/Rules/ValuesOfCorrectType.php b/src/Validator/Rules/ValuesOfCorrectType.php index 53b15423d..82edf4379 100644 --- a/src/Validator/Rules/ValuesOfCorrectType.php +++ b/src/Validator/Rules/ValuesOfCorrectType.php @@ -105,7 +105,7 @@ static function ($field) : string { ); foreach ($inputFields as $fieldName => $fieldDef) { $fieldType = $fieldDef->getType(); - if (isset($fieldNodeMap[$fieldName]) || ! ($fieldType instanceof NonNull) || ($fieldDef->defaultValueExists())) { + if (isset($fieldNodeMap[$fieldName]) || ! $fieldDef->isRequired()) { continue; } From 6000db9d4990873dc0c406328ebe0f4a505dd052 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Fri, 19 Jun 2020 17:24:14 +0700 Subject: [PATCH 183/256] Add deprecation comment about "commentDescriptions" option --- src/Utils/BuildSchema.php | 1 + src/Utils/SchemaPrinter.php | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Utils/BuildSchema.php b/src/Utils/BuildSchema.php index 8ffed96bd..6b46024f2 100644 --- a/src/Utils/BuildSchema.php +++ b/src/Utils/BuildSchema.php @@ -84,6 +84,7 @@ public static function build($source, ?callable $typeConfigDecorator = null, arr * * - commentDescriptions: * Provide true to use preceding comments as the description. + * This option is provided to ease adoption and will be removed in v16. * * @param bool[] $options * diff --git a/src/Utils/SchemaPrinter.php b/src/Utils/SchemaPrinter.php index cc68d26c8..763cac619 100644 --- a/src/Utils/SchemaPrinter.php +++ b/src/Utils/SchemaPrinter.php @@ -42,6 +42,7 @@ class SchemaPrinter * Available options: * - commentDescriptions: * Provide true to use preceding comments as the description. + * This option is provided to ease adoption and will be removed in v16. * * @api */ From 7654ef1c1cf525274c4bd59e5baf17a2d4435fe1 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Fri, 19 Jun 2020 18:21:42 +0700 Subject: [PATCH 184/256] Improved threshold for a list of suggestions on typos --- src/Utils/Utils.php | 3 +-- tests/Utils/SuggestionListTest.php | 2 +- tests/Validator/ValuesOfCorrectTypeTest.php | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Utils/Utils.php b/src/Utils/Utils.php index 0e6c18641..5811ac0af 100644 --- a/src/Utils/Utils.php +++ b/src/Utils/Utils.php @@ -635,7 +635,7 @@ static function ($list, $index) use ($selected, $selectedLength) : string { public static function suggestionList($input, array $options) { $optionsByDistance = []; - $inputThreshold = mb_strlen($input) / 2; + $threshold = mb_strlen($input) * 0.4 + 1; foreach ($options as $option) { if ($input === $option) { $distance = 0; @@ -644,7 +644,6 @@ public static function suggestionList($input, array $options) ? 1 : levenshtein($input, $option)); } - $threshold = max($inputThreshold, mb_strlen($option) / 2, 1); if ($distance > $threshold) { continue; } diff --git a/tests/Utils/SuggestionListTest.php b/tests/Utils/SuggestionListTest.php index 642382f06..6f5f10fcb 100644 --- a/tests/Utils/SuggestionListTest.php +++ b/tests/Utils/SuggestionListTest.php @@ -40,7 +40,7 @@ public function testReturnsOptionsSortedBasedOnSimilarity() : void { self::assertEquals( Utils::suggestionList('abc', ['a', 'ab', 'abc']), - ['abc', 'ab'] + ['abc', 'ab', 'a'] ); } } diff --git a/tests/Validator/ValuesOfCorrectTypeTest.php b/tests/Validator/ValuesOfCorrectTypeTest.php index 721f01f6b..525065e94 100644 --- a/tests/Validator/ValuesOfCorrectTypeTest.php +++ b/tests/Validator/ValuesOfCorrectTypeTest.php @@ -1321,8 +1321,7 @@ public function testPartialObjectUnknownFieldArg() : void 'ComplexInput', 'unknownField', 6, - 15, - 'Did you mean nonNullField, intField, or booleanField?' + 15 ), ] ); From cab159942136de30f105f8ea64f3124f600c90ed Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Fri, 19 Jun 2020 19:58:07 +0700 Subject: [PATCH 185/256] Refactor: validate directive arguments inside SDL --- phpstan-baseline.neon | 22 +-- src/Language/AST/DirectiveDefinitionNode.php | 2 +- src/Validator/Rules/KnownArgumentNames.php | 91 ++++-------- .../Rules/KnownArgumentNamesOnDirectives.php | 54 ++++--- .../Rules/ProvidedRequiredArguments.php | 49 +----- .../ProvidedRequiredArgumentsOnDirectives.php | 111 +++++++------- tests/Validator/KnownArgumentNamesTest.php | 140 +++++++++++++++++- .../ProvidedRequiredArgumentsTest.php | 118 ++++++++++++++- tests/Validator/ValidatorTestCase.php | 1 + 9 files changed, 391 insertions(+), 197 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index e47bdc0d7..8f51ee0b9 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -516,7 +516,7 @@ parameters: path: src/Utils/AST.php - - message: "#^Only booleans are allowed in a ternary operator condition, array\\ given\\.$#" + message: "#^Only booleans are allowed in a ternary operator condition, array\\ given\\.$#" count: 1 path: src/Utils/ASTDefinitionBuilder.php @@ -700,16 +700,6 @@ parameters: count: 2 path: src/Validator/Rules/FragmentsOnCompositeTypes.php - - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 2 - path: src/Validator/Rules/KnownArgumentNames.php - - - - message: "#^Only booleans are allowed in a negated boolean, array\\\\|null given\\.$#" - count: 1 - path: src/Validator/Rules/KnownArgumentNamesOnDirectives.php - - message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Type\\\\Schema\\|null given\\.$#" count: 1 @@ -810,21 +800,11 @@ parameters: count: 1 path: src/Validator/Rules/ProvidedRequiredArguments.php - - - message: "#^Only booleans are allowed in \\|\\|, array\\\\|null given on the left side\\.$#" - count: 1 - path: src/Validator/Rules/ProvidedRequiredArguments.php - - message: "#^Only booleans are allowed in a ternary operator condition, GraphQL\\\\Type\\\\Schema\\|null given\\.$#" count: 1 path: src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php - - - message: "#^Only booleans are allowed in a ternary operator condition, array\\ given\\.$#" - count: 1 - path: src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php - - message: "#^Only booleans are allowed in a negated boolean, array\\|null given\\.$#" count: 1 diff --git a/src/Language/AST/DirectiveDefinitionNode.php b/src/Language/AST/DirectiveDefinitionNode.php index 26aa23e3c..f95c42fb8 100644 --- a/src/Language/AST/DirectiveDefinitionNode.php +++ b/src/Language/AST/DirectiveDefinitionNode.php @@ -15,7 +15,7 @@ class DirectiveDefinitionNode extends Node implements TypeSystemDefinitionNode /** @var StringValueNode|null */ public $description; - /** @var ArgumentNode[] */ + /** @var InputValueDefinitionNode[] */ public $arguments; /** @var bool */ diff --git a/src/Validator/Rules/KnownArgumentNames.php b/src/Validator/Rules/KnownArgumentNames.php index 685f4176f..d3013797a 100644 --- a/src/Validator/Rules/KnownArgumentNames.php +++ b/src/Validator/Rules/KnownArgumentNames.php @@ -27,58 +27,40 @@ class KnownArgumentNames extends ValidationRule { public function getVisitor(ValidationContext $context) { - return [ - NodeKind::ARGUMENT => static function (ArgumentNode $node, $key, $parent, $path, $ancestors) use ($context) { + $knownArgumentNamesOnDirectives = new KnownArgumentNamesOnDirectives(); + + return $knownArgumentNamesOnDirectives->getVisitor($context) + [ + NodeKind::ARGUMENT => static function (ArgumentNode $node) use ($context) : void { $argDef = $context->getArgument(); if ($argDef !== null) { - return null; + return; } - /** @var Node|mixed $argumentOf */ - $argumentOf = $ancestors[count($ancestors) - 1]; - if ($argumentOf instanceof FieldNode) { - $fieldDef = $context->getFieldDef(); - $parentType = $context->getParentType(); - if ($fieldDef !== null && $parentType instanceof Type) { - $context->reportError(new Error( - self::unknownArgMessage( - $node->name->value, - $fieldDef->name, - $parentType->name, - Utils::suggestionList( - $node->name->value, - array_map( - static function ($arg) : string { - return $arg->name; - }, - $fieldDef->args - ) - ) - ), - [$node] - )); - } - } elseif ($argumentOf instanceof DirectiveNode) { - $directive = $context->getDirective(); - if ($directive) { - $context->reportError(new Error( - self::unknownDirectiveArgMessage( - $node->name->value, - $directive->name, - Utils::suggestionList( - $node->name->value, - array_map( - static function ($arg) { - return $arg->name; - }, - $directive->args - ) - ) - ), - [$node] - )); - } + $fieldDef = $context->getFieldDef(); + $parentType = $context->getParentType(); + if ($fieldDef === null || ! ($parentType instanceof Type)) { + return; } + + $context->reportError(new Error( + self::unknownArgMessage( + $node->name->value, + $fieldDef->name, + $parentType->name, + Utils::suggestionList( + $node->name->value, + array_map( + static function ($arg) : string { + return $arg->name; + }, + $fieldDef->args + ) + ) + ), + [$node] + )); + + return; }, ]; } @@ -89,20 +71,7 @@ static function ($arg) { public static function unknownArgMessage($argName, $fieldName, $typeName, array $suggestedArgs) { $message = sprintf('Unknown argument "%s" on field "%s" of type "%s".', $argName, $fieldName, $typeName); - if (! empty($suggestedArgs)) { - $message .= sprintf(' Did you mean %s?', Utils::quotedOrList($suggestedArgs)); - } - - return $message; - } - - /** - * @param string[] $suggestedArgs - */ - public static function unknownDirectiveArgMessage($argName, $directiveName, array $suggestedArgs) - { - $message = sprintf('Unknown argument "%s" on directive "@%s".', $argName, $directiveName); - if (! empty($suggestedArgs)) { + if (isset($suggestedArgs[0])) { $message .= sprintf(' Did you mean %s?', Utils::quotedOrList($suggestedArgs)); } diff --git a/src/Validator/Rules/KnownArgumentNamesOnDirectives.php b/src/Validator/Rules/KnownArgumentNamesOnDirectives.php index e636482c6..49b431fdd 100644 --- a/src/Validator/Rules/KnownArgumentNamesOnDirectives.php +++ b/src/Validator/Rules/KnownArgumentNamesOnDirectives.php @@ -9,13 +9,15 @@ use GraphQL\Language\AST\DirectiveNode; use GraphQL\Language\AST\InputValueDefinitionNode; use GraphQL\Language\AST\NodeKind; -use GraphQL\Language\AST\NodeList; use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\FieldArgument; +use GraphQL\Utils\Utils; +use GraphQL\Validator\ASTValidationContext; use GraphQL\Validator\SDLValidationContext; +use GraphQL\Validator\ValidationContext; use function array_map; use function in_array; -use function iterator_to_array; +use function sprintf; /** * Known argument names on directives @@ -25,12 +27,30 @@ */ class KnownArgumentNamesOnDirectives extends ValidationRule { - protected static function unknownDirectiveArgMessage(string $argName, string $directionName) + /** + * @param string[] $suggestedArgs + */ + public static function unknownDirectiveArgMessage($argName, $directiveName, array $suggestedArgs) { - return 'Unknown argument "' . $argName . '" on directive "@' . $directionName . '".'; + $message = sprintf('Unknown argument "%s" on directive "@%s".', $argName, $directiveName); + if (isset($suggestedArgs[0])) { + $message .= sprintf(' Did you mean %s?', Utils::quotedOrList($suggestedArgs)); + } + + return $message; } public function getSDLVisitor(SDLValidationContext $context) + { + return $this->getASTVisitor($context); + } + + public function getVisitor(ValidationContext $context) + { + return $this->getASTVisitor($context); + } + + public function getASTVisitor(ASTValidationContext $context) { $directiveArgs = []; $schema = $context->getSchema(); @@ -53,27 +73,24 @@ static function (FieldArgument $arg) : string { $name = $def->name->value; if ($def->arguments !== null) { - $arguments = $def->arguments; - - if ($arguments instanceof NodeList) { - $arguments = iterator_to_array($arguments->getIterator()); - } - - $directiveArgs[$name] = array_map(static function (InputValueDefinitionNode $arg) : string { - return $arg->name->value; - }, $arguments); + $directiveArgs[$name] = Utils::map( + $def->arguments ?? [], + static function (InputValueDefinitionNode $arg) : string { + return $arg->name->value; + } + ); } else { $directiveArgs[$name] = []; } } return [ - NodeKind::DIRECTIVE => static function (DirectiveNode $directiveNode) use ($directiveArgs, $context) : void { + NodeKind::DIRECTIVE => static function (DirectiveNode $directiveNode) use ($directiveArgs, $context) : bool { $directiveName = $directiveNode->name->value; $knownArgs = $directiveArgs[$directiveName] ?? null; - if ($directiveNode->arguments === null || ! $knownArgs) { - return; + if ($directiveNode->arguments === null || $knownArgs === null) { + return false; } foreach ($directiveNode->arguments as $argNode) { @@ -82,11 +99,14 @@ static function (FieldArgument $arg) : string { continue; } + $suggestions = Utils::suggestionList($argName, $knownArgs); $context->reportError(new Error( - self::unknownDirectiveArgMessage($argName, $directiveName), + self::unknownDirectiveArgMessage($argName, $directiveName, $suggestions), [$argNode] )); } + + return false; }, ]; } diff --git a/src/Validator/Rules/ProvidedRequiredArguments.php b/src/Validator/Rules/ProvidedRequiredArguments.php index f179a09d3..77a4aa5a8 100644 --- a/src/Validator/Rules/ProvidedRequiredArguments.php +++ b/src/Validator/Rules/ProvidedRequiredArguments.php @@ -5,12 +5,10 @@ namespace GraphQL\Validator\Rules; use GraphQL\Error\Error; -use GraphQL\Language\AST\DirectiveNode; use GraphQL\Language\AST\FieldNode; use GraphQL\Language\AST\NodeKind; use GraphQL\Language\Visitor; use GraphQL\Language\VisitorOperation; -use GraphQL\Type\Definition\NonNull; use GraphQL\Validator\ValidationContext; use function sprintf; @@ -18,8 +16,10 @@ class ProvidedRequiredArguments extends ValidationRule { public function getVisitor(ValidationContext $context) { - return [ - NodeKind::FIELD => [ + $providedRequiredArgumentsOnDirectives = new ProvidedRequiredArgumentsOnDirectives(); + + return $providedRequiredArgumentsOnDirectives->getVisitor($context) + [ + NodeKind::FIELD => [ 'leave' => static function (FieldNode $fieldNode) use ($context) : ?VisitorOperation { $fieldDef = $context->getFieldDef(); @@ -44,37 +44,6 @@ public function getVisitor(ValidationContext $context) )); } - return null; - }, - ], - NodeKind::DIRECTIVE => [ - 'leave' => static function (DirectiveNode $directiveNode) use ($context) : ?VisitorOperation { - $directiveDef = $context->getDirective(); - if (! $directiveDef) { - return Visitor::skipNode(); - } - $argNodes = $directiveNode->arguments ?? []; - $argNodeMap = []; - foreach ($argNodes as $argNode) { - $argNodeMap[$argNode->name->value] = $argNodes; - } - - foreach ($directiveDef->args as $argDef) { - $argNode = $argNodeMap[$argDef->name] ?? null; - if ($argNode || (! ($argDef->getType() instanceof NonNull)) || $argDef->defaultValueExists()) { - continue; - } - - $context->reportError(new Error( - self::missingDirectiveArgMessage( - $directiveNode->name->value, - $argDef->name, - $argDef->getType() - ), - [$directiveNode] - )); - } - return null; }, ], @@ -90,14 +59,4 @@ public static function missingFieldArgMessage($fieldName, $argName, $type) $type ); } - - public static function missingDirectiveArgMessage($directiveName, $argName, $type) - { - return sprintf( - 'Directive "@%s" argument "%s" of type "%s" is required but not provided.', - $directiveName, - $argName, - $type - ); - } } diff --git a/src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php b/src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php index 3c7c84b08..f8b34c713 100644 --- a/src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php +++ b/src/Validator/Rules/ProvidedRequiredArgumentsOnDirectives.php @@ -8,19 +8,17 @@ use GraphQL\Language\AST\ArgumentNode; use GraphQL\Language\AST\DirectiveDefinitionNode; use GraphQL\Language\AST\DirectiveNode; -use GraphQL\Language\AST\NamedTypeNode; -use GraphQL\Language\AST\Node; +use GraphQL\Language\AST\InputValueDefinitionNode; use GraphQL\Language\AST\NodeKind; -use GraphQL\Language\AST\NodeList; use GraphQL\Language\AST\NonNullTypeNode; +use GraphQL\Language\Printer; use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\FieldArgument; -use GraphQL\Type\Definition\NonNull; use GraphQL\Utils\Utils; +use GraphQL\Validator\ASTValidationContext; use GraphQL\Validator\SDLValidationContext; +use GraphQL\Validator\ValidationContext; use function array_filter; -use function is_array; -use function iterator_to_array; /** * Provided required arguments on directives @@ -30,12 +28,23 @@ */ class ProvidedRequiredArgumentsOnDirectives extends ValidationRule { - protected static function missingDirectiveArgMessage(string $directiveName, string $argName) + public static function missingDirectiveArgMessage(string $directiveName, string $argName, string $type) { - return 'Directive "' . $directiveName . '" argument "' . $argName . '" is required but ont provided.'; + return 'Directive "@' . $directiveName . '" argument "' . $argName + . '" of type "' . $type . '" is required but not provided.'; } public function getSDLVisitor(SDLValidationContext $context) + { + return $this->getASTVisitor($context); + } + + public function getVisitor(ValidationContext $context) + { + return $this->getASTVisitor($context); + } + + public function getASTVisitor(ASTValidationContext $context) { $requiredArgsMap = []; $schema = $context->getSchema(); @@ -46,7 +55,7 @@ public function getSDLVisitor(SDLValidationContext $context) foreach ($definedDirectives as $directive) { $requiredArgsMap[$directive->name] = Utils::keyMap( array_filter($directive->args, static function (FieldArgument $arg) : bool { - return $arg->getType() instanceof NonNull && ! isset($arg->defaultValue); + return $arg->isRequired(); }), static function (FieldArgument $arg) : string { return $arg->name; @@ -59,59 +68,61 @@ static function (FieldArgument $arg) : string { if (! ($def instanceof DirectiveDefinitionNode)) { continue; } - - if (is_array($def->arguments)) { - $arguments = $def->arguments; - } elseif ($def->arguments instanceof NodeList) { - $arguments = iterator_to_array($def->arguments->getIterator()); - } else { - $arguments = null; - } + $arguments = $def->arguments ?? []; $requiredArgsMap[$def->name->value] = Utils::keyMap( - $arguments - ? array_filter($arguments, static function (Node $argument) : bool { - return $argument instanceof NonNullTypeNode && - ( - ! isset($argument->defaultValue) || - $argument->defaultValue === null - ); - }) - : [], - static function (NamedTypeNode $argument) : string { + Utils::filter($arguments, static function (InputValueDefinitionNode $argument) : bool { + return $argument->type instanceof NonNullTypeNode && + ( + ! isset($argument->defaultValue) || + $argument->defaultValue === null + ); + }), + static function (InputValueDefinitionNode $argument) : string { return $argument->name->value; } ); } return [ - NodeKind::DIRECTIVE => static function (DirectiveNode $directiveNode) use ($requiredArgsMap, $context) : ?string { - $directiveName = $directiveNode->name->value; - $requiredArgs = $requiredArgsMap[$directiveName] ?? null; - if (! $requiredArgs) { - return null; - } - - $argNodes = $directiveNode->arguments ?? []; - $argNodeMap = Utils::keyMap( - $argNodes, - static function (ArgumentNode $arg) : string { - return $arg->name->value; + NodeKind::DIRECTIVE => [ + // Validate on leave to allow for deeper errors to appear first. + 'leave' => static function (DirectiveNode $directiveNode) use ($requiredArgsMap, $context) : ?string { + $directiveName = $directiveNode->name->value; + $requiredArgs = $requiredArgsMap[$directiveName] ?? null; + if (! $requiredArgs) { + return null; } - ); - foreach ($requiredArgs as $argName => $arg) { - if (isset($argNodeMap[$argName])) { - continue; - } - - $context->reportError( - new Error(static::missingDirectiveArgMessage($directiveName, $argName), [$directiveNode]) + $argNodes = $directiveNode->arguments ?? []; + $argNodeMap = Utils::keyMap( + $argNodes, + static function (ArgumentNode $arg) : string { + return $arg->name->value; + } ); - } - return null; - }, + foreach ($requiredArgs as $argName => $arg) { + if (isset($argNodeMap[$argName])) { + continue; + } + + if ($arg instanceof FieldArgument) { + $argType = (string) $arg->getType(); + } elseif ($arg instanceof InputValueDefinitionNode) { + $argType = Printer::doPrint($arg->type); + } else { + $argType = ''; + } + + $context->reportError( + new Error(static::missingDirectiveArgMessage($directiveName, $argName, $argType), [$directiveNode]) + ); + } + + return null; + }, + ], ]; } } diff --git a/tests/Validator/KnownArgumentNamesTest.php b/tests/Validator/KnownArgumentNamesTest.php index 7cb034f45..d29f92b76 100644 --- a/tests/Validator/KnownArgumentNamesTest.php +++ b/tests/Validator/KnownArgumentNamesTest.php @@ -6,7 +6,9 @@ use GraphQL\Error\FormattedError; use GraphQL\Language\SourceLocation; +use GraphQL\Utils\BuildSchema; use GraphQL\Validator\Rules\KnownArgumentNames; +use GraphQL\Validator\Rules\KnownArgumentNamesOnDirectives; class KnownArgumentNamesTest extends ValidatorTestCase { @@ -147,7 +149,7 @@ public function testUndirectiveArgsAreInvalid() : void private function unknownDirectiveArg($argName, $directiveName, $suggestedArgs, $line, $column) { return FormattedError::create( - KnownArgumentNames::unknownDirectiveArgMessage($argName, $directiveName, $suggestedArgs), + KnownArgumentNamesOnDirectives::unknownDirectiveArgMessage($argName, $directiveName, $suggestedArgs), [new SourceLocation($line, $column)] ); } @@ -260,4 +262,140 @@ public function testUnknownArgsDeeply() : void ] ); } + + // within SDL: + + /** + * @see it('known arg on directive defined inside SDL') + */ + public function testKnownArgOnDirectiveDefinedInsideSDL() + { + $this->expectPassesRule( + new KnownArgumentNamesOnDirectives(), + ' + type Query { + foo: String @test(arg: "") + } + + directive @test(arg: String) on FIELD_DEFINITION + ' + ); + } + + /** + * @see it('unknown arg on directive defined inside SDL') + */ + public function testUnknownArgOnDirectiveDefinedInsideSDL() + { + $this->expectFailsRule( + new KnownArgumentNamesOnDirectives(), + ' + type Query { + foo: String @test(unknown: "") + } + + directive @test(arg: String) on FIELD_DEFINITION + ', + [ + $this->unknownDirectiveArg('unknown', 'test', [], 3, 37), + ] + ); + } + + /** + * @see it('misspelled arg name is reported on directive defined inside SDL') + */ + public function testMisspelledArgNameIsReportedOnDirectiveDefinedInsideSDL() + { + $this->expectFailsRule( + new KnownArgumentNamesOnDirectives(), + ' + type Query { + foo: String @test(agr: "") + } + + directive @test(arg: String) on FIELD_DEFINITION + ', + [$this->unknownDirectiveArg('agr', 'test', ['arg'], 3, 37)] + ); + } + + /** + * @see it('unknown arg on standard directive') + */ + public function testUnknownArgOnStandardDirective() + { + $this->expectFailsRule( + new KnownArgumentNamesOnDirectives(), + ' + type Query { + foo: String @deprecated(unknown: "") + } + ', + [$this->unknownDirectiveArg('unknown', 'deprecated', [], 3, 43)] + ); + } + + /** + * @see it('unknown arg on overrided standard directive') + */ + public function testUnknownArgOnOverriddenStandardDirective() + { + $this->expectFailsRule( + new KnownArgumentNamesOnDirectives(), + ' + type Query { + foo: String @deprecated(reason: "") + } + directive @deprecated(arg: String) on FIELD + ', + [$this->unknownDirectiveArg('reason', 'deprecated', [], 3, 43)] + ); + } + + /** + * @see it('unknown arg on directive defined in schema extension') + */ + public function testUnknownArgOnDirectiveDefinedInSchemaExtension() + { + $schema = BuildSchema::build(' + type Query { + foo: String + } + '); + $sdl = ' + directive @test(arg: String) on OBJECT + + extend type Query @test(unknown: "") + '; + $this->expectInvalid( + $schema, + [new KnownArgumentNamesOnDirectives()], + $sdl, + [$this->unknownDirectiveArg('unknown', 'test', [], 4, 38)] + ); + } + + /** + * @see it('unknown arg on directive used in schema extension') + */ + public function testUnknownArgOnDirectiveUsedInSchemaExtension() + { + $schema = BuildSchema::build(' + directive @test(arg: String) on OBJECT + + type Query { + foo: String + } + '); + $sdl = ' + extend type Query @test(unknown: "") + '; + $this->expectInvalid( + $schema, + [new KnownArgumentNamesOnDirectives()], + $sdl, + [$this->unknownDirectiveArg('unknown', 'test', [], 2, 37)] + ); + } } diff --git a/tests/Validator/ProvidedRequiredArgumentsTest.php b/tests/Validator/ProvidedRequiredArgumentsTest.php index bf1d98e34..bb0c09e3d 100644 --- a/tests/Validator/ProvidedRequiredArgumentsTest.php +++ b/tests/Validator/ProvidedRequiredArgumentsTest.php @@ -6,7 +6,9 @@ use GraphQL\Error\FormattedError; use GraphQL\Language\SourceLocation; +use GraphQL\Utils\BuildSchema; use GraphQL\Validator\Rules\ProvidedRequiredArguments; +use GraphQL\Validator\Rules\ProvidedRequiredArgumentsOnDirectives; class ProvidedRequiredArgumentsTest extends ValidatorTestCase { @@ -346,10 +348,124 @@ public function testWithDirectiveWithMissingTypes() : void ); } + // Describe: within SDL + + /** + * @see it('Missing optional args on directive defined inside SDL') + */ + public function testMissingOptionalArgsOnDirectiveDefinedInsideSDL() + { + $this->expectPassesRule( + new ProvidedRequiredArgumentsOnDirectives(), + ' + type Query { + foo: String @test + } + + directive @test(arg1: String, arg2: String! = "") on FIELD_DEFINITION + ' + ); + } + + /** + * @see it('Missing arg on directive defined inside SDL') + */ + public function testMissingArgOnDirectiveDefinedInsideSDL() + { + $this->expectFailsRule( + new ProvidedRequiredArgumentsOnDirectives(), + ' + type Query { + foo: String @test + } + + directive @test(arg: String!) on FIELD_DEFINITION + ', + [$this->missingDirectiveArg('test', 'arg', 'String!', 3, 31)] + ); + } + + /** + * @see it('Missing arg on standard directive') + */ + public function testMissingArgOnStandardDirective() + { + $this->expectFailsRule( + new ProvidedRequiredArgumentsOnDirectives(), + ' + type Query { + foo: String @include + } + ', + [$this->missingDirectiveArg('include', 'if', 'Boolean!', 3, 31)] + ); + } + + /** + * @see it('Missing arg on overrided standard directive') + */ + public function testMissingArgOnOverridedStandardDirective() + { + $this->expectFailsRule( + new ProvidedRequiredArgumentsOnDirectives(), + ' + type Query { + foo: String @deprecated + } + directive @deprecated(reason: String!) on FIELD + ', + [$this->missingDirectiveArg('deprecated', 'reason', 'String!', 3, 31)] + ); + } + + /** + * @see it('Missing arg on directive defined in schema extension') + */ + public function testMissingArgOnDirectiveDefinedInSchemaExtension() + { + $schema = BuildSchema::build(' + type Query { + foo: String + } + '); + $this->expectInvalid( + $schema, + [new ProvidedRequiredArgumentsOnDirectives()], + ' + directive @test(arg: String!) on OBJECT + + extend type Query @test + ', + [$this->missingDirectiveArg('test', 'arg', 'String!', 4, 36)] + ); + } + + /** + * @see it('Missing arg on directive used in schema extension') + */ + public function testMissingArgOnDirectiveUsedInSchemaExtension() + { + $schema = BuildSchema::build(' + directive @test(arg: String!) on OBJECT + + type Query { + foo: String + } + '); + $this->expectInvalid( + $schema, + [new ProvidedRequiredArgumentsOnDirectives()], + ' + extend type Query @test + ', + [$this->missingDirectiveArg('test', 'arg', 'String!', 2, 35)] + ); + } + private function missingDirectiveArg($directiveName, $argName, $typeName, $line, $column) { return FormattedError::create( - ProvidedRequiredArguments::missingDirectiveArgMessage($directiveName, $argName, $typeName), + ProvidedRequiredArgumentsOnDirectives::missingDirectiveArgMessage($directiveName, $argName, $typeName), [new SourceLocation($line, $column)] ); } diff --git a/tests/Validator/ValidatorTestCase.php b/tests/Validator/ValidatorTestCase.php index 6af9b580c..018dcb527 100644 --- a/tests/Validator/ValidatorTestCase.php +++ b/tests/Validator/ValidatorTestCase.php @@ -355,6 +355,7 @@ public static function getTestSchema() 'directives' => [ Directive::includeDirective(), Directive::skipDirective(), + Directive::deprecatedDirective(), new Directive([ 'name' => 'directive', 'locations' => [DirectiveLocation::FIELD], From ba7e60ff1398c6f0aabcf69c0aad298d0f7e0386 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Fri, 19 Jun 2020 20:02:17 +0700 Subject: [PATCH 186/256] Fix link and description of supported Markdown See https://spec.graphql.org/June2018/#sec--deprecated --- src/Type/Definition/Directive.php | 4 ++-- tests/Utils/SchemaPrinterTest.php | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Type/Definition/Directive.php b/src/Type/Definition/Directive.php index c27025c50..10878e99c 100644 --- a/src/Type/Definition/Directive.php +++ b/src/Type/Definition/Directive.php @@ -140,8 +140,8 @@ public static function getInternalDirectives() : array 'type' => Type::string(), 'description' => 'Explains why this element was deprecated, usually also including a ' . - 'suggestion for how to access supported similar data. Formatted ' . - 'in [Markdown](https://daringfireball.net/projects/markdown/).', + 'suggestion for how to access supported similar data. Formatted using ' . + 'the Markdown syntax (as specified by [CommonMark](https://commonmark.org/).', 'defaultValue' => self::DEFAULT_DEPRECATION_REASON, ]), ], diff --git a/tests/Utils/SchemaPrinterTest.php b/tests/Utils/SchemaPrinterTest.php index be8c4d7db..9ffc51437 100644 --- a/tests/Utils/SchemaPrinterTest.php +++ b/tests/Utils/SchemaPrinterTest.php @@ -878,8 +878,8 @@ public function testPrintIntrospectionSchema() : void directive @deprecated( """ Explains why this element was deprecated, usually also including a suggestion - for how to access supported similar data. Formatted in - [Markdown](https://daringfireball.net/projects/markdown/). + for how to access supported similar data. Formatted using the Markdown syntax + (as specified by [CommonMark](https://commonmark.org/). """ reason: String = "No longer supported" ) on FIELD_DEFINITION | ENUM_VALUE @@ -1122,8 +1122,8 @@ public function testPrintIntrospectionSchemaWithCommentDescriptions() : void # Marks an element of a GraphQL schema as no longer supported. directive @deprecated( # Explains why this element was deprecated, usually also including a suggestion - # for how to access supported similar data. Formatted in - # [Markdown](https://daringfireball.net/projects/markdown/). + # for how to access supported similar data. Formatted using the Markdown syntax + # (as specified by [CommonMark](https://commonmark.org/). reason: String = "No longer supported" ) on FIELD_DEFINITION | ENUM_VALUE From 51211256bb99aa4d45093ce63ee4ab8ca1f037a2 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Fri, 19 Jun 2020 20:24:24 +0700 Subject: [PATCH 187/256] Correctly detect required/optional args/fields See https://github.com/graphql/graphql-js/pull/1465#issuecomment-413699023 --- CHANGELOG.md | 6 ++++ src/Utils/BreakingChangesFinder.php | 38 +++++++++++------------ tests/Utils/BreakingChangesFinderTest.php | 36 +++++++++++---------- 3 files changed, 45 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6da18f874..d973a2ca6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ - **BREAKING:** Removal of `VariablesDefaultValueAllowed` validation rule. All variables may now specify a default value. - **BREAKING:** renamed `ProvidedNonNullArguments` to `ProvidedRequiredArguments` (no longer require values to be provided to non-null arguments which provide a default value). - **BREAKING:** `GraphQL\Deferred` now extends `GraphQL\Executor\Promise\Adapter\SyncPromise` +- **BREAKING:** renamed following types of dangerous/breaking changes (returned by `BreakingChangesFinder`): + - `NON_NULL_ARG_ADDED` to `REQUIRED_ARG_ADDED` + - `NON_NULL_INPUT_FIELD_ADDED` to `REQUIRED_INPUT_FIELD_ADDED` + - `NON_NULL_DIRECTIVE_ARG_ADDED` to `REQUIRED_DIRECTIVE_ARG_ADDED` + - `NULLABLE_INPUT_FIELD_ADDED` to `OPTIONAL_INPUT_FIELD_ADDED` + - `NULLABLE_ARG_ADDED` to `OPTIONAL_ARG_ADDED` - Add schema validation: Input Objects must not contain non-nullable circular references (#492) - Added retrieving query complexity once query has been completed (#316) - Allow input types to be passed in from variables using \stdClass instead of associative arrays (#535) diff --git a/src/Utils/BreakingChangesFinder.php b/src/Utils/BreakingChangesFinder.php index 3eec2b071..e0d244366 100644 --- a/src/Utils/BreakingChangesFinder.php +++ b/src/Utils/BreakingChangesFinder.php @@ -39,19 +39,19 @@ class BreakingChangesFinder public const BREAKING_CHANGE_VALUE_REMOVED_FROM_ENUM = 'VALUE_REMOVED_FROM_ENUM'; public const BREAKING_CHANGE_ARG_REMOVED = 'ARG_REMOVED'; public const BREAKING_CHANGE_ARG_CHANGED_KIND = 'ARG_CHANGED_KIND'; - public const BREAKING_CHANGE_NON_NULL_ARG_ADDED = 'NON_NULL_ARG_ADDED'; - public const BREAKING_CHANGE_NON_NULL_INPUT_FIELD_ADDED = 'NON_NULL_INPUT_FIELD_ADDED'; + public const BREAKING_CHANGE_REQUIRED_ARG_ADDED = 'REQUIRED_ARG_ADDED'; + public const BREAKING_CHANGE_REQUIRED_INPUT_FIELD_ADDED = 'REQUIRED_INPUT_FIELD_ADDED'; public const BREAKING_CHANGE_INTERFACE_REMOVED_FROM_OBJECT = 'INTERFACE_REMOVED_FROM_OBJECT'; public const BREAKING_CHANGE_DIRECTIVE_REMOVED = 'DIRECTIVE_REMOVED'; public const BREAKING_CHANGE_DIRECTIVE_ARG_REMOVED = 'DIRECTIVE_ARG_REMOVED'; public const BREAKING_CHANGE_DIRECTIVE_LOCATION_REMOVED = 'DIRECTIVE_LOCATION_REMOVED'; - public const BREAKING_CHANGE_NON_NULL_DIRECTIVE_ARG_ADDED = 'NON_NULL_DIRECTIVE_ARG_ADDED'; + public const BREAKING_CHANGE_REQUIRED_DIRECTIVE_ARG_ADDED = 'REQUIRED_DIRECTIVE_ARG_ADDED'; public const DANGEROUS_CHANGE_ARG_DEFAULT_VALUE_CHANGED = 'ARG_DEFAULT_VALUE_CHANGE'; public const DANGEROUS_CHANGE_VALUE_ADDED_TO_ENUM = 'VALUE_ADDED_TO_ENUM'; public const DANGEROUS_CHANGE_INTERFACE_ADDED_TO_OBJECT = 'INTERFACE_ADDED_TO_OBJECT'; public const DANGEROUS_CHANGE_TYPE_ADDED_TO_UNION = 'TYPE_ADDED_TO_UNION'; - public const DANGEROUS_CHANGE_NULLABLE_INPUT_FIELD_ADDED = 'NULLABLE_INPUT_FIELD_ADDED'; - public const DANGEROUS_CHANGE_NULLABLE_ARG_ADDED = 'NULLABLE_ARG_ADDED'; + public const DANGEROUS_CHANGE_OPTIONAL_INPUT_FIELD_ADDED = 'OPTIONAL_INPUT_FIELD_ADDED'; + public const DANGEROUS_CHANGE_OPTIONAL_ARG_ADDED = 'OPTIONAL_ARG_ADDED'; /** * Given two schemas, returns an Array containing descriptions of all the types @@ -328,15 +328,15 @@ public static function findFieldsThatChangedTypeOnInputObjectTypes( } $newTypeName = $newType->name; - if ($fieldDef->getType() instanceof NonNull) { + if ($fieldDef->isRequired()) { $breakingChanges[] = [ - 'type' => self::BREAKING_CHANGE_NON_NULL_INPUT_FIELD_ADDED, - 'description' => "A non-null field ${fieldName} on input type ${newTypeName} was added.", + 'type' => self::BREAKING_CHANGE_REQUIRED_INPUT_FIELD_ADDED, + 'description' => "A required field ${fieldName} on input type ${newTypeName} was added.", ]; } else { $dangerousChanges[] = [ - 'type' => self::DANGEROUS_CHANGE_NULLABLE_INPUT_FIELD_ADDED, - 'description' => "A nullable field ${fieldName} on input type ${newTypeName} was added.", + 'type' => self::DANGEROUS_CHANGE_OPTIONAL_INPUT_FIELD_ADDED, + 'description' => "An optional field ${fieldName} on input type ${newTypeName} was added.", ]; } } @@ -539,7 +539,7 @@ static function ($arg) use ($oldArgDef) : bool { ), ]; } - // Check if a non-null arg was added to the field + // Check if arg was added to the field foreach ($newTypeFields[$fieldName]->args as $newTypeFieldArgDef) { $oldArgs = $oldTypeFields[$fieldName]->args; $oldArgDef = Utils::find( @@ -555,15 +555,15 @@ static function ($arg) use ($newTypeFieldArgDef) : bool { $newTypeName = $newType->name; $newArgName = $newTypeFieldArgDef->name; - if ($newTypeFieldArgDef->getType() instanceof NonNull) { + if ($newTypeFieldArgDef->isRequired()) { $breakingChanges[] = [ - 'type' => self::BREAKING_CHANGE_NON_NULL_ARG_ADDED, - 'description' => "A non-null arg ${newArgName} on ${newTypeName}.${fieldName} was added", + 'type' => self::BREAKING_CHANGE_REQUIRED_ARG_ADDED, + 'description' => "A required arg ${newArgName} on ${newTypeName}.${fieldName} was added", ]; } else { $dangerousChanges[] = [ - 'type' => self::DANGEROUS_CHANGE_NULLABLE_ARG_ADDED, - 'description' => "A nullable arg ${newArgName} on ${newTypeName}.${fieldName} was added", + 'type' => self::DANGEROUS_CHANGE_OPTIONAL_ARG_ADDED, + 'description' => "An optional arg ${newArgName} on ${newTypeName}.${fieldName} was added", ]; } } @@ -712,13 +712,13 @@ public static function findAddedNonNullDirectiveArgs(Schema $oldSchema, Schema $ $oldSchemaDirectiveMap[$newDirective->name], $newDirective ) as $arg) { - if (! $arg->getType() instanceof NonNull) { + if (! $arg->isRequired()) { continue; } $addedNonNullableArgs[] = [ - 'type' => self::BREAKING_CHANGE_NON_NULL_DIRECTIVE_ARG_ADDED, + 'type' => self::BREAKING_CHANGE_REQUIRED_DIRECTIVE_ARG_ADDED, 'description' => sprintf( - 'A non-null arg %s on directive %s was added', + 'A required arg %s on directive %s was added', $arg->name, $newDirective->name ), diff --git a/tests/Utils/BreakingChangesFinderTest.php b/tests/Utils/BreakingChangesFinderTest.php index cf2d022a6..dc09e58fa 100644 --- a/tests/Utils/BreakingChangesFinderTest.php +++ b/tests/Utils/BreakingChangesFinderTest.php @@ -486,7 +486,7 @@ public function testShouldDetectIfFieldsOnInputTypesChangedKindOrWereRemoved() : } /** - * @see it('should detect if a non-null field is added to an input type') + * @see it('should detect if a required field is added to an input type') */ public function testShouldDetectIfANonNullFieldIsAddedToAnInputType() : void { @@ -500,9 +500,13 @@ public function testShouldDetectIfANonNullFieldIsAddedToAnInputType() : void $newInputType = new InputObjectType([ 'name' => 'InputType1', 'fields' => [ - 'field1' => Type::string(), - 'requiredField' => Type::nonNull(Type::int()), - 'optionalField' => Type::boolean(), + 'field1' => Type::string(), + 'requiredField' => Type::nonNull(Type::int()), + 'optionalField1' => Type::boolean(), + 'optionalField2' => [ + 'type' => Type::nonNull(Type::boolean()), + 'defaultValue' => false, + ], ], ]); @@ -518,8 +522,8 @@ public function testShouldDetectIfANonNullFieldIsAddedToAnInputType() : void $expected = [ [ - 'type' => BreakingChangesFinder::BREAKING_CHANGE_NON_NULL_INPUT_FIELD_ADDED, - 'description' => 'A non-null field requiredField on input type InputType1 was added.', + 'type' => BreakingChangesFinder::BREAKING_CHANGE_REQUIRED_INPUT_FIELD_ADDED, + 'description' => 'A required field requiredField on input type InputType1 was added.', ], ]; @@ -889,8 +893,8 @@ public function testShouldDetectIfANonNullFieldArgumentWasAdded() : void $expected = [ [ - 'type' => BreakingChangesFinder::BREAKING_CHANGE_NON_NULL_ARG_ADDED, - 'description' => 'A non-null arg newRequiredArg on Type1.field1 was added', + 'type' => BreakingChangesFinder::BREAKING_CHANGE_REQUIRED_ARG_ADDED, + 'description' => 'A required arg newRequiredArg on Type1.field1 was added', ], ]; @@ -1299,8 +1303,8 @@ public function testShouldDetectAllBreakingChanges() : void 'description' => 'arg1 was removed from DirectiveThatRemovesArg', ], [ - 'type' => BreakingChangesFinder::BREAKING_CHANGE_NON_NULL_DIRECTIVE_ARG_ADDED, - 'description' => 'A non-null arg arg1 on directive NonNullDirectiveAdded was added', + 'type' => BreakingChangesFinder::BREAKING_CHANGE_REQUIRED_DIRECTIVE_ARG_ADDED, + 'description' => 'A required arg arg1 on directive NonNullDirectiveAdded was added', ], [ 'type' => BreakingChangesFinder::BREAKING_CHANGE_DIRECTIVE_LOCATION_REMOVED, @@ -1441,8 +1445,8 @@ public function testShouldDetectIfANonNullableDirectiveArgumentWasAdded() : void $expectedBreakingChanges = [ [ - 'type' => BreakingChangesFinder::BREAKING_CHANGE_NON_NULL_DIRECTIVE_ARG_ADDED, - 'description' => 'A non-null arg arg1 on directive DirectiveName was added', + 'type' => BreakingChangesFinder::BREAKING_CHANGE_REQUIRED_DIRECTIVE_ARG_ADDED, + 'description' => 'A required arg arg1 on directive DirectiveName was added', ], ]; @@ -1761,8 +1765,8 @@ public function testShouldDetectIfANullableFieldWasAddedToAnInput() : void $expectedFieldChanges = [ [ - 'description' => 'A nullable field field2 on input type InputType1 was added.', - 'type' => BreakingChangesFinder::DANGEROUS_CHANGE_NULLABLE_INPUT_FIELD_ADDED, + 'description' => 'An optional field field2 on input type InputType1 was added.', + 'type' => BreakingChangesFinder::DANGEROUS_CHANGE_OPTIONAL_INPUT_FIELD_ADDED, ], ]; @@ -1962,8 +1966,8 @@ public function testShouldDetectIfANullableFieldArgumentWasAdded() : void $expectedFieldChanges = [ [ - 'description' => 'A nullable arg arg2 on Type1.field1 was added', - 'type' => BreakingChangesFinder::DANGEROUS_CHANGE_NULLABLE_ARG_ADDED, + 'description' => 'An optional arg arg2 on Type1.field1 was added', + 'type' => BreakingChangesFinder::DANGEROUS_CHANGE_OPTIONAL_ARG_ADDED, ], ]; From 21426667c5f1372107325bd6b2de3df6213723dd Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Fri, 19 Jun 2020 20:31:18 +0700 Subject: [PATCH 188/256] Allow to add optional args to fields implemented from interfaces --- src/Type/SchemaValidationContext.php | 7 +++---- tests/Type/ValidationTest.php | 18 ++++++++++++------ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/Type/SchemaValidationContext.php b/src/Type/SchemaValidationContext.php index 3ac18ede6..f16d5e7cd 100644 --- a/src/Type/SchemaValidationContext.php +++ b/src/Type/SchemaValidationContext.php @@ -841,22 +841,21 @@ private function validateObjectImplementsInterface(ObjectType $object, $iface) } } - if ($ifaceArg || ! ($objectArg->getType() instanceof NonNull)) { + if ($ifaceArg || ! $objectArg->isRequired()) { continue; } $this->reportError( sprintf( - 'Object field argument %s.%s(%s:) is of required type %s but is not also provided by the Interface field %s.%s.', + 'Object field %s.%s includes required argument %s that is missing from the Interface field %s.%s.', $object->name, $fieldName, $argName, - Utils::printSafe($objectArg->getType()), $iface->name, $fieldName ), [ - $this->getFieldArgTypeNode($object, $fieldName, $argName), + $this->getFieldArgNode($object, $fieldName, $argName), $this->getFieldNode($iface, $fieldName), ] ); diff --git a/tests/Type/ValidationTest.php b/tests/Type/ValidationTest.php index b696abb67..f27a128ca 100644 --- a/tests/Type/ValidationTest.php +++ b/tests/Type/ValidationTest.php @@ -2178,21 +2178,27 @@ public function testRejectsAnObjectWhichImplementsAnInterfaceFieldAlongWithAddit } interface AnotherInterface { - field(input: String): String + field(baseArg: String): String } type AnotherObject implements AnotherInterface { - field(input: String, anotherInput: String!): String + field( + baseArg: String, + requiredArg: String! + optionalArg1: String, + optionalArg2: String = "", + ): String } '); $this->assertMatchesValidationMessage( $schema->validate(), [[ - 'message' => 'Object field argument AnotherObject.field(anotherInput:) is of ' . - 'required type String! but is not also provided by the Interface ' . - 'field AnotherInterface.field.', - 'locations' => [['line' => 11, 'column' => 44], ['line' => 7, 'column' => 9]], + 'message' => + 'Object field AnotherObject.field includes required argument ' . + 'requiredArg that is missing from the Interface field ' . + 'AnotherInterface.field.', + 'locations' => [['line' => 13, 'column' => 11], ['line' => 7, 'column' => 9]], ], ] ); From 4d7302c384f5eefe71ec6674cbfd50b5fba4bf68 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Fri, 19 Jun 2020 20:34:25 +0700 Subject: [PATCH 189/256] Correct error message about resolve specified on input fields --- src/Type/Definition/InputObjectField.php | 2 +- tests/Type/DefinitionTest.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Type/Definition/InputObjectField.php b/src/Type/Definition/InputObjectField.php index 6af6fc8ae..e05ff4eb5 100644 --- a/src/Type/Definition/InputObjectField.php +++ b/src/Type/Definition/InputObjectField.php @@ -100,7 +100,7 @@ public function assertValid(Type $parentType) Utils::invariant( empty($this->config['resolve']), sprintf( - '%s.%s field type has a resolve property, but Input Types cannot define resolvers.', + '%s.%s field has a resolve property, but Input Types cannot define resolvers.', $parentType->name, $this->name ) diff --git a/tests/Type/DefinitionTest.php b/tests/Type/DefinitionTest.php index 3a750be6a..c189c8d17 100644 --- a/tests/Type/DefinitionTest.php +++ b/tests/Type/DefinitionTest.php @@ -1494,7 +1494,7 @@ public function testRejectsAnInputObjectTypeWithResolvers() : void ]); $this->expectException(InvariantViolation::class); $this->expectExceptionMessage( - 'SomeInputObject.f field type has a resolve property, ' . + 'SomeInputObject.f field has a resolve property, ' . 'but Input Types cannot define resolvers.' ); $inputObjType->assertValid(); @@ -1518,7 +1518,7 @@ public function testRejectsAnInputObjectTypeWithResolverConstant() : void ]); $this->expectException(InvariantViolation::class); $this->expectExceptionMessage( - 'SomeInputObject.f field type has a resolve property, ' . + 'SomeInputObject.f field has a resolve property, ' . 'but Input Types cannot define resolvers.' ); $inputObjType->assertValid(); From 3fd08ebf671b240e6d0add1562cefd3c401ff327 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Fri, 19 Jun 2020 15:39:11 +0200 Subject: [PATCH 190/256] Allow extending SchemaPrinter by making its methods protected (#671) This opens up a much needed escape hatch for experimental features such as Apollo Federation, see https://github.com/webonyx/graphql-php/issues/552 --- src/Utils/SchemaPrinter.php | 40 ++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Utils/SchemaPrinter.php b/src/Utils/SchemaPrinter.php index 763cac619..ac904a6e3 100644 --- a/src/Utils/SchemaPrinter.php +++ b/src/Utils/SchemaPrinter.php @@ -63,7 +63,7 @@ static function ($type) : bool { /** * @param array $options */ - private static function printFilteredSchema(Schema $schema, callable $directiveFilter, callable $typeFilter, array $options) : string + protected static function printFilteredSchema(Schema $schema, callable $directiveFilter, callable $typeFilter, array $options) : string { $directives = array_filter($schema->getDirectives(), $directiveFilter); @@ -96,7 +96,7 @@ static function ($type) use ($options) : string { ); } - private static function printSchemaDefinition(Schema $schema) : string + protected static function printSchemaDefinition(Schema $schema) : string { if (self::isSchemaOfCommonNames($schema)) { return ''; @@ -134,7 +134,7 @@ private static function printSchemaDefinition(Schema $schema) : string * * When using this naming convention, the schema description can be omitted. */ - private static function isSchemaOfCommonNames(Schema $schema) : bool + protected static function isSchemaOfCommonNames(Schema $schema) : bool { $queryType = $schema->getQueryType(); if ($queryType !== null && $queryType->name !== 'Query') { @@ -154,7 +154,7 @@ private static function isSchemaOfCommonNames(Schema $schema) : bool /** * @param array $options */ - private static function printDirective(Directive $directive, array $options) : string + protected static function printDirective(Directive $directive, array $options) : string { return self::printDescription($options, $directive) . 'directive @' . $directive->name @@ -166,7 +166,7 @@ private static function printDirective(Directive $directive, array $options) : s /** * @param array $options */ - private static function printDescription(array $options, $def, $indentation = '', $firstInBlock = true) : string + protected static function printDescription(array $options, $def, $indentation = '', $firstInBlock = true) : string { if (! $def->description) { return ''; @@ -213,7 +213,7 @@ private static function printDescription(array $options, $def, $indentation = '' /** * @return string[] */ - private static function descriptionLines(string $description, int $maxLen) : array + protected static function descriptionLines(string $description, int $maxLen) : array { $lines = []; $rawLines = explode("\n", $description); @@ -236,7 +236,7 @@ private static function descriptionLines(string $description, int $maxLen) : arr /** * @return string[] */ - private static function breakLine(string $line, int $maxLen) : array + protected static function breakLine(string $line, int $maxLen) : array { if (strlen($line) < $maxLen + 5) { return [$line]; @@ -247,7 +247,7 @@ private static function breakLine(string $line, int $maxLen) : array return array_map('trim', $parts); } - private static function printDescriptionWithComments($lines, $indentation, $firstInBlock) : string + protected static function printDescriptionWithComments($lines, $indentation, $firstInBlock) : string { $description = $indentation && ! $firstInBlock ? "\n" : ''; foreach ($lines as $line) { @@ -261,7 +261,7 @@ private static function printDescriptionWithComments($lines, $indentation, $firs return $description; } - private static function escapeQuote($line) : string + protected static function escapeQuote($line) : string { return str_replace('"""', '\\"""', $line); } @@ -269,7 +269,7 @@ private static function escapeQuote($line) : string /** * @param array $options */ - private static function printArgs(array $options, $args, $indentation = '') : string + protected static function printArgs(array $options, $args, $indentation = '') : string { if (! $args) { return ''; @@ -302,7 +302,7 @@ static function ($arg, $i) use ($indentation, $options) : string { ); } - private static function printInputValue($arg) : string + protected static function printInputValue($arg) : string { $argDecl = $arg->name . ': ' . (string) $arg->getType(); if ($arg->defaultValueExists()) { @@ -347,7 +347,7 @@ public static function printType(Type $type, array $options = []) : string /** * @param array $options */ - private static function printScalar(ScalarType $type, array $options) : string + protected static function printScalar(ScalarType $type, array $options) : string { return sprintf('%sscalar %s', self::printDescription($options, $type), $type->name); } @@ -355,7 +355,7 @@ private static function printScalar(ScalarType $type, array $options) : string /** * @param array $options */ - private static function printObject(ObjectType $type, array $options) : string + protected static function printObject(ObjectType $type, array $options) : string { $interfaces = $type->getInterfaces(); $implementedInterfaces = ! empty($interfaces) @@ -377,7 +377,7 @@ static function (InterfaceType $interface) : string { /** * @param array $options */ - private static function printFields(array $options, $type) : string + protected static function printFields(array $options, $type) : string { $fields = array_values($type->getFields()); @@ -395,7 +395,7 @@ static function ($f, $i) use ($options) : string { ); } - private static function printDeprecated($fieldOrEnumVal) : string + protected static function printDeprecated($fieldOrEnumVal) : string { $reason = $fieldOrEnumVal->deprecationReason; if ($reason === null) { @@ -412,7 +412,7 @@ private static function printDeprecated($fieldOrEnumVal) : string /** * @param array $options */ - private static function printInterface(InterfaceType $type, array $options) : string + protected static function printInterface(InterfaceType $type, array $options) : string { return self::printDescription($options, $type) . sprintf("interface %s {\n%s\n}", $type->name, self::printFields($options, $type)); @@ -421,7 +421,7 @@ private static function printInterface(InterfaceType $type, array $options) : st /** * @param array $options */ - private static function printUnion(UnionType $type, array $options) : string + protected static function printUnion(UnionType $type, array $options) : string { return self::printDescription($options, $type) . sprintf('union %s = %s', $type->name, implode(' | ', $type->getTypes())); @@ -430,7 +430,7 @@ private static function printUnion(UnionType $type, array $options) : string /** * @param array $options */ - private static function printEnum(EnumType $type, array $options) : string + protected static function printEnum(EnumType $type, array $options) : string { return self::printDescription($options, $type) . sprintf("enum %s {\n%s\n}", $type->name, self::printEnumValues($type->getValues(), $options)); @@ -439,7 +439,7 @@ private static function printEnum(EnumType $type, array $options) : string /** * @param array $options */ - private static function printEnumValues($values, array $options) : string + protected static function printEnumValues($values, array $options) : string { return implode( "\n", @@ -457,7 +457,7 @@ static function ($value, $i) use ($options) : string { /** * @param array $options */ - private static function printInputObject(InputObjectType $type, array $options) : string + protected static function printInputObject(InputObjectType $type, array $options) : string { $fields = array_values($type->getFields()); From e05835020ffe972006633a2f08eef3ef05011acf Mon Sep 17 00:00:00 2001 From: Alex Bouma Date: Fri, 19 Jun 2020 15:49:17 +0200 Subject: [PATCH 191/256] Rule to validate subscriptions include one field (#644) * Rule to validate subscriptions include one field Subscriptions must only include one field. A GraphQL subscription is valid only if it contains a single root field. Spec: http://spec.graphql.org/June2018/#sec-Single-root-field * CR --- src/Validator/DocumentValidator.php | 2 + .../Rules/SingleFieldSubscription.php | 57 ++++++ .../SingleFieldSubscriptionsTest.php | 193 ++++++++++++++++++ tests/Validator/ValidatorTestCase.php | 13 +- 4 files changed, 263 insertions(+), 2 deletions(-) create mode 100644 src/Validator/Rules/SingleFieldSubscription.php create mode 100644 tests/Validator/SingleFieldSubscriptionsTest.php diff --git a/src/Validator/DocumentValidator.php b/src/Validator/DocumentValidator.php index fa94dc4b1..3ec800fbb 100644 --- a/src/Validator/DocumentValidator.php +++ b/src/Validator/DocumentValidator.php @@ -34,6 +34,7 @@ use GraphQL\Validator\Rules\QueryDepth; use GraphQL\Validator\Rules\QuerySecurityRule; use GraphQL\Validator\Rules\ScalarLeafs; +use GraphQL\Validator\Rules\SingleFieldSubscription; use GraphQL\Validator\Rules\UniqueArgumentNames; use GraphQL\Validator\Rules\UniqueDirectivesPerLocation; use GraphQL\Validator\Rules\UniqueFragmentNames; @@ -139,6 +140,7 @@ public static function defaultRules() ExecutableDefinitions::class => new ExecutableDefinitions(), UniqueOperationNames::class => new UniqueOperationNames(), LoneAnonymousOperation::class => new LoneAnonymousOperation(), + SingleFieldSubscription::class => new SingleFieldSubscription(), KnownTypeNames::class => new KnownTypeNames(), FragmentsOnCompositeTypes::class => new FragmentsOnCompositeTypes(), VariablesAreInputTypes::class => new VariablesAreInputTypes(), diff --git a/src/Validator/Rules/SingleFieldSubscription.php b/src/Validator/Rules/SingleFieldSubscription.php new file mode 100644 index 000000000..b98ee95ed --- /dev/null +++ b/src/Validator/Rules/SingleFieldSubscription.php @@ -0,0 +1,57 @@ + + */ + public function getVisitor(ValidationContext $context) : array + { + return [ + NodeKind::OPERATION_DEFINITION => static function (OperationDefinitionNode $node) use ($context) : VisitorOperation { + if ($node->operation === 'subscription') { + $selections = $node->selectionSet->selections; + + if (count($selections) !== 1) { + if ($selections instanceof NodeList) { + $offendingSelections = $selections->splice(1, count($selections)); + } else { + $offendingSelections = array_splice($selections, 1); + } + + $context->reportError(new Error( + self::multipleFieldsInOperation($node->name->value ?? null), + $offendingSelections + )); + } + } + + return Visitor::skipNode(); + }, + ]; + } + + public static function multipleFieldsInOperation(?string $operationName) : string + { + if ($operationName === null) { + return sprintf('Anonymous Subscription must select only one top level field.'); + } + + return sprintf('Subscription "%s" must select only one top level field.', $operationName); + } +} diff --git a/tests/Validator/SingleFieldSubscriptionsTest.php b/tests/Validator/SingleFieldSubscriptionsTest.php new file mode 100644 index 000000000..245f311da --- /dev/null +++ b/tests/Validator/SingleFieldSubscriptionsTest.php @@ -0,0 +1,193 @@ +expectPassesRule( + new SingleFieldSubscription(), + ' + subscription sub { + catSubscribe { + meows + } + } + ' + ); + } + + /** + * @see it('valid single field bulk subscriptions') + */ + public function testValidSingleFieldBulkSubscriptions() : void + { + $this->expectPassesRule( + new SingleFieldSubscription(), + ' + subscription sub { + catSubscribe { + meows + } + } + + subscription sub2 { + dogSubscribe { + barks + } + } + ' + ); + } + + /** + * @see it('valid single field anonymous subscription') + */ + public function testValidSingleFieldAnonymousSubscription() : void + { + $this->expectPassesRule( + new SingleFieldSubscription(), + ' + subscription { + catSubscribe { + meows + } + } + ' + ); + } + + /** + * @see it('valid single field subscription') + */ + public function testValidSingleFieldSubscriptionWithMultipleResultFields() : void + { + $this->expectPassesRule( + new SingleFieldSubscription(), + ' + subscription { + catSubscribe { + meows + meowVolume + } + } + ' + ); + } + + /** + * @see it('invalid multiple field subscription') + */ + public function testInvalidMultipleFieldSubscription() : void + { + $this->expectFailsRule( + new SingleFieldSubscription(), + ' + subscription sub { + catSubscribe { + meows + } + barkSubscribe { + barks + } + } + ', + [$this->multipleFieldsInOperation('sub', [6, 9])] + ); + } + + /** + * @see it('invalid multiple field anonymous subscription') + */ + public function testInvalidMultipleFieldAnonymousSubscription() : void + { + $this->expectFailsRule( + new SingleFieldSubscription(), + ' + subscription { + catSubscribe { + meows + } + barkSubscribe { + barks + } + } + ', + [$this->multipleFieldsInOperation(null, [6, 9])] + ); + } + + /** + * @see it('invalid many fields subscription') + */ + public function testInvalidManyFieldsSubscription() : void + { + $this->expectFailsRule( + new SingleFieldSubscription(), + ' + subscription sub { + first: catSubscribe { + meows + } + second: catSubscribe { + meows + } + third: catSubscribe { + meows + } + } + ', + [$this->multipleFieldsInOperation('sub', [6, 9], [9, 9])] + ); + } + + /** + * @see it('invalid many fields anonymous subscription') + */ + public function testInvalidManyFieldAnonymousSubscription() : void + { + $this->expectFailsRule( + new SingleFieldSubscription(), + ' + subscription { + first: catSubscribe { + meows + } + second: catSubscribe { + meows + } + third: catSubscribe { + meows + } + } + ', + [$this->multipleFieldsInOperation(null, [6, 9], [9, 9])] + ); + } + + /** + * @param array ...$locations A tuple of line and column + */ + private function multipleFieldsInOperation(?string $operationName, array ...$locations) + { + return FormattedError::create( + SingleFieldSubscription::multipleFieldsInOperation($operationName), + array_map(static function (array $location) : SourceLocation { + [$line, $column] = $location; + + return new SourceLocation($line, $column); + }, $locations) + ); + } +} diff --git a/tests/Validator/ValidatorTestCase.php b/tests/Validator/ValidatorTestCase.php index 018dcb527..12b6a9240 100644 --- a/tests/Validator/ValidatorTestCase.php +++ b/tests/Validator/ValidatorTestCase.php @@ -350,9 +350,18 @@ public static function getTestSchema() ], ]); + $subscriptionRoot = new ObjectType([ + 'name' => 'SubscriptionRoot', + 'fields' => [ + 'catSubscribe' => ['type' => $Cat], + 'barkSubscribe' => ['type' => $Dog], + ], + ]); + return new Schema([ - 'query' => $queryRoot, - 'directives' => [ + 'query' => $queryRoot, + 'subscription' => $subscriptionRoot, + 'directives' => [ Directive::includeDirective(), Directive::skipDirective(), Directive::deprecatedDirective(), From 0fa6330f9f9ccc93ad90a049bd5b8dce38d92181 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Thu, 27 Dec 2018 22:51:53 +0100 Subject: [PATCH 192/256] Fix Error --- UPGRADE.md | 2 +- src/Error/Error.php | 28 +++++++++++++++------------- src/Utils/ASTDefinitionBuilder.php | 2 +- src/Utils/Value.php | 2 +- tests/Error/ErrorTest.php | 6 +++--- tests/Executor/ExecutorTest.php | 2 +- 6 files changed, 22 insertions(+), 20 deletions(-) diff --git a/UPGRADE.md b/UPGRADE.md index 0a7e12639..325b34a30 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -21,7 +21,7 @@ $e = new Error( 'msg', null, null, - null, + [], null, null, ['foo' => 'bar'] diff --git a/src/Error/Error.php b/src/Error/Error.php index 35bedb6da..ecf383ebf 100644 --- a/src/Error/Error.php +++ b/src/Error/Error.php @@ -66,7 +66,7 @@ class Error extends Exception implements JsonSerializable, ClientAware */ private $source; - /** @var int[]|null */ + /** @var int[] */ private $positions; /** @var bool */ @@ -81,7 +81,7 @@ class Error extends Exception implements JsonSerializable, ClientAware /** * @param string $message * @param Node|Node[]|Traversable|null $nodes - * @param mixed[]|null $positions + * @param mixed[] $positions * @param mixed[]|null $path * @param Throwable $previous * @param mixed[] $extensions @@ -90,7 +90,7 @@ public function __construct( $message, $nodes = null, ?Source $source = null, - $positions = null, + array $positions = [], $path = null, $previous = null, array $extensions = [] @@ -100,7 +100,7 @@ public function __construct( // Compute list of blame nodes. if ($nodes instanceof Traversable) { $nodes = iterator_to_array($nodes); - } elseif ($nodes && ! is_array($nodes)) { + } elseif ($nodes !== null && ! is_array($nodes)) { $nodes = [$nodes]; } @@ -109,7 +109,7 @@ public function __construct( $this->positions = $positions; $this->path = $path; $this->extensions = count($extensions) > 0 ? $extensions : ( - $previous && $previous instanceof self + $previous instanceof self ? $previous->extensions : [] ); @@ -118,7 +118,7 @@ public function __construct( $this->isClientSafe = $previous->isClientSafe(); $cat = $previous->getCategory(); $this->category = $cat === '' || $cat === null ? self::CATEGORY_INTERNAL: $cat; - } elseif ($previous) { + } elseif ($previous !== null) { $this->isClientSafe = false; $this->category = self::CATEGORY_INTERNAL; } else { @@ -141,7 +141,7 @@ public function __construct( public static function createLocatedError($error, $nodes = null, $path = null) { if ($error instanceof self) { - if ($error->path && $error->nodes) { + if ($error->path !== null && $error->nodes !== null && count($error->nodes) !== 0) { return $error; } @@ -149,8 +149,10 @@ public static function createLocatedError($error, $nodes = null, $path = null) $path = $path ?? $error->path; } - $source = $positions = $originalError = null; - $extensions = []; + $source = null; + $originalError = null; + $positions = []; + $extensions = []; if ($error instanceof self) { $message = $error->getMessage(); @@ -218,9 +220,9 @@ public function getSource() /** * @return int[] */ - public function getPositions() + public function getPositions() : array { - if ($this->positions === null && ! empty($this->nodes)) { + if (count($this->positions) === 0 && ! empty($this->nodes)) { $positions = array_map( static function ($node) : ?int { return isset($node->loc) ? $node->loc->start : null; @@ -263,14 +265,14 @@ public function getLocations() $source = $this->getSource(); $nodes = $this->nodes; - if ($positions && $source) { + if ($source !== null && count($positions) !== 0) { $this->locations = array_map( static function ($pos) use ($source) : SourceLocation { return $source->getLocation($pos); }, $positions ); - } elseif ($nodes) { + } elseif ($nodes !== null && count($nodes) !== 0) { $locations = array_filter( array_map( static function ($node) : ?SourceLocation { diff --git a/src/Utils/ASTDefinitionBuilder.php b/src/Utils/ASTDefinitionBuilder.php index 05a3b5ad5..226c6332b 100644 --- a/src/Utils/ASTDefinitionBuilder.php +++ b/src/Utils/ASTDefinitionBuilder.php @@ -215,7 +215,7 @@ private function internalBuildType($typeName, $typeNode = null) sprintf('when building %s type: %s', $typeName, $e->getMessage()), null, null, - null, + [], null, $e ); diff --git a/src/Utils/Value.php b/src/Utils/Value.php index 2920883a6..e1e42e98e 100644 --- a/src/Utils/Value.php +++ b/src/Utils/Value.php @@ -249,7 +249,7 @@ private static function coercionError( ($subMessage ? '; ' . $subMessage : '.'), $blameNode, null, - null, + [], null, $originalError ); diff --git a/tests/Error/ErrorTest.php b/tests/Error/ErrorTest.php index 4614177d5..9486ce90b 100644 --- a/tests/Error/ErrorTest.php +++ b/tests/Error/ErrorTest.php @@ -20,7 +20,7 @@ class ErrorTest extends TestCase public function testUsesTheStackOfAnOriginalError() : void { $prev = new Exception('Original'); - $err = new Error('msg', null, null, null, null, $prev); + $err = new Error('msg', null, null, [], null, $prev); self::assertSame($err->getPrevious(), $prev); } @@ -134,7 +134,7 @@ public function testSerializesToIncludePath() : void 'msg', null, null, - null, + [], ['path', 3, 'to', 'field'] ); @@ -151,7 +151,7 @@ public function testDefaultErrorFormatterIncludesExtensionFields() : void 'msg', null, null, - null, + [], null, null, ['foo' => 'bar'] diff --git a/tests/Executor/ExecutorTest.php b/tests/Executor/ExecutorTest.php index 33ae342b7..7ad87a607 100644 --- a/tests/Executor/ExecutorTest.php +++ b/tests/Executor/ExecutorTest.php @@ -461,7 +461,7 @@ public function testNullsOutErrorSubtrees() : void 'Error getting asyncReturnErrorWithExtensions', null, null, - null, + [], null, null, ['foo' => 'bar'] From e206b3acd640a5f37c81fd6f4261c0b50102ebdd Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Mon, 30 Dec 2019 13:48:13 +0100 Subject: [PATCH 193/256] changelog --- CHANGELOG.md | 1 + UPGRADE.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 242560160..fd77f71bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ - Having an empty string in `deprecationReason` will now print the `@deprecated` directive (only a `null` `deprecationReason` won't print the `@deprecated` directive). - Renamed `GraphQL\Error\Debug` to `GraphQL\Error\DebugFlag`. - Debug flags in `GraphQL\Executor\ExecutionResult`, `GraphQL\Error\FormattedError` and `GraphQL\Server\ServerConfig` do not accept `boolean` value anymore but `int` only. +- `$positions` in `GraphQL\Error\Error` are not nullable anymore. Same can be expressesed by passing empty array. #### v0.13.5 - Fix coroutine executor when using with promise (#486) diff --git a/UPGRADE.md b/UPGRADE.md index 325b34a30..0a7e12639 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -21,7 +21,7 @@ $e = new Error( 'msg', null, null, - [], + null, null, null, ['foo' => 'bar'] From c04523b7d34d23325fb6b621056fe4a730762f10 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Sun, 21 Jun 2020 18:58:18 +0700 Subject: [PATCH 194/256] Fix code style --- src/Error/FormattedError.php | 2 +- tests/Executor/MutationsTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Error/FormattedError.php b/src/Error/FormattedError.php index e8f05d317..c727b5c1a 100644 --- a/src/Error/FormattedError.php +++ b/src/Error/FormattedError.php @@ -194,7 +194,7 @@ public static function createFromException(Throwable $exception, int $debug = De if ($exception instanceof Error) { $locations = Utils::map( $exception->getLocations(), - static function (SourceLocation $loc): array { + static function (SourceLocation $loc) : array { return $loc->toSerializableArray(); } ); diff --git a/tests/Executor/MutationsTest.php b/tests/Executor/MutationsTest.php index 97a7e6ffa..923267b3b 100644 --- a/tests/Executor/MutationsTest.php +++ b/tests/Executor/MutationsTest.php @@ -4,8 +4,8 @@ namespace GraphQL\Tests\Executor; -use GraphQL\Error\DebugFlag; use GraphQL\Deferred; +use GraphQL\Error\DebugFlag; use GraphQL\Executor\Executor; use GraphQL\Language\Parser; use GraphQL\Tests\Executor\TestClasses\NumberHolder; From 419eb8208173e2760fb312c4eb57ee5495415e24 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Sun, 21 Jun 2020 19:15:02 +0700 Subject: [PATCH 195/256] Fix static analysis errors --- phpstan-baseline.neon | 82 +----------------------------------- src/Error/FormattedError.php | 2 +- tests/Type/QueryPlanTest.php | 2 +- 3 files changed, 3 insertions(+), 83 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 8f51ee0b9..5c4aab776 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,30 +1,5 @@ parameters: ignoreErrors: - - - message: "#^Only booleans are allowed in &&, GraphQL\\\\Language\\\\AST\\\\Node\\|null given on the left side\\.$#" - count: 1 - path: src/Error/Error.php - - - - message: "#^Only booleans are allowed in &&, Throwable\\|null given on the left side\\.$#" - count: 1 - path: src/Error/Error.php - - - - message: "#^Only booleans are allowed in an elseif condition, Throwable\\|null given\\.$#" - count: 1 - path: src/Error/Error.php - - - - message: "#^Only booleans are allowed in &&, array\\\\|null given on the right side\\.$#" - count: 1 - path: src/Error/Error.php - - - - message: "#^Only booleans are allowed in &&, array\\|null given on the left side\\.$#" - count: 1 - path: src/Error/Error.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 6 @@ -32,49 +7,14 @@ parameters: - message: "#^Only booleans are allowed in &&, GraphQL\\\\Language\\\\Source\\|null given on the right side\\.$#" - count: 2 - path: src/Error/Error.php - - - - message: "#^Only booleans are allowed in &&, array\\ given on the left side\\.$#" count: 1 path: src/Error/Error.php - - message: "#^Only booleans are allowed in an elseif condition, array\\\\|null given\\.$#" + message: "#^Only booleans are allowed in &&, GraphQL\\\\Language\\\\AST\\\\Location\\|null given on the left side\\.$#" count: 1 path: src/Error/Error.php - - - message: "#^Only booleans are allowed in &&, GraphQL\\\\Language\\\\AST\\\\Location given on the left side\\.$#" - count: 1 - path: src/Error/Error.php - - - - message: "#^Only booleans are allowed in an if condition, array\\\\|null given\\.$#" - count: 1 - path: src/Error/FormattedError.php - - - - message: "#^Only booleans are allowed in a negated boolean, GraphQL\\\\Language\\\\AST\\\\Location given\\.$#" - count: 1 - path: src/Error/FormattedError.php - - - - message: "#^Only booleans are allowed in &&, GraphQL\\\\Language\\\\Source\\|null given on the left side\\.$#" - count: 1 - path: src/Error/FormattedError.php - - - - message: "#^Only booleans are allowed in &&, array\\ given on the right side\\.$#" - count: 1 - path: src/Error/FormattedError.php - - - - message: "#^Only booleans are allowed in a negated boolean, array\\ given\\.$#" - count: 1 - path: src/Error/FormattedError.php - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" count: 5 @@ -82,21 +22,6 @@ parameters: - message: "#^Only booleans are allowed in an if condition, int given\\.$#" - count: 2 - path: src/Error/FormattedError.php - - - - message: "#^Only booleans are allowed in an if condition, Throwable\\|null given\\.$#" - count: 2 - path: src/Error/FormattedError.php - - - - message: "#^Only booleans are allowed in &&, int given on the left side\\.$#" - count: 2 - path: src/Error/FormattedError.php - - - - message: "#^Only booleans are allowed in a negated boolean, Throwable\\|null given\\.$#" count: 1 path: src/Error/FormattedError.php @@ -160,11 +85,6 @@ parameters: count: 2 path: src/GraphQL.php - - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 1 - path: src/Language/AST/Node.php - - message: "#^Variable property access on GraphQL\\\\Language\\\\AST\\\\Node\\.$#" count: 1 diff --git a/src/Error/FormattedError.php b/src/Error/FormattedError.php index c727b5c1a..4b41ea608 100644 --- a/src/Error/FormattedError.php +++ b/src/Error/FormattedError.php @@ -173,7 +173,7 @@ private static function lpad($len, $str) */ public static function createFromException(Throwable $exception, int $debug = DebugFlag::NONE, $internalErrorMessage = null) : array { - $internalErrorMessage = $internalErrorMessage ?: self::$internalErrorMessage; + $internalErrorMessage = $internalErrorMessage ?? self::$internalErrorMessage; if ($exception instanceof ClientAware) { $formattedError = [ diff --git a/tests/Type/QueryPlanTest.php b/tests/Type/QueryPlanTest.php index 198b30d1b..3654d7fda 100644 --- a/tests/Type/QueryPlanTest.php +++ b/tests/Type/QueryPlanTest.php @@ -734,7 +734,7 @@ public function testQueryPlanGroupingImplementorFieldsForAbstractTypes() : void $transmission = new UnionType([ 'name' => 'Transmission', 'types' => [$manualTransmission, $automaticTransmission], - 'resolveType' => static function () use ($manualTransmission) { + 'resolveType' => static function () use ($manualTransmission) : ObjectType { return $manualTransmission; }, ]); From 7087a4ef27242b0217ea000c0c5f169e70e4b839 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Sun, 21 Jun 2020 19:34:43 +0700 Subject: [PATCH 196/256] Fixed broken Error constructor call --- src/Validator/Rules/ValuesOfCorrectType.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Validator/Rules/ValuesOfCorrectType.php b/src/Validator/Rules/ValuesOfCorrectType.php index 936bc14c0..9c48c1253 100644 --- a/src/Validator/Rules/ValuesOfCorrectType.php +++ b/src/Validator/Rules/ValuesOfCorrectType.php @@ -229,7 +229,7 @@ private function isValidScalar(ValidationContext $context, ValueNode $node, $fie ), $node, null, - null, + [], null, $error ) From bb3178d20f43e5f637eb8b44b54987ec0b6497a3 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Sun, 21 Jun 2020 19:47:14 +0700 Subject: [PATCH 197/256] Add missing type hints --- src/Type/Definition/Type.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Type/Definition/Type.php b/src/Type/Definition/Type.php index f79a49945..bbc64c3c6 100644 --- a/src/Type/Definition/Type.php +++ b/src/Type/Definition/Type.php @@ -250,11 +250,9 @@ public static function isOutputType($type) : bool /** * @param Type $type * - * @return bool - * * @api */ - public static function isLeafType($type) + public static function isLeafType($type) : bool { return $type instanceof LeafType; } @@ -262,11 +260,9 @@ public static function isLeafType($type) /** * @param Type $type * - * @return bool - * * @api */ - public static function isCompositeType($type) + public static function isCompositeType($type) : bool { return $type instanceof CompositeType; } From e709615260f6e1c169a602bfa9521878d93a5cdf Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Sun, 21 Jun 2020 19:48:47 +0700 Subject: [PATCH 198/256] Updated documentation --- docs/error-handling.md | 10 +- docs/reference.md | 324 ++++++++++++++++++++--------------------- tools/gendocs.php | 2 + 3 files changed, 164 insertions(+), 172 deletions(-) diff --git a/docs/error-handling.md b/docs/error-handling.md index 11962bb36..c3841b21e 100644 --- a/docs/error-handling.md +++ b/docs/error-handling.md @@ -93,9 +93,9 @@ GraphQL\Error\FormattedError::setInternalErrorMessage("Unexpected error"); During development or debugging use `$result->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE)` to add **debugMessage** key to each formatted error entry. If you also want to add exception trace - pass flags instead: -``` -use GraphQL\Error\Debug; -$debug = Debug::INCLUDE_DEBUG_MESSAGE | Debug::INCLUDE_TRACE; +```php +use GraphQL\Error\DebugFlag; +$debug = DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE; $result = GraphQL::executeQuery(/*args*/)->toArray($debug); ``` @@ -126,8 +126,8 @@ If you prefer the first resolver exception to be re-thrown, use following flags: ```php toArray($debug); diff --git a/docs/reference.md b/docs/reference.md index 4bd857533..7af8cc34a 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -20,9 +20,11 @@ See [related documentation](executing-queries.md). * rootValue: * The value provided as the first argument to resolver functions on the top * level type (e.g. the query object type). - * context: - * The value provided as the third argument to all resolvers. - * Use this to pass current session, user data, etc + * contextValue: + * The context value is provided as an argument to resolver functions after + * field arguments. It is used to pass shared information useful at any point + * during executing this query, for example the currently logged in user and + * connections to databases or other services. * variableValues: * A mapping of variable name to runtime value to use for all variables * defined in the requestString. @@ -33,7 +35,7 @@ See [related documentation](executing-queries.md). * fieldResolver: * A resolver function to use when one is not provided by the schema. * If not provided, the default field resolver is used (which looks for a - * value on the object value with the field's name). + * value on the source value with the field's name). * validationRules: * A set of rules for query validation step. Default value is all available rules. * Empty array would allow to skip query validation (may be convenient for persisted @@ -41,7 +43,7 @@ See [related documentation](executing-queries.md). * * @param string|DocumentNode $source * @param mixed $rootValue - * @param mixed $context + * @param mixed $contextValue * @param mixed[]|null $variableValues * @param ValidationRule[] $validationRules * @@ -51,12 +53,12 @@ static function executeQuery( GraphQL\Type\Schema $schema, $source, $rootValue = null, - $context = null, + $contextValue = null, $variableValues = null, string $operationName = null, callable $fieldResolver = null, array $validationRules = null -) +): GraphQL\Executor\ExecutionResult ``` ```php @@ -82,7 +84,7 @@ static function promiseToExecute( string $operationName = null, callable $fieldResolver = null, array $validationRules = null -) +): GraphQL\Executor\Promise\Promise ``` ```php @@ -93,7 +95,7 @@ static function promiseToExecute( * * @api */ -static function getStandardDirectives() +static function getStandardDirectives(): array ``` ```php @@ -104,7 +106,7 @@ static function getStandardDirectives() * * @api */ -static function getStandardTypes() +static function getStandardTypes(): array ``` ```php @@ -112,7 +114,7 @@ static function getStandardTypes() * Replaces standard types with types from this list (matching by name) * Standard types not listed here remain untouched. * - * @param Type[] $types + * @param array $types * * @api */ @@ -127,7 +129,7 @@ static function overrideStandardTypes(array $types) * * @api */ -static function getStandardValidationRules() +static function getStandardValidationRules(): array ``` ```php @@ -136,7 +138,7 @@ static function getStandardValidationRules() * * @api */ -static function setDefaultFieldResolver(callable $fn) +static function setDefaultFieldResolver(callable $fn): void ``` # GraphQL\Type\Definition\Type Registry of standard GraphQL types @@ -145,198 +147,164 @@ and a base class for all other types. **Class Methods:** ```php /** - * @return IDType - * * @api */ -static function id() +static function id(): GraphQL\Type\Definition\ScalarType ``` ```php /** - * @return StringType - * * @api */ -static function string() +static function string(): GraphQL\Type\Definition\ScalarType ``` ```php /** - * @return BooleanType - * * @api */ -static function boolean() +static function boolean(): GraphQL\Type\Definition\ScalarType ``` ```php /** - * @return IntType - * * @api */ -static function int() +static function int(): GraphQL\Type\Definition\ScalarType ``` ```php /** - * @return FloatType - * * @api */ -static function float() +static function float(): GraphQL\Type\Definition\ScalarType ``` ```php /** - * @param Type|ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType|NonNull $wrappedType - * - * @return ListOfType - * * @api */ -static function listOf($wrappedType) +static function listOf(GraphQL\Type\Definition\Type $wrappedType): GraphQL\Type\Definition\ListOfType ``` ```php /** - * @param ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType $wrappedType - * - * @return NonNull + * @param callable|NullableType $wrappedType * * @api */ -static function nonNull($wrappedType) +static function nonNull($wrappedType): GraphQL\Type\Definition\NonNull ``` ```php /** * @param Type $type * - * @return bool - * * @api */ -static function isInputType($type) +static function isInputType($type): bool ``` ```php /** * @param Type $type * - * @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType - * * @api */ -static function getNamedType($type) +static function getNamedType($type): GraphQL\Type\Definition\Type ``` ```php /** * @param Type $type * - * @return bool - * * @api */ -static function isOutputType($type) +static function isOutputType($type): bool ``` ```php /** * @param Type $type * - * @return bool - * * @api */ -static function isLeafType($type) +static function isLeafType($type): bool ``` ```php /** * @param Type $type * - * @return bool - * * @api */ -static function isCompositeType($type) +static function isCompositeType($type): bool ``` ```php /** * @param Type $type * - * @return bool - * * @api */ -static function isAbstractType($type) +static function isAbstractType($type): bool ``` ```php /** - * @param Type $type - * - * @return bool - * * @api */ -static function isType($type) +static function getNullableType(GraphQL\Type\Definition\Type $type): GraphQL\Type\Definition\Type ``` +# GraphQL\Type\Definition\ResolveInfo +Structure containing information useful for field resolution process. +Passed as 4th argument to every field resolver. See [docs on field resolving (data fetching)](data-fetching.md). + +**Class Props:** ```php /** - * @param Type $type - * - * @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType + * The definition of the field being resolved. * * @api + * @var FieldDefinition */ -static function getNullableType($type) -``` -# GraphQL\Type\Definition\ResolveInfo -Structure containing information useful for field resolution process. -Passed as 4th argument to every field resolver. See [docs on field resolving (data fetching)](data-fetching.md). +public $fieldDefinition; -**Class Props:** -```php /** - * The name of the field being resolved + * The name of the field being resolved. * * @api - * @var string|null + * @var string */ public $fieldName; /** - * AST of all nodes referencing this field in the query. + * Expected return type of the field being resolved. * * @api - * @var FieldNode[]|null + * @var Type */ -public $fieldNodes; +public $returnType; /** - * Expected return type of the field being resolved + * AST of all nodes referencing this field in the query. * * @api - * @var ScalarType|ObjectType|InterfaceType|UnionType|EnumType|ListOfType|NonNull + * @var FieldNode[] */ -public $returnType; +public $fieldNodes; /** - * Parent type of the field being resolved + * Parent type of the field being resolved. * * @api - * @var ObjectType|null + * @var ObjectType */ public $parentType; /** - * Path to this field from the very root value + * Path to this field from the very root value. * * @api * @var string[] @@ -344,31 +312,31 @@ public $parentType; public $path; /** - * Instance of a schema used for execution + * Instance of a schema used for execution. * * @api - * @var Schema|null + * @var Schema */ public $schema; /** - * AST of all fragments defined in query + * AST of all fragments defined in query. * * @api - * @var FragmentDefinitionNode[]|null + * @var FragmentDefinitionNode[] */ public $fragments; /** - * Root value passed to query execution + * Root value passed to query execution. * * @api - * @var mixed|null + * @var mixed */ public $rootValue; /** - * AST of operation definition node (query, mutation) + * AST of operation definition node (query, mutation). * * @api * @var OperationDefinitionNode|null @@ -376,10 +344,10 @@ public $rootValue; public $operation; /** - * Array of variables passed to query execution + * Array of variables passed to query execution. * * @api - * @var mixed[]|null + * @var mixed[] */ public $variableValues; ``` @@ -388,7 +356,7 @@ public $variableValues; ```php /** * Helper method that returns names of all fields selected in query for - * $this->fieldName up to $depth levels + * $this->fieldName up to $depth levels. * * Example: * query MyQuery{ @@ -419,7 +387,7 @@ public $variableValues; * * @param int $depth How many levels to include in output * - * @return bool[] + * @return array * * @api */ @@ -437,6 +405,7 @@ const FIELD = "FIELD"; const FRAGMENT_DEFINITION = "FRAGMENT_DEFINITION"; const FRAGMENT_SPREAD = "FRAGMENT_SPREAD"; const INLINE_FRAGMENT = "INLINE_FRAGMENT"; +const VARIABLE_DEFINITION = "VARIABLE_DEFINITION"; const SCHEMA = "SCHEMA"; const SCALAR = "SCALAR"; const OBJECT = "OBJECT"; @@ -480,7 +449,7 @@ static function create(array $options = []) ```php /** - * @return ObjectType + * @return ObjectType|null * * @api */ @@ -489,7 +458,7 @@ function getQuery() ```php /** - * @param ObjectType $query + * @param ObjectType|null $query * * @return SchemaConfig * @@ -500,7 +469,7 @@ function setQuery($query) ```php /** - * @return ObjectType + * @return ObjectType|null * * @api */ @@ -509,7 +478,7 @@ function getMutation() ```php /** - * @param ObjectType $mutation + * @param ObjectType|null $mutation * * @return SchemaConfig * @@ -520,7 +489,7 @@ function setMutation($mutation) ```php /** - * @return ObjectType + * @return ObjectType|null * * @api */ @@ -529,7 +498,7 @@ function getSubscription() ```php /** - * @param ObjectType $subscription + * @param ObjectType|null $subscription * * @return SchemaConfig * @@ -540,7 +509,7 @@ function setSubscription($subscription) ```php /** - * @return Type[] + * @return Type[]|callable * * @api */ @@ -560,7 +529,7 @@ function setTypes($types) ```php /** - * @return Directive[] + * @return Directive[]|null * * @api */ @@ -580,7 +549,7 @@ function setDirectives(array $directives) ```php /** - * @return callable + * @return callable(string $name):Type|null * * @api */ @@ -658,7 +627,7 @@ function getDirectives() * * @api */ -function getQueryType() +function getQueryType(): GraphQL\Type\Definition\Type ``` ```php @@ -669,7 +638,7 @@ function getQueryType() * * @api */ -function getMutationType() +function getMutationType(): GraphQL\Type\Definition\Type ``` ```php @@ -680,7 +649,7 @@ function getMutationType() * * @api */ -function getSubscriptionType() +function getSubscriptionType(): GraphQL\Type\Definition\Type ``` ```php @@ -694,15 +663,11 @@ function getConfig() ```php /** - * Returns type by it's name - * - * @param string $name - * - * @return Type|null + * Returns type by its name * * @api */ -function getType($name) +function getType(string $name): GraphQL\Type\Definition\Type ``` ```php @@ -712,11 +677,13 @@ function getType($name) * * This operation requires full schema scan. Do not use in production environment. * - * @return ObjectType[] + * @param InterfaceType|UnionType $abstractType + * + * @return array * * @api */ -function getPossibleTypes(GraphQL\Type\Definition\AbstractType $abstractType) +function getPossibleTypes(GraphQL\Type\Definition\Type $abstractType): array ``` ```php @@ -724,27 +691,21 @@ function getPossibleTypes(GraphQL\Type\Definition\AbstractType $abstractType) * Returns true if object type is concrete type of given abstract type * (implementation for interfaces and members of union type for unions) * - * @return bool - * * @api */ function isPossibleType( GraphQL\Type\Definition\AbstractType $abstractType, GraphQL\Type\Definition\ObjectType $possibleType -) +): bool ``` ```php /** * Returns instance of directive by name * - * @param string $name - * - * @return Directive - * * @api */ -function getDirective($name) +function getDirective(string $name): GraphQL\Type\Definition\Directive ``` ```php @@ -775,6 +736,36 @@ function validate() # GraphQL\Language\Parser Parses string containing GraphQL query or [type definition](type-system/type-language.md) to Abstract Syntax Tree. +@method static DocumentNode document(Source|string $source, bool[] $options = []) +@method static ExecutableDefinitionNode executableDefinition(Source|string $source, bool[] $options = []) +@method static string operationType(Source|string $source, bool[] $options = []) +@method static VariableDefinitionNode variableDefinition(Source|string $source, bool[] $options = []) +@method static SelectionSetNode selectionSet(Source|string $source, bool[] $options = []) +@method static FieldNode field(Source|string $source, bool[] $options = []) +@method static ArgumentNode argument(Source|string $source, bool[] $options = []) +@method static FragmentSpreadNode|InlineFragmentNode fragment(Source|string $source, bool[] $options = []) +@method static NameNode fragmentName(Source|string $source, bool[] $options = []) +@method static StringValueNode stringLiteral(Source|string $source, bool[] $options = []) +@method static BooleanValueNode|EnumValueNode|FloatValueNode|IntValueNode|ListValueNode|ObjectValueNode|StringValueNode|VariableNode variableValue(Source|string $source, bool[] $options = []) +@method static ObjectValueNode object(Source|string $source, bool[] $options = []) +@method static NodeList directives(Source|string $source, bool[] $options = []) +@method static ListTypeNode|NameNode|NonNullTypeNode typeReference(Source|string $source, bool[] $options = []) +@method static TypeSystemDefinitionNode typeSystemDefinition(Source|string $source, bool[] $options = []) +@method static SchemaDefinitionNode schemaDefinition(Source|string $source, bool[] $options = []) +@method static ScalarTypeDefinitionNode scalarTypeDefinition(Source|string $source, bool[] $options = []) +@method static NamedTypeNode[] implementsInterfaces(Source|string $source, bool[] $options = []) +@method static FieldDefinitionNode fieldDefinition(Source|string $source, bool[] $options = []) +@method static InputValueDefinitionNode inputValueDefinition(Source|string $source, bool[] $options = []) +@method static UnionTypeDefinitionNode unionTypeDefinition(Source|string $source, bool[] $options = []) +@method static EnumTypeDefinitionNode enumTypeDefinition(Source|string $source, bool[] $options = []) +@method static EnumValueDefinitionNode enumValueDefinition(Source|string $source, bool[] $options = []) +@method static InputValueDefinitionNode[] inputFieldsDefinition(Source|string $source, bool[] $options = []) +@method static SchemaTypeExtensionNode schemaTypeExtension(Source|string $source, bool[] $options = []) +@method static ObjectTypeExtensionNode objectTypeExtension(Source|string $source, bool[] $options = []) +@method static UnionTypeExtensionNode unionTypeExtension(Source|string $source, bool[] $options = []) +@method static InputObjectTypeExtensionNode inputObjectTypeExtension(Source|string $source, bool[] $options = []) +@method static DirectiveLocation[] directiveLocations(Source|string $source, bool[] $options = []) + **Class Methods:** ```php /** @@ -1095,7 +1086,7 @@ Implements the "Evaluating requests" section of the GraphQL specification. * execution are collected in `$result->errors`. * * @param mixed|null $rootValue - * @param mixed[]|null $contextValue + * @param mixed|null $contextValue * @param mixed[]|ArrayAccess|null $variableValues * @param string|null $operationName * @@ -1121,8 +1112,8 @@ static function execute( * * Useful for async PHP platforms. * - * @param mixed[]|null $rootValue - * @param mixed[]|null $contextValue + * @param mixed|null $rootValue + * @param mixed|null $contextValue * @param mixed[]|null $variableValues * @param string|null $operationName * @@ -1228,16 +1219,13 @@ function setErrorsHandler(callable $handler) * If debug argument is passed, output of error formatter is enriched which debugging information * ("debugMessage", "trace" keys depending on flags). * - * $debug argument must be either bool (only adds "debugMessage" to result) or sum of flags from - * GraphQL\Error\Debug - * - * @param bool|int $debug + * $debug argument must sum of flags from @see \GraphQL\Error\DebugFlag * * @return mixed[] * * @api */ -function toArray($debug = false) +function toArray(int $debug = "GraphQL\Error\DebugFlag::NONE"): array ``` # GraphQL\Executor\Promise\PromiseAdapter Provides a means for integration of async PHP platforms ([related docs](data-fetching.md#async-php)) @@ -1486,7 +1474,7 @@ const ALL = 63; * * @api */ -static function setWarningHandler(callable $warningHandler = null) +static function setWarningHandler(callable $warningHandler = null): void ``` ```php @@ -1502,7 +1490,7 @@ static function setWarningHandler(callable $warningHandler = null) * * @api */ -static function suppress($suppress = true) +static function suppress($suppress = true): void ``` ```php @@ -1518,7 +1506,7 @@ static function suppress($suppress = true) * * @api */ -static function enable($enable = true) +static function enable($enable = true): void ``` # GraphQL\Error\ClientAware This interface is used for [default error formatting](error-handling.md). @@ -1552,11 +1540,12 @@ function isClientSafe() */ function getCategory() ``` -# GraphQL\Error\Debug +# GraphQL\Error\DebugFlag Collection of flags for [error debugging](error-handling.md#debugging-tools). **Class Constants:** ```php +const NONE = 0; const INCLUDE_DEBUG_MESSAGE = 1; const INCLUDE_TRACE = 2; const RETHROW_INTERNAL_EXCEPTIONS = 4; @@ -1589,11 +1578,9 @@ static function setInternalErrorMessage($msg) * This method only exposes exception message when exception implements ClientAware interface * (or when debug flags are passed). * - * For a list of available debug flags see GraphQL\Error\Debug constants. + * For a list of available debug flags @see \GraphQL\Error\DebugFlag constants. * - * @param Throwable $e - * @param bool|int $debug - * @param string $internalErrorMessage + * @param string $internalErrorMessage * * @return mixed[] * @@ -1601,7 +1588,11 @@ static function setInternalErrorMessage($msg) * * @api */ -static function createFromException($e, $debug = false, $internalErrorMessage = null) +static function createFromException( + Throwable $exception, + int $debug = "GraphQL\Error\DebugFlag::NONE", + $internalErrorMessage = null +): array ``` ```php @@ -1831,7 +1822,7 @@ function setErrorsHandler(callable $handler) /** * Set validation rules for this server. * - * @param ValidationRule[]|callable $validationRules + * @param ValidationRule[]|callable|null $validationRules * * @return self * @@ -1864,28 +1855,20 @@ function setPersistentQueryLoader(callable $persistentQueryLoader) ```php /** - * Set response debug flags. @see GraphQL\Error\DebugFlag class for a list of all available flags - * - * @param int $debug - * - * @return self + * Set response debug flags. @see \GraphQL\Error\DebugFlag class for a list of all available flags * * @api */ -function setDebugFlag($debugFlag = DebugFlag::NONE) +function setDebugFlag(int $debugFlag = "GraphQL\Error\DebugFlag::INCLUDE_DEBUG_MESSAGE"): self ``` ```php /** * Allow batching queries (disabled by default) * - * @param bool $enableBatching - * - * @return self - * * @api */ -function setQueryBatching($enableBatching) +function setQueryBatching(bool $enableBatching): self ``` ```php @@ -1950,7 +1933,7 @@ function parseRequestParams($method, array $bodyParams, array $queryParams) * Checks validity of OperationParams extracted from HTTP request and returns an array of errors * if params are invalid (or empty array when params are valid) * - * @return Error[] + * @return array * * @api */ @@ -2059,6 +2042,12 @@ public $operation; * @var mixed[]|null */ public $variables; + +/** + * @api + * @var mixed[]|null + */ +public $extensions; ``` **Class Methods:** @@ -2067,13 +2056,10 @@ public $variables; * Creates an instance from given array * * @param mixed[] $params - * @param bool $readonly - * - * @return OperationParams * * @api */ -static function create(array $params, $readonly = false) +static function create(array $params, bool $readonly = false): GraphQL\Server\OperationParams ``` ```php @@ -2133,6 +2119,7 @@ static function build($source, callable $typeConfigDecorator = null, array $opti * * - commentDescriptions: * Provide true to use preceding comments as the description. + * This option is provided to ease adoption and will be removed in v16. * * @param bool[] $options * @@ -2178,7 +2165,7 @@ Various utilities dealing with AST * * @api */ -static function fromArray(array $node) +static function fromArray(array $node): GraphQL\Language\AST\Node ``` ```php @@ -2189,7 +2176,7 @@ static function fromArray(array $node) * * @api */ -static function toArray(GraphQL\Language\AST\Node $node) +static function toArray(GraphQL\Language\AST\Node $node): array ``` ```php @@ -2213,7 +2200,7 @@ static function toArray(GraphQL\Language\AST\Node $node) * * @param Type|mixed|null $value * - * @return ObjectValueNode|ListValueNode|BooleanValueNode|IntValueNode|FloatValueNode|EnumValueNode|StringValueNode|NullValueNode + * @return ObjectValueNode|ListValueNode|BooleanValueNode|IntValueNode|FloatValueNode|EnumValueNode|StringValueNode|NullValueNode|null * * @api */ @@ -2241,7 +2228,7 @@ static function astFromValue($value, GraphQL\Type\Definition\InputType $type) * | Null Value | null | * * @param VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode|null $valueNode - * @param mixed[]|null $variables + * @param mixed[]|null $variables * * @return mixed[]|stdClass|null * @@ -2249,7 +2236,11 @@ static function astFromValue($value, GraphQL\Type\Definition\InputType $type) * * @api */ -static function valueFromAST($valueNode, GraphQL\Type\Definition\InputType $type, array $variables = null) +static function valueFromAST( + GraphQL\Language\AST\ValueNode $valueNode, + GraphQL\Type\Definition\Type $type, + array $variables = null +) ``` ```php @@ -2302,7 +2293,7 @@ static function typeFromAST(GraphQL\Type\Schema $schema, $inputTypeNode) * * @param string $operationName * - * @return bool + * @return bool|string * * @api */ @@ -2314,23 +2305,22 @@ Given an instance of Schema, prints it in GraphQL type language. **Class Methods:** ```php /** - * Accepts options as a second argument: - * + * @param array $options + * Available options: * - commentDescriptions: * Provide true to use preceding comments as the description. - * - * @param bool[] $options + * This option is provided to ease adoption and will be removed in v16. * * @api */ -static function doPrint(GraphQL\Type\Schema $schema, array $options = []) +static function doPrint(GraphQL\Type\Schema $schema, array $options = []): string ``` ```php /** - * @param bool[] $options + * @param array $options * * @api */ -static function printIntrospectionSchema(GraphQL\Type\Schema $schema, array $options = []) +static function printIntrospectionSchema(GraphQL\Type\Schema $schema, array $options = []): string ``` diff --git a/tools/gendocs.php b/tools/gendocs.php index 262aaf9ea..4acd4eb11 100644 --- a/tools/gendocs.php +++ b/tools/gendocs.php @@ -52,8 +52,10 @@ function renderClassMethod(ReflectionMethod $method) { if (strlen($argsStr) >= 80) { $argsStr = "\n " . implode(",\n ", $args) . "\n"; } + $returnType = $method->getReturnType(); $def = "function {$method->getName()}($argsStr)"; $def = $method->isStatic() ? "static $def" : $def; + $def = $returnType ? "$def: $returnType" : $def; $docBlock = unpadDocblock($method->getDocComment()); return <<