From d591eccd9f3f511c9ab25bc5658d85f5e29f2260 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Fri, 23 Nov 2018 10:53:19 +0100 Subject: [PATCH 01/45] Clean complementary tools --- docs/complementary-tools.md | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/docs/complementary-tools.md b/docs/complementary-tools.md index 5036d61..a92d66b 100644 --- a/docs/complementary-tools.md +++ b/docs/complementary-tools.md @@ -1,22 +1,25 @@ # Integrations -* [Integration with Relay](https://github.com/ivome/graphql-relay-php) -* [Integration with Laravel 5](https://github.com/Folkloreatelier/laravel-graphql) + [Relay Helpers for Laravel](https://github.com/nuwave/laravel-graphql-relay) + [Nuwave Lighthouse](https://github.com/nuwave/lighthouse) -* [Symfony Bundle](https://github.com/overblog/GraphQLBundle) by Overblog -* 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/)) via [Standard Server](executing-queries.md/#using-server) +* [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. +* Laravel + - [Laravel GraphQL](https://github.com/Folkloreatelier/laravel-graphql) – Integration with Laravel 5 + - [laravel-graphql-relay](https://github.com/nuwave/laravel-graphql-relay) – Relay Helpers for Laravel + - [Lighthouse](https://github.com/nuwave/lighthouse) – GraphQL Server for Laravel +* [OverblogGraphQLBundle](https://github.com/overblog/GraphQLBundle) – Bundle for Symfony # GraphQL PHP Tools -* Define types with Doctrine ORM annotations ([for PHP7.1](https://github.com/Ecodev/graphql-doctrine), for [earlier PHP versions](https://github.com/rahuljayaraman/doctrine-graphql)) -* [DataLoader PHP](https://github.com/overblog/dataloader-php) - as a ready implementation for [deferred resolvers](data-fetching.md#solving-n1-problem) -* [PSR 15 compliant middleware](https://github.com/phps-cans/psr7-middleware-graphql) for the Standard Server (experimental) -* [GraphQL Uploads](https://github.com/Ecodev/graphql-upload) for the Standard Server -* [GraphQL Batch Processor](https://github.com/vasily-kartashov/graphql-batch-processing) - Simple library that provides a builder interface for defining collection, querying, filtering, and post-processing logic of batched data fetches. +* [GraphQL Doctrine](https://github.com/Ecodev/graphql-doctrine) – Define types with Doctrine ORM annotations +* [DataLoaderPHP](https://github.com/overblog/dataloader-php) – as a ready implementation for [deferred resolvers](data-fetching.md#solving-n1-problem) +* [GraphQL Uploads](https://github.com/Ecodev/graphql-upload) – A PSR-15 middleware to support file uploads in GraphQL. +* [GraphQL Batch Processor](https://github.com/vasily-kartashov/graphql-batch-processing) – Provides a builder interface for defining collection, querying, filtering, and post-processing logic of batched data fetches. +* [PSR 15 compliant middleware](https://github.com/phps-cans/psr7-middleware-graphql) for the Standard Server _(experimental)_ # General GraphQL Tools -* [GraphiQL](https://github.com/graphql/graphiql) - An in-browser IDE for exploring GraphQL +* [GraphQL Playground](https://github.com/prismagraphql/graphql-playground) – GraphQL IDE for better development workflows (GraphQL Subscriptions, interactive docs & collaboration). +* [GraphiQL](https://github.com/graphql/graphiql) – An in-browser IDE for exploring GraphQL * [ChromeiQL](https://chrome.google.com/webstore/detail/chromeiql/fkkiamalmpiidkljmicmjfbieiclmeij) - or [GraphiQL Feen](https://chrome.google.com/webstore/detail/graphiql-feen/mcbfdonlkfpbfdpimkjilhdneikhfklp) - + or [GraphiQL Feen](https://chrome.google.com/webstore/detail/graphiql-feen/mcbfdonlkfpbfdpimkjilhdneikhfklp) – GraphiQL as Google Chrome extension -* [GraphQL Playground](https://github.com/prismagraphql/graphql-playground) - GraphQL IDE for better development workflows (GraphQL Subscriptions, interactive docs & collaboration). From 396d3370dcca9a86dd174fb9a33b99b31b7bdb42 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Fri, 23 Nov 2018 11:00:26 +0100 Subject: [PATCH 02/45] Mention simPod/GraphQL-Utils Resolves #70 Closes #393 --- docs/complementary-tools.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/complementary-tools.md b/docs/complementary-tools.md index a92d66b..1f42449 100644 --- a/docs/complementary-tools.md +++ b/docs/complementary-tools.md @@ -14,6 +14,7 @@ * [DataLoaderPHP](https://github.com/overblog/dataloader-php) – as a ready implementation for [deferred resolvers](data-fetching.md#solving-n1-problem) * [GraphQL Uploads](https://github.com/Ecodev/graphql-upload) – A PSR-15 middleware to support file uploads in GraphQL. * [GraphQL Batch Processor](https://github.com/vasily-kartashov/graphql-batch-processing) – Provides a builder interface for defining collection, querying, filtering, and post-processing logic of batched data fetches. +* [GraphQL Utils](https://github.com/simPod/GraphQL-Utils) – Objective schema definition builders (no need for arrays anymore) and `DateTime` scalar * [PSR 15 compliant middleware](https://github.com/phps-cans/psr7-middleware-graphql) for the Standard Server _(experimental)_ # General GraphQL Tools From e344c8a44145899dc3b6648967547d4c581ea236 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Fri, 23 Nov 2018 12:17:33 +0100 Subject: [PATCH 03/45] Improve comparison 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 837628a..5944a6e 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -153,7 +153,7 @@ class ReferenceExecutor implements ExecutorImplementation break; } } - if (! $operation) { + if ($operation === null) { if ($operationName) { $errors[] = new Error(sprintf('Unknown operation named "%s".', $operationName)); } else { @@ -165,7 +165,7 @@ class ReferenceExecutor implements ExecutorImplementation ); } $variableValues = null; - if ($operation) { + if ($operation !== null) { [$coercionErrors, $coercedVariableValues] = Values::getVariableValues( $schema, $operation->variableDefinitions ?: [], From ead1b864bc82e4ede561ede0d3e9085d4fac3ca0 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Tue, 27 Nov 2018 12:22:01 +0100 Subject: [PATCH 04/45] Added all possible scalar types to Node constructor --- src/Language/AST/Node.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Language/AST/Node.php b/src/Language/AST/Node.php index 826478b..2205d6d 100644 --- a/src/Language/AST/Node.php +++ b/src/Language/AST/Node.php @@ -40,7 +40,7 @@ abstract class Node public $loc; /** - * @param (string|NameNode|NodeList|SelectionSetNode|Location|null)[] $vars + * @param (NameNode|NodeList|SelectionSetNode|Location|string|int|bool|float|null)[] $vars */ public function __construct(array $vars) { From 376e9275054f9b63a27b8fa2215553f050898602 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Tue, 27 Nov 2018 13:39:41 +0100 Subject: [PATCH 05/45] Document BC and fix types in ResolveInfo --- UPGRADE.md | 3 +++ src/Type/Definition/ResolveInfo.php | 32 ++++++++++++++++++----------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/UPGRADE.md b/UPGRADE.md index 943f530..a9d7256 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -78,6 +78,9 @@ Parser::parse($source, [ 'allowLegacySDLImplementsInterfaces' => true]) - `AbstractQuerySecurity` renamed to `QuerySecurityRule` (NS `GraphQL\Validator\Rules`) - `FindBreakingChanges` renamed to `BreakingChangesFinder` (NS `GraphQL\Utils`) +### Breaking: new constructors + +`GraphQL\Type\Definition\ResolveInfo` now takes 10 arguments instead of one array. ## Upgrade v0.11.x > v0.12.x diff --git a/src/Type/Definition/ResolveInfo.php b/src/Type/Definition/ResolveInfo.php index 8a752b1..377bb49 100644 --- a/src/Type/Definition/ResolveInfo.php +++ b/src/Type/Definition/ResolveInfo.php @@ -23,7 +23,7 @@ class ResolveInfo * The name of the field being resolved * * @api - * @var string|null + * @var string */ public $fieldName; @@ -31,9 +31,9 @@ class ResolveInfo * AST of all nodes referencing this field in the query. * * @api - * @var FieldNode[]|null + * @var FieldNode[] */ - public $fieldNodes; + public $fieldNodes = []; /** * Expected return type of the field being resolved @@ -47,7 +47,7 @@ class ResolveInfo * Parent type of the field being resolved * * @api - * @var ObjectType|null + * @var ObjectType */ public $parentType; @@ -55,7 +55,7 @@ class ResolveInfo * Path to this field from the very root value * * @api - * @var string[] + * @var string[][] */ public $path; @@ -71,9 +71,9 @@ class ResolveInfo * AST of all fragments defined in query * * @api - * @var FragmentDefinitionNode[]|null + * @var FragmentDefinitionNode[] */ - public $fragments; + public $fragments = []; /** * Root value passed to query execution @@ -95,21 +95,29 @@ class ResolveInfo * Array of variables passed to query execution * * @api - * @var mixed[]|null + * @var mixed[] */ - public $variableValues; + public $variableValues = []; + /** + * @param FieldNode[] $fieldNodes + * @param ScalarType|ObjectType|InterfaceType|UnionType|EnumType|ListOfType|NonNull $returnType + * @param string[][] $path + * @param FragmentDefinitionNode[] $fragments + * @param mixed|null $rootValue + * @param mixed[] $variableValues + */ public function __construct( string $fieldName, $fieldNodes, $returnType, ObjectType $parentType, - $path, + array $path, Schema $schema, - $fragments, + array $fragments, $rootValue, ?OperationDefinitionNode $operation, - $variableValues + array $variableValues ) { $this->fieldName = $fieldName; $this->fieldNodes = $fieldNodes; From 33e3c9c338b3c77edcdc009d546015e8d74ae049 Mon Sep 17 00:00:00 2001 From: Yury Date: Wed, 28 Nov 2018 17:35:21 +0300 Subject: [PATCH 06/45] Error handling improvements --- src/Validator/Rules/ValuesOfCorrectType.php | 96 ++++++++++++-------- tests/Type/EnumTypeTest.php | 10 +-- tests/Validator/ValidationTest.php | 2 +- tests/Validator/ValuesOfCorrectTypeTest.php | 97 +++++++++------------ 4 files changed, 109 insertions(+), 96 deletions(-) diff --git a/src/Validator/Rules/ValuesOfCorrectType.php b/src/Validator/Rules/ValuesOfCorrectType.php index a08d99b..ad2468e 100644 --- a/src/Validator/Rules/ValuesOfCorrectType.php +++ b/src/Validator/Rules/ValuesOfCorrectType.php @@ -8,6 +8,7 @@ use Exception; use GraphQL\Error\Error; use GraphQL\Language\AST\BooleanValueNode; use GraphQL\Language\AST\EnumValueNode; +use GraphQL\Language\AST\FieldNode; use GraphQL\Language\AST\FloatValueNode; use GraphQL\Language\AST\IntValueNode; use GraphQL\Language\AST\ListValueNode; @@ -21,6 +22,7 @@ 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; @@ -46,8 +48,14 @@ class ValuesOfCorrectType extends ValidationRule { public function getVisitor(ValidationContext $context) { + $fieldName = ''; return [ - NodeKind::NULL => static function (NullValueNode $node) use ($context) { + NodeKind::FIELD => [ + 'enter' => static function (FieldNode $node) use (&$fieldName) { + $fieldName = $node->name->value; + }, + ], + NodeKind::NULL => static function (NullValueNode $node) use ($context, &$fieldName) { $type = $context->getInputType(); if (! ($type instanceof NonNull)) { return; @@ -55,30 +63,31 @@ class ValuesOfCorrectType extends ValidationRule $context->reportError( new Error( - self::badValueMessage((string) $type, Printer::doPrint($node)), + self::getBadValueMessage((string) $type, Printer::doPrint($node), null, $context, $fieldName), $node ) ); }, - NodeKind::LST => function (ListValueNode $node) use ($context) { + NodeKind::LST => function (ListValueNode $node) use ($context, &$fieldName) { // 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()); if (! $type instanceof ListOfType) { - $this->isValidScalar($context, $node); + $this->isValidScalar($context, $node, $fieldName); return Visitor::skipNode(); } }, - NodeKind::OBJECT => function (ObjectValueNode $node) use ($context) { + NodeKind::OBJECT => function (ObjectValueNode $node) use ($context, &$fieldName) { // 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::getNamedType($context->getInputType()); if (! $type instanceof InputObjectType) { - $this->isValidScalar($context, $node); + $this->isValidScalar($context, $node, $fieldName); return Visitor::skipNode(); } + unset($fieldName); // Ensure every required field exists. $inputFields = $type->getFields(); $nodeFields = iterator_to_array($node->fields); @@ -127,34 +136,36 @@ class ValuesOfCorrectType extends ValidationRule ) ); }, - NodeKind::ENUM => function (EnumValueNode $node) use ($context) { + NodeKind::ENUM => function (EnumValueNode $node) use ($context, &$fieldName) { $type = Type::getNamedType($context->getInputType()); if (! $type instanceof EnumType) { - $this->isValidScalar($context, $node); + $this->isValidScalar($context, $node, $fieldName); } elseif (! $type->getValue($node->value)) { $context->reportError( new Error( - self::badValueMessage( + self::getBadValueMessage( $type->name, Printer::doPrint($node), - $this->enumTypeSuggestion($type, $node) + $this->enumTypeSuggestion($type, $node), + $context, + $fieldName ), $node ) ); } }, - NodeKind::INT => function (IntValueNode $node) use ($context) { - $this->isValidScalar($context, $node); + NodeKind::INT => function (IntValueNode $node) use ($context, &$fieldName) { + $this->isValidScalar($context, $node, $fieldName); }, - NodeKind::FLOAT => function (FloatValueNode $node) use ($context) { - $this->isValidScalar($context, $node); + NodeKind::FLOAT => function (FloatValueNode $node) use ($context, &$fieldName) { + $this->isValidScalar($context, $node, $fieldName); }, - NodeKind::STRING => function (StringValueNode $node) use ($context) { - $this->isValidScalar($context, $node); + NodeKind::STRING => function (StringValueNode $node) use ($context, &$fieldName) { + $this->isValidScalar($context, $node, $fieldName); }, - NodeKind::BOOLEAN => function (BooleanValueNode $node) use ($context) { - $this->isValidScalar($context, $node); + NodeKind::BOOLEAN => function (BooleanValueNode $node) use ($context, &$fieldName) { + $this->isValidScalar($context, $node, $fieldName); }, ]; } @@ -165,7 +176,7 @@ class ValuesOfCorrectType extends ValidationRule ($message ? "; ${message}" : '.'); } - private function isValidScalar(ValidationContext $context, ValueNode $node) + private function isValidScalar(ValidationContext $context, ValueNode $node, $fieldName) { // Report any error at the full type expected by the location. $locationType = $context->getInputType(); @@ -179,10 +190,12 @@ class ValuesOfCorrectType extends ValidationRule if (! $type instanceof ScalarType) { $context->reportError( new Error( - self::badValueMessage( + self::getBadValueMessage( (string) $locationType, Printer::doPrint($node), - $this->enumTypeSuggestion($type, $node) + $this->enumTypeSuggestion($type, $node), + $context, + $fieldName ), $node ) @@ -199,32 +212,28 @@ class ValuesOfCorrectType extends ValidationRule // Ensure a reference to the original error is maintained. $context->reportError( new Error( - self::badValueMessage( + self::getBadValueMessage( (string) $locationType, Printer::doPrint($node), - $error->getMessage() + $error->getMessage(), + $context, + $fieldName ), - $node, - null, - null, - null, - $error + $node ) ); } catch (Throwable $error) { // Ensure a reference to the original error is maintained. $context->reportError( new Error( - self::badValueMessage( + self::getBadValueMessage( (string) $locationType, Printer::doPrint($node), - $error->getMessage() + $error->getMessage(), + $context, + $fieldName ), - $node, - null, - null, - null, - $error + $node ) ); } @@ -247,6 +256,12 @@ class ValuesOfCorrectType extends ValidationRule } } + public static function badArgumentValueMessage($typeName, $valueName, $fieldName, $argName, $message = null) + { + return sprintf('Field "%s" argument "%s" requires type %s, found %s', $fieldName, $argName, $typeName, $valueName) . + ($message ? sprintf('; %s', $message) : '.'); + } + public static function requiredFieldMessage($typeName, $fieldName, $fieldTypeName) { return sprintf('Field %s.%s of required type %s was not provided.', $typeName, $fieldName, $fieldTypeName); @@ -257,4 +272,15 @@ class ValuesOfCorrectType extends ValidationRule return sprintf('Field "%s" is not defined by type %s', $fieldName, $typeName) . ($message ? sprintf('; %s', $message) : '.'); } + + private static function getBadValueMessage($typeName, $valueName, $message = null, $context = null, $fieldName = null) + { + if ($context) { + $arg = $context->getArgument(); + if ($arg) { + return self::badArgumentValueMessage($typeName, $valueName, $fieldName, $arg->name, $message); + } + } + return self::badValueMessage($typeName, $valueName, $message); + } } diff --git a/tests/Type/EnumTypeTest.php b/tests/Type/EnumTypeTest.php index df920e2..9b2e8e6 100644 --- a/tests/Type/EnumTypeTest.php +++ b/tests/Type/EnumTypeTest.php @@ -231,7 +231,7 @@ class EnumTypeTest extends TestCase '{ colorEnum(fromEnum: "GREEN") }', null, [ - 'message' => 'Expected type Color, found "GREEN"; Did you mean the enum value GREEN?', + 'message' => 'Field "colorEnum" argument "fromEnum" requires type Color, found "GREEN"; Did you mean the enum value GREEN?', 'locations' => [new SourceLocation(1, 23)], ] ); @@ -268,7 +268,7 @@ class EnumTypeTest extends TestCase '{ colorEnum(fromEnum: GREENISH) }', null, [ - 'message' => 'Expected type Color, found GREENISH; Did you mean the enum value GREEN?', + 'message' => 'Field "colorEnum" argument "fromEnum" requires type Color, found GREENISH; Did you mean the enum value GREEN?', 'locations' => [new SourceLocation(1, 23)], ] ); @@ -283,7 +283,7 @@ class EnumTypeTest extends TestCase '{ colorEnum(fromEnum: green) }', null, [ - 'message' => 'Expected type Color, found green; Did you mean the enum value GREEN?', + 'message' => 'Field "colorEnum" argument "fromEnum" requires type Color, found green; Did you mean the enum value GREEN?', 'locations' => [new SourceLocation(1, 23)], ] ); @@ -313,7 +313,7 @@ class EnumTypeTest extends TestCase $this->expectFailure( '{ colorEnum(fromEnum: 1) }', null, - 'Expected type Color, found 1.' + 'Field "colorEnum" argument "fromEnum" requires type Color, found 1.' ); } @@ -325,7 +325,7 @@ class EnumTypeTest extends TestCase $this->expectFailure( '{ colorEnum(fromInt: GREEN) }', null, - 'Expected type Int, found GREEN.' + 'Field "colorEnum" argument "fromInt" requires type Int, found GREEN.' ); } diff --git a/tests/Validator/ValidationTest.php b/tests/Validator/ValidationTest.php index 05dbbf2..e823cdc 100644 --- a/tests/Validator/ValidationTest.php +++ b/tests/Validator/ValidationTest.php @@ -38,7 +38,7 @@ class ValidationTest extends ValidatorTestCase '; $expectedError = [ - 'message' => 'Expected type Invalid, found "bad value"; Invalid scalar is always invalid: bad value', + 'message' => 'Field "invalidArg" argument "arg" requires type Invalid, found "bad value"; Invalid scalar is always invalid: bad value', 'locations' => [['line' => 3, 'column' => 25]], ]; diff --git a/tests/Validator/ValuesOfCorrectTypeTest.php b/tests/Validator/ValuesOfCorrectTypeTest.php index 4de4df4..a174808 100644 --- a/tests/Validator/ValuesOfCorrectTypeTest.php +++ b/tests/Validator/ValuesOfCorrectTypeTest.php @@ -240,7 +240,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('String', '1', 4, 39), + $this->badValueWithMessage('Field "stringArgField" argument "stringArg" requires type String, found 1.', 4, 39), ] ); } @@ -257,6 +257,11 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase ); } + private function badValueWithMessage($message, $line, $column) + { + return FormattedError::create($message, [new SourceLocation($line, $column)]); + } + /** * @see it('Float into String') */ @@ -272,7 +277,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('String', '1.0', 4, 39), + $this->badValueWithMessage('Field "stringArgField" argument "stringArg" requires type String, found 1.0.', 4, 39), ] ); } @@ -294,7 +299,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('String', 'true', 4, 39), + $this->badValueWithMessage('Field "stringArgField" argument "stringArg" requires type String, found true.', 4, 39), ] ); } @@ -314,7 +319,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('String', 'BAR', 4, 39), + $this->badValueWithMessage('Field "stringArgField" argument "stringArg" requires type String, found BAR.', 4, 39), ] ); } @@ -334,7 +339,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('Int', '"3"', 4, 33), + $this->badValueWithMessage('Field "intArgField" argument "intArg" requires type Int, found "3".', 4, 33), ] ); } @@ -354,7 +359,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('Int', '829384293849283498239482938', 4, 33), + $this->badValueWithMessage('Field "intArgField" argument "intArg" requires type Int, found 829384293849283498239482938.', 4, 33), ] ); } @@ -376,7 +381,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('Int', 'FOO', 4, 33), + $this->badValueWithMessage('Field "intArgField" argument "intArg" requires type Int, found FOO.', 4, 33), ] ); } @@ -396,7 +401,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('Int', '3.0', 4, 33), + $this->badValueWithMessage('Field "intArgField" argument "intArg" requires type Int, found 3.0.', 4, 33), ] ); } @@ -416,7 +421,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('Int', '3.333', 4, 33), + $this->badValueWithMessage('Field "intArgField" argument "intArg" requires type Int, found 3.333.', 4, 33), ] ); } @@ -436,7 +441,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('Float', '"3.333"', 4, 37), + $this->badValueWithMessage('Field "floatArgField" argument "floatArg" requires type Float, found "3.333".', 4, 37), ] ); } @@ -456,7 +461,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('Float', 'true', 4, 37), + $this->badValueWithMessage('Field "floatArgField" argument "floatArg" requires type Float, found true.', 4, 37), ] ); } @@ -478,7 +483,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('Float', 'FOO', 4, 37), + $this->badValueWithMessage('Field "floatArgField" argument "floatArg" requires type Float, found FOO.', 4, 37), ] ); } @@ -498,7 +503,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('Boolean', '2', 4, 41), + $this->badValueWithMessage('Field "booleanArgField" argument "booleanArg" requires type Boolean, found 2.', 4, 41), ] ); } @@ -518,7 +523,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('Boolean', '1.0', 4, 41), + $this->badValueWithMessage('Field "booleanArgField" argument "booleanArg" requires type Boolean, found 1.0.', 4, 41), ] ); } @@ -540,7 +545,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('Boolean', '"true"', 4, 41), + $this->badValueWithMessage('Field "booleanArgField" argument "booleanArg" requires type Boolean, found "true".', 4, 41), ] ); } @@ -560,7 +565,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('Boolean', 'TRUE', 4, 41), + $this->badValueWithMessage('Field "booleanArgField" argument "booleanArg" requires type Boolean, found TRUE.', 4, 41), ] ); } @@ -580,7 +585,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('ID', '1.0', 4, 31), + $this->badValueWithMessage('Field "idArgField" argument "idArg" requires type ID, found 1.0.', 4, 31), ] ); } @@ -600,7 +605,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('ID', 'true', 4, 31), + $this->badValueWithMessage('Field "idArgField" argument "idArg" requires type ID, found true.', 4, 31), ] ); } @@ -622,7 +627,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('ID', 'SOMETHING', 4, 31), + $this->badValueWithMessage('Field "idArgField" argument "idArg" requires type ID, found SOMETHING.', 4, 31), ] ); } @@ -642,7 +647,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('DogCommand', '2', 4, 41), + $this->badValueWithMessage('Field "doesKnowCommand" argument "dogCommand" requires type DogCommand, found 2.', 4, 41), ] ); } @@ -662,7 +667,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('DogCommand', '1.0', 4, 41), + $this->badValueWithMessage('Field "doesKnowCommand" argument "dogCommand" requires type DogCommand, found 1.0.', 4, 41), ] ); } @@ -684,13 +689,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue( - 'DogCommand', - '"SIT"', - 4, - 41, - 'Did you mean the enum value SIT?' - ), + $this->badValueWithMessage('Field "doesKnowCommand" argument "dogCommand" requires type DogCommand, found "SIT"; Did you mean the enum value SIT?', 4, 41), ] ); } @@ -710,7 +709,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('DogCommand', 'true', 4, 41), + $this->badValueWithMessage('Field "doesKnowCommand" argument "dogCommand" requires type DogCommand, found true.', 4, 41), ] ); } @@ -730,7 +729,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('DogCommand', 'JUGGLE', 4, 41), + $this->badValueWithMessage('Field "doesKnowCommand" argument "dogCommand" requires type DogCommand, found JUGGLE.', 4, 41), ] ); } @@ -750,13 +749,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue( - 'DogCommand', - 'sit', - 4, - 41, - 'Did you mean the enum value SIT?' - ), + $this->badValueWithMessage('Field "doesKnowCommand" argument "dogCommand" requires type DogCommand, found sit; Did you mean the enum value SIT?', 4, 41), ] ); } @@ -846,7 +839,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('String', '2', 4, 55), + $this->badValueWithMessage('Field "stringListArgField" argument "stringListArg" requires type String, found 2.', 4, 55), ] ); } @@ -866,7 +859,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('[String]', '1', 4, 47), + $this->badValueWithMessage('Field "stringListArgField" argument "stringListArg" requires type [String], found 1.', 4, 47), ] ); } @@ -1060,8 +1053,8 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('Int!', '"two"', 4, 32), - $this->badValue('Int!', '"one"', 4, 45), + $this->badValueWithMessage('Field "multipleReqs" argument "req2" requires type Int!, found "two".', 4, 32), + $this->badValueWithMessage('Field "multipleReqs" argument "req1" requires type Int!, found "one".', 4, 45), ] ); } @@ -1081,7 +1074,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('Int!', '"one"', 4, 32), + $this->badValueWithMessage('Field "multipleReqs" argument "req1" requires type Int!, found "one".', 4, 32), ] ); } @@ -1103,7 +1096,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('Int!', 'null', 4, 32), + $this->badValueWithMessage('Field "multipleReqs" argument "req1" requires type Int!, found null.', 4, 32), ] ); } @@ -1277,7 +1270,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('String', '2', 5, 40), + $this->badValueWithMessage('Field "complexArgField" argument "complexArg" requires type String, found 2.', 5, 40), ] ); } @@ -1340,19 +1333,13 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue( - 'Invalid', - '123', - 3, - 27, - 'Invalid scalar is always invalid: 123' - ), + $this->badValueWithMessage('Field "invalidArg" argument "arg" requires type Invalid, found 123; Invalid scalar is always invalid: 123', 3, 27), ] ); self::assertEquals( - 'Invalid scalar is always invalid: 123', - $errors[0]->getPrevious()->getMessage() + 'Field "invalidArg" argument "arg" requires type Invalid, found 123; Invalid scalar is always invalid: 123', + $errors[0]->getMessage() ); } @@ -1411,8 +1398,8 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('Boolean!', '"yes"', 3, 28), - $this->badValue('Boolean!', 'ENUM', 4, 28), + $this->badValueWithMessage('Field "dog" argument "if" requires type Boolean!, found "yes".', 3, 28), + $this->badValueWithMessage('Field "name" argument "if" requires type Boolean!, found ENUM.', 4, 28), ] ); } From f95d1e81eabfae0b83bba19f2b260b5d04640d4f Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Thu, 29 Nov 2018 11:10:51 +0100 Subject: [PATCH 07/45] Add PHP 7.3 to Travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 98acdaa..d27fa09 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ language: php php: - 7.1 - 7.2 + - 7.3 - nightly env: From 63b4e3f0a4527c491f51e37bbda99169a47690f1 Mon Sep 17 00:00:00 2001 From: chriszarate Date: Sat, 1 Dec 2018 18:49:23 -0500 Subject: [PATCH 08/45] Apollo server/client compatibility. Look for queryid in extensions. --- src/Server/OperationParams.php | 5 +++++ tests/Server/RequestParsingTest.php | 26 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/Server/OperationParams.php b/src/Server/OperationParams.php index 8085d1e..a76f86e 100644 --- a/src/Server/OperationParams.php +++ b/src/Server/OperationParams.php @@ -95,6 +95,11 @@ class OperationParams $instance->variables = $params['variables']; $instance->readOnly = (bool) $readonly; + // Apollo server/client compatibility: look for the queryid in extensions + if (isset($params['extensions']['persistedQuery']['sha256Hash']) && empty($instance->query) && empty($instance->queryid)) { + $instance->queryId = $params['extensions']['persistedQuery']['sha256Hash']; + } + return $instance; } diff --git a/tests/Server/RequestParsingTest.php b/tests/Server/RequestParsingTest.php index b6c73d6..75412b1 100644 --- a/tests/Server/RequestParsingTest.php +++ b/tests/Server/RequestParsingTest.php @@ -333,6 +333,32 @@ class RequestParsingTest extends TestCase } } + public function testParsesApolloPersistedQueryJSONRequest() : void + { + $queryId = 'my-query-id'; + $extensions = [ + 'persistedQuery' => [ + 'sha256Hash' => $queryId, + ], + ]; + $variables = ['test' => 1, 'test2' => 2]; + $operation = 'op'; + + $body = [ + 'extensions' => $extensions, + 'variables' => $variables, + 'operationName' => $operation, + ]; + $parsed = [ + 'raw' => $this->parseRawRequest('application/json', json_encode($body)), + 'psr' => $this->parsePsrRequest('application/json', json_encode($body)), + ]; + foreach ($parsed as $method => $parsedBody) { + self::assertValidOperationParams($parsedBody, null, $queryId, $variables, $operation, $method); + self::assertFalse($parsedBody->isReadOnly(), $method); + } + } + public function testParsesBatchJSONRequest() : void { $body = [ From c33e41f2bf44b20b3d79b00d335c3a5ea5975116 Mon Sep 17 00:00:00 2001 From: chriszarate Date: Sat, 1 Dec 2018 19:43:51 -0500 Subject: [PATCH 09/45] Fix linting issue and typos. --- src/Server/OperationParams.php | 4 ++-- tests/Server/RequestParsingTest.php | 8 ++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Server/OperationParams.php b/src/Server/OperationParams.php index a76f86e..65edfc8 100644 --- a/src/Server/OperationParams.php +++ b/src/Server/OperationParams.php @@ -96,8 +96,8 @@ class OperationParams $instance->readOnly = (bool) $readonly; // Apollo server/client compatibility: look for the queryid in extensions - if (isset($params['extensions']['persistedQuery']['sha256Hash']) && empty($instance->query) && empty($instance->queryid)) { - $instance->queryId = $params['extensions']['persistedQuery']['sha256Hash']; + if (isset($params['extensions']['persistedQuery']['sha256Hash']) && empty($instance->query) && empty($instance->queryId)) { + $instance->queryId = $params['extensions']['persistedQuery']['sha256Hash']; } return $instance; diff --git a/tests/Server/RequestParsingTest.php b/tests/Server/RequestParsingTest.php index 75412b1..f935281 100644 --- a/tests/Server/RequestParsingTest.php +++ b/tests/Server/RequestParsingTest.php @@ -335,12 +335,8 @@ class RequestParsingTest extends TestCase public function testParsesApolloPersistedQueryJSONRequest() : void { - $queryId = 'my-query-id'; - $extensions = [ - 'persistedQuery' => [ - 'sha256Hash' => $queryId, - ], - ]; + $queryId = 'my-query-id'; + $extensions = ['persistedQuery' => ['sha256Hash' => $queryId]]; $variables = ['test' => 1, 'test2' => 2]; $operation = 'op'; From f644c1a837890e4ca854d16371c0730bc32b9314 Mon Sep 17 00:00:00 2001 From: chriszarate Date: Sun, 2 Dec 2018 15:10:09 -0500 Subject: [PATCH 10/45] Add extensions to OperationParams instance. --- src/Server/OperationParams.php | 22 +++++++++++++++------- tests/Server/RequestParsingTest.php | 20 ++++++++++++-------- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/Server/OperationParams.php b/src/Server/OperationParams.php index 65edfc8..0d14614 100644 --- a/src/Server/OperationParams.php +++ b/src/Server/OperationParams.php @@ -46,6 +46,12 @@ class OperationParams */ public $variables; + /** + * @api + * @var mixed[]|null + */ + public $extensions; + /** @var mixed[] */ private $originalInput; @@ -76,6 +82,7 @@ class OperationParams 'id' => null, // alias to queryid 'operationname' => null, 'variables' => null, + 'extensions' => null, ]; if ($params['variables'] === '') { @@ -89,15 +96,16 @@ class OperationParams } } - $instance->query = $params['query']; - $instance->queryId = $params['queryid'] ?: $params['documentid'] ?: $params['id']; - $instance->operation = $params['operationname']; - $instance->variables = $params['variables']; - $instance->readOnly = (bool) $readonly; + $instance->query = $params['query']; + $instance->queryId = $params['queryid'] ?: $params['documentid'] ?: $params['id']; + $instance->operation = $params['operationname']; + $instance->variables = $params['variables']; + $instance->extensions = $params['extensions']; + $instance->readOnly = (bool) $readonly; // Apollo server/client compatibility: look for the queryid in extensions - if (isset($params['extensions']['persistedQuery']['sha256Hash']) && empty($instance->query) && empty($instance->queryId)) { - $instance->queryId = $params['extensions']['persistedQuery']['sha256Hash']; + if (isset($instance->extensions['persistedQuery']['sha256Hash']) && empty($instance->query) && empty($instance->queryId)) { + $instance->queryId = $instance->extensions['persistedQuery']['sha256Hash']; } return $instance; diff --git a/tests/Server/RequestParsingTest.php b/tests/Server/RequestParsingTest.php index f935281..cf2e9c5 100644 --- a/tests/Server/RequestParsingTest.php +++ b/tests/Server/RequestParsingTest.php @@ -25,7 +25,7 @@ class RequestParsingTest extends TestCase ]; foreach ($parsed as $source => $parsedBody) { - self::assertValidOperationParams($parsedBody, $query, null, null, null, $source); + self::assertValidOperationParams($parsedBody, $query, null, null, null, null, $source); self::assertFalse($parsedBody->isReadOnly(), $source); } } @@ -91,6 +91,7 @@ class RequestParsingTest extends TestCase $queryId = null, $variables = null, $operation = null, + $extensions = null, $message = '' ) { self::assertInstanceOf(OperationParams::class, $params, $message); @@ -99,6 +100,7 @@ class RequestParsingTest extends TestCase self::assertSame($queryId, $params->queryId, $message); self::assertSame($variables, $params->variables, $message); self::assertSame($operation, $params->operation, $message); + self::assertSame($extensions, $params->extensions, $message); } public function testParsesUrlencodedRequest() : void @@ -118,7 +120,7 @@ class RequestParsingTest extends TestCase ]; foreach ($parsed as $method => $parsedBody) { - self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method); + self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method); self::assertFalse($parsedBody->isReadOnly(), $method); } } @@ -175,7 +177,7 @@ class RequestParsingTest extends TestCase ]; foreach ($parsed as $method => $parsedBody) { - self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method); + self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method); self::assertTrue($parsedBody->isReadonly(), $method); } } @@ -230,7 +232,7 @@ class RequestParsingTest extends TestCase ]; foreach ($parsed as $method => $parsedBody) { - self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method); + self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method); self::assertFalse($parsedBody->isReadOnly(), $method); } } @@ -286,7 +288,7 @@ class RequestParsingTest extends TestCase 'psr' => $this->parsePsrRequest('application/json', json_encode($body)), ]; foreach ($parsed as $method => $parsedBody) { - self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method); + self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method); self::assertFalse($parsedBody->isReadOnly(), $method); } } @@ -307,7 +309,7 @@ class RequestParsingTest extends TestCase 'psr' => $this->parsePsrRequest('application/json', json_encode($body)), ]; foreach ($parsed as $method => $parsedBody) { - self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method); + self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method); self::assertFalse($parsedBody->isReadOnly(), $method); } } @@ -328,7 +330,7 @@ class RequestParsingTest extends TestCase 'psr' => $this->parsePsrRequest('application/json', json_encode($body)), ]; foreach ($parsed as $method => $parsedBody) { - self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method); + self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method); self::assertFalse($parsedBody->isReadOnly(), $method); } } @@ -350,7 +352,7 @@ class RequestParsingTest extends TestCase 'psr' => $this->parsePsrRequest('application/json', json_encode($body)), ]; foreach ($parsed as $method => $parsedBody) { - self::assertValidOperationParams($parsedBody, null, $queryId, $variables, $operation, $method); + self::assertValidOperationParams($parsedBody, null, $queryId, $variables, $operation, $extensions, $method); self::assertFalse($parsedBody->isReadOnly(), $method); } } @@ -382,6 +384,7 @@ class RequestParsingTest extends TestCase null, $body[0]['variables'], $body[0]['operationName'], + null, $method ); self::assertValidOperationParams( @@ -390,6 +393,7 @@ class RequestParsingTest extends TestCase $body[1]['queryId'], $body[1]['variables'], $body[1]['operationName'], + null, $method ); } From 9a0dbff26b72cca1528ba4fac536fafc7ba3f624 Mon Sep 17 00:00:00 2001 From: Erik Gaal Date: Wed, 5 Dec 2018 11:42:17 +0100 Subject: [PATCH 11/45] Add NullableType interface --- src/Type/Definition/EnumType.php | 2 +- src/Type/Definition/InputObjectType.php | 2 +- src/Type/Definition/InterfaceType.php | 2 +- src/Type/Definition/ListOfType.php | 2 +- src/Type/Definition/NonNull.php | 14 ++++---------- src/Type/Definition/NullableType.php | 20 ++++++++++++++++++++ src/Type/Definition/ObjectType.php | 2 +- src/Type/Definition/ScalarType.php | 2 +- src/Type/Definition/Type.php | 4 ++-- src/Type/Definition/UnionType.php | 2 +- 10 files changed, 33 insertions(+), 19 deletions(-) create mode 100644 src/Type/Definition/NullableType.php diff --git a/src/Type/Definition/EnumType.php b/src/Type/Definition/EnumType.php index 2b42590..b565a25 100644 --- a/src/Type/Definition/EnumType.php +++ b/src/Type/Definition/EnumType.php @@ -22,7 +22,7 @@ use function sprintf; /** * Class EnumType */ -class EnumType extends Type implements InputType, OutputType, LeafType, NamedType +class EnumType extends Type implements InputType, OutputType, LeafType, NullableType, NamedType { /** @var EnumTypeDefinitionNode|null */ public $astNode; diff --git a/src/Type/Definition/InputObjectType.php b/src/Type/Definition/InputObjectType.php index e43caad..756e142 100644 --- a/src/Type/Definition/InputObjectType.php +++ b/src/Type/Definition/InputObjectType.php @@ -18,7 +18,7 @@ use function sprintf; /** * Class InputObjectType */ -class InputObjectType extends Type implements InputType, NamedType +class InputObjectType extends Type implements InputType, NullableType, NamedType { /** @var InputObjectTypeDefinitionNode|null */ public $astNode; diff --git a/src/Type/Definition/InterfaceType.php b/src/Type/Definition/InterfaceType.php index ff01a48..52ad81d 100644 --- a/src/Type/Definition/InterfaceType.php +++ b/src/Type/Definition/InterfaceType.php @@ -15,7 +15,7 @@ use function sprintf; /** * Class InterfaceType */ -class InterfaceType extends Type implements AbstractType, OutputType, CompositeType, NamedType +class InterfaceType extends Type implements AbstractType, OutputType, CompositeType, NullableType, NamedType { /** @var InterfaceTypeDefinitionNode|null */ public $astNode; diff --git a/src/Type/Definition/ListOfType.php b/src/Type/Definition/ListOfType.php index 7fdb6e6..29e3438 100644 --- a/src/Type/Definition/ListOfType.php +++ b/src/Type/Definition/ListOfType.php @@ -7,7 +7,7 @@ namespace GraphQL\Type\Definition; /** * Class ListOfType */ -class ListOfType extends Type implements WrappingType, OutputType, InputType +class ListOfType extends Type implements WrappingType, OutputType, NullableType, InputType { /** @var ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType */ public $ofType; diff --git a/src/Type/Definition/NonNull.php b/src/Type/Definition/NonNull.php index f3ebbca..cc80be0 100644 --- a/src/Type/Definition/NonNull.php +++ b/src/Type/Definition/NonNull.php @@ -4,8 +4,6 @@ declare(strict_types=1); namespace GraphQL\Type\Definition; -use Exception; -use GraphQL\Error\InvariantViolation; use GraphQL\Utils\Utils; /** @@ -13,13 +11,11 @@ use GraphQL\Utils\Utils; */ class NonNull extends Type implements WrappingType, OutputType, InputType { - /** @var ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType */ + /** @var NullableType */ private $ofType; /** - * @param callable|Type $type - * - * @throws Exception + * @param NullableType $type */ public function __construct($type) { @@ -29,7 +25,7 @@ class NonNull extends Type implements WrappingType, OutputType, InputType /** * @param mixed $type * - * @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType + * @return NullableType */ public static function assertNullableType($type) { @@ -67,9 +63,7 @@ class NonNull extends Type implements WrappingType, OutputType, InputType /** * @param bool $recurse * - * @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType - * - * @throws InvariantViolation + * @return Type */ public function getWrappedType($recurse = false) { diff --git a/src/Type/Definition/NullableType.php b/src/Type/Definition/NullableType.php new file mode 100644 index 0000000..7ff70b2 --- /dev/null +++ b/src/Type/Definition/NullableType.php @@ -0,0 +1,20 @@ +; + */ + +interface NullableType +{ +} diff --git a/src/Type/Definition/ObjectType.php b/src/Type/Definition/ObjectType.php index ac40258..f509293 100644 --- a/src/Type/Definition/ObjectType.php +++ b/src/Type/Definition/ObjectType.php @@ -54,7 +54,7 @@ use function sprintf; * } * ]); */ -class ObjectType extends Type implements OutputType, CompositeType, NamedType +class ObjectType extends Type implements OutputType, CompositeType, NullableType, NamedType { /** @var ObjectTypeDefinitionNode|null */ public $astNode; diff --git a/src/Type/Definition/ScalarType.php b/src/Type/Definition/ScalarType.php index db4e63f..17bf6e7 100644 --- a/src/Type/Definition/ScalarType.php +++ b/src/Type/Definition/ScalarType.php @@ -27,7 +27,7 @@ use function is_string; * } * } */ -abstract class ScalarType extends Type implements OutputType, InputType, LeafType, NamedType +abstract class ScalarType extends Type implements OutputType, InputType, LeafType, NullableType, NamedType { /** @var ScalarTypeDefinitionNode|null */ public $astNode; diff --git a/src/Type/Definition/Type.php b/src/Type/Definition/Type.php index c349d11..5b8e414 100644 --- a/src/Type/Definition/Type.php +++ b/src/Type/Definition/Type.php @@ -137,7 +137,7 @@ abstract class Type implements JsonSerializable } /** - * @param ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType $wrappedType + * @param NullableType $wrappedType * * @return NonNull * @@ -338,7 +338,7 @@ abstract class Type implements JsonSerializable /** * @param Type $type * - * @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType + * @return NullableType * * @api */ diff --git a/src/Type/Definition/UnionType.php b/src/Type/Definition/UnionType.php index a8e6fd7..8a735e3 100644 --- a/src/Type/Definition/UnionType.php +++ b/src/Type/Definition/UnionType.php @@ -17,7 +17,7 @@ use function sprintf; /** * Class UnionType */ -class UnionType extends Type implements AbstractType, OutputType, CompositeType, NamedType +class UnionType extends Type implements AbstractType, OutputType, CompositeType, NullableType, NamedType { /** @var UnionTypeDefinitionNode */ public $astNode; From 62b003643779246403c0fd71d7e7486d94c50835 Mon Sep 17 00:00:00 2001 From: Torsten Blindert Date: Wed, 5 Dec 2018 18:55:14 +0100 Subject: [PATCH 12/45] BUGFIX: expect ->getType() to throw --- src/Utils/SchemaExtender.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Utils/SchemaExtender.php b/src/Utils/SchemaExtender.php index fab006e..110e750 100644 --- a/src/Utils/SchemaExtender.php +++ b/src/Utils/SchemaExtender.php @@ -513,7 +513,14 @@ class SchemaExtender $schemaExtensions[] = $def; } elseif ($def instanceof TypeDefinitionNode) { $typeName = isset($def->name) ? $def->name->value : null; - if ($schema->getType($typeName)) { + + try { + $type = $schema->getType($typeName); + } catch (Error $error) { + $type = null; + } + + if ($type) { throw new Error('Type "' . $typeName . '" already exists in the schema. It cannot also be defined in this type definition.', [$def]); } $typeDefinitionMap[$typeName] = $def; From d5fddfd504d438d92a32be28546a615cc49f3c6f Mon Sep 17 00:00:00 2001 From: Torsten Blindert Date: Wed, 5 Dec 2018 19:13:32 +0100 Subject: [PATCH 13/45] TASK: Added test --- tests/Utils/SchemaExtenderTest.php | 45 ++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/Utils/SchemaExtenderTest.php b/tests/Utils/SchemaExtenderTest.php index 8965fa7..be07d1c 100644 --- a/tests/Utils/SchemaExtenderTest.php +++ b/tests/Utils/SchemaExtenderTest.php @@ -24,6 +24,7 @@ use GraphQL\Type\Definition\ScalarType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Schema; +use GraphQL\Utils\BuildSchema; use GraphQL\Utils\SchemaExtender; use GraphQL\Utils\SchemaPrinter; use PHPUnit\Framework\TestCase; @@ -1962,4 +1963,48 @@ extend type Query { $result = GraphQL::executeQuery($extendedSchema, $query); self::assertSame(['data' => ['hello' => 'Hello World!']], $result->toArray()); } + + + /** + * @see https://github.com/webonyx/graphql-php/issues/180 + */ + public function testShouldBeAbleToIntroduceNewTypesThroughExtension() + { + $sdl = ' + type Query { + defaultValue: String + } + type Foo { + value: Int + } + '; + + $documentNode = Parser::parse($sdl); + $schema = BuildSchema::build($documentNode); + + $extensionSdl = ' + type Bar { + foo: Foo + } + '; + + $extendedDocumentNode = Parser::parse($extensionSdl); + $extendedSchema = SchemaExtender::extend($schema, $extendedDocumentNode); + + $expected = ' + type Bar { + foo: Foo + } + + type Foo { + value: Int + } + + type Query { + defaultValue: String + } + '; + + static::assertEquals($this->dedent($expected), SchemaPrinter::doPrint($extendedSchema)); + } } From 9609d2ac84623291c76c4c72b61235799ad07f5e Mon Sep 17 00:00:00 2001 From: Torsten Blindert Date: Wed, 5 Dec 2018 19:23:07 +0100 Subject: [PATCH 14/45] TASK: Code style --- tests/Utils/SchemaExtenderTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Utils/SchemaExtenderTest.php b/tests/Utils/SchemaExtenderTest.php index be07d1c..bc9842d 100644 --- a/tests/Utils/SchemaExtenderTest.php +++ b/tests/Utils/SchemaExtenderTest.php @@ -1980,7 +1980,7 @@ extend type Query { '; $documentNode = Parser::parse($sdl); - $schema = BuildSchema::build($documentNode); + $schema = BuildSchema::build($documentNode); $extensionSdl = ' type Bar { @@ -1989,7 +1989,7 @@ extend type Query { '; $extendedDocumentNode = Parser::parse($extensionSdl); - $extendedSchema = SchemaExtender::extend($schema, $extendedDocumentNode); + $extendedSchema = SchemaExtender::extend($schema, $extendedDocumentNode); $expected = ' type Bar { From 244ec66ecc0677d25ad2d958baed7de50e551043 Mon Sep 17 00:00:00 2001 From: chriszarate Date: Tue, 11 Dec 2018 22:58:57 -0500 Subject: [PATCH 15/45] Allow extensions to be provided in GET request. --- src/Server/OperationParams.php | 15 +++++++++++---- tests/Server/RequestParsingTest.php | 12 +++++++----- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/Server/OperationParams.php b/src/Server/OperationParams.php index 0d14614..6d2cfd1 100644 --- a/src/Server/OperationParams.php +++ b/src/Server/OperationParams.php @@ -89,11 +89,18 @@ class OperationParams $params['variables'] = null; } - if (is_string($params['variables'])) { - $tmp = json_decode($params['variables'], true); - if (! json_last_error()) { - $params['variables'] = $tmp; + // Some parameters could be provided as serialized JSON. + foreach (['extensions', 'variables'] as $param) { + if (! is_string($params[$param])) { + continue; } + + $tmp = json_decode($params[$param], true); + if (json_last_error()) { + continue; + } + + $params[$param] = $tmp; } $instance->query = $params['query']; diff --git a/tests/Server/RequestParsingTest.php b/tests/Server/RequestParsingTest.php index cf2e9c5..9de6eab 100644 --- a/tests/Server/RequestParsingTest.php +++ b/tests/Server/RequestParsingTest.php @@ -293,14 +293,16 @@ class RequestParsingTest extends TestCase } } - public function testParsesVariablesAsJSON() : void + public function testParsesParamsAsJSON() : void { - $query = '{my query}'; - $variables = ['test' => 1, 'test2' => 2]; - $operation = 'op'; + $query = '{my query}'; + $variables = ['test1' => 1, 'test2' => 2]; + $extensions = ['test3' => 3, 'test4' => 4]; + $operation = 'op'; $body = [ 'query' => $query, + 'extensions' => json_encode($extensions), 'variables' => json_encode($variables), 'operationName' => $operation, ]; @@ -309,7 +311,7 @@ class RequestParsingTest extends TestCase 'psr' => $this->parsePsrRequest('application/json', json_encode($body)), ]; foreach ($parsed as $method => $parsedBody) { - self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method); + self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $extensions, $method); self::assertFalse($parsedBody->isReadOnly(), $method); } } From 31d89acfae5a27b772cd0ebec992d0e564796cf5 Mon Sep 17 00:00:00 2001 From: Jan Bukva Date: Thu, 27 Dec 2018 14:48:03 +0100 Subject: [PATCH 16/45] Failing test for CoroutineExecutor removes associative array when using custom scalar producing JSON --- tests/Executor/ExecutorSchemaTest.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/Executor/ExecutorSchemaTest.php b/tests/Executor/ExecutorSchemaTest.php index 4c3b9d6..36a2a73 100644 --- a/tests/Executor/ExecutorSchemaTest.php +++ b/tests/Executor/ExecutorSchemaTest.php @@ -6,6 +6,7 @@ namespace GraphQL\Tests\Executor; use GraphQL\Executor\Executor; use GraphQL\Language\Parser; +use GraphQL\Type\Definition\CustomScalarType; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Schema; @@ -20,6 +21,13 @@ class ExecutorSchemaTest extends TestCase */ public function testExecutesUsingASchema() : void { + $BlogSerializableValueType = new CustomScalarType([ + 'name' => 'JsonSerializableValueScalar', + 'serialize' => static function ($value) { return $value; }, + 'parseValue' => static function ($value) { return $value; }, + 'parseLiteral' => static function ($value) { return $value; }, + ]); + $BlogArticle = null; $BlogImage = new ObjectType([ 'name' => 'Image', @@ -57,6 +65,7 @@ class ExecutorSchemaTest extends TestCase 'title' => ['type' => Type::string()], 'body' => ['type' => Type::string()], 'keywords' => ['type' => Type::listOf(Type::string())], + 'meta' => ['type' => $BlogSerializableValueType], ], ]); @@ -113,6 +122,7 @@ class ExecutorSchemaTest extends TestCase keywords } } + meta } } @@ -191,6 +201,9 @@ class ExecutorSchemaTest extends TestCase 'keywords' => ['foo', 'bar', '1', 'true', null], ], ], + 'meta' => [ + 'title' => 'My Article 1 | My Blog' + ] ], ], ]; @@ -210,6 +223,9 @@ class ExecutorSchemaTest extends TestCase 'body' => 'This is a post', 'hidden' => 'This data is not exposed in the schema', 'keywords' => ['foo', 'bar', 1, true, null], + 'meta' => [ + 'title' => 'My Article 1 | My Blog', + ], ]; }; From 2295b96a493b9ce6528abd8e1a644e1164bb9fbe Mon Sep 17 00:00:00 2001 From: Jan Bukva Date: Thu, 27 Dec 2018 16:00:57 +0100 Subject: [PATCH 17/45] Fix CoroutineExecutor::resultToArray for associative arrays --- src/Experimental/Executor/CoroutineExecutor.php | 14 +++++++++++--- tests/Executor/ExecutorSchemaTest.php | 14 +++++--------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/Experimental/Executor/CoroutineExecutor.php b/src/Experimental/Executor/CoroutineExecutor.php index b473c56..7d985c1 100644 --- a/src/Experimental/Executor/CoroutineExecutor.php +++ b/src/Experimental/Executor/CoroutineExecutor.php @@ -34,8 +34,11 @@ use GraphQL\Utils\Utils; use SplQueue; use stdClass; use Throwable; +use function array_keys; +use function count; use function is_array; use function is_string; +use function range; use function sprintf; class CoroutineExecutor implements Runtime, ExecutorImplementation @@ -151,9 +154,14 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation } if (is_array($value)) { - $array = []; - foreach ($value as $item) { - $array[] = self::resultToArray($item); + $array = []; + $isAssoc = array_keys($value) !== range(0, count($value) - 1); + foreach ($value as $key => $item) { + if ($isAssoc) { + $array[$key] = self::resultToArray($item); + } else { + $array[] = self::resultToArray($item); + } } return $array; } diff --git a/tests/Executor/ExecutorSchemaTest.php b/tests/Executor/ExecutorSchemaTest.php index 36a2a73..4946c59 100644 --- a/tests/Executor/ExecutorSchemaTest.php +++ b/tests/Executor/ExecutorSchemaTest.php @@ -23,9 +23,9 @@ class ExecutorSchemaTest extends TestCase { $BlogSerializableValueType = new CustomScalarType([ 'name' => 'JsonSerializableValueScalar', - 'serialize' => static function ($value) { return $value; }, - 'parseValue' => static function ($value) { return $value; }, - 'parseLiteral' => static function ($value) { return $value; }, + 'serialize' => static function ($value) { + return $value; + }, ]); $BlogArticle = null; @@ -201,9 +201,7 @@ class ExecutorSchemaTest extends TestCase 'keywords' => ['foo', 'bar', '1', 'true', null], ], ], - 'meta' => [ - 'title' => 'My Article 1 | My Blog' - ] + 'meta' => [ 'title' => 'My Article 1 | My Blog' ], ], ], ]; @@ -223,9 +221,7 @@ class ExecutorSchemaTest extends TestCase 'body' => 'This is a post', 'hidden' => 'This data is not exposed in the schema', 'keywords' => ['foo', 'bar', 1, true, null], - 'meta' => [ - 'title' => 'My Article 1 | My Blog', - ], + 'meta' => ['title' => 'My Article 1 | My Blog'], ]; }; From 00490d289c0a858842e08cead45635016f147921 Mon Sep 17 00:00:00 2001 From: Jan Bukva Date: Thu, 27 Dec 2018 21:53:16 +0100 Subject: [PATCH 18/45] Use key-value foreach --- src/Experimental/Executor/CoroutineExecutor.php | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/Experimental/Executor/CoroutineExecutor.php b/src/Experimental/Executor/CoroutineExecutor.php index 7d985c1..b089fad 100644 --- a/src/Experimental/Executor/CoroutineExecutor.php +++ b/src/Experimental/Executor/CoroutineExecutor.php @@ -34,11 +34,8 @@ use GraphQL\Utils\Utils; use SplQueue; use stdClass; use Throwable; -use function array_keys; -use function count; use function is_array; use function is_string; -use function range; use function sprintf; class CoroutineExecutor implements Runtime, ExecutorImplementation @@ -154,14 +151,9 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation } if (is_array($value)) { - $array = []; - $isAssoc = array_keys($value) !== range(0, count($value) - 1); + $array = []; foreach ($value as $key => $item) { - if ($isAssoc) { - $array[$key] = self::resultToArray($item); - } else { - $array[] = self::resultToArray($item); - } + $array[$key] = self::resultToArray($item); } return $array; } From 828a9fb002e975ddf5945d3f4af91e4e4e8f23e2 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Thu, 27 Dec 2018 22:39:16 +0100 Subject: [PATCH 19/45] Fix Deferred --- src/Deferred.php | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/Deferred.php b/src/Deferred.php index 197eeeb..6b7bd78 100644 --- a/src/Deferred.php +++ b/src/Deferred.php @@ -11,7 +11,7 @@ use Throwable; class Deferred { - /** @var SplQueue */ + /** @var SplQueue|null */ private static $queue; /** @var callable */ @@ -20,21 +20,6 @@ class Deferred /** @var SyncPromise */ public $promise; - public static function getQueue() - { - return self::$queue ?: self::$queue = new SplQueue(); - } - - public static function runQueue() - { - $q = self::$queue; - while ($q && ! $q->isEmpty()) { - /** @var self $dfd */ - $dfd = $q->dequeue(); - $dfd->run(); - } - } - public function __construct(callable $callback) { $this->callback = $callback; @@ -42,6 +27,25 @@ class Deferred 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) { return $this->promise->then($onFulfilled, $onRejected); From fd7465521a2a6e75dc69c8547c8551c0afe8e60e Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Sun, 30 Dec 2018 00:51:55 +0100 Subject: [PATCH 20/45] Merge clovers --- .scrutinizer.yml | 2 +- .travis.yml | 16 ++++++++++------ composer.json | 1 + 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index a211422..9cef7f5 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -20,7 +20,7 @@ build: tools: external_code_coverage: - timeout: 600 + timeout: 900 build_failure_conditions: - 'elements.rating(<= C).new.exists' # No new classes/methods with a rating of C or worse allowed diff --git a/.travis.yml b/.travis.yml index d27fa09..6065add 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,8 +9,10 @@ php: env: matrix: - - EXECUTOR=coroutine + - EXECUTOR= DEPENDENCIES=--prefer-lowest + - EXECUTOR=coroutine DEPENDENCIES=--prefer-lowest - EXECUTOR= + - EXECUTOR=coroutine cache: @@ -31,9 +33,8 @@ jobs: include: - stage: Test - env: DEPENDENCIES=low install: - - travis_retry composer update --prefer-dist --prefer-lowest + - travis_retry composer update --prefer-dist {$DEPENDENCIES} - stage: Test env: COVERAGE @@ -41,10 +42,12 @@ jobs: - 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-clover clover.xml + - ./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: - - wget https://scrutinizer-ci.com/ocular.phar - - php ocular.phar code-coverage:upload --format=php-clover clover.xml + - ./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 @@ -58,3 +61,4 @@ jobs: env: STATIC_ANALYSIS install: travis_retry composer install --prefer-dist script: composer static-analysis + diff --git a/composer.json b/composer.json index 188e435..34f9d61 100644 --- a/composer.json +++ b/composer.json @@ -19,6 +19,7 @@ "phpstan/phpstan": "0.10.5", "phpstan/phpstan-phpunit": "0.10.0", "phpstan/phpstan-strict-rules": "0.10.1", + "phpunit/phpcov": "^5.0", "phpunit/phpunit": "^7.2", "psr/http-message": "^1.0", "react/promise": "2.*" From edb52685839c12db3b50e51df035ee6ab5c6a67f Mon Sep 17 00:00:00 2001 From: Jan Bukva Date: Mon, 28 Jan 2019 22:43:23 +0100 Subject: [PATCH 21/45] Fix return annotation of resolveType() in InterfaceType --- src/Type/Definition/InterfaceType.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Type/Definition/InterfaceType.php b/src/Type/Definition/InterfaceType.php index 52ad81d..1c7c389 100644 --- a/src/Type/Definition/InterfaceType.php +++ b/src/Type/Definition/InterfaceType.php @@ -107,7 +107,7 @@ class InterfaceType extends Type implements AbstractType, OutputType, CompositeT * @param object $objectValue * @param mixed[] $context * - * @return callable|null + * @return Type|null */ public function resolveType($objectValue, $context, ResolveInfo $info) { From 5b3f44c7a3f56a779c2f95254e7340f7f00afa60 Mon Sep 17 00:00:00 2001 From: Jonas Elfering Date: Fri, 25 Jan 2019 17:12:33 +0100 Subject: [PATCH 22/45] add queryplan --- src/Type/Definition/QueryPlan.php | 239 +++++++++++ src/Type/Definition/ResolveInfo.php | 23 +- tests/Executor/ExecutorTest.php | 16 - tests/Type/QueryPlanTest.php | 588 ++++++++++++++++++++++++++++ 4 files changed, 848 insertions(+), 18 deletions(-) create mode 100644 src/Type/Definition/QueryPlan.php create mode 100644 tests/Type/QueryPlanTest.php diff --git a/src/Type/Definition/QueryPlan.php b/src/Type/Definition/QueryPlan.php new file mode 100644 index 0000000..7564f96 --- /dev/null +++ b/src/Type/Definition/QueryPlan.php @@ -0,0 +1,239 @@ +schema = $schema; + $this->variableValues = $variableValues; + $this->fragments = $fragments; + $this->analyzeQueryPlan($parentType, $fieldNodes); + } + + /** + * @return mixed[] + */ + public function queryPlan() : array + { + return $this->queryPlan; + } + + /** + * @return string[] + */ + public function getReferencedTypes() : array + { + return array_keys($this->types); + } + + public function hasType(string $type) : bool + { + return count(array_filter($this->getReferencedTypes(), static function (string $referencedType) use ($type) { + return $type === $referencedType; + })) > 0; + } + + /** + * @return string[] + */ + public function getReferencedFields() : array + { + return array_values(array_unique(array_merge(...array_values($this->types)))); + } + + public function hasField(string $field) : bool + { + return count(array_filter($this->getReferencedFields(), static function (string $referencedField) use ($field) { + return $field === $referencedField; + })) > 0; + } + + /** + * @return string[] + */ + public function subFields(string $typename) : array + { + if (! array_key_exists($typename, $this->types)) { + return []; + } + + return $this->types[$typename]; + } + + /** + * @param FieldNode[] $fieldNodes + */ + private function analyzeQueryPlan(ObjectType $parentType, iterable $fieldNodes) : void + { + $queryPlan = []; + /** @var FieldNode $fieldNode */ + foreach ($fieldNodes as $fieldNode) { + if (! $fieldNode->selectionSet) { + continue; + } + + $type = $parentType->getField($fieldNode->name->value)->getType(); + if ($type instanceof WrappingType) { + $type = $type->getWrappedType(); + } + + $subfields = $this->analyzeSelectionSet($fieldNode->selectionSet, $type); + + $this->types[$type->name] = array_unique(array_merge( + array_key_exists($type->name, $this->types) ? $this->types[$type->name] : [], + array_keys($subfields) + )); + + $queryPlan = array_merge_recursive( + $queryPlan, + $subfields + ); + } + + $this->queryPlan = $queryPlan; + } + + /** + * @return mixed[] + * + * @throws Error + */ + private function analyzeSelectionSet(SelectionSetNode $selectionSet, ObjectType $parentType) : array + { + $fields = []; + foreach ($selectionSet->selections as $selectionNode) { + if ($selectionNode instanceof FieldNode) { + $fieldName = $selectionNode->name->value; + $type = $parentType->getField($fieldName); + $selectionType = $type->getType(); + + $subfields = []; + if ($selectionNode->selectionSet) { + $subfields = $this->analyzeSubFields($selectionType, $selectionNode->selectionSet); + } + + $fields[$fieldName] = [ + 'type' => $selectionType, + 'fields' => $subfields ?? [], + 'args' => Values::getArgumentValues($type, $selectionNode, $this->variableValues), + ]; + } elseif ($selectionNode instanceof FragmentSpreadNode) { + $spreadName = $selectionNode->name->value; + if (isset($this->fragments[$spreadName])) { + $fragment = $this->fragments[$spreadName]; + $type = $this->schema->getType($fragment->typeCondition->name->value); + $subfields = $this->analyzeSubFields($type, $fragment->selectionSet); + + $fields = $this->arrayMergeDeep( + $subfields, + $fields + ); + } + } elseif ($selectionNode instanceof InlineFragmentNode) { + $type = $this->schema->getType($selectionNode->typeCondition->name->value); + $subfields = $this->analyzeSubFields($type, $selectionNode->selectionSet); + + $fields = $this->arrayMergeDeep( + $subfields, + $fields + ); + } + } + return $fields; + } + + /** + * @return mixed[] + */ + private function analyzeSubFields(Type $type, SelectionSetNode $selectionSet) : array + { + if ($type instanceof WrappingType) { + $type = $type->getWrappedType(); + } + + $subfields = []; + if ($type instanceof ObjectType) { + $subfields = $this->analyzeSelectionSet($selectionSet, $type); + $this->types[$type->name] = array_unique(array_merge( + array_key_exists($type->name, $this->types) ? $this->types[$type->name] : [], + array_keys($subfields) + )); + } + + return $subfields; + } + + /** + * 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 + * + * @see https://stackoverflow.com/a/25712428 + * + * @param mixed[] $array1 + * @param mixed[] $array2 + * + * @return mixed[] + */ + private function arrayMergeDeep(array $array1, array $array2) : array + { + $merged = $array1; + + foreach ($array2 as $key => & $value) { + if (is_numeric($key)) { + if (! in_array($value, $merged, true)) { + $merged[] = $value; + } + } elseif (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) { + $merged[$key] = $this->arrayMergeDeep($merged[$key], $value); + } else { + $merged[$key] = $value; + } + } + + return $merged; + } +} diff --git a/src/Type/Definition/ResolveInfo.php b/src/Type/Definition/ResolveInfo.php index 377bb49..83354c0 100644 --- a/src/Type/Definition/ResolveInfo.php +++ b/src/Type/Definition/ResolveInfo.php @@ -63,7 +63,7 @@ class ResolveInfo * Instance of a schema used for execution * * @api - * @var Schema|null + * @var Schema */ public $schema; @@ -99,6 +99,9 @@ class ResolveInfo */ public $variableValues = []; + /** @var QueryPlan */ + private $queryPlan; + /** * @param FieldNode[] $fieldNodes * @param ScalarType|ObjectType|InterfaceType|UnionType|EnumType|ListOfType|NonNull $returnType @@ -109,7 +112,7 @@ class ResolveInfo */ public function __construct( string $fieldName, - $fieldNodes, + iterable $fieldNodes, $returnType, ObjectType $parentType, array $path, @@ -179,6 +182,22 @@ class ResolveInfo return $fields; } + + public function lookAhead() : QueryPlan + { + if ($this->queryPlan === null) { + $this->queryPlan = new QueryPlan( + $this->parentType, + $this->schema, + $this->fieldNodes, + $this->variableValues, + $this->fragments + ); + } + + return $this->queryPlan; + } + /** * @return bool[] */ diff --git a/tests/Executor/ExecutorTest.php b/tests/Executor/ExecutorTest.php index 9a5d266..fe6768c 100644 --- a/tests/Executor/ExecutorTest.php +++ b/tests/Executor/ExecutorTest.php @@ -284,22 +284,6 @@ class ExecutorTest extends TestCase Executor::execute($schema, $ast, $rootValue, null, ['var' => '123']); - self::assertEquals( - [ - 'fieldName', - 'fieldNodes', - 'returnType', - 'parentType', - 'path', - 'schema', - 'fragments', - 'rootValue', - 'operation', - 'variableValues', - ], - array_keys((array) $info) - ); - self::assertEquals('test', $info->fieldName); self::assertEquals(1, count($info->fieldNodes)); self::assertSame($ast->definitions[0]->selectionSet->selections[0], $info->fieldNodes[0]); diff --git a/tests/Type/QueryPlanTest.php b/tests/Type/QueryPlanTest.php new file mode 100644 index 0000000..50ec6ae --- /dev/null +++ b/tests/Type/QueryPlanTest.php @@ -0,0 +1,588 @@ + 'Image', + 'fields' => [ + 'url' => ['type' => Type::string()], + 'width' => ['type' => Type::int()], + 'height' => ['type' => Type::int()], + ], + ]); + + $article = null; + + $author = new ObjectType([ + 'name' => 'Author', + 'fields' => static function () use ($image, &$article) { + return [ + 'id' => ['type' => Type::string()], + 'name' => ['type' => Type::string()], + 'pic' => [ + 'type' => $image, + 'args' => [ + 'width' => ['type' => Type::int()], + 'height' => ['type' => Type::int()], + ], + ], + 'recentArticle' => ['type' => $article], + ]; + }, + ]); + + $reply = new ObjectType([ + 'name' => 'Reply', + 'fields' => [ + 'author' => ['type' => $author], + 'body' => ['type' => Type::string()], + ], + ]); + + $article = new ObjectType([ + 'name' => 'Article', + 'fields' => [ + 'id' => ['type' => Type::string()], + 'isPublished' => ['type' => Type::boolean()], + 'author' => ['type' => $author], + 'title' => ['type' => Type::string()], + 'body' => ['type' => Type::string()], + 'image' => ['type' => $image], + 'replies' => ['type' => Type::listOf($reply)], + ], + ]); + + $doc = ' + query Test { + article { + author { + name + pic(width: 100, height: 200) { + url + width + } + } + image { + width + height + ...MyImage + } + replies { + body + author { + id + name + pic { + url + width + ... on Image { + height + } + } + recentArticle { + id + title + body + } + } + } + } + } + fragment MyImage on Image { + url + } +'; + $expectedQueryPlan = [ + 'author' => [ + 'type' => $author, + 'args' => [], + 'fields' => [ + 'name' => [ + 'type' => Type::string(), + 'args' => [], + 'fields' => [], + ], + 'pic' => [ + 'type' => $image, + 'args' => [ + 'width' => 100, + 'height' => 200, + ], + 'fields' => [ + 'url' => [ + 'type' => Type::string(), + 'args' => [], + 'fields' => [], + ], + 'width' => [ + 'type' => Type::int(), + 'args' => [], + 'fields' => [], + ], + ], + ], + ], + ], + 'image' => [ + 'type' => $image, + 'args' => [], + 'fields' => [ + 'url' => [ + 'type' => Type::string(), + 'args' => [], + 'fields' => [], + ], + 'width' => [ + 'type' => Type::int(), + 'args' => [], + 'fields' => [], + ], + 'height' => [ + 'type' => Type::int(), + 'args' => [], + 'fields' => [], + ], + ], + ], + 'replies' => [ + 'type' => Type::listOf($reply), + 'args' => [], + 'fields' => [ + 'body' => [ + 'type' => Type::string(), + 'args' => [], + 'fields' => [], + ], + 'author' => [ + 'type' => $author, + 'args' => [], + 'fields' => [ + 'id' => [ + 'type' => Type::string(), + 'args' => [], + 'fields' => [], + ], + 'name' => [ + 'type' => Type::string(), + 'args' => [], + 'fields' => [], + ], + 'pic' => [ + 'type' => $image, + 'args' => [], + 'fields' => [ + 'url' => [ + 'type' => Type::string(), + 'args' => [], + 'fields' => [], + ], + 'width' => [ + 'type' => Type::int(), + 'args' => [], + 'fields' => [], + ], + 'height' => [ + 'type' => Type::int(), + 'args' => [], + 'fields' => [], + ], + ], + ], + 'recentArticle' => [ + 'type' => $article, + 'args' => [], + 'fields' => [ + 'id' => [ + 'type' => Type::string(), + 'args' => [], + 'fields' => [], + ], + 'title' => [ + 'type' => Type::string(), + 'args' => [], + 'fields' => [], + ], + 'body' => [ + 'type' => Type::string(), + 'args' => [], + 'fields' => [], + ], + ], + ], + ], + ], + ], + ], + ]; + + $expectedReferencedTypes = [ + 'Image', + 'Author', + 'Article', + 'Reply', + ]; + + $expectedReferencedFields = [ + 'url', + 'width', + 'height', + 'name', + 'pic', + 'id', + 'recentArticle', + 'title', + 'body', + 'author', + 'image', + 'replies', + ]; + + $hasCalled = false; + /** @var QueryPlan $queryPlan */ + $queryPlan = null; + + $blogQuery = new ObjectType([ + 'name' => 'Query', + 'fields' => [ + 'article' => [ + 'type' => $article, + 'resolve' => static function ( + $value, + $args, + $context, + ResolveInfo $info + ) use ( + &$hasCalled, + &$queryPlan + ) { + $hasCalled = true; + $queryPlan = $info->lookAhead(); + + return null; + }, + ], + ], + ]); + + $schema = new Schema(['query' => $blogQuery]); + $result = GraphQL::executeQuery($schema, $doc)->toArray(); + + self::assertTrue($hasCalled); + self::assertEquals(['data' => ['article' => null]], $result); + self::assertEquals($expectedQueryPlan, $queryPlan->queryPlan()); + self::assertEquals($expectedReferencedTypes, $queryPlan->getReferencedTypes()); + self::assertEquals($expectedReferencedFields, $queryPlan->getReferencedFields()); + self::assertEquals(['url', 'width', 'height'], $queryPlan->subFields('Image')); + + self::assertTrue($queryPlan->hasField('url')); + self::assertFalse($queryPlan->hasField('test')); + + self::assertTrue($queryPlan->hasType('Image')); + self::assertFalse($queryPlan->hasType('Test')); + } + + public function testMergedFragmentsQueryPlan() : void + { + $image = new ObjectType([ + 'name' => 'Image', + 'fields' => [ + 'url' => ['type' => Type::string()], + 'width' => ['type' => Type::int()], + 'height' => ['type' => Type::int()], + ], + ]); + + $article = null; + + $author = new ObjectType([ + 'name' => 'Author', + 'fields' => static function () use ($image, &$article) { + return [ + 'id' => ['type' => Type::string()], + 'name' => ['type' => Type::string()], + 'pic' => [ + 'type' => $image, + 'args' => [ + 'width' => ['type' => Type::int()], + 'height' => ['type' => Type::int()], + ], + ], + 'recentArticle' => ['type' => $article], + ]; + }, + ]); + + $reply = new ObjectType([ + 'name' => 'Reply', + 'fields' => [ + 'author' => ['type' => $author], + 'body' => ['type' => Type::string()], + ], + ]); + + $article = new ObjectType([ + 'name' => 'Article', + 'fields' => [ + 'id' => ['type' => Type::string()], + 'isPublished' => ['type' => Type::boolean()], + 'author' => ['type' => $author], + 'title' => ['type' => Type::string()], + 'body' => ['type' => Type::string()], + 'image' => ['type' => $image], + 'replies' => ['type' => Type::listOf($reply)], + ], + ]); + + $doc = ' + query Test { + article { + author { + name + pic(width: 100, height: 200) { + url + width + } + } + image { + width + height + ...MyImage + } + ...Replies01 + ...Replies02 + } + } + fragment MyImage on Image { + url + } + + fragment Replies01 on Article { + _replies012: replies { + body + } + } + fragment Replies02 on Article { + _replies012: replies { + author { + id + name + pic { + url + width + ... on Image { + height + } + } + recentArticle { + id + title + body + } + } + } + } +'; + + $expectedQueryPlan = [ + 'author' => [ + 'type' => $author, + 'args' => [], + 'fields' => [ + 'name' => [ + 'type' => Type::string(), + 'args' => [], + 'fields' => [], + ], + 'pic' => [ + 'type' => $image, + 'args' => [ + 'width' => 100, + 'height' => 200, + ], + 'fields' => [ + 'url' => [ + 'type' => Type::string(), + 'args' => [], + 'fields' => [], + ], + 'width' => [ + 'type' => Type::int(), + 'args' => [], + 'fields' => [], + ], + ], + ], + ], + ], + 'image' => [ + 'type' => $image, + 'args' => [], + 'fields' => [ + 'url' => [ + 'type' => Type::string(), + 'args' => [], + 'fields' => [], + ], + 'width' => [ + 'type' => Type::int(), + 'args' => [], + 'fields' => [], + ], + 'height' => [ + 'type' => Type::int(), + 'args' => [], + 'fields' => [], + ], + ], + ], + 'replies' => [ + 'type' => Type::listOf($reply), + 'args' => [], + 'fields' => [ + 'body' => [ + 'type' => Type::string(), + 'args' => [], + 'fields' => [], + ], + 'author' => [ + 'type' => $author, + 'args' => [], + 'fields' => [ + 'id' => [ + 'type' => Type::string(), + 'args' => [], + 'fields' => [], + ], + 'name' => [ + 'type' => Type::string(), + 'args' => [], + 'fields' => [], + ], + 'pic' => [ + 'type' => $image, + 'args' => [], + 'fields' => [ + 'url' => [ + 'type' => Type::string(), + 'args' => [], + 'fields' => [], + ], + 'width' => [ + 'type' => Type::int(), + 'args' => [], + 'fields' => [], + ], + 'height' => [ + 'type' => Type::int(), + 'args' => [], + 'fields' => [], + ], + ], + ], + 'recentArticle' => [ + 'type' => $article, + 'args' => [], + 'fields' => [ + 'id' => [ + 'type' => Type::string(), + 'args' => [], + 'fields' => [], + ], + 'title' => [ + 'type' => Type::string(), + 'args' => [], + 'fields' => [], + ], + 'body' => [ + 'type' => Type::string(), + 'args' => [], + 'fields' => [], + ], + ], + ], + ], + ], + ], + ], + ]; + + $expectedReferencedTypes = [ + 'Image', + 'Author', + 'Reply', + 'Article', + ]; + + $expectedReferencedFields = [ + 'url', + 'width', + 'height', + 'name', + 'pic', + 'id', + 'recentArticle', + 'body', + 'author', + 'replies', + 'title', + 'image', + ]; + + $hasCalled = false; + /** @var QueryPlan $queryPlan */ + $queryPlan = null; + + $blogQuery = new ObjectType([ + 'name' => 'Query', + 'fields' => [ + 'article' => [ + 'type' => $article, + 'resolve' => static function ( + $value, + $args, + $context, + ResolveInfo $info + ) use ( + &$hasCalled, + &$queryPlan + ) { + $hasCalled = true; + $queryPlan = $info->lookAhead(); + + return null; + }, + ], + ], + ]); + + $schema = new Schema(['query' => $blogQuery]); + $result = GraphQL::executeQuery($schema, $doc)->toArray(); + + self::assertTrue($hasCalled); + self::assertEquals(['data' => ['article' => null]], $result); + self::assertEquals($expectedQueryPlan, $queryPlan->queryPlan()); + self::assertEquals($expectedReferencedTypes, $queryPlan->getReferencedTypes()); + self::assertEquals($expectedReferencedFields, $queryPlan->getReferencedFields()); + self::assertEquals(['url', 'width', 'height'], $queryPlan->subFields('Image')); + + self::assertTrue($queryPlan->hasField('url')); + self::assertFalse($queryPlan->hasField('test')); + + self::assertTrue($queryPlan->hasType('Image')); + self::assertFalse($queryPlan->hasType('Test')); + } +} From b1ab1820b684ac659dbdfd04ae2b0f502dbc0696 Mon Sep 17 00:00:00 2001 From: Stefano Torresi Date: Mon, 4 Feb 2019 12:38:31 +0100 Subject: [PATCH 23/45] Update docs intro verbiage Just throwing my 2 cents: I don't think it's fair to say that "it's intended to be a replacement", given that multiple API paradigms can coexist in the same system and each of them have their trade-offs. --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index c5c4e66..92a354e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -7,7 +7,7 @@ # About GraphQL GraphQL is a modern way to build HTTP APIs consumed by the web and mobile clients. -It is intended to be a replacement for REST and SOAP APIs (even for **existing applications**). +It is intended to be an alternative to REST and SOAP APIs (even for **existing applications**). GraphQL itself is a [specification](https://github.com/facebook/graphql) designed by Facebook engineers. Various implementations of this specification were written From 7d59811c4ff3134d1e4fd79652f8601e9bd37d36 Mon Sep 17 00:00:00 2001 From: Petr Skoda Date: Thu, 7 Feb 2019 09:14:11 +1300 Subject: [PATCH 24/45] Fix incorrect array type of contextValue in PHPDocs --- src/Executor/Executor.php | 6 +++--- src/Executor/ReferenceExecutor.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Executor/Executor.php b/src/Executor/Executor.php index bf07cdd..c87558d 100644 --- a/src/Executor/Executor.php +++ b/src/Executor/Executor.php @@ -74,7 +74,7 @@ class Executor * 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 * @@ -120,7 +120,7 @@ class Executor * Useful for async PHP platforms. * * @param mixed[]|null $rootValue - * @param mixed[]|null $contextValue + * @param mixed|null $contextValue * @param mixed[]|null $variableValues * @param string|null $operationName * @@ -163,7 +163,7 @@ class Executor * * @param mixed $source * @param mixed[] $args - * @param mixed[]|null $context + * @param mixed|null $context * * @return mixed|null */ diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 5944a6e..5ab906e 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -116,7 +116,7 @@ class ReferenceExecutor implements ExecutorImplementation * execute, which we will pass throughout the other execution methods. * * @param mixed[] $rootValue - * @param mixed[] $contextValue + * @param mixed $contextValue * @param mixed[]|Traversable $rawVariableValues * @param string|null $operationName * From bc637414e5158f5b2b8144be19f4baf8b4c7abc2 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Fri, 8 Feb 2019 15:08:11 +0100 Subject: [PATCH 25/45] Support PHP 8 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 34f9d61..27a3dac 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 77448ba6239f7a8bd413c2dfc4ef6d2c3d040009 Mon Sep 17 00:00:00 2001 From: Petr Skoda Date: Mon, 11 Feb 2019 08:53:24 +1300 Subject: [PATCH 26/45] Fix incorrect array type of rootValue in PHPDocs --- src/Executor/Executor.php | 2 +- src/Executor/ReferenceExecutor.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Executor/Executor.php b/src/Executor/Executor.php index c87558d..55fd0fb 100644 --- a/src/Executor/Executor.php +++ b/src/Executor/Executor.php @@ -119,7 +119,7 @@ class Executor * * Useful for async PHP platforms. * - * @param mixed[]|null $rootValue + * @param mixed|null $rootValue * @param mixed|null $contextValue * @param mixed[]|null $variableValues * @param string|null $operationName diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 5ab906e..90030de 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -115,7 +115,7 @@ class ReferenceExecutor implements ExecutorImplementation * Constructs an ExecutionContext object from the arguments passed to * execute, which we will pass throughout the other execution methods. * - * @param mixed[] $rootValue + * @param mixed $rootValue * @param mixed $contextValue * @param mixed[]|Traversable $rawVariableValues * @param string|null $operationName From d20a6a9d56fd02639e6cf0750b1b441f98e639f8 Mon Sep 17 00:00:00 2001 From: Petr Skoda Date: Mon, 11 Feb 2019 08:53:36 +1300 Subject: [PATCH 27/45] Standardise whitespace --- src/Executor/Executor.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Executor/Executor.php b/src/Executor/Executor.php index 55fd0fb..30ccad4 100644 --- a/src/Executor/Executor.php +++ b/src/Executor/Executor.php @@ -161,9 +161,9 @@ class Executor * 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[] $args - * @param mixed|null $context + * @param mixed $source + * @param mixed[] $args + * @param mixed|null $context * * @return mixed|null */ From dfefdf24cb482e0551147aeae5eda4fe1364105e Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Sat, 9 Feb 2019 20:18:27 +0100 Subject: [PATCH 28/45] Test against PHP 7.4 --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 6065add..ac70819 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ php: - 7.1 - 7.2 - 7.3 + - 7.4snapshot - nightly env: @@ -29,6 +30,7 @@ script: ./vendor/bin/phpunit --group default,ReactPromise jobs: allow_failures: + - php: 7.4snapshot - php: nightly include: From d97fac6ab0811a2923766cf1fbf2aa2fe42204bf Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Thu, 10 Jan 2019 14:27:04 +0100 Subject: [PATCH 29/45] Invert instance of in BCFinder --- src/Utils/BreakingChangesFinder.php | 30 ++++++++++------- tests/Utils/BreakingChangesFinderTest.php | 41 +++++++++++++++++++++++ 2 files changed, 58 insertions(+), 13 deletions(-) diff --git a/src/Utils/BreakingChangesFinder.php b/src/Utils/BreakingChangesFinder.php index 597cd16..93736b6 100644 --- a/src/Utils/BreakingChangesFinder.php +++ b/src/Utils/BreakingChangesFinder.php @@ -112,27 +112,31 @@ class BreakingChangesFinder * @return string[][] */ public static function findTypesThatChangedKind( - Schema $oldSchema, - Schema $newSchema - ) { - $oldTypeMap = $oldSchema->getTypeMap(); - $newTypeMap = $newSchema->getTypeMap(); + Schema $schemaA, + Schema $schemaB + ) : iterable { + $schemaATypeMap = $schemaA->getTypeMap(); + $schemaBTypeMap = $schemaB->getTypeMap(); $breakingChanges = []; - foreach ($oldTypeMap as $typeName => $oldType) { - if (! isset($newTypeMap[$typeName])) { + foreach ($schemaATypeMap as $typeName => $schemaAType) { + if (! isset($schemaBTypeMap[$typeName])) { continue; } - $newType = $newTypeMap[$typeName]; - if ($oldType instanceof $newType) { + $schemaBType = $schemaBTypeMap[$typeName]; + if ($schemaAType instanceof $schemaBType) { continue; } - $oldTypeKindName = self::typeKindName($oldType); - $newTypeKindName = self::typeKindName($newType); - $breakingChanges[] = [ + if ($schemaBType instanceof $schemaAType) { + continue; + } + + $schemaATypeKindName = self::typeKindName($schemaAType); + $schemaBTypeKindName = self::typeKindName($schemaBType); + $breakingChanges[] = [ 'type' => self::BREAKING_CHANGE_TYPE_CHANGED_KIND, - 'description' => "${typeName} changed from ${oldTypeKindName} to ${newTypeKindName}.", + 'description' => "${typeName} changed from ${schemaATypeKindName} to ${schemaBTypeKindName}.", ]; } diff --git a/tests/Utils/BreakingChangesFinderTest.php b/tests/Utils/BreakingChangesFinderTest.php index 91eb677..898bc29 100644 --- a/tests/Utils/BreakingChangesFinderTest.php +++ b/tests/Utils/BreakingChangesFinderTest.php @@ -125,6 +125,47 @@ class BreakingChangesFinderTest extends TestCase ); } + /** + * We need to compare type of class A (old type) and type of class B (new type) + * Class B extends A but are evaluated as same types (if all properties match). + * The reason is that when constructing schema from remote schema, + * we have no certain way to get information about our classes. + * Thus object types from remote schema are constructed as Object Type + * while their local counterparts are usually a subclass of Object Type. + * + * @see https://github.com/webonyx/graphql-php/pull/431 + */ + public function testShouldNotMarkTypesWithInheritedClassesAsChanged() : void + { + $objectTypeConstructedFromRemoteSchema = new ObjectType([ + 'name' => 'ObjectType', + 'fields' => [ + 'field1' => ['type' => Type::string()], + ], + ]); + + $localObjectType = new class([ + 'name' => 'ObjectType', + 'fields' => [ + 'field1' => ['type' => Type::string()], + ], + ]) extends ObjectType{ + }; + + $schemaA = new Schema([ + 'query' => $this->queryType, + 'types' => [$objectTypeConstructedFromRemoteSchema], + ]); + + $schemaB = new Schema([ + 'query' => $this->queryType, + 'types' => [$localObjectType], + ]); + + self::assertEmpty(BreakingChangesFinder::findTypesThatChangedKind($schemaA, $schemaB)); + self::assertEmpty(BreakingChangesFinder::findTypesThatChangedKind($schemaB, $schemaA)); + } + /** * @see it('should detect if a field on a type was deleted or changed type') */ From 972532cf6c1497c87ac59a162f75f86e269f96cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Tue, 12 Feb 2019 10:46:28 +0100 Subject: [PATCH 30/45] Adding GraphQLite to the list of PHP Tools --- docs/complementary-tools.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/complementary-tools.md b/docs/complementary-tools.md index 1f42449..90133b1 100644 --- a/docs/complementary-tools.md +++ b/docs/complementary-tools.md @@ -10,6 +10,7 @@ # GraphQL PHP Tools +* [GraphQLite](https://graphqlite.thecodingmachine.io) – Define your complete schema with annotations * [GraphQL Doctrine](https://github.com/Ecodev/graphql-doctrine) – Define types with Doctrine ORM annotations * [DataLoaderPHP](https://github.com/overblog/dataloader-php) – as a ready implementation for [deferred resolvers](data-fetching.md#solving-n1-problem) * [GraphQL Uploads](https://github.com/Ecodev/graphql-upload) – A PSR-15 middleware to support file uploads in GraphQL. From 441d70c7e530a773feec9457f12020a7529b6f34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=AE=B5=E5=B0=8F=E5=BC=BA?= Date: Tue, 19 Feb 2019 11:21:25 +0800 Subject: [PATCH 31/45] change Document url change Document url --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9da473c..e41ce46 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ composer require webonyx/graphql-php ``` ## Documentation -Full documentation is available on the [Documentation site](http://webonyx.github.io/graphql-php/) as well +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) From 4a39dadd0d88f08ad06b34a3a938e682a37b03cc Mon Sep 17 00:00:00 2001 From: Hughie Devore Date: Thu, 28 Feb 2019 13:23:23 -0700 Subject: [PATCH 32/45] Add WP-GraphQL to complementary tools. We utilized this repo for our work in WP GraphQL and wanted to let WP users know that it's available for use. --- docs/complementary-tools.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/complementary-tools.md b/docs/complementary-tools.md index 90133b1..d8261f7 100644 --- a/docs/complementary-tools.md +++ b/docs/complementary-tools.md @@ -7,6 +7,7 @@ - [laravel-graphql-relay](https://github.com/nuwave/laravel-graphql-relay) – Relay Helpers for Laravel - [Lighthouse](https://github.com/nuwave/lighthouse) – GraphQL Server for Laravel * [OverblogGraphQLBundle](https://github.com/overblog/GraphQLBundle) – Bundle for Symfony +* [WP-GraphQL](https://github.com/wp-graphql/wp-graphql) - GraphQL API for WordPress # GraphQL PHP Tools From d4742a76e5f894e7a9bcdaeec6e2781161e09d76 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Thu, 14 Mar 2019 11:16:15 +0100 Subject: [PATCH 33/45] Remove deprecated tools and improve Lighthouse description - The folklore package was archived by the owner. - The lighthouse relay tools were integrated with the core --- docs/complementary-tools.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/complementary-tools.md b/docs/complementary-tools.md index d8261f7..4710ebd 100644 --- a/docs/complementary-tools.md +++ b/docs/complementary-tools.md @@ -2,10 +2,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. -* Laravel - - [Laravel GraphQL](https://github.com/Folkloreatelier/laravel-graphql) – Integration with Laravel 5 - - [laravel-graphql-relay](https://github.com/nuwave/laravel-graphql-relay) – Relay Helpers for Laravel - - [Lighthouse](https://github.com/nuwave/lighthouse) – GraphQL Server for Laravel +* [Lighthouse](https://github.com/nuwave/lighthouse) – Laravel based, uses Schema Definition Language * [OverblogGraphQLBundle](https://github.com/overblog/GraphQLBundle) – Bundle for Symfony * [WP-GraphQL](https://github.com/wp-graphql/wp-graphql) - GraphQL API for WordPress From 1864facda8dad5f8cd79331b258471a61df27e18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1chym=20Tou=C5=A1ek?= Date: Mon, 18 Mar 2019 12:31:33 +0100 Subject: [PATCH 34/45] Fix annotations --- src/Error/FormattedError.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Error/FormattedError.php b/src/Error/FormattedError.php index b200774..799ee74 100644 --- a/src/Error/FormattedError.php +++ b/src/Error/FormattedError.php @@ -231,7 +231,7 @@ class FormattedError * * @param mixed[] $formattedError * @param Throwable $e - * @param bool $debug + * @param bool|int $debug * * @return mixed[] * @@ -297,7 +297,7 @@ class FormattedError * Prepares final error formatter taking in account $debug flags. * If initial formatter is not set, FormattedError::createFromException is used * - * @param bool $debug + * @param bool|int $debug * * @return callable|callable */ From 23ece094074459018e44e27c3b18d6b73bbe3153 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Mon, 18 Mar 2019 12:59:34 +0100 Subject: [PATCH 35/45] Upgrade PHPStan --- composer.json | 6 +++--- phpstan.neon.dist | 2 ++ src/Error/Warning.php | 4 ---- src/Executor/ExecutionResult.php | 2 +- src/Server/OperationParams.php | 7 ++----- src/Server/ServerConfig.php | 8 ++------ src/Type/Definition/BooleanType.php | 8 ++++---- src/Type/Definition/Directive.php | 2 +- src/Type/Definition/ListOfType.php | 10 ++-------- src/Utils/BreakingChangesFinder.php | 14 +++++++------- src/Utils/TypeInfo.php | 8 ++++---- src/Utils/Utils.php | 4 ++-- src/Validator/Rules/QueryDepth.php | 6 ++---- 13 files changed, 32 insertions(+), 49 deletions(-) diff --git a/composer.json b/composer.json index 27a3dac..7ace361 100644 --- a/composer.json +++ b/composer.json @@ -16,9 +16,9 @@ "require-dev": { "doctrine/coding-standard": "^5.0", "phpbench/phpbench": "^0.14.0", - "phpstan/phpstan": "0.10.5", - "phpstan/phpstan-phpunit": "0.10.0", - "phpstan/phpstan-strict-rules": "0.10.1", + "phpstan/phpstan": "^0.11.4", + "phpstan/phpstan-phpunit": "^0.11.0", + "phpstan/phpstan-strict-rules": "^0.11.0", "phpunit/phpcov": "^5.0", "phpunit/phpunit": "^7.2", "psr/http-message": "^1.0", diff --git a/phpstan.neon.dist b/phpstan.neon.dist index af2455b..02a7a9a 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -8,6 +8,8 @@ parameters: 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 .+~" + - "~Variable method call on static\\(GraphQL\\\\Server\\\\ServerConfig\\)~" # TODO get rid of includes: - vendor/phpstan/phpstan-phpunit/extension.neon diff --git a/src/Error/Warning.php b/src/Error/Warning.php index a652968..9157b05 100644 --- a/src/Error/Warning.php +++ b/src/Error/Warning.php @@ -61,8 +61,6 @@ final class Warning } elseif ($suppress === false) { self::$enableWarnings = self::ALL; } else { - $suppress = (int) $suppress; - self::$enableWarnings &= ~$suppress; } } @@ -86,8 +84,6 @@ final class Warning } elseif ($enable === false) { self::$enableWarnings = 0; } else { - $enable = (int) $enable; - self::$enableWarnings |= $enable; } } diff --git a/src/Executor/ExecutionResult.php b/src/Executor/ExecutionResult.php index e9dbe4e..db16dd3 100644 --- a/src/Executor/ExecutionResult.php +++ b/src/Executor/ExecutionResult.php @@ -154,7 +154,7 @@ class ExecutionResult implements JsonSerializable } if (! empty($this->extensions)) { - $result['extensions'] = (array) $this->extensions; + $result['extensions'] = $this->extensions; } return $result; diff --git a/src/Server/OperationParams.php b/src/Server/OperationParams.php index 6d2cfd1..3ab12de 100644 --- a/src/Server/OperationParams.php +++ b/src/Server/OperationParams.php @@ -62,13 +62,10 @@ class OperationParams * Creates an instance from given array * * @param mixed[] $params - * @param bool $readonly - * - * @return OperationParams * * @api */ - public static function create(array $params, $readonly = false) + public static function create(array $params, bool $readonly = false) : OperationParams { $instance = new static(); @@ -108,7 +105,7 @@ class OperationParams $instance->operation = $params['operationname']; $instance->variables = $params['variables']; $instance->extensions = $params['extensions']; - $instance->readOnly = (bool) $readonly; + $instance->readOnly = $readonly; // Apollo server/client compatibility: look for the queryid in extensions if (isset($instance->extensions['persistedQuery']['sha256Hash']) && empty($instance->query) && empty($instance->queryId)) { diff --git a/src/Server/ServerConfig.php b/src/Server/ServerConfig.php index d121ec5..cb36ceb 100644 --- a/src/Server/ServerConfig.php +++ b/src/Server/ServerConfig.php @@ -225,15 +225,11 @@ class ServerConfig /** * Allow batching queries (disabled by default) * - * @param bool $enableBatching - * - * @return self - * * @api */ - public function setQueryBatching($enableBatching) + public function setQueryBatching(bool $enableBatching) : self { - $this->queryBatching = (bool) $enableBatching; + $this->queryBatching = $enableBatching; return $this; } diff --git a/src/Type/Definition/BooleanType.php b/src/Type/Definition/BooleanType.php index 1c24a53..67b8178 100644 --- a/src/Type/Definition/BooleanType.php +++ b/src/Type/Definition/BooleanType.php @@ -58,11 +58,11 @@ class BooleanType extends ScalarType */ public function parseLiteral($valueNode, ?array $variables = null) { - if ($valueNode instanceof BooleanValueNode) { - return (bool) $valueNode->value; + if (! $valueNode instanceof BooleanValueNode) { + // Intentionally without message, as all information already in wrapped Exception + throw new Exception(); } - // Intentionally without message, as all information already in wrapped Exception - throw new Exception(); + return $valueNode->value; } } diff --git a/src/Type/Definition/Directive.php b/src/Type/Definition/Directive.php index ab7d5d5..9baa7ff 100644 --- a/src/Type/Definition/Directive.php +++ b/src/Type/Definition/Directive.php @@ -40,7 +40,7 @@ class Directive public $locations; /** @var FieldArgument[] */ - public $args; + public $args = []; /** @var DirectiveDefinitionNode|null */ public $astNode; diff --git a/src/Type/Definition/ListOfType.php b/src/Type/Definition/ListOfType.php index 29e3438..e45bc14 100644 --- a/src/Type/Definition/ListOfType.php +++ b/src/Type/Definition/ListOfType.php @@ -20,15 +20,9 @@ class ListOfType extends Type implements WrappingType, OutputType, NullableType, $this->ofType = Type::assertType($type); } - /** - * @return string - */ - public function toString() + public function toString() : string { - $type = $this->ofType; - $str = $type instanceof Type ? $type->toString() : (string) $type; - - return '[' . $str . ']'; + return '[' . $this->ofType->toString() . ']'; } /** diff --git a/src/Utils/BreakingChangesFinder.php b/src/Utils/BreakingChangesFinder.php index 93736b6..533abf6 100644 --- a/src/Utils/BreakingChangesFinder.php +++ b/src/Utils/BreakingChangesFinder.php @@ -531,12 +531,12 @@ class BreakingChangesFinder ]; } // Check if a non-null arg was added to the field - foreach ($newTypeFields[$fieldName]->args as $newArgDef) { + foreach ($newTypeFields[$fieldName]->args as $newTypeFieldArgDef) { $oldArgs = $oldTypeFields[$fieldName]->args; $oldArgDef = Utils::find( $oldArgs, - static function ($arg) use ($newArgDef) { - return $arg->name === $newArgDef->name; + static function ($arg) use ($newTypeFieldArgDef) { + return $arg->name === $newTypeFieldArgDef->name; } ); @@ -545,8 +545,8 @@ class BreakingChangesFinder } $newTypeName = $newType->name; - $newArgName = $newArgDef->name; - if ($newArgDef->getType() instanceof NonNull) { + $newArgName = $newTypeFieldArgDef->name; + if ($newTypeFieldArgDef->getType() instanceof NonNull) { $breakingChanges[] = [ 'type' => self::BREAKING_CHANGE_NON_NULL_ARG_ADDED, 'description' => "A non-null arg ${newArgName} on ${newTypeName}.${fieldName} was added", @@ -668,7 +668,7 @@ class BreakingChangesFinder { $removedArgs = []; $newArgMap = self::getArgumentMapForDirective($newDirective); - foreach ((array) $oldDirective->args as $arg) { + foreach ($oldDirective->args as $arg) { if (isset($newArgMap[$arg->name])) { continue; } @@ -727,7 +727,7 @@ class BreakingChangesFinder { $addedArgs = []; $oldArgMap = self::getArgumentMapForDirective($oldDirective); - foreach ((array) $newDirective->args as $arg) { + foreach ($newDirective->args as $arg) { if (isset($oldArgMap[$arg->name])) { continue; } diff --git a/src/Utils/TypeInfo.php b/src/Utils/TypeInfo.php index b187511..63c3f4a 100644 --- a/src/Utils/TypeInfo.php +++ b/src/Utils/TypeInfo.php @@ -178,7 +178,7 @@ class TypeInfo $nestedTypes = array_merge($nestedTypes, $type->getInterfaces()); } if ($type instanceof ObjectType || $type instanceof InterfaceType) { - foreach ((array) $type->getFields() as $fieldName => $field) { + foreach ($type->getFields() as $fieldName => $field) { if (! empty($field->args)) { $fieldArgTypes = array_map( static function (FieldArgument $arg) { @@ -193,12 +193,12 @@ class TypeInfo } } if ($type instanceof InputObjectType) { - foreach ((array) $type->getFields() as $fieldName => $field) { + foreach ($type->getFields() as $fieldName => $field) { $nestedTypes[] = $field->getType(); } } - foreach ($nestedTypes as $type) { - $typeMap = self::extractTypes($type, $typeMap); + foreach ($nestedTypes as $nestedType) { + $typeMap = self::extractTypes($nestedType, $typeMap); } return $typeMap; diff --git a/src/Utils/Utils.php b/src/Utils/Utils.php index a1a983d..8dddd65 100644 --- a/src/Utils/Utils.php +++ b/src/Utils/Utils.php @@ -264,8 +264,8 @@ class Utils $grouped = []; foreach ($traversable as $key => $value) { $newKeys = (array) $keyFn($value, $key); - foreach ($newKeys as $key) { - $grouped[$key][] = $value; + foreach ($newKeys as $newKey) { + $grouped[$newKey][] = $value; } } diff --git a/src/Validator/Rules/QueryDepth.php b/src/Validator/Rules/QueryDepth.php index 5a35f0c..2f5c9c8 100644 --- a/src/Validator/Rules/QueryDepth.php +++ b/src/Validator/Rules/QueryDepth.php @@ -98,14 +98,12 @@ class QueryDepth extends QuerySecurityRule /** * Set max query depth. If equal to 0 no check is done. Must be greater or equal to 0. - * - * @param int $maxQueryDepth */ - public function setMaxQueryDepth($maxQueryDepth) + public function setMaxQueryDepth(int $maxQueryDepth) { $this->checkIfGreaterOrEqualToZero('maxQueryDepth', $maxQueryDepth); - $this->maxQueryDepth = (int) $maxQueryDepth; + $this->maxQueryDepth = $maxQueryDepth; } public static function maxQueryDepthErrorMessage($max, $count) From 569945cd374c13e79c706fbf0d7916db8463e3af Mon Sep 17 00:00:00 2001 From: spawnia Date: Thu, 28 Mar 2019 11:24:06 +0100 Subject: [PATCH 36/45] Clarify some bits of CONTRIBUTING.md and unify formatting --- CONTRIBUTING.md | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8c12d16..399716c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,6 @@ # Contributing to GraphQL PHP ## Workflow - If your contribution requires significant or breaking changes, or if you plan to propose a major new feature, we recommend you to create an issue on the [GitHub](https://github.com/webonyx/graphql-php/issues) with a brief proposal and discuss it with us first. @@ -14,9 +13,10 @@ For smaller contributions just use this workflow: * Check your changes using `composer check-all` * Send a pull request -## Using GraphQL PHP from a Git checkout +## Setup the Development Environment +First, copy the URL of your fork and `git clone` it to your local machine. + ```sh -git clone https://github.com/webonyx/graphql-php.git cd graphql-php composer install ``` @@ -29,28 +29,26 @@ composer install Some tests have annotation `@see it('')`. It is used for reference to same tests in [graphql-js implementation](https://github.com/graphql/graphql-js) with the same description. ## Coding Standard -Coding standard of this project is based on [Doctrine CS](https://github.com/doctrine/coding-standard). To run the inspection: +The coding standard of this project is based on [Doctrine CS](https://github.com/doctrine/coding-standard). +Run the inspections: ```sh ./vendor/bin/phpcs ``` -Auto-fixing: +Apply automatic code style fixes: ```sh ./vendor/bin/phpcbf ``` ## Static analysis -Based on [PHPStan](https://github.com/phpstan/phpstan) +Based on [PHPStan](https://github.com/phpstan/phpstan). ```sh ./vendor/bin/phpstan analyse --ansi --memory-limit 256M ``` - ## Running benchmarks - -Benchmarks are run via phpbench: - +Benchmarks are run via [PHPBench](https://github.com/phpbench/phpbench). ```sh ./vendor/bin/phpbench run . ``` From 1acddf4e22e59c1d946075cb912e05eccd49e111 Mon Sep 17 00:00:00 2001 From: spawnia Date: Thu, 28 Mar 2019 11:25:57 +0100 Subject: [PATCH 37/45] Mention default value definition for enum args in docs --- docs/type-system/input-types.md | 2 +- docs/type-system/object-types.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/type-system/input-types.md b/docs/type-system/input-types.md index f48b83f..1b7ffc8 100644 --- a/docs/type-system/input-types.md +++ b/docs/type-system/input-types.md @@ -114,7 +114,7 @@ Option | Type | Notes name | `string` | **Required.** Name of the input field. When not set - inferred from **fields** array key type | `Type` | **Required.** Instance of one of [Input Types](input-types.md) (**Scalar**, **Enum**, **InputObjectType** + any combination of those with **nonNull** and **listOf** modifiers) description | `string` | Plain-text description of this input field for clients (e.g. used by [GraphiQL](https://github.com/graphql/graphiql) for auto-generated documentation) -defaultValue | `scalar` | Default value of this input field +defaultValue | `scalar` | Default value of this input field. Use the internal value if specifying a default for an **enum** type # Using Input Object Type In the example above we defined our InputObjectType. Now let's use it in one of field arguments: diff --git a/docs/type-system/object-types.md b/docs/type-system/object-types.md index a853be3..9c1f2fb 100644 --- a/docs/type-system/object-types.md +++ b/docs/type-system/object-types.md @@ -94,7 +94,7 @@ Option | Type | Notes name | `string` | **Required.** Name of the argument. When not set - inferred from **args** array key type | `Type` | **Required.** Instance of one of [Input Types](input-types.md) (**scalar**, **enum**, **InputObjectType** + any combination of those with **nonNull** and **listOf** modifiers) description | `string` | Plain-text description of this argument for clients (e.g. used by [GraphiQL](https://github.com/graphql/graphiql) for auto-generated documentation) -defaultValue | `scalar` | Default value for this argument +defaultValue | `scalar` | Default value for this argument. Use the internal value if specifying a default for an **enum** type # Shorthand field definitions Fields can be also defined in **shorthand** notation (with only **name** and **type** options): From ddebd9a414d58c5ed105328309bab3a1cb09743c Mon Sep 17 00:00:00 2001 From: spawnia Date: Thu, 28 Mar 2019 11:26:30 +0100 Subject: [PATCH 38/45] Add test for default enum input coercion --- tests/Executor/ExecutorTest.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/Executor/ExecutorTest.php b/tests/Executor/ExecutorTest.php index fe6768c..d3679e8 100644 --- a/tests/Executor/ExecutorTest.php +++ b/tests/Executor/ExecutorTest.php @@ -11,6 +11,7 @@ use GraphQL\Executor\Executor; use GraphQL\Language\Parser; use GraphQL\Tests\Executor\TestClasses\NotSpecial; use GraphQL\Tests\Executor\TestClasses\Special; +use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\ObjectType; @@ -1108,6 +1109,16 @@ class ExecutorTest extends TestCase ], ]), 'defaultValue' => ['a' => 1, 'b' => 'test'], ], + 'i' => [ + 'type' => new EnumType([ + 'name' => 'EnumType', + 'values' => [ + 'VALUE1' => 1, + 'VALUE2' => 2, + ] + ]), + 'defaultValue' => 1 + ] ], ], ], @@ -1117,7 +1128,7 @@ class ExecutorTest extends TestCase $query = Parser::parse('{ field }'); $result = Executor::execute($schema, $query); $expected = [ - 'data' => ['field' => '{"a":1,"b":null,"c":0,"d":false,"e":"0","f":"some-string","h":{"a":1,"b":"test"}}'], + 'data' => ['field' => '{"a":1,"b":null,"c":0,"d":false,"e":"0","f":"some-string","h":{"a":1,"b":"test"},"i":1}'], ]; self::assertEquals($expected, $result->toArray()); From 06529e1924c35ceab4ac45baef20353bf88d4d2c Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Thu, 28 Mar 2019 11:39:12 +0100 Subject: [PATCH 39/45] Upgrade Doctrine CS --- composer.json | 2 +- src/Error/Error.php | 12 ++-- src/Error/InvariantViolation.php | 2 - src/Error/UserError.php | 2 - src/Executor/Promise/Adapter/SyncPromise.php | 2 - .../Promise/Adapter/SyncPromiseAdapter.php | 4 +- src/Executor/ReferenceExecutor.php | 52 ++++++++++++++-- src/Experimental/Executor/Collector.php | 6 +- .../Executor/CoroutineExecutor.php | 3 + src/Language/AST/Location.php | 1 + src/Language/AST/NodeList.php | 1 + src/Language/Lexer.php | 60 ++++++++++++------- src/Language/Parser.php | 5 +- src/Language/Printer.php | 1 + src/Server/StandardServer.php | 2 + src/Type/Definition/BooleanType.php | 3 - src/Type/Definition/CustomScalarType.php | 3 - src/Type/Definition/Directive.php | 10 +++- src/Type/Definition/EnumType.php | 7 +-- src/Type/Definition/EnumValueDefinition.php | 3 - src/Type/Definition/FieldArgument.php | 3 - src/Type/Definition/FieldDefinition.php | 2 +- src/Type/Definition/FloatType.php | 3 - src/Type/Definition/InputObjectType.php | 3 - src/Type/Definition/IntType.php | 3 - src/Type/Definition/InterfaceType.php | 3 - src/Type/Definition/ListOfType.php | 3 - src/Type/Definition/NonNull.php | 3 - src/Type/Definition/QueryPlan.php | 1 + src/Type/Definition/ResolveInfo.php | 1 + src/Type/Definition/StringType.php | 3 - src/Type/Definition/Type.php | 1 + src/Type/Definition/UnionType.php | 3 - src/Utils/AST.php | 1 + src/Utils/ASTDefinitionBuilder.php | 3 +- src/Utils/MixedStore.php | 2 - src/Utils/SchemaExtender.php | 2 + src/Utils/SchemaPrinter.php | 5 +- src/Utils/TypeComparators.php | 12 +--- src/Utils/TypeInfo.php | 13 ++-- src/Validator/Rules/DisableIntrospection.php | 2 +- src/Validator/Rules/LoneSchemaDefinition.php | 1 + src/Validator/Rules/NoUndefinedVariables.php | 2 - src/Validator/Rules/QueryComplexity.php | 4 +- src/Validator/Rules/QueryDepth.php | 2 +- src/Validator/Rules/ValuesOfCorrectType.php | 2 + tests/Executor/MutationsTest.php | 1 + tests/Executor/ResolveTest.php | 1 - tests/Executor/VariablesTest.php | 1 - tests/StarWarsIntrospectionTest.php | 1 - tests/StarWarsValidationTest.php | 1 - tests/Utils/BuildSchemaTest.php | 1 - tests/Utils/SchemaExtenderTest.php | 6 +- tests/Utils/SchemaPrinterTest.php | 1 - tests/Validator/ValuesOfCorrectTypeTest.php | 1 - 55 files changed, 150 insertions(+), 128 deletions(-) diff --git a/composer.json b/composer.json index 7ace361..113e79d 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ "ext-mbstring": "*" }, "require-dev": { - "doctrine/coding-standard": "^5.0", + "doctrine/coding-standard": "^6.0", "phpbench/phpbench": "^0.14.0", "phpstan/phpstan": "^0.11.4", "phpstan/phpstan-phpunit": "^0.11.0", diff --git a/src/Error/Error.php b/src/Error/Error.php index e0c36e3..2e78221 100644 --- a/src/Error/Error.php +++ b/src/Error/Error.php @@ -122,13 +122,13 @@ class Error extends Exception implements JsonSerializable, ClientAware if ($previous instanceof ClientAware) { $this->isClientSafe = $previous->isClientSafe(); - $this->category = $previous->getCategory() ?: static::CATEGORY_INTERNAL; + $this->category = $previous->getCategory() ?: self::CATEGORY_INTERNAL; } elseif ($previous) { $this->isClientSafe = false; - $this->category = static::CATEGORY_INTERNAL; + $this->category = self::CATEGORY_INTERNAL; } else { $this->isClientSafe = true; - $this->category = static::CATEGORY_GRAPHQL; + $this->category = self::CATEGORY_GRAPHQL; } } @@ -148,10 +148,10 @@ class Error extends Exception implements JsonSerializable, ClientAware if ($error instanceof self) { if ($error->path && $error->nodes) { return $error; - } else { - $nodes = $nodes ?: $error->nodes; - $path = $path ?: $error->path; } + + $nodes = $nodes ?: $error->nodes; + $path = $path ?: $error->path; } $source = $positions = $originalError = null; diff --git a/src/Error/InvariantViolation.php b/src/Error/InvariantViolation.php index 6f9cfad..24d6dbc 100644 --- a/src/Error/InvariantViolation.php +++ b/src/Error/InvariantViolation.php @@ -7,8 +7,6 @@ namespace GraphQL\Error; use LogicException; /** - * Class InvariantVoilation - * * Note: * This exception should not inherit base Error exception as it is raised when there is an error somewhere in * user-land code diff --git a/src/Error/UserError.php b/src/Error/UserError.php index 67bb9bb..2ffe811 100644 --- a/src/Error/UserError.php +++ b/src/Error/UserError.php @@ -7,8 +7,6 @@ namespace GraphQL\Error; use RuntimeException; /** - * Class UserError - * * Error caused by actions of GraphQL clients. Can be safely displayed to a client... */ class UserError extends RuntimeException implements ClientAware diff --git a/src/Executor/Promise/Adapter/SyncPromise.php b/src/Executor/Promise/Adapter/SyncPromise.php index 91e139c..26aecd6 100644 --- a/src/Executor/Promise/Adapter/SyncPromise.php +++ b/src/Executor/Promise/Adapter/SyncPromise.php @@ -13,8 +13,6 @@ use function is_object; use function method_exists; /** - * Class SyncPromise - * * Simplistic (yet full-featured) implementation of Promises A+ spec for regular PHP `sync` mode * (using queue to defer promises execution) */ diff --git a/src/Executor/Promise/Adapter/SyncPromiseAdapter.php b/src/Executor/Promise/Adapter/SyncPromiseAdapter.php index 2df02af..e5900a9 100644 --- a/src/Executor/Promise/Adapter/SyncPromiseAdapter.php +++ b/src/Executor/Promise/Adapter/SyncPromiseAdapter.php @@ -160,7 +160,9 @@ class SyncPromiseAdapter implements PromiseAdapter if ($syncPromise->state === SyncPromise::FULFILLED) { return $syncPromise->result; - } elseif ($syncPromise->state === SyncPromise::REJECTED) { + } + + if ($syncPromise->state === SyncPromise::REJECTED) { throw $syncPromise->result; } diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 90030de..7b54afc 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -182,6 +182,7 @@ class ReferenceExecutor implements ExecutorImplementation } Utils::invariant($operation, 'Has operation if no errors.'); Utils::invariant($variableValues !== null, 'Has variables if no errors.'); + return new ExecutionContext( $schema, $fragments, @@ -206,6 +207,7 @@ class ReferenceExecutor implements ExecutorImplementation // resolved Promise. $data = $this->executeOperation($this->exeContext->operation, $this->exeContext->rootValue); $result = $this->buildResponse($data); + // Note: we deviate here from the reference implementation a bit by always returning promise // But for the "sync" case it is always fulfilled return $this->isPromise($result) @@ -228,6 +230,7 @@ class ReferenceExecutor implements ExecutorImplementation if ($data !== null) { $data = (array) $data; } + return new ExecutionResult($data, $this->exeContext->errors); } @@ -257,13 +260,16 @@ class ReferenceExecutor implements ExecutorImplementation null, function ($error) { $this->exeContext->addError($error); + return $this->exeContext->promises->createFulfilled(null); } ); } + return $result; } catch (Error $error) { $this->exeContext->addError($error); + return null; } } @@ -286,6 +292,7 @@ class ReferenceExecutor implements ExecutorImplementation [$operation] ); } + return $queryType; case 'mutation': $mutationType = $schema->getMutationType(); @@ -295,6 +302,7 @@ class ReferenceExecutor implements ExecutorImplementation [$operation] ); } + return $mutationType; case 'subscription': $subscriptionType = $schema->getSubscriptionType(); @@ -304,6 +312,7 @@ class ReferenceExecutor implements ExecutorImplementation [$operation] ); } + return $subscriptionType; default: throw new Error( @@ -378,6 +387,7 @@ class ReferenceExecutor implements ExecutorImplementation break; } } + return $fields; } @@ -407,10 +417,8 @@ class ReferenceExecutor implements ExecutorImplementation $node, $variableValues ); - if (isset($include['if']) && $include['if'] === false) { - return false; - } - return true; + + return ! isset($include['if']) || $include['if'] !== false; } /** @@ -445,6 +453,7 @@ class ReferenceExecutor implements ExecutorImplementation if ($conditionalType instanceof AbstractType) { return $this->exeContext->schema->isPossibleType($conditionalType, $type); } + return false; } @@ -474,10 +483,12 @@ class ReferenceExecutor implements ExecutorImplementation if ($promise) { return $promise->then(static function ($resolvedResult) use ($responseName, $results) { $results[$responseName] = $resolvedResult; + return $results; }); } $results[$responseName] = $result; + return $results; }, [] @@ -487,6 +498,7 @@ class ReferenceExecutor implements ExecutorImplementation return self::fixResultsIfEmptyArray($resolvedResults); }); } + return self::fixResultsIfEmptyArray($result); } @@ -554,6 +566,7 @@ class ReferenceExecutor implements ExecutorImplementation $path, $result ); + return $result; } @@ -578,12 +591,17 @@ class ReferenceExecutor implements ExecutorImplementation $typeNameMetaFieldDef = $typeNameMetaFieldDef ?: Introspection::typeNameMetaFieldDef(); if ($fieldName === $schemaMetaFieldDef->name && $schema->getQueryType() === $parentType) { return $schemaMetaFieldDef; - } elseif ($fieldName === $typeMetaFieldDef->name && $schema->getQueryType() === $parentType) { + } + + if ($fieldName === $typeMetaFieldDef->name && $schema->getQueryType() === $parentType) { return $typeMetaFieldDef; - } elseif ($fieldName === $typeNameMetaFieldDef->name) { + } + + if ($fieldName === $typeNameMetaFieldDef->name) { return $typeNameMetaFieldDef; } $tmp = $parentType->getFields(); + return $tmp[$fieldName] ?? null; } @@ -610,6 +628,7 @@ class ReferenceExecutor implements ExecutorImplementation $fieldNode, $this->exeContext->variableValues ); + return $resolveFn($source, $args, $context, $info); } catch (Exception $error) { return $error; @@ -663,15 +682,18 @@ class ReferenceExecutor implements ExecutorImplementation null, function ($error) use ($exeContext) { $exeContext->addError($error); + return $this->exeContext->promises->createFulfilled(null); } ); } + return $completed; } catch (Error $err) { // If `completeValueWithLocatedError` returned abruptly (threw an error), log the error // and return null. $exeContext->addError($err); + return null; } } @@ -716,6 +738,7 @@ class ReferenceExecutor implements ExecutorImplementation } ); } + return $completed; } catch (Exception $error) { throw Error::createLocatedError($error, $fieldNodes, $path); @@ -786,6 +809,7 @@ class ReferenceExecutor implements ExecutorImplementation 'Cannot return null for non-nullable field ' . $info->parentType . '.' . $info->fieldName . '.' ); } + return $completed; } // If result is null-like, return null. @@ -863,8 +887,10 @@ class ReferenceExecutor implements ExecutorImplementation Utils::printSafe($promise) )); } + return $promise; } + return null; } @@ -891,6 +917,7 @@ class ReferenceExecutor implements ExecutorImplementation return $callback($resolved, $value); }); } + return $callback($previous, $value); }, $initialValue @@ -928,6 +955,7 @@ class ReferenceExecutor implements ExecutorImplementation } $completedItems[] = $completedItem; } + return $containsPromise ? $this->exeContext->promises->all($completedItems) : $completedItems; } @@ -1001,6 +1029,7 @@ class ReferenceExecutor implements ExecutorImplementation ); }); } + return $this->completeObjectValue( $this->ensureValidRuntimeType( $runtimeType, @@ -1076,9 +1105,11 @@ class ReferenceExecutor implements ExecutorImplementation return $possibleTypes[$index]; } } + return null; }); } + return null; } @@ -1111,6 +1142,7 @@ class ReferenceExecutor implements ExecutorImplementation if (! $isTypeOfResult) { throw $this->invalidReturnTypeError($returnType, $result, $fieldNodes); } + return $this->collectAndExecuteSubfields( $returnType, $fieldNodes, @@ -1123,6 +1155,7 @@ class ReferenceExecutor implements ExecutorImplementation throw $this->invalidReturnTypeError($returnType, $result, $fieldNodes); } } + return $this->collectAndExecuteSubfields( $returnType, $fieldNodes, @@ -1164,6 +1197,7 @@ class ReferenceExecutor implements ExecutorImplementation &$result ) { $subFieldNodes = $this->collectSubFields($returnType, $fieldNodes); + return $this->executeFields($returnType, $result, $path, $subFieldNodes); } @@ -1189,6 +1223,7 @@ class ReferenceExecutor implements ExecutorImplementation } $this->subFieldCache[$returnType][$fieldNodes] = $subFieldNodes; } + return $this->subFieldCache[$returnType][$fieldNodes]; } @@ -1222,6 +1257,7 @@ class ReferenceExecutor implements ExecutorImplementation if (! $containsPromise) { return self::fixResultsIfEmptyArray($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 @@ -1241,6 +1277,7 @@ class ReferenceExecutor implements ExecutorImplementation if ($results === []) { return new stdClass(); } + return $results; } @@ -1260,11 +1297,13 @@ class ReferenceExecutor implements ExecutorImplementation $keys = array_keys($assoc); $valuesAndPromises = array_values($assoc); $promise = $this->exeContext->promises->all($valuesAndPromises); + return $promise->then(static function ($values) use ($keys) { $resolvedResults = []; foreach ($values as $i => $value) { $resolvedResults[$keys[$i]] = $value; } + return self::fixResultsIfEmptyArray($resolvedResults); }); } @@ -1318,6 +1357,7 @@ class ReferenceExecutor implements ExecutorImplementation ) ); } + return $runtimeType; } } diff --git a/src/Experimental/Executor/Collector.php b/src/Experimental/Executor/Collector.php index 34f1e0e..99131ba 100644 --- a/src/Experimental/Executor/Collector.php +++ b/src/Experimental/Executor/Collector.php @@ -86,11 +86,13 @@ class Collector } else { $this->runtime->addError(new Error('Must provide an operation.')); } + return; } if ($hasMultipleAssumedOperations) { $this->runtime->addError(new Error('Must provide operation name if query contains multiple operations.')); + return; } @@ -211,7 +213,9 @@ class Collector if (isset($this->visitedFragments[$fragmentName])) { continue; - } elseif (! isset($this->fragments[$fragmentName])) { + } + + if (! isset($this->fragments[$fragmentName])) { $this->runtime->addError(new Error( sprintf('Fragment "%s" does not exist.', $fragmentName), $selection diff --git a/src/Experimental/Executor/CoroutineExecutor.php b/src/Experimental/Executor/CoroutineExecutor.php index b089fad..d985633 100644 --- a/src/Experimental/Executor/CoroutineExecutor.php +++ b/src/Experimental/Executor/CoroutineExecutor.php @@ -147,6 +147,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation if ($emptyObjectAsStdClass && empty($array)) { return new stdClass(); } + return $array; } @@ -155,6 +156,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation foreach ($value as $key => $item) { $array[$key] = self::resultToArray($item); } + return $array; } @@ -365,6 +367,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation // short-circuit evaluation for __typename if ($ctx->shared->fieldName === Introspection::TYPE_NAME_FIELD_NAME) { $ctx->result->{$ctx->shared->resultName} = $ctx->type->name; + return; } diff --git a/src/Language/AST/Location.php b/src/Language/AST/Location.php index 7ea05ad..72135fc 100644 --- a/src/Language/AST/Location.php +++ b/src/Language/AST/Location.php @@ -59,6 +59,7 @@ class Location $tmp = new static(); $tmp->start = $start; $tmp->end = $end; + return $tmp; } diff --git a/src/Language/AST/NodeList.php b/src/Language/AST/NodeList.php index 2e7de79..cede5a4 100644 --- a/src/Language/AST/NodeList.php +++ b/src/Language/AST/NodeList.php @@ -105,6 +105,7 @@ class NodeList implements ArrayAccess, IteratorAggregate, Countable if ($list instanceof self) { $list = $list->nodes; } + return new NodeList(array_merge($this->nodes, $list)); } diff --git a/src/Language/Lexer.php b/src/Language/Lexer.php index fb4157c..3aad4dd 100644 --- a/src/Language/Lexer.php +++ b/src/Language/Lexer.php @@ -23,6 +23,22 @@ use function preg_match; */ class Lexer { + private const TOKEN_BANG = 33; + private const TOKEN_HASH = 35; + private const TOKEN_DOLLAR = 36; + private const TOKEN_AMP = 38; + private const TOKEN_PAREN_L = 40; + private const TOKEN_PAREN_R = 41; + private const TOKEN_DOT = 46; + private const TOKEN_COLON = 58; + private const TOKEN_EQUALS = 61; + private const TOKEN_AT = 64; + private const TOKEN_BRACKET_L = 91; + private const TOKEN_BRACKET_R = 93; + private const TOKEN_BRACE_L = 123; + private const TOKEN_PIPE = 124; + private const TOKEN_BRACE_R = 125; + /** @var Source */ public $source; @@ -92,7 +108,8 @@ class Lexer */ public function advance() { - $this->lastToken = $this->token; + $this->lastToken = $this->token; + return $this->token = $this->lookahead(); } @@ -131,44 +148,45 @@ class Lexer [, $code, $bytes] = $this->readChar(true); switch ($code) { - case 33: // ! + case self::TOKEN_BANG: return new Token(Token::BANG, $position, $position + 1, $line, $col, $prev); - case 35: // # + case self::TOKEN_HASH: // # $this->moveStringCursor(-1, -1 * $bytes); return $this->readComment($line, $col, $prev); - case 36: // $ + case self::TOKEN_DOLLAR: return new Token(Token::DOLLAR, $position, $position + 1, $line, $col, $prev); - case 38: // & + case self::TOKEN_AMP: return new Token(Token::AMP, $position, $position + 1, $line, $col, $prev); - case 40: // ( + case self::TOKEN_PAREN_L: return new Token(Token::PAREN_L, $position, $position + 1, $line, $col, $prev); - case 41: // ) + case self::TOKEN_PAREN_R: return new Token(Token::PAREN_R, $position, $position + 1, $line, $col, $prev); - case 46: // . + case self::TOKEN_DOT: // . [, $charCode1] = $this->readChar(true); [, $charCode2] = $this->readChar(true); - if ($charCode1 === 46 && $charCode2 === 46) { + if ($charCode1 === self::TOKEN_DOT && $charCode2 === self::TOKEN_DOT) { return new Token(Token::SPREAD, $position, $position + 3, $line, $col, $prev); } break; - case 58: // : + case self::TOKEN_COLON: return new Token(Token::COLON, $position, $position + 1, $line, $col, $prev); - case 61: // = + case self::TOKEN_EQUALS: return new Token(Token::EQUALS, $position, $position + 1, $line, $col, $prev); - case 64: // @ + case self::TOKEN_AT: return new Token(Token::AT, $position, $position + 1, $line, $col, $prev); - case 91: // [ + case self::TOKEN_BRACKET_L: return new Token(Token::BRACKET_L, $position, $position + 1, $line, $col, $prev); - case 93: // ] + case self::TOKEN_BRACKET_R: return new Token(Token::BRACKET_R, $position, $position + 1, $line, $col, $prev); - case 123: // { + case self::TOKEN_BRACE_L: return new Token(Token::BRACE_L, $position, $position + 1, $line, $col, $prev); - case 124: // | + case self::TOKEN_PIPE: return new Token(Token::PIPE, $position, $position + 1, $line, $col, $prev); - case 125: // } + case self::TOKEN_BRACE_R: return new Token(Token::BRACE_R, $position, $position + 1, $line, $col, $prev); + // A-Z case 65: case 66: @@ -227,6 +245,7 @@ class Lexer case 122: return $this->moveStringCursor(-1, -1 * $bytes) ->readName($line, $col, $prev); + // - case 45: // 0-9 @@ -242,6 +261,7 @@ class Lexer case 57: return $this->moveStringCursor(-1, -1 * $bytes) ->readNumber($line, $col, $prev); + // " case 34: [, $nextCode] = $this->readChar(); @@ -564,10 +584,10 @@ class Lexer $prev, BlockString::value($value) ); - } else { - // move cursor back to before the first quote - $this->moveStringCursor(-2, -2); } + + // move cursor back to before the first quote + $this->moveStringCursor(-2, -2); } $this->assertValidBlockStringCharacterCode($code, $this->position); diff --git a/src/Language/Parser.php b/src/Language/Parser.php index 10f0859..f5c9280 100644 --- a/src/Language/Parser.php +++ b/src/Language/Parser.php @@ -439,7 +439,6 @@ class Parser case 'mutation': case 'subscription': return $this->parseOperationDefinition(); - case 'fragment': return $this->parseFragmentDefinition(); } @@ -827,7 +826,9 @@ class Parser 'value' => $token->value === 'true', 'loc' => $this->loc($token), ]); - } elseif ($token->value === 'null') { + } + + if ($token->value === 'null') { $this->lexer->advance(); return new NullValueNode([ diff --git a/src/Language/Printer.php b/src/Language/Printer.php index ff11d50..ae96222 100644 --- a/src/Language/Printer.php +++ b/src/Language/Printer.php @@ -115,6 +115,7 @@ class Printer $varDefs = $this->wrap('(', $this->join($node->variableDefinitions, ', '), ')'); $directives = $this->join($node->directives, ' '); $selectionSet = $node->selectionSet; + // Anonymous queries with no directives or variable definitions can use // the query short form. return ! $name && ! $directives && ! $varDefs && $op === 'query' diff --git a/src/Server/StandardServer.php b/src/Server/StandardServer.php index 63bd140..7b7e627 100644 --- a/src/Server/StandardServer.php +++ b/src/Server/StandardServer.php @@ -151,6 +151,7 @@ class StandardServer StreamInterface $writableBodyStream ) { $result = $this->executePsrRequest($request); + return $this->helper->toPsrResponse($result, $response, $writableBodyStream); } @@ -165,6 +166,7 @@ class StandardServer public function executePsrRequest(ServerRequestInterface $request) { $parsedBody = $this->helper->parsePsrRequest($request); + return $this->executeRequest($parsedBody); } diff --git a/src/Type/Definition/BooleanType.php b/src/Type/Definition/BooleanType.php index 67b8178..56b5f0f 100644 --- a/src/Type/Definition/BooleanType.php +++ b/src/Type/Definition/BooleanType.php @@ -11,9 +11,6 @@ use GraphQL\Language\AST\Node; use GraphQL\Utils\Utils; use function is_bool; -/** - * Class BooleanType - */ class BooleanType extends ScalarType { /** @var string */ diff --git a/src/Type/Definition/CustomScalarType.php b/src/Type/Definition/CustomScalarType.php index 21766ab..50b28ce 100644 --- a/src/Type/Definition/CustomScalarType.php +++ b/src/Type/Definition/CustomScalarType.php @@ -12,9 +12,6 @@ use function call_user_func; use function is_callable; use function sprintf; -/** - * Class CustomScalarType - */ class CustomScalarType extends ScalarType { /** diff --git a/src/Type/Definition/Directive.php b/src/Type/Definition/Directive.php index 9baa7ff..0ffe496 100644 --- a/src/Type/Definition/Directive.php +++ b/src/Type/Definition/Directive.php @@ -12,9 +12,6 @@ use function array_keys; use function in_array; use function is_array; -/** - * Class Directive - */ class Directive { public const DEFAULT_DEPRECATION_REASON = 'No longer supported'; @@ -80,6 +77,7 @@ class Directive public static function includeDirective() { $internal = self::getInternalDirectives(); + return $internal['include']; } @@ -140,24 +138,30 @@ class Directive ]), ]; } + return self::$internalDirectives; } + /** * @return Directive */ public static function skipDirective() { $internal = self::getInternalDirectives(); + return $internal['skip']; } + /** * @return Directive */ public static function deprecatedDirective() { $internal = self::getInternalDirectives(); + return $internal['deprecated']; } + /** * @return bool */ diff --git a/src/Type/Definition/EnumType.php b/src/Type/Definition/EnumType.php index b565a25..dc83d69 100644 --- a/src/Type/Definition/EnumType.php +++ b/src/Type/Definition/EnumType.php @@ -19,9 +19,6 @@ use function is_int; use function is_string; use function sprintf; -/** - * Class EnumType - */ class EnumType extends Type implements InputType, OutputType, LeafType, NullableType, NamedType { /** @var EnumTypeDefinitionNode|null */ @@ -33,7 +30,7 @@ class EnumType extends Type implements InputType, OutputType, LeafType, Nullable /** @var MixedStore */ private $valueLookup; - /** @var \ArrayObject */ + /** @var ArrayObject */ private $nameLookup; /** @var EnumTypeExtensionNode[] */ @@ -71,7 +68,7 @@ class EnumType extends Type implements InputType, OutputType, LeafType, Nullable } /** - * @return \ArrayObject + * @return ArrayObject */ private function getNameLookup() { diff --git a/src/Type/Definition/EnumValueDefinition.php b/src/Type/Definition/EnumValueDefinition.php index 33494be..9454f35 100644 --- a/src/Type/Definition/EnumValueDefinition.php +++ b/src/Type/Definition/EnumValueDefinition.php @@ -6,9 +6,6 @@ namespace GraphQL\Type\Definition; use GraphQL\Language\AST\EnumValueDefinitionNode; -/** - * Class EnumValueDefinition - */ class EnumValueDefinition { /** @var string */ diff --git a/src/Type/Definition/FieldArgument.php b/src/Type/Definition/FieldArgument.php index 33cad0d..3bffe57 100644 --- a/src/Type/Definition/FieldArgument.php +++ b/src/Type/Definition/FieldArgument.php @@ -11,9 +11,6 @@ use function is_array; use function is_string; use function sprintf; -/** - * Class FieldArgument - */ class FieldArgument { /** @var string */ diff --git a/src/Type/Definition/FieldDefinition.php b/src/Type/Definition/FieldDefinition.php index 34ad66f..8f409bf 100644 --- a/src/Type/Definition/FieldDefinition.php +++ b/src/Type/Definition/FieldDefinition.php @@ -81,7 +81,7 @@ class FieldDefinition $this->config = $config; - $this->complexityFn = $config['complexity'] ?? static::DEFAULT_COMPLEXITY_FN; + $this->complexityFn = $config['complexity'] ?? self::DEFAULT_COMPLEXITY_FN; } public static function defineFieldMap(Type $type, $fields) diff --git a/src/Type/Definition/FloatType.php b/src/Type/Definition/FloatType.php index 0e6eba0..e8923ca 100644 --- a/src/Type/Definition/FloatType.php +++ b/src/Type/Definition/FloatType.php @@ -12,9 +12,6 @@ use GraphQL\Language\AST\Node; use GraphQL\Utils\Utils; use function is_numeric; -/** - * Class FloatType - */ class FloatType extends ScalarType { /** @var string */ diff --git a/src/Type/Definition/InputObjectType.php b/src/Type/Definition/InputObjectType.php index 756e142..c921dda 100644 --- a/src/Type/Definition/InputObjectType.php +++ b/src/Type/Definition/InputObjectType.php @@ -15,9 +15,6 @@ use function is_callable; use function is_string; use function sprintf; -/** - * Class InputObjectType - */ class InputObjectType extends Type implements InputType, NullableType, NamedType { /** @var InputObjectTypeDefinitionNode|null */ diff --git a/src/Type/Definition/IntType.php b/src/Type/Definition/IntType.php index 1f59a92..e6176c3 100644 --- a/src/Type/Definition/IntType.php +++ b/src/Type/Definition/IntType.php @@ -14,9 +14,6 @@ use function intval; use function is_bool; use function is_numeric; -/** - * Class IntType - */ class IntType extends ScalarType { // As per the GraphQL Spec, Integers are only treated as valid when a valid diff --git a/src/Type/Definition/InterfaceType.php b/src/Type/Definition/InterfaceType.php index 1c7c389..c13d7fb 100644 --- a/src/Type/Definition/InterfaceType.php +++ b/src/Type/Definition/InterfaceType.php @@ -12,9 +12,6 @@ use function is_callable; use function is_string; use function sprintf; -/** - * Class InterfaceType - */ class InterfaceType extends Type implements AbstractType, OutputType, CompositeType, NullableType, NamedType { /** @var InterfaceTypeDefinitionNode|null */ diff --git a/src/Type/Definition/ListOfType.php b/src/Type/Definition/ListOfType.php index e45bc14..eb8200d 100644 --- a/src/Type/Definition/ListOfType.php +++ b/src/Type/Definition/ListOfType.php @@ -4,9 +4,6 @@ declare(strict_types=1); namespace GraphQL\Type\Definition; -/** - * Class ListOfType - */ class ListOfType extends Type implements WrappingType, OutputType, NullableType, InputType { /** @var ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType */ diff --git a/src/Type/Definition/NonNull.php b/src/Type/Definition/NonNull.php index cc80be0..6799523 100644 --- a/src/Type/Definition/NonNull.php +++ b/src/Type/Definition/NonNull.php @@ -6,9 +6,6 @@ namespace GraphQL\Type\Definition; use GraphQL\Utils\Utils; -/** - * Class NonNull - */ class NonNull extends Type implements WrappingType, OutputType, InputType { /** @var NullableType */ diff --git a/src/Type/Definition/QueryPlan.php b/src/Type/Definition/QueryPlan.php index 7564f96..235066d 100644 --- a/src/Type/Definition/QueryPlan.php +++ b/src/Type/Definition/QueryPlan.php @@ -183,6 +183,7 @@ class QueryPlan ); } } + return $fields; } diff --git a/src/Type/Definition/ResolveInfo.php b/src/Type/Definition/ResolveInfo.php index 83354c0..f2525de 100644 --- a/src/Type/Definition/ResolveInfo.php +++ b/src/Type/Definition/ResolveInfo.php @@ -226,6 +226,7 @@ class ResolveInfo ); } } + return $fields; } } diff --git a/src/Type/Definition/StringType.php b/src/Type/Definition/StringType.php index ebfcf4a..533298f 100644 --- a/src/Type/Definition/StringType.php +++ b/src/Type/Definition/StringType.php @@ -14,9 +14,6 @@ use function is_object; use function is_scalar; use function method_exists; -/** - * Class StringType - */ class StringType extends ScalarType { /** @var string */ diff --git a/src/Type/Definition/Type.php b/src/Type/Definition/Type.php index 5b8e414..69faa1c 100644 --- a/src/Type/Definition/Type.php +++ b/src/Type/Definition/Type.php @@ -194,6 +194,7 @@ abstract class Type implements JsonSerializable public static function getInternalTypes() { trigger_error(__METHOD__ . ' is deprecated. Use Type::getStandardTypes() instead', E_USER_DEPRECATED); + return self::getStandardTypes(); } diff --git a/src/Type/Definition/UnionType.php b/src/Type/Definition/UnionType.php index 8a735e3..3229df4 100644 --- a/src/Type/Definition/UnionType.php +++ b/src/Type/Definition/UnionType.php @@ -14,9 +14,6 @@ use function is_callable; use function is_string; use function sprintf; -/** - * Class UnionType - */ class UnionType extends Type implements AbstractType, OutputType, CompositeType, NullableType, NamedType { /** @var UnionTypeDefinitionNode */ diff --git a/src/Utils/AST.php b/src/Utils/AST.php index 5ef4233..ae01ac5 100644 --- a/src/Utils/AST.php +++ b/src/Utils/AST.php @@ -353,6 +353,7 @@ class AST // No valid return value. 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. diff --git a/src/Utils/ASTDefinitionBuilder.php b/src/Utils/ASTDefinitionBuilder.php index 8e452e2..95f8d5a 100644 --- a/src/Utils/ASTDefinitionBuilder.php +++ b/src/Utils/ASTDefinitionBuilder.php @@ -144,7 +144,8 @@ class ASTDefinitionBuilder // 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->internalBuildWrappedType($value->type); + $config = [ 'name' => $value->name->value, 'type' => $type, diff --git a/src/Utils/MixedStore.php b/src/Utils/MixedStore.php index 2ee1918..469abaa 100644 --- a/src/Utils/MixedStore.php +++ b/src/Utils/MixedStore.php @@ -22,8 +22,6 @@ use function is_string; * * 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) - * - * Class MixedStore */ class MixedStore implements ArrayAccess { diff --git a/src/Utils/SchemaExtender.php b/src/Utils/SchemaExtender.php index 110e750..07a1447 100644 --- a/src/Utils/SchemaExtender.php +++ b/src/Utils/SchemaExtender.php @@ -66,6 +66,7 @@ class SchemaExtender return $type->extensionASTNodes; } + return static::$typeExtensionsMap[$name] ?? null; } @@ -284,6 +285,7 @@ class SchemaExtender } } } + return $interfaces; } diff --git a/src/Utils/SchemaPrinter.php b/src/Utils/SchemaPrinter.php index 023ecb9..9e9e1ca 100644 --- a/src/Utils/SchemaPrinter.php +++ b/src/Utils/SchemaPrinter.php @@ -153,11 +153,8 @@ class SchemaPrinter } $subscriptionType = $schema->getSubscriptionType(); - if ($subscriptionType && $subscriptionType->name !== 'Subscription') { - return false; - } - return true; + return ! $subscriptionType || $subscriptionType->name === 'Subscription'; } private static function printDirective($directive, $options) : string diff --git a/src/Utils/TypeComparators.php b/src/Utils/TypeComparators.php index 24e4b00..2beb942 100644 --- a/src/Utils/TypeComparators.php +++ b/src/Utils/TypeComparators.php @@ -86,18 +86,12 @@ class TypeComparators // If superType type is an abstract type, maybeSubType type may be a currently // possible object type. - if (Type::isAbstractType($superType) && + return Type::isAbstractType($superType) && $maybeSubType instanceof ObjectType && $schema->isPossibleType( $superType, $maybeSubType - ) - ) { - return true; - } - - // Otherwise, the child type is not a valid subtype of the parent type. - return false; + ); } /** @@ -131,13 +125,11 @@ class TypeComparators return false; } - /** @var $typeB ObjectType */ // Determine if the latter type is a possible concrete type of the former. return $schema->isPossibleType($typeA, $typeB); } if ($typeB instanceof AbstractType) { - /** @var $typeA ObjectType */ // Determine if the former type is a possible concrete type of the latter. return $schema->isPossibleType($typeB, $typeA); } diff --git a/src/Utils/TypeInfo.php b/src/Utils/TypeInfo.php index 63c3f4a..89d4ea1 100644 --- a/src/Utils/TypeInfo.php +++ b/src/Utils/TypeInfo.php @@ -28,6 +28,7 @@ use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Definition\WrappingType; use GraphQL\Type\Introspection; use GraphQL\Type\Schema; +use SplStack; use function array_map; use function array_merge; use function array_pop; @@ -35,24 +36,21 @@ use function count; use function is_array; use function sprintf; -/** - * Class TypeInfo - */ class TypeInfo { /** @var Schema */ private $schema; - /** @var \SplStack */ + /** @var SplStack */ private $typeStack; - /** @var \SplStack */ + /** @var SplStack */ private $parentTypeStack; - /** @var \SplStack */ + /** @var SplStack */ private $inputTypeStack; - /** @var \SplStack */ + /** @var SplStack */ private $fieldDefStack; /** @var Directive */ @@ -155,6 +153,7 @@ class TypeInfo if (! $alreadyInMap) { $typeMap[$i] = $type; } + return $typeMap; } diff --git a/src/Validator/Rules/DisableIntrospection.php b/src/Validator/Rules/DisableIntrospection.php index bb2dc3e..6e0efab 100644 --- a/src/Validator/Rules/DisableIntrospection.php +++ b/src/Validator/Rules/DisableIntrospection.php @@ -52,6 +52,6 @@ class DisableIntrospection extends QuerySecurityRule protected function isEnabled() { - return $this->isEnabled !== static::DISABLED; + return $this->isEnabled !== self::DISABLED; } } diff --git a/src/Validator/Rules/LoneSchemaDefinition.php b/src/Validator/Rules/LoneSchemaDefinition.php index 4cef206..1a8da67 100644 --- a/src/Validator/Rules/LoneSchemaDefinition.php +++ b/src/Validator/Rules/LoneSchemaDefinition.php @@ -32,6 +32,7 @@ class LoneSchemaDefinition extends ValidationRule 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)); + return; } diff --git a/src/Validator/Rules/NoUndefinedVariables.php b/src/Validator/Rules/NoUndefinedVariables.php index 5c2d4a5..c0cd22c 100644 --- a/src/Validator/Rules/NoUndefinedVariables.php +++ b/src/Validator/Rules/NoUndefinedVariables.php @@ -12,8 +12,6 @@ use GraphQL\Validator\ValidationContext; use function sprintf; /** - * Class NoUndefinedVariables - * * A GraphQL operation is only valid if all variables encountered, both directly * and via fragment spreads, are defined by that operation. */ diff --git a/src/Validator/Rules/QueryComplexity.php b/src/Validator/Rules/QueryComplexity.php index 6a3da59..85bd1ac 100644 --- a/src/Validator/Rules/QueryComplexity.php +++ b/src/Validator/Rules/QueryComplexity.php @@ -206,10 +206,12 @@ class QueryComplexity extends QuerySecurityRule $directive = Directive::includeDirective(); /** @var bool $directiveArgsIf */ $directiveArgsIf = Values::getArgumentValues($directive, $directiveNode, $variableValues)['if']; + return ! $directiveArgsIf; } $directive = Directive::skipDirective(); $directiveArgsIf = Values::getArgumentValues($directive, $directiveNode, $variableValues); + return $directiveArgsIf['if']; } } @@ -282,6 +284,6 @@ class QueryComplexity extends QuerySecurityRule protected function isEnabled() { - return $this->getMaxQueryComplexity() !== static::DISABLED; + return $this->getMaxQueryComplexity() !== self::DISABLED; } } diff --git a/src/Validator/Rules/QueryDepth.php b/src/Validator/Rules/QueryDepth.php index 2f5c9c8..6dd9168 100644 --- a/src/Validator/Rules/QueryDepth.php +++ b/src/Validator/Rules/QueryDepth.php @@ -113,6 +113,6 @@ class QueryDepth extends QuerySecurityRule protected function isEnabled() { - return $this->getMaxQueryDepth() !== static::DISABLED; + return $this->getMaxQueryDepth() !== self::DISABLED; } } diff --git a/src/Validator/Rules/ValuesOfCorrectType.php b/src/Validator/Rules/ValuesOfCorrectType.php index ad2468e..4e8199b 100644 --- a/src/Validator/Rules/ValuesOfCorrectType.php +++ b/src/Validator/Rules/ValuesOfCorrectType.php @@ -49,6 +49,7 @@ class ValuesOfCorrectType extends ValidationRule public function getVisitor(ValidationContext $context) { $fieldName = ''; + return [ NodeKind::FIELD => [ 'enter' => static function (FieldNode $node) use (&$fieldName) { @@ -281,6 +282,7 @@ class ValuesOfCorrectType extends ValidationRule return self::badArgumentValueMessage($typeName, $valueName, $fieldName, $arg->name, $message); } } + return self::badValueMessage($typeName, $valueName, $message); } } diff --git a/tests/Executor/MutationsTest.php b/tests/Executor/MutationsTest.php index 6671b2d..e511b70 100644 --- a/tests/Executor/MutationsTest.php +++ b/tests/Executor/MutationsTest.php @@ -59,6 +59,7 @@ class MutationsTest extends TestCase ], 'name' => 'NumberHolder', ]); + return new Schema([ 'query' => new ObjectType([ 'fields' => [ diff --git a/tests/Executor/ResolveTest.php b/tests/Executor/ResolveTest.php index 8fe4ab7..0baddef 100644 --- a/tests/Executor/ResolveTest.php +++ b/tests/Executor/ResolveTest.php @@ -16,7 +16,6 @@ use function uniqid; class ResolveTest extends TestCase { // Execute: resolve function - /** * @see it('default function accesses properties') */ diff --git a/tests/Executor/VariablesTest.php b/tests/Executor/VariablesTest.php index c2625ae..533beb5 100644 --- a/tests/Executor/VariablesTest.php +++ b/tests/Executor/VariablesTest.php @@ -355,7 +355,6 @@ class VariablesTest extends TestCase self::assertEquals($expected, $result->toArray()); } - // Describe: Handles non-nullable scalars /** diff --git a/tests/StarWarsIntrospectionTest.php b/tests/StarWarsIntrospectionTest.php index 94c2b7e..35da7da 100644 --- a/tests/StarWarsIntrospectionTest.php +++ b/tests/StarWarsIntrospectionTest.php @@ -11,7 +11,6 @@ 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 7e736c1..cdee2e3 100644 --- a/tests/StarWarsValidationTest.php +++ b/tests/StarWarsValidationTest.php @@ -12,7 +12,6 @@ class StarWarsValidationTest extends TestCase { // Star Wars Validation Tests // Basic Queries - /** * @see it('Validates a complex but valid query') */ diff --git a/tests/Utils/BuildSchemaTest.php b/tests/Utils/BuildSchemaTest.php index dfdf98a..e8e3280 100644 --- a/tests/Utils/BuildSchemaTest.php +++ b/tests/Utils/BuildSchemaTest.php @@ -24,7 +24,6 @@ use function count; class BuildSchemaTest extends TestCase { // Describe: Schema Builder - /** * @see it('can use built schema for limited execution') */ diff --git a/tests/Utils/SchemaExtenderTest.php b/tests/Utils/SchemaExtenderTest.php index bc9842d..341a381 100644 --- a/tests/Utils/SchemaExtenderTest.php +++ b/tests/Utils/SchemaExtenderTest.php @@ -198,6 +198,7 @@ class SchemaExtenderTest extends TestCase preg_match('/^[ \t]*/', $trimmedStr, $indentMatch); $indent = $indentMatch[0]; + return preg_replace('/^' . $indent . '/m', '', $trimmedStr); } @@ -210,6 +211,7 @@ class SchemaExtenderTest extends TestCase $ast = Parser::parse($sdl); $extendedSchema = SchemaExtender::extend($this->testSchema, $ast, $options); self::assertEquals(SchemaPrinter::doPrint($this->testSchema), $originalPrint); + return $extendedSchema; } @@ -1250,7 +1252,6 @@ class SchemaExtenderTest extends TestCase self::assertEquals('new directive', $newDirective->description); } - /** * @see it('may extend directives with new complex directive') */ @@ -1735,7 +1736,6 @@ class SchemaExtenderTest extends TestCase self::assertEquals($queryType->name, 'Foo'); } - /** * @see it('adds new root types via schema extension') */ @@ -1875,7 +1875,6 @@ class SchemaExtenderTest extends TestCase } } - /** * @see it('does not allow defining a root operation type twice') */ @@ -1964,7 +1963,6 @@ extend type Query { self::assertSame(['data' => ['hello' => 'Hello World!']], $result->toArray()); } - /** * @see https://github.com/webonyx/graphql-php/issues/180 */ diff --git a/tests/Utils/SchemaPrinterTest.php b/tests/Utils/SchemaPrinterTest.php index e014e6b..afc851b 100644 --- a/tests/Utils/SchemaPrinterTest.php +++ b/tests/Utils/SchemaPrinterTest.php @@ -21,7 +21,6 @@ use PHPUnit\Framework\TestCase; class SchemaPrinterTest extends TestCase { // Describe: Type System Printer - /** * @see it('Prints String Field') */ diff --git a/tests/Validator/ValuesOfCorrectTypeTest.php b/tests/Validator/ValuesOfCorrectTypeTest.php index a174808..ac92708 100644 --- a/tests/Validator/ValuesOfCorrectTypeTest.php +++ b/tests/Validator/ValuesOfCorrectTypeTest.php @@ -1135,7 +1135,6 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase ); } - // DESCRIBE: Valid input object value /** From edec095055ece955450d3023d03f5f9ee74e1935 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Thu, 28 Mar 2019 19:04:50 +0700 Subject: [PATCH 40/45] Update README.md --- README.md | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e41ce46..2cf46ba 100644 --- a/README.md +++ b/README.md @@ -24,11 +24,29 @@ by the Facebook engineering team. There are several ready examples in the [examples](examples/) folder of the distribution with specific README file per example. -## Contribute -Please refer to [CONTRIBUTING.md](CONTRIBUTING.md) for information on how to contribute. +## Contributors -## Old README.md -Here is a [link to the old README.md](https://github.com/webonyx/graphql-php/blob/v0.9.14/README.md). +This project exists thanks to [all the people](https://github.com/webonyx/graphql-php/graphs/contributors) who contribute. [[Contribute](CONTRIBUTING.md)]. -Keep in mind that it relates to the version 0.9.x. It may contain outdated information for -newer versions (even though we try to preserve backwards compatibility). +## Backers + + + +## Sponsors + +Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/webonyx-graphql-php#sponsor)] + + + + + + + + + + + + +## License + +See [LICENCE](LICENSE). From db915d881212e8eb8c24934e8e1db728214c96a2 Mon Sep 17 00:00:00 2001 From: spawnia Date: Fri, 29 Mar 2019 09:27:33 +0100 Subject: [PATCH 41/45] Fix codestyle --- tests/Executor/ExecutorTest.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/Executor/ExecutorTest.php b/tests/Executor/ExecutorTest.php index d3679e8..9f5480f 100644 --- a/tests/Executor/ExecutorTest.php +++ b/tests/Executor/ExecutorTest.php @@ -1101,13 +1101,14 @@ class ExecutorTest extends TestCase 'f' => ['type' => Type::int(), 'defaultValue' => 'some-string'], 'g' => ['type' => Type::boolean()], 'h' => [ - 'type' => new InputObjectType([ + 'type' => new InputObjectType([ 'name' => 'ComplexType', 'fields' => [ 'a' => ['type' => Type::int()], 'b' => ['type' => Type::string()], ], - ]), 'defaultValue' => ['a' => 1, 'b' => 'test'], + ]), + 'defaultValue' => ['a' => 1, 'b' => 'test'], ], 'i' => [ 'type' => new EnumType([ @@ -1115,10 +1116,10 @@ class ExecutorTest extends TestCase 'values' => [ 'VALUE1' => 1, 'VALUE2' => 2, - ] + ], ]), - 'defaultValue' => 1 - ] + 'defaultValue' => 1, + ], ], ], ], From 0308cf0c0cd7f17eec4dbbf28d1f33224289bbad Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Fri, 3 May 2019 00:56:11 +0200 Subject: [PATCH 42/45] Fix invalid query in docs --- docs/executing-queries.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/executing-queries.md b/docs/executing-queries.md index c356391..5cfea25 100644 --- a/docs/executing-queries.md +++ b/docs/executing-queries.md @@ -136,13 +136,13 @@ So for example following batch will require single DB request (if user field is ```json [ { - "query": "{user(id: 1)} { id }" + "query": "{user(id: 1) { id }}" }, { - "query": "{user(id: 2)} { id }" + "query": "{user(id: 2) { id }}" }, { - "query": "{user(id: 3)} { id }" + "query": "{user(id: 3) { id }}" } ] ``` From 179944495e16f6340706a7abc2617fa90c562ba6 Mon Sep 17 00:00:00 2001 From: Lea Rosema Date: Fri, 3 May 2019 15:15:22 +0200 Subject: [PATCH 43/45] fix: add missing semicolon in generated cache file The `require $cacheFilename` statement leads to a PHP error due to a missing semicolon in the generated cache file. --- docs/type-system/type-language.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/type-system/type-language.md b/docs/type-system/type-language.md index 8f1c06f..2961551 100644 --- a/docs/type-system/type-language.md +++ b/docs/type-system/type-language.md @@ -81,7 +81,7 @@ $cacheFilename = 'cached_schema.php'; if (!file_exists($cacheFilename)) { $document = Parser::parse(file_get_contents('./schema.graphql')); - file_put_contents($cacheFilename, " Date: Thu, 9 May 2019 17:04:17 +0200 Subject: [PATCH 44/45] Use array_keys for iterating over NodeList --- src/Language/AST/NodeList.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Language/AST/NodeList.php b/src/Language/AST/NodeList.php index cede5a4..c91fbcf 100644 --- a/src/Language/AST/NodeList.php +++ b/src/Language/AST/NodeList.php @@ -114,9 +114,8 @@ class NodeList implements ArrayAccess, IteratorAggregate, Countable */ public function getIterator() { - $count = count($this->nodes); - for ($i = 0; $i < $count; $i++) { - yield $this->offsetGet($i); + foreach (array_keys($this->nodes) as $key) { + yield $this->offsetGet($key); } } From 019ed04a5160aa20b92b96ea83502ae794458976 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Thu, 9 May 2019 19:33:37 +0200 Subject: [PATCH 45/45] Update src/Language/AST/NodeList.php MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Šimon Podlipský --- src/Language/AST/NodeList.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Language/AST/NodeList.php b/src/Language/AST/NodeList.php index c91fbcf..648f68a 100644 --- a/src/Language/AST/NodeList.php +++ b/src/Language/AST/NodeList.php @@ -114,7 +114,7 @@ class NodeList implements ArrayAccess, IteratorAggregate, Countable */ public function getIterator() { - foreach (array_keys($this->nodes) as $key) { + foreach ($this->nodes as $key => $_) { yield $this->offsetGet($key); } }