From 4e26de3588ef6657e9bc0aa31227789be82057a6 Mon Sep 17 00:00:00 2001 From: Daniel Tschinder Date: Fri, 9 Feb 2018 12:54:34 +0100 Subject: [PATCH] Support for union types when using buildSchema * Adds support for resolving union/interface types when using a generated schema * Move resolveType __typename checking into defaultResolveType * Clean up existing tests and improve error messages ref: graphql/graphql-js#947 # Conflicts: # src/Utils/BuildSchema.php # tests/Utils/BuildSchemaTest.php --- src/Executor/Executor.php | 45 ++++-- src/Type/Definition/ObjectType.php | 9 -- src/Type/Definition/UnionType.php | 9 -- src/Utils/BuildSchema.php | 64 ++++---- tests/Executor/AbstractPromiseTest.php | 4 - tests/Executor/UnionInterfaceTest.php | 8 +- tests/Type/DefinitionTest.php | 7 +- tests/Type/ValidationTest.php | 118 --------------- tests/Utils/BuildSchemaTest.php | 140 +++++++++++++++++- tests/Utils/FindBreakingChangesTest.php | 29 +--- tests/Utils/SchemaPrinterTest.php | 7 +- .../OverlappingFieldsCanBeMergedTest.php | 3 - tests/Validator/TestCase.php | 16 -- 13 files changed, 198 insertions(+), 261 deletions(-) diff --git a/src/Executor/Executor.php b/src/Executor/Executor.php index 998038d..b2ba19a 100644 --- a/src/Executor/Executor.php +++ b/src/Executor/Executor.php @@ -1053,15 +1053,6 @@ class Executor $runtimeType = $returnType->resolveType($result, $exeContext->contextValue, $info); if (null === $runtimeType) { - if ($returnType instanceof InterfaceType && $info->schema->getConfig()->typeLoader) { - Warning::warnOnce( - "GraphQL Interface Type `{$returnType->name}` returned `null` from it`s `resolveType` function ". - 'for value: ' . Utils::printSafe($result) . '. Switching to slow resolution method using `isTypeOf` ' . - 'of all possible implementations. It requires full schema scan and degrades query performance significantly. '. - ' Make sure your `resolveType` always returns valid implementation or throws.', - Warning::WARNING_FULL_SCHEMA_SCAN - ); - } $runtimeType = self::defaultTypeResolver($result, $exeContext->contextValue, $info, $returnType); } @@ -1122,9 +1113,11 @@ class Executor if (!$runtimeType instanceof ObjectType) { throw new InvariantViolation( - "Abstract type {$returnType} must resolve to an Object type at runtime " . - "for field {$info->parentType}.{$info->fieldName} with " . - 'value "' . Utils::printSafe($result) . '", received "'. Utils::printSafe($runtimeType) . '".' + "Abstract type {$returnType} must resolve to an Object type at " . + "runtime for field {$info->parentType}.{$info->fieldName} with " . + 'value "' . Utils::printSafe($result) . '", received "'. Utils::printSafe($runtimeType) . '".' . + 'Either the ' . $returnType . ' type should provide a "resolveType" ' . + 'function or each possible types should provide an "isTypeOf" function.' ); } @@ -1307,7 +1300,12 @@ class Executor /** * If a resolveType function is not given, then a default resolve behavior is - * used which tests each possible type for the abstract type by calling + * used which attempts two strategies: + * + * First, See if the provided value has a `__typename` field defined, if so, use + * that value as name of the resolved type. + * + * Otherwise, test each possible type for the abstract type by calling * isTypeOf for the object being coerced, returning the first type that matches. * * @param $value @@ -1318,6 +1316,27 @@ class Executor */ private function defaultTypeResolver($value, $context, ResolveInfo $info, AbstractType $abstractType) { + // First, look for `__typename`. + if ( + $value !== null && + is_array($value) && + isset($value['__typename']) && + is_string($value['__typename']) + ) { + return $value['__typename']; + } + + if ($abstractType instanceof InterfaceType && $info->schema->getConfig()->typeLoader) { + Warning::warnOnce( + "GraphQL Interface Type `{$abstractType->name}` returned `null` from it`s `resolveType` function ". + 'for value: ' . Utils::printSafe($value) . '. Switching to slow resolution method using `isTypeOf` ' . + 'of all possible implementations. It requires full schema scan and degrades query performance significantly. '. + ' Make sure your `resolveType` always returns valid implementation or throws.', + Warning::WARNING_FULL_SCHEMA_SCAN + ); + } + + // Otherwise, test each possible type. $possibleTypes = $info->schema->getPossibleTypes($abstractType); $promisedIsTypeOfResults = []; diff --git a/src/Type/Definition/ObjectType.php b/src/Type/Definition/ObjectType.php index 43d2355..d77a731 100644 --- a/src/Type/Definition/ObjectType.php +++ b/src/Type/Definition/ObjectType.php @@ -240,15 +240,6 @@ class ObjectType extends Type implements OutputType, CompositeType "{$this->name} may declare it implements {$iface->name} only once." ); $implemented[$iface->name] = true; - if (!isset($iface->config['resolveType'])) { - Utils::invariant( - isset($this->config['isTypeOf']), - "Interface Type {$iface->name} does not provide a \"resolveType\" " . - "function and implementing Type {$this->name} does not provide a " . - '"isTypeOf" function. There is no way to resolve this implementing ' . - 'type during execution.' - ); - } } } } diff --git a/src/Type/Definition/UnionType.php b/src/Type/Definition/UnionType.php index 49855b7..06d57fc 100644 --- a/src/Type/Definition/UnionType.php +++ b/src/Type/Definition/UnionType.php @@ -158,15 +158,6 @@ class UnionType extends Type implements AbstractType, OutputType, CompositeType "{$this->name} can include {$objType->name} type only once." ); $includedTypeNames[$objType->name] = true; - if (!isset($this->config['resolveType'])) { - Utils::invariant( - isset($objType->config['isTypeOf']) && is_callable($objType->config['isTypeOf']), - "Union type \"{$this->name}\" does not provide a \"resolveType\" " . - "function and possible type \"{$objType->name}\" does not provide an " . - '"isTypeOf" function. There is no way to resolve this possible type ' . - 'during execution.' - ); - } } } } diff --git a/src/Utils/BuildSchema.php b/src/Utils/BuildSchema.php index 078427a..9dc966c 100644 --- a/src/Utils/BuildSchema.php +++ b/src/Utils/BuildSchema.php @@ -216,21 +216,21 @@ class BuildSchema $directives = array_map([$this, 'getDirective'], $directiveDefs); // If specified directives were not explicitly declared, add them. - $skip = array_reduce($directives, function($hasSkip, $directive) { + $skip = array_reduce($directives, function ($hasSkip, $directive) { return $hasSkip || $directive->name == 'skip'; }); if (!$skip) { $directives[] = Directive::skipDirective(); } - $include = array_reduce($directives, function($hasInclude, $directive) { + $include = array_reduce($directives, function ($hasInclude, $directive) { return $hasInclude || $directive->name == 'include'; }); if (!$include) { $directives[] = Directive::includeDirective(); } - $deprecated = array_reduce($directives, function($hasDeprecated, $directive) { + $deprecated = array_reduce($directives, function ($hasDeprecated, $directive) { return $hasDeprecated || $directive->name == 'deprecated'; }); if (!$deprecated) { @@ -245,12 +245,12 @@ class BuildSchema 'subscription' => $subscriptionTypeName ? $this->getObjectType($this->nodeMap[$subscriptionTypeName]) : null, - 'typeLoader' => function($name) { + 'typeLoader' => function ($name) { return $this->typeDefNamed($name); }, 'directives' => $directives, 'astNode' => $schemaDef, - 'types' => function() { + 'types' => function () { $types = []; foreach ($this->nodeMap as $name => $def) { if (!isset($this->loadedTypeDefs[$name])) { @@ -269,7 +269,7 @@ class BuildSchema return new Directive([ 'name' => $directiveNode->name->value, 'description' => $this->getDescription($directiveNode), - 'locations' => Utils::map($directiveNode->locations, function($node) { + 'locations' => Utils::map($directiveNode->locations, function ($node) { return $node->value; }), 'args' => $directiveNode->arguments ? FieldArgument::createMap($this->makeInputValues($directiveNode->arguments)) : null, @@ -342,7 +342,7 @@ class BuildSchema $config = $fn($config, $this->nodeMap[$typeName], $this->nodeMap); } catch (\Exception $e) { throw new Error( - "Type config decorator passed to " . (static::class) . " threw an error ". + "Type config decorator passed to " . (static::class) . " threw an error " . "when building $typeName type: {$e->getMessage()}", null, null, @@ -352,7 +352,7 @@ class BuildSchema ); } catch (\Throwable $e) { throw new Error( - "Type config decorator passed to " . (static::class) . " threw an error ". + "Type config decorator passed to " . (static::class) . " threw an error " . "when building $typeName type: {$e->getMessage()}", null, null, @@ -363,7 +363,7 @@ class BuildSchema } if (!is_array($config) || isset($config[0])) { throw new Error( - "Type config decorator passed to " . (static::class) . " is expected to return an array, but got ". + "Type config decorator passed to " . (static::class) . " is expected to return an array, but got " . Utils::getVariableType($config) ); } @@ -433,10 +433,10 @@ class BuildSchema return [ 'name' => $typeName, 'description' => $this->getDescription($def), - 'fields' => function() use ($def) { + 'fields' => function () use ($def) { return $this->makeFieldDefMap($def); }, - 'interfaces' => function() use ($def) { + 'interfaces' => function () use ($def) { return $this->makeImplementedInterfaces($def); }, 'astNode' => $def @@ -450,7 +450,7 @@ class BuildSchema function ($field) { return $field->name->value; }, - function($field) { + function ($field) { return [ 'type' => $this->produceOutputType($field->type), 'description' => $this->getDescription($field), @@ -479,7 +479,7 @@ class BuildSchema function ($value) { return $value->name->value; }, - function($value) { + function ($value) { $type = $this->produceInputType($value->type); $config = [ 'name' => $value->name->value, @@ -501,13 +501,10 @@ class BuildSchema return [ 'name' => $typeName, 'description' => $this->getDescription($def), - 'fields' => function() use ($def) { + 'fields' => function () use ($def) { return $this->makeFieldDefMap($def); }, - 'astNode' => $def, - 'resolveType' => function() { - $this->cannotExecuteSchema(); - } + 'astNode' => $def ]; } @@ -519,10 +516,10 @@ class BuildSchema 'astNode' => $def, 'values' => Utils::keyValMap( $def->values, - function($enumValue) { + function ($enumValue) { return $enumValue->name->value; }, - function($enumValue) { + function ($enumValue) { return [ 'description' => $this->getDescription($enumValue), 'deprecationReason' => $this->getDeprecationReason($enumValue), @@ -538,11 +535,10 @@ class BuildSchema return [ 'name' => $def->name->value, 'description' => $this->getDescription($def), - 'types' => Utils::map($def->types, function($typeNode) { + 'types' => Utils::map($def->types, function ($typeNode) { return $this->produceObjectType($typeNode); }), - 'astNode' => $def, - 'resolveType' => [$this, 'cannotExecuteSchema'] + 'astNode' => $def ]; } @@ -552,17 +548,17 @@ class BuildSchema 'name' => $def->name->value, 'description' => $this->getDescription($def), 'astNode' => $def, - 'serialize' => function() { + 'serialize' => function () { return false; }, // Note: validation calls the parse functions to determine if a // literal value is correct. Returning null would cause use of custom // scalars to always fail validation. Returning false causes them to // always pass validation. - 'parseValue' => function() { + 'parseValue' => function () { return false; }, - 'parseLiteral' => function() { + 'parseLiteral' => function () { return false; } ]; @@ -573,7 +569,9 @@ class BuildSchema return [ 'name' => $def->name->value, 'description' => $this->getDescription($def), - 'fields' => function() use ($def) { return $this->makeInputValues($def->fields); }, + 'fields' => function () use ($def) { + return $this->makeInputValues($def->fields); + }, 'astNode' => $def, ]; } @@ -611,7 +609,7 @@ class BuildSchema { $loc = $node->loc; if (!$loc || !$loc->startToken) { - return ; + return; } $comments = []; $token = $loc->startToken->prev; @@ -644,12 +642,4 @@ class BuildSchema $doc = $source instanceof DocumentNode ? $source : Parser::parse($source); return self::buildAST($doc, $typeConfigDecorator); } - - public function cannotExecuteSchema() - { - throw new Error( - 'Generated Schema cannot use Interface or Union types for execution.' - ); - } - -} \ No newline at end of file +} diff --git a/tests/Executor/AbstractPromiseTest.php b/tests/Executor/AbstractPromiseTest.php index 5b0f576..7652d7e 100644 --- a/tests/Executor/AbstractPromiseTest.php +++ b/tests/Executor/AbstractPromiseTest.php @@ -87,9 +87,7 @@ class AbstractPromiseTest extends \PHPUnit_Framework_TestCase } }'; - Warning::suppress(Warning::WARNING_FULL_SCHEMA_SCAN); $result = GraphQL::execute($schema, $query); - Warning::enable(Warning::WARNING_FULL_SCHEMA_SCAN); $expected = [ 'data' => [ @@ -174,9 +172,7 @@ class AbstractPromiseTest extends \PHPUnit_Framework_TestCase } }'; - Warning::suppress(Warning::WARNING_FULL_SCHEMA_SCAN); $result = GraphQL::execute($schema, $query); - Warning::enable(Warning::WARNING_FULL_SCHEMA_SCAN); $expected = [ 'data' => [ diff --git a/tests/Executor/UnionInterfaceTest.php b/tests/Executor/UnionInterfaceTest.php index 8fc3d8a..d2b0f14 100644 --- a/tests/Executor/UnionInterfaceTest.php +++ b/tests/Executor/UnionInterfaceTest.php @@ -256,9 +256,7 @@ class UnionInterfaceTest extends \PHPUnit_Framework_TestCase ] ]; - Warning::suppress(Warning::WARNING_FULL_SCHEMA_SCAN); $this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray()); - Warning::enable(Warning::WARNING_FULL_SCHEMA_SCAN); } /** @@ -294,9 +292,7 @@ class UnionInterfaceTest extends \PHPUnit_Framework_TestCase ] ]; - Warning::suppress(Warning::WARNING_FULL_SCHEMA_SCAN); - $this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray()); - Warning::enable(Warning::WARNING_FULL_SCHEMA_SCAN); + $this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray(true)); } /** @@ -351,9 +347,7 @@ class UnionInterfaceTest extends \PHPUnit_Framework_TestCase ] ]; - Warning::suppress(Warning::WARNING_FULL_SCHEMA_SCAN); $this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->john)->toArray()); - Warning::enable(Warning::WARNING_FULL_SCHEMA_SCAN); } /** diff --git a/tests/Type/DefinitionTest.php b/tests/Type/DefinitionTest.php index f0f3fe3..1622220 100644 --- a/tests/Type/DefinitionTest.php +++ b/tests/Type/DefinitionTest.php @@ -74,10 +74,7 @@ class DefinitionTest extends \PHPUnit_Framework_TestCase public function setUp() { - $this->objectType = new ObjectType([ - 'name' => 'Object', - 'isTypeOf' => function() {return true;} - ]); + $this->objectType = new ObjectType(['name' => 'Object']); $this->interfaceType = new InterfaceType(['name' => 'Interface']); $this->unionType = new UnionType(['name' => 'Union', 'types' => [$this->objectType]]); $this->enumType = new EnumType(['name' => 'Enum']); @@ -363,7 +360,6 @@ class DefinitionTest extends \PHPUnit_Framework_TestCase 'f' => ['type' => Type::int()] ], 'interfaces' => [$someInterface], - 'isTypeOf' => function() {return true;} ]); $schema = new Schema([ @@ -391,7 +387,6 @@ class DefinitionTest extends \PHPUnit_Framework_TestCase 'f' => ['type' => Type::int()] ], 'interfaces' => function() use (&$someInterface) { return [$someInterface]; }, - 'isTypeOf' => function() {return true;} ]); $someInterface = new InterfaceType([ diff --git a/tests/Type/ValidationTest.php b/tests/Type/ValidationTest.php index 2552470..6d689c6 100644 --- a/tests/Type/ValidationTest.php +++ b/tests/Type/ValidationTest.php @@ -58,24 +58,15 @@ class ValidationTest extends \PHPUnit_Framework_TestCase $this->ObjectWithIsTypeOf = new ObjectType([ 'name' => 'ObjectWithIsTypeOf', - 'isTypeOf' => function() { - return true; - }, 'fields' => [ 'f' => [ 'type' => Type::string() ]] ]); $this->SomeUnionType = new UnionType([ 'name' => 'SomeUnion', - 'resolveType' => function() { - return null; - }, 'types' => [ $this->SomeObjectType ] ]); $this->SomeInterfaceType = new InterfaceType([ 'name' => 'SomeInterface', - 'resolveType' => function() { - return null; - }, 'fields' => [ 'f' => ['type' => Type::string() ]] ]); @@ -404,7 +395,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase { $AnotherInterface = new InterfaceType([ 'name' => 'AnotherInterface', - 'resolveType' => function() {}, 'fields' => [ 'f' => [ 'type' => Type::string() ]], ]); @@ -736,8 +726,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase { $AnotherInterfaceType = new InterfaceType([ 'name' => 'AnotherInterface', - 'resolveType' => function () { - }, 'fields' => ['f' => ['type' => Type::string()]] ]); @@ -756,8 +744,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase { $AnotherInterfaceType = new InterfaceType([ 'name' => 'AnotherInterface', - 'resolveType' => function () { - }, 'fields' => ['f' => ['type' => Type::string()]] ]); @@ -795,14 +781,11 @@ class ValidationTest extends \PHPUnit_Framework_TestCase { $NonUniqInterface = new InterfaceType([ 'name' => 'NonUniqInterface', - 'resolveType' => function () { - }, 'fields' => ['f' => ['type' => Type::string()]], ]); $AnotherInterface = new InterfaceType([ 'name' => 'AnotherInterface', - 'resolveType' => function(){}, 'fields' => ['f' => ['type' => Type::string()]], ]); @@ -851,9 +834,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase { $schema = $this->schemaWithFieldType(new UnionType([ 'name' => 'SomeUnion', - 'resolveType' => function () { - return null; - }, 'types' => [$this->SomeObjectType], ])); $schema->assertValid(); @@ -866,9 +846,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase { $schema = $this->schemaWithFieldType(new UnionType([ 'name' => 'SomeUnion', - 'resolveType' => function () { - return null; - }, 'types' => function () { return [$this->SomeObjectType]; }, @@ -887,7 +864,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase ); $this->schemaWithFieldType(new UnionType([ 'name' => 'SomeUnion', - 'resolveType' => function() {return null;} ])); } @@ -898,8 +874,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase { $schema = $this->schemaWithFieldType(new UnionType([ 'name' => 'SomeUnion', - 'resolveType' => function () { - }, 'types' => [] ])); @@ -921,8 +895,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase ); $this->schemaWithFieldType(new UnionType([ 'name' => 'SomeUnion', - 'resolveType' => function () { - }, 'types' => $this->SomeObjectType ])); } @@ -934,7 +906,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase { $schema = $this->schemaWithFieldType(new UnionType([ 'name' => 'SomeUnion', - 'resolveType' => function(){}, 'types' => [ $this->SomeObjectType, $this->SomeObjectType, @@ -1193,8 +1164,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase { $AnotherInterfaceType = new InterfaceType([ 'name' => 'AnotherInterface', - 'resolveType' => function () { - }, 'fields' => ['f' => ['type' => Type::string()]] ]); @@ -1234,8 +1203,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase { $AnotherInterfaceType = new InterfaceType([ 'name' => 'AnotherInterface', - 'resolveType' => function () { - }, 'fields' => ['f' => ['type' => Type::string()]] ]); @@ -1270,32 +1237,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase $type->assertValid(); } - /** - * @it rejects an Interface type not defining resolveType with implementing type not defining isTypeOf - */ - public function testRejectsAnInterfaceTypeNotDefiningResolveTypeWithImplementingTypeNotDefiningIsTypeOf() - { - $InterfaceTypeWithoutResolveType = new InterfaceType([ - 'name' => 'InterfaceTypeWithoutResolveType', - 'fields' => ['f' => ['type' => Type::string()]] - ]); - - $schema = $this->schemaWithFieldType(new ObjectType([ - 'name' => 'SomeObject', - 'interfaces' => [$InterfaceTypeWithoutResolveType], - 'fields' => ['f' => ['type' => Type::string()]] - ])); - - $this->setExpectedException( - InvariantViolation::class, - 'Interface Type InterfaceTypeWithoutResolveType does not provide a "resolveType" function and implementing '. - 'Type SomeObject does not provide a "isTypeOf" function. There is no way to resolve this implementing type '. - 'during execution.' - ); - - $schema->assertValid(); - } - // DESCRIBE: Type System: Union types must be resolvable /** @@ -1305,8 +1246,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase { $schema = $this->schemaWithFieldType(new UnionType([ 'name' => 'SomeUnion', - 'resolveType' => function () { - }, 'types' => [$this->SomeObjectType], ])); $schema->assertValid(); @@ -1332,8 +1271,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase { $schema = $this->schemaWithFieldType(new UnionType([ 'name' => 'SomeUnion', - 'resolveType' => function () { - }, 'types' => [$this->ObjectWithIsTypeOf], ])); $schema->assertValid(); @@ -1358,25 +1295,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase $schema->assertValid(); } - /** - * @it rejects a Union type not defining resolveType of Object types not defining isTypeOf - */ - public function testRejectsAUnionTypeNotDefiningResolveTypeOfObjectTypesNotDefiningIsTypeOf() - { - $schema = $this->schemaWithFieldType(new UnionType([ - 'name' => 'SomeUnion', - 'types' => [$this->SomeObjectType], - ])); - - $this->setExpectedException( - InvariantViolation::class, - 'Union type "SomeUnion" does not provide a "resolveType" function and possible type "SomeObject" '. - 'does not provide an "isTypeOf" function. There is no way to resolve this possible type during execution.' - ); - - $schema->assertValid(); - } - // DESCRIBE: Type System: Scalar types must be serializable /** @@ -1747,8 +1665,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase { $AnotherInterfaceType = new InterfaceType([ 'name' => 'AnotherInterface', - 'resolveType' => function () { - }, 'fields' => ['f' => ['type' => Type::string()]] ]); @@ -2085,8 +2001,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase { $AnotherInterface = new InterfaceType([ 'name' => 'AnotherInterface', - 'resolveType' => function () { - }, 'fields' => [ 'field' => [ 'type' => Type::string(), @@ -2121,8 +2035,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase { $AnotherInterface = new InterfaceType([ 'name' => 'AnotherInterface', - 'resolveType' => function () { - }, 'fields' => [ 'field' => [ 'type' => Type::string(), @@ -2158,8 +2070,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase { $AnotherInterface = new InterfaceType([ 'name' => 'AnotherInterface', - 'resolveType' => function () { - }, 'fields' => [ 'field' => [ 'type' => Type::string(), @@ -2195,8 +2105,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase { $AnotherInterface = new InterfaceType([ 'name' => 'AnotherInterface', - 'resolveType' => function () { - }, 'fields' => [ 'field' => [ 'type' => Type::string(), @@ -2238,8 +2146,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase { $AnotherInterface = new InterfaceType([ 'name' => 'AnotherInterface', - 'resolveType' => function () { - }, 'fields' => [ 'field' => [ 'type' => Type::string(), @@ -2274,8 +2180,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase { $AnotherInterface = new InterfaceType([ 'name' => 'AnotherInterface', - 'resolveType' => function () { - }, 'fields' => [ 'field' => ['type' => Type::string()] ] @@ -2318,8 +2222,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase $AnotherInterface = new InterfaceType([ 'name' => 'AnotherInterface', - 'resolveType' => function () { - }, 'fields' => [ 'field' => ['type' => $TypeA] ] @@ -2350,8 +2252,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase { $AnotherInterface = new InterfaceType([ 'name' => 'AnotherInterface', - 'resolveType' => function () { - }, 'fields' => function () use (&$AnotherInterface) { return [ 'field' => ['type' => $AnotherInterface] @@ -2380,8 +2280,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase { $AnotherInterface = new InterfaceType([ 'name' => 'AnotherInterface', - 'resolveType' => function () { - }, 'fields' => [ 'field' => ['type' => $this->SomeUnionType] ] @@ -2406,8 +2304,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase { $AnotherInterface = new InterfaceType([ 'name' => 'AnotherInterface', - 'resolveType' => function () { - }, 'fields' => [ 'field' => [ 'type' => Type::string(), @@ -2445,8 +2341,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase { $AnotherInterface = new InterfaceType([ 'name' => 'AnotherInterface', - 'resolveType' => function () { - }, 'fields' => [ 'field' => [ 'type' => Type::string(), @@ -2487,8 +2381,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase { $AnotherInterface = new InterfaceType([ 'name' => 'AnotherInterface', - 'resolveType' => function () { - }, 'fields' => [ 'field' => ['type' => Type::nonNull(Type::listOf(Type::string()))] ] @@ -2513,8 +2405,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase { $AnotherInterface = new InterfaceType([ 'name' => 'AnotherInterface', - 'resolveType' => function () { - }, 'fields' => [ 'field' => ['type' => Type::listOf(Type::string())] ] @@ -2545,8 +2435,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase { $AnotherInterface = new InterfaceType([ 'name' => 'AnotherInterface', - 'resolveType' => function () { - }, 'fields' => [ 'field' => ['type' => Type::string()] ] @@ -2575,8 +2463,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase { $AnotherInterface = new InterfaceType([ 'name' => 'AnotherInterface', - 'resolveType' => function () { - }, 'fields' => [ 'field' => ['type' => Type::string()] ] @@ -2601,8 +2487,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase { $AnotherInterface = new InterfaceType([ 'name' => 'AnotherInterface', - 'resolveType' => function () { - }, 'fields' => [ 'field' => ['type' => Type::nonNull(Type::string())] ] @@ -2820,8 +2704,6 @@ class ValidationTest extends \PHPUnit_Framework_TestCase { $BadUnionType = new UnionType([ 'name' => 'BadUnion', - 'resolveType' => function () { - }, 'types' => [$type], ]); return new Schema([ diff --git a/tests/Utils/BuildSchemaTest.php b/tests/Utils/BuildSchemaTest.php index 095f315..7f0c47e 100644 --- a/tests/Utils/BuildSchemaTest.php +++ b/tests/Utils/BuildSchemaTest.php @@ -36,8 +36,8 @@ class BuildSchemaTest extends \PHPUnit_Framework_TestCase } ')); - $result = GraphQL::execute($schema, '{ str }', ['str' => 123]); - $this->assertEquals($result['data'], ['str' => 123]); + $result = GraphQL::executeQuery($schema, '{ str }', ['str' => 123]); + $this->assertEquals(['str' => 123], $result->toArray(true)['data']); } /** @@ -52,7 +52,7 @@ class BuildSchemaTest extends \PHPUnit_Framework_TestCase } "); - $result = GraphQL::execute( + $result = GraphQL::executeQuery( $schema, '{ add(x: 34, y: 55) }', [ @@ -61,7 +61,7 @@ class BuildSchemaTest extends \PHPUnit_Framework_TestCase } ] ); - $this->assertEquals($result, ['data' => ['add' => 89]]); + $this->assertEquals(['data' => ['add' => 89]], $result->toArray(true)); } /** @@ -447,6 +447,135 @@ type WorldTwo { $this->assertEquals($output, $body); } + /** + * @it Specifying Union type using __typename + */ + public function testSpecifyingUnionTypeUsingTypename() + { + $schema = BuildSchema::buildAST(Parser::parse(' + schema { + query: Root + } + + type Root { + fruits: [Fruit] + } + + union Fruit = Apple | Banana + + type Apple { + color: String + } + + type Banana { + length: Int + } + ')); + $query = ' + { + fruits { + ... on Apple { + color + } + ... on Banana { + length + } + } + } + '; + $root = [ + 'fruits' => [ + [ + 'color' => 'green', + '__typename' => 'Apple', + ], + [ + 'length' => 5, + '__typename' => 'Banana', + ] + ] + ]; + $expected = [ + 'data' => [ + 'fruits' => [ + ['color' => 'green'], + ['length' => 5], + ] + ] + ]; + + $result = GraphQL::executeQuery($schema, $query, $root); + $this->assertEquals($expected, $result->toArray(true)); + } + + /** + * @it Specifying Interface type using __typename + */ + public function testSpecifyingInterfaceUsingTypename() + { + $schema = BuildSchema::buildAST(Parser::parse(' + schema { + query: Root + } + + type Root { + characters: [Character] + } + + interface Character { + name: String! + } + + type Human implements Character { + name: String! + totalCredits: Int + } + + type Droid implements Character { + name: String! + primaryFunction: String + } + ')); + $query = ' + { + characters { + name + ... on Human { + totalCredits + } + ... on Droid { + primaryFunction + } + } + } + '; + $root = [ + 'characters' => [ + [ + 'name' => 'Han Solo', + 'totalCredits' => 10, + '__typename' => 'Human', + ], + [ + 'name' => 'R2-D2', + 'primaryFunction' => 'Astromech', + '__typename' => 'Droid', + ] + ] + ]; + $expected = [ + 'data' => [ + 'characters' => [ + ['name' => 'Han Solo', 'totalCredits' => 10], + ['name' => 'R2-D2', 'primaryFunction' => 'Astromech'], + ] + ] + ]; + + $result = GraphQL::executeQuery($schema, $query, $root); + $this->assertEquals($expected, $result->toArray(true)); + } + /** * @it CustomScalar */ @@ -1093,9 +1222,8 @@ interface Hello { $this->assertInstanceOf(InterfaceTypeDefinitionNode::class, $node); $this->assertEquals('Hello', $defaultConfig['name']); $this->assertInstanceOf(\Closure::class, $defaultConfig['fields']); - $this->assertInstanceOf(\Closure::class, $defaultConfig['resolveType']); $this->assertArrayHasKey('description', $defaultConfig); - $this->assertCount(5, $defaultConfig); + $this->assertCount(4, $defaultConfig); $this->assertEquals(array_keys($allNodesMap), ['Query', 'Color', 'Hello']); $this->assertEquals('My description of Hello', $schema->getType('Hello')->description); } diff --git a/tests/Utils/FindBreakingChangesTest.php b/tests/Utils/FindBreakingChangesTest.php index 2fc0f6f..b48f961 100644 --- a/tests/Utils/FindBreakingChangesTest.php +++ b/tests/Utils/FindBreakingChangesTest.php @@ -1,8 +1,4 @@ 'Type1', - 'types' => [new ObjectType(['name' => 'blah'])], + 'types' => [$objectType], ]); $oldSchema = new Schema([ @@ -510,16 +505,12 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase $oldUnionType = new UnionType([ 'name' => 'UnionType1', 'types' => [$type1, $type2], - 'resolveType' => function () { - } ]); $newUnionType = new UnionType([ 'name' => 'UnionType1', 'types' => [$type1a, $type3], - 'resolveType' => function () { - } ]); $oldSchema = new Schema([ @@ -978,8 +969,6 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase 'fields' => [ 'field1' => Type::string() ], - 'resolveType' => function () { - } ]); $oldType = new ObjectType([ 'name' => 'Type1', @@ -1099,15 +1088,11 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase $unionTypeThatLosesATypeOld = new UnionType([ 'name' => 'UnionTypeThatLosesAType', 'types' => [$typeInUnion1, $typeInUnion2], - 'resolveType' => function () { - } ]); $unionTypeThatLosesATypeNew = new UnionType([ 'name' => 'UnionTypeThatLosesAType', 'types' => [$typeInUnion1], - 'resolveType' => function () { - } ]); $enumTypeThatLosesAValueOld = new EnumType([ @@ -1132,8 +1117,6 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase 'fields' => [ 'field1' => Type::string() ], - 'resolveType' => function () { - } ]); $typeThatLosesInterfaceOld = new ObjectType([ @@ -1353,15 +1336,11 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase $oldUnionType = new UnionType([ 'name' => 'UnionType1', 'types' => [$type1], - 'resolveType' => function () { - } ]); $newUnionType = new UnionType([ 'name' => 'UnionType1', 'types' => [$type1a, $type2], - 'resolveType' => function () { - } ]); $oldSchema = new Schema([ @@ -1452,15 +1431,11 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase $unionTypeThatGainsATypeOld = new UnionType([ 'name' => 'UnionType1', 'types' => [$typeInUnion1], - 'resolveType' => function () { - } ]); $unionTypeThatGainsATypeNew = new UnionType([ 'name' => 'UnionType1', 'types' => [$typeInUnion1, $typeInUnion2], - 'resolveType' => function () { - } ]); $oldSchema = new Schema([ @@ -1498,4 +1473,4 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase $this->assertEquals($expectedDangerousChanges, FindBreakingChanges::findDangerousChanges($oldSchema, $newSchema)); } -} \ No newline at end of file +} diff --git a/tests/Utils/SchemaPrinterTest.php b/tests/Utils/SchemaPrinterTest.php index 0acf0c2..5dac505 100644 --- a/tests/Utils/SchemaPrinterTest.php +++ b/tests/Utils/SchemaPrinterTest.php @@ -360,7 +360,6 @@ type Root { { $fooType = new InterfaceType([ 'name' => 'Foo', - 'resolveType' => function() { return null; }, 'fields' => ['str' => ['type' => Type::string()]] ]); @@ -406,13 +405,11 @@ type Root { { $fooType = new InterfaceType([ 'name' => 'Foo', - 'resolveType' => function() { return null; }, 'fields' => ['str' => ['type' => Type::string()]] ]); $baazType = new InterfaceType([ 'name' => 'Baaz', - 'resolveType' => function() { return null; }, 'fields' => ['int' => ['type' => Type::int()]] ]); @@ -476,13 +473,11 @@ type Root { $singleUnion = new UnionType([ 'name' => 'SingleUnion', - 'resolveType' => function() { return null; }, 'types' => [$fooType] ]); $multipleUnion = new UnionType([ 'name' => 'MultipleUnion', - 'resolveType' => function() { return null; }, 'types' => [$fooType, $barType] ]); @@ -1098,4 +1093,4 @@ enum __TypeKind { EOT; $this->assertEquals($introspectionSchema, $output); } -} \ No newline at end of file +} diff --git a/tests/Validator/OverlappingFieldsCanBeMergedTest.php b/tests/Validator/OverlappingFieldsCanBeMergedTest.php index 48d3053..c9900e7 100644 --- a/tests/Validator/OverlappingFieldsCanBeMergedTest.php +++ b/tests/Validator/OverlappingFieldsCanBeMergedTest.php @@ -795,7 +795,6 @@ class OverlappingFieldsCanBeMergedTest extends TestCase $SomeBox = new InterfaceType([ 'name' => 'SomeBox', - 'resolveType' => function() use (&$StringBox) {return $StringBox;}, 'fields' => function() use (&$SomeBox) { return [ 'deepBox' => ['type' => $SomeBox], @@ -837,7 +836,6 @@ class OverlappingFieldsCanBeMergedTest extends TestCase $NonNullStringBox1 = new InterfaceType([ 'name' => 'NonNullStringBox1', - 'resolveType' => function() use (&$StringBox) {return $StringBox;}, 'fields' => [ 'scalar' => [ 'type' => Type::nonNull(Type::string()) ] ] @@ -855,7 +853,6 @@ class OverlappingFieldsCanBeMergedTest extends TestCase $NonNullStringBox2 = new InterfaceType([ 'name' => 'NonNullStringBox2', - 'resolveType' => function() use (&$StringBox) {return $StringBox;}, 'fields' => [ 'scalar' => ['type' => Type::nonNull(Type::string())] ] diff --git a/tests/Validator/TestCase.php b/tests/Validator/TestCase.php index 387012a..c96a08d 100644 --- a/tests/Validator/TestCase.php +++ b/tests/Validator/TestCase.php @@ -67,7 +67,6 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase $Dog = new ObjectType([ 'name' => 'Dog', - 'isTypeOf' => function() {return true;}, 'fields' => [ 'name' => [ 'type' => Type::string(), @@ -94,7 +93,6 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase $Cat = new ObjectType([ 'name' => 'Cat', - 'isTypeOf' => function() {return true;}, 'fields' => function() use (&$FurColor) { return [ 'name' => [ @@ -113,10 +111,6 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase $CatOrDog = new UnionType([ 'name' => 'CatOrDog', 'types' => [$Dog, $Cat], - 'resolveType' => function($value) { - // not used for validation - return null; - } ]); $Intelligent = new InterfaceType([ @@ -129,7 +123,6 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase $Human = null; $Human = new ObjectType([ 'name' => 'Human', - 'isTypeOf' => function() {return true;}, 'interfaces' => [$Being, $Intelligent], 'fields' => function() use (&$Human, $Pet) { return [ @@ -146,7 +139,6 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase $Alien = new ObjectType([ 'name' => 'Alien', - 'isTypeOf' => function() {return true;}, 'interfaces' => [$Being, $Intelligent], 'fields' => [ 'iq' => ['type' => Type::int()], @@ -161,19 +153,11 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase $DogOrHuman = new UnionType([ 'name' => 'DogOrHuman', 'types' => [$Dog, $Human], - 'resolveType' => function() { - // not used for validation - return null; - } ]); $HumanOrAlien = new UnionType([ 'name' => 'HumanOrAlien', 'types' => [$Human, $Alien], - 'resolveType' => function() { - // not used for validation - return null; - } ]); $FurColor = new EnumType([