mirror of
https://github.com/retailcrm/graphql-php.git
synced 2025-02-16 20:33:14 +03:00
Ability for interface types to resolve type asynchronously
This commit is contained in:
parent
445f579f09
commit
d64c352262
@ -969,9 +969,59 @@ class Executor
|
|||||||
$runtimeType = $returnType->resolveType($result, $exeContext->contextValue, $info);
|
$runtimeType = $returnType->resolveType($result, $exeContext->contextValue, $info);
|
||||||
|
|
||||||
if (null === $runtimeType) {
|
if (null === $runtimeType) {
|
||||||
$runtimeType = self::inferTypeOf($result, $exeContext->contextValue, $info, $returnType);
|
$runtimeType = self::defaultTypeResolver($result, $exeContext->contextValue, $info, $returnType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->promises->isThenable($runtimeType)) {
|
||||||
|
$runtimeType = $this->promises->convertThenable($runtimeType);
|
||||||
|
Utils::invariant($runtimeType instanceof Promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($runtimeType instanceof Promise) {
|
||||||
|
return $runtimeType->then(function($resolvedRuntimeType) use ($returnType, $fieldNodes, $info, $path, &$result) {
|
||||||
|
return $this->validateRuntimeTypeAndCompleteObjectValue(
|
||||||
|
$returnType,
|
||||||
|
$fieldNodes,
|
||||||
|
$info,
|
||||||
|
$path,
|
||||||
|
$resolvedRuntimeType,
|
||||||
|
$result
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->validateRuntimeTypeAndCompleteObjectValue(
|
||||||
|
$returnType,
|
||||||
|
$fieldNodes,
|
||||||
|
$info,
|
||||||
|
$path,
|
||||||
|
$runtimeType,
|
||||||
|
$result
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param AbstractType $returnType
|
||||||
|
* @param FieldNode[] $fieldNodes
|
||||||
|
* @param ResolveInfo $info
|
||||||
|
* @param array $path
|
||||||
|
* @param mixed $returnedRuntimeType
|
||||||
|
* @param $result
|
||||||
|
* @return array|Promise|\stdClass
|
||||||
|
* @throws Error
|
||||||
|
*/
|
||||||
|
private function validateRuntimeTypeAndCompleteObjectValue(
|
||||||
|
AbstractType $returnType,
|
||||||
|
$fieldNodes,
|
||||||
|
ResolveInfo $info,
|
||||||
|
$path,
|
||||||
|
$returnedRuntimeType,
|
||||||
|
&$result
|
||||||
|
)
|
||||||
|
{
|
||||||
|
$exeContext = $this->exeContext;
|
||||||
|
$runtimeType = $returnedRuntimeType;
|
||||||
|
|
||||||
// If resolveType returns a string, we assume it's a ObjectType name.
|
// If resolveType returns a string, we assume it's a ObjectType name.
|
||||||
if (is_string($runtimeType)) {
|
if (is_string($runtimeType)) {
|
||||||
$runtimeType = $exeContext->schema->getType($runtimeType);
|
$runtimeType = $exeContext->schema->getType($runtimeType);
|
||||||
@ -1064,12 +1114,61 @@ class Executor
|
|||||||
*/
|
*/
|
||||||
private function completeObjectValue(ObjectType $returnType, $fieldNodes, ResolveInfo $info, $path, &$result)
|
private function completeObjectValue(ObjectType $returnType, $fieldNodes, ResolveInfo $info, $path, &$result)
|
||||||
{
|
{
|
||||||
$exeContext = $this->exeContext;
|
$isTypeOf = $returnType->isTypeOf($result, $this->exeContext->contextValue, $info);
|
||||||
|
|
||||||
// If there is an isTypeOf predicate function, call it with the
|
if (null === $isTypeOf) {
|
||||||
// current result. If isTypeOf returns false, then raise an error rather
|
return $this->validateResultTypeAndExecuteFields(
|
||||||
// than continuing execution.
|
$returnType,
|
||||||
if (false === $returnType->isTypeOf($result, $exeContext->contextValue, $info)) {
|
$fieldNodes,
|
||||||
|
$info,
|
||||||
|
$path,
|
||||||
|
$result,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->promises->isThenable($isTypeOf)) {
|
||||||
|
/** @var Promise $isTypeOf */
|
||||||
|
$isTypeOf = $this->promises->convertThenable($isTypeOf);
|
||||||
|
Utils::invariant($isTypeOf instanceof Promise);
|
||||||
|
|
||||||
|
return $isTypeOf->then(function($isTypeOfResult) use ($returnType, $fieldNodes, $info, $path, &$result) {
|
||||||
|
return $this->validateResultTypeAndExecuteFields(
|
||||||
|
$returnType,
|
||||||
|
$fieldNodes,
|
||||||
|
$info,
|
||||||
|
$path,
|
||||||
|
$result,
|
||||||
|
$isTypeOfResult
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->validateResultTypeAndExecuteFields($returnType, $fieldNodes, $info, $path, $result, $isTypeOf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ObjectType $returnType
|
||||||
|
* @param FieldNode[] $fieldNodes
|
||||||
|
* @param ResolveInfo $info
|
||||||
|
* @param array $path
|
||||||
|
* @param array $result
|
||||||
|
* @param bool $isTypeOfResult
|
||||||
|
* @return array|Promise|\stdClass
|
||||||
|
* @throws Error
|
||||||
|
*/
|
||||||
|
private function validateResultTypeAndExecuteFields(
|
||||||
|
ObjectType $returnType,
|
||||||
|
$fieldNodes,
|
||||||
|
ResolveInfo $info,
|
||||||
|
$path,
|
||||||
|
&$result,
|
||||||
|
$isTypeOfResult
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// If isTypeOf returns false, then raise an error
|
||||||
|
// rather than continuing execution.
|
||||||
|
if (false === $isTypeOfResult) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Expected value of type $returnType but got: " . Utils::getVariableType($result),
|
"Expected value of type $returnType but got: " . Utils::getVariableType($result),
|
||||||
$fieldNodes
|
$fieldNodes
|
||||||
@ -1095,23 +1194,47 @@ class Executor
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Infer type of the value using isTypeOf of corresponding AbstractType
|
* 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
|
||||||
|
* isTypeOf for the object being coerced, returning the first type that matches.
|
||||||
*
|
*
|
||||||
* @param $value
|
* @param $value
|
||||||
* @param $context
|
* @param $context
|
||||||
* @param ResolveInfo $info
|
* @param ResolveInfo $info
|
||||||
* @param AbstractType $abstractType
|
* @param AbstractType $abstractType
|
||||||
* @return ObjectType|null
|
* @return ObjectType|Promise|null
|
||||||
*/
|
*/
|
||||||
private static function inferTypeOf($value, $context, ResolveInfo $info, AbstractType $abstractType)
|
private function defaultTypeResolver($value, $context, ResolveInfo $info, AbstractType $abstractType)
|
||||||
{
|
{
|
||||||
$possibleTypes = $info->schema->getPossibleTypes($abstractType);
|
$possibleTypes = $info->schema->getPossibleTypes($abstractType);
|
||||||
|
$promisedIsTypeOfResults = [];
|
||||||
|
$promisedIsTypeOfResultTypes = [];
|
||||||
|
|
||||||
foreach ($possibleTypes as $type) {
|
foreach ($possibleTypes as $type) {
|
||||||
if ($type->isTypeOf($value, $context, $info)) {
|
$isTypeOfResult = $type->isTypeOf($value, $context, $info);
|
||||||
return $type;
|
|
||||||
|
if (null !== $isTypeOfResult) {
|
||||||
|
if ($this->promises->isThenable($isTypeOfResult)) {
|
||||||
|
$promisedIsTypeOfResults[] = $this->promises->convertThenable($isTypeOfResult);
|
||||||
|
$promisedIsTypeOfResultTypes[] = $type;
|
||||||
|
} else if ($isTypeOfResult) {
|
||||||
|
return $type;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!empty($promisedIsTypeOfResults)) {
|
||||||
|
return $this->promises->all($promisedIsTypeOfResults)
|
||||||
|
->then(function($isTypeOfResults) use ($promisedIsTypeOfResultTypes) {
|
||||||
|
foreach ($isTypeOfResults as $index => $result) {
|
||||||
|
if ($result) {
|
||||||
|
return $promisedIsTypeOfResultTypes[$index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
654
tests/Executor/AbstractPromiseTest.php
Normal file
654
tests/Executor/AbstractPromiseTest.php
Normal file
@ -0,0 +1,654 @@
|
|||||||
|
<?php
|
||||||
|
namespace GraphQL\Tests\Executor;
|
||||||
|
|
||||||
|
use GraphQL\Deferred;
|
||||||
|
use GraphQL\GraphQL;
|
||||||
|
use GraphQL\Schema;
|
||||||
|
use GraphQL\Type\Definition\InterfaceType;
|
||||||
|
use GraphQL\Type\Definition\ObjectType;
|
||||||
|
use GraphQL\Type\Definition\Type;
|
||||||
|
use GraphQL\Type\Definition\UnionType;
|
||||||
|
|
||||||
|
require_once __DIR__ . '/TestClasses.php';
|
||||||
|
|
||||||
|
class AbstractPromiseTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
// DESCRIBE: Execute: Handles execution of abstract types with promises
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it isTypeOf used to resolve runtime type for Interface
|
||||||
|
*/
|
||||||
|
public function testIsTypeOfUsedToResolveRuntimeTypeForInterface()
|
||||||
|
{
|
||||||
|
$PetType = new InterfaceType([
|
||||||
|
'name' => 'Pet',
|
||||||
|
'fields' => [
|
||||||
|
'name' => [ 'type' => Type::string() ]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$DogType = new ObjectType([
|
||||||
|
'name' => 'Dog',
|
||||||
|
'interfaces' => [ $PetType ],
|
||||||
|
'isTypeOf' => function($obj) {
|
||||||
|
return new Deferred(function() use ($obj) {
|
||||||
|
return $obj instanceof Dog;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'fields' => [
|
||||||
|
'name' => [ 'type' => Type::string() ],
|
||||||
|
'woofs' => [ 'type' => Type::boolean() ],
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$CatType = new ObjectType([
|
||||||
|
'name' => 'Cat',
|
||||||
|
'interfaces' => [ $PetType ],
|
||||||
|
'isTypeOf' => function($obj) {
|
||||||
|
return new Deferred(function() use ($obj) {
|
||||||
|
return $obj instanceof Cat;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'fields' => [
|
||||||
|
'name' => [ 'type' => Type::string() ],
|
||||||
|
'meows' => [ 'type' => Type::boolean() ],
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$schema = new Schema([
|
||||||
|
'query' => new ObjectType([
|
||||||
|
'name' => 'Query',
|
||||||
|
'fields' => [
|
||||||
|
'pets' => [
|
||||||
|
'type' => Type::listOf($PetType),
|
||||||
|
'resolve' => function() {
|
||||||
|
return [
|
||||||
|
new Dog('Odie', true),
|
||||||
|
new Cat('Garfield', false)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]),
|
||||||
|
'types' => [ $CatType, $DogType ]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$query = '{
|
||||||
|
pets {
|
||||||
|
name
|
||||||
|
... on Dog {
|
||||||
|
woofs
|
||||||
|
}
|
||||||
|
... on Cat {
|
||||||
|
meows
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}';
|
||||||
|
|
||||||
|
$result = GraphQL::execute($schema, $query);
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
'data' => [
|
||||||
|
'pets' => [
|
||||||
|
[ 'name' => 'Odie', 'woofs' => true ],
|
||||||
|
[ 'name' => 'Garfield', 'meows' => false ]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it isTypeOf can be rejected
|
||||||
|
*/
|
||||||
|
public function testIsTypeOfCanBeRejected()
|
||||||
|
{
|
||||||
|
|
||||||
|
$PetType = new InterfaceType([
|
||||||
|
'name' => 'Pet',
|
||||||
|
'fields' => [
|
||||||
|
'name' => ['type' => Type::string()]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$DogType = new ObjectType([
|
||||||
|
'name' => 'Dog',
|
||||||
|
'interfaces' => [$PetType],
|
||||||
|
'isTypeOf' => function () {
|
||||||
|
return new Deferred(function () {
|
||||||
|
throw new \Exception('We are testing this error');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'fields' => [
|
||||||
|
'name' => ['type' => Type::string()],
|
||||||
|
'woofs' => ['type' => Type::boolean()],
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$CatType = new ObjectType([
|
||||||
|
'name' => 'Cat',
|
||||||
|
'interfaces' => [$PetType],
|
||||||
|
'isTypeOf' => function ($obj) {
|
||||||
|
return new Deferred(function () use ($obj) {
|
||||||
|
return $obj instanceof Cat;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'fields' => [
|
||||||
|
'name' => ['type' => Type::string()],
|
||||||
|
'meows' => ['type' => Type::boolean()],
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$schema = new Schema([
|
||||||
|
'query' => new ObjectType([
|
||||||
|
'name' => 'Query',
|
||||||
|
'fields' => [
|
||||||
|
'pets' => [
|
||||||
|
'type' => Type::listOf($PetType),
|
||||||
|
'resolve' => function () {
|
||||||
|
return [
|
||||||
|
new Dog('Odie', true),
|
||||||
|
new Cat('Garfield', false)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]),
|
||||||
|
'types' => [$CatType, $DogType]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$query = '{
|
||||||
|
pets {
|
||||||
|
name
|
||||||
|
... on Dog {
|
||||||
|
woofs
|
||||||
|
}
|
||||||
|
... on Cat {
|
||||||
|
meows
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}';
|
||||||
|
|
||||||
|
$result = GraphQL::execute($schema, $query);
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
'data' => [
|
||||||
|
'pets' => [null, null]
|
||||||
|
],
|
||||||
|
'errors' => [
|
||||||
|
[
|
||||||
|
'message' => 'We are testing this error',
|
||||||
|
'locations' => [['line' => 2, 'column' => 7]],
|
||||||
|
'path' => ['pets', 0]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'message' => 'We are testing this error',
|
||||||
|
'locations' => [['line' => 2, 'column' => 7]],
|
||||||
|
'path' => ['pets', 1]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it isTypeOf used to resolve runtime type for Union
|
||||||
|
*/
|
||||||
|
public function testIsTypeOfUsedToResolveRuntimeTypeForUnion()
|
||||||
|
{
|
||||||
|
|
||||||
|
$DogType = new ObjectType([
|
||||||
|
'name' => 'Dog',
|
||||||
|
'isTypeOf' => function ($obj) {
|
||||||
|
return new Deferred(function () use ($obj) {
|
||||||
|
return $obj instanceof Dog;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'fields' => [
|
||||||
|
'name' => ['type' => Type::string()],
|
||||||
|
'woofs' => ['type' => Type::boolean()],
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$CatType = new ObjectType([
|
||||||
|
'name' => 'Cat',
|
||||||
|
'isTypeOf' => function ($obj) {
|
||||||
|
return new Deferred(function () use ($obj) {
|
||||||
|
return $obj instanceof Cat;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'fields' => [
|
||||||
|
'name' => ['type' => Type::string()],
|
||||||
|
'meows' => ['type' => Type::boolean()],
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$PetType = new UnionType([
|
||||||
|
'name' => 'Pet',
|
||||||
|
'types' => [$DogType, $CatType]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$schema = new Schema([
|
||||||
|
'query' => new ObjectType([
|
||||||
|
'name' => 'Query',
|
||||||
|
'fields' => [
|
||||||
|
'pets' => [
|
||||||
|
'type' => Type::listOf($PetType),
|
||||||
|
'resolve' => function () {
|
||||||
|
return [new Dog('Odie', true), new Cat('Garfield', false)];
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
|
||||||
|
$query = '{
|
||||||
|
pets {
|
||||||
|
... on Dog {
|
||||||
|
name
|
||||||
|
woofs
|
||||||
|
}
|
||||||
|
... on Cat {
|
||||||
|
name
|
||||||
|
meows
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}';
|
||||||
|
|
||||||
|
$result = GraphQL::execute($schema, $query);
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
'data' => [
|
||||||
|
'pets' => [
|
||||||
|
['name' => 'Odie', 'woofs' => true],
|
||||||
|
['name' => 'Garfield', 'meows' => false]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it resolveType on Interface yields useful error
|
||||||
|
*/
|
||||||
|
public function testResolveTypeOnInterfaceYieldsUsefulError()
|
||||||
|
{
|
||||||
|
$PetType = new InterfaceType([
|
||||||
|
'name' => 'Pet',
|
||||||
|
'resolveType' => function ($obj) use (&$DogType, &$CatType, &$HumanType) {
|
||||||
|
return new Deferred(function () use ($obj, $DogType, $CatType, $HumanType) {
|
||||||
|
if ($obj instanceof Dog) {
|
||||||
|
return $DogType;
|
||||||
|
}
|
||||||
|
if ($obj instanceof Cat) {
|
||||||
|
return $CatType;
|
||||||
|
}
|
||||||
|
if ($obj instanceof Human) {
|
||||||
|
return $HumanType;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'fields' => [
|
||||||
|
'name' => ['type' => Type::string()]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$HumanType = new ObjectType([
|
||||||
|
'name' => 'Human',
|
||||||
|
'fields' => [
|
||||||
|
'name' => ['type' => Type::string()],
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$DogType = new ObjectType([
|
||||||
|
'name' => 'Dog',
|
||||||
|
'interfaces' => [$PetType],
|
||||||
|
'fields' => [
|
||||||
|
'name' => ['type' => Type::string()],
|
||||||
|
'woofs' => ['type' => Type::boolean()],
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$CatType = new ObjectType([
|
||||||
|
'name' => 'Cat',
|
||||||
|
'interfaces' => [$PetType],
|
||||||
|
'fields' => [
|
||||||
|
'name' => ['type' => Type::string()],
|
||||||
|
'meows' => ['type' => Type::boolean()],
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$schema = new Schema([
|
||||||
|
'query' => new ObjectType([
|
||||||
|
'name' => 'Query',
|
||||||
|
'fields' => [
|
||||||
|
'pets' => [
|
||||||
|
'type' => Type::listOf($PetType),
|
||||||
|
'resolve' => function () {
|
||||||
|
return new Deferred(function () {
|
||||||
|
return [
|
||||||
|
new Dog('Odie', true),
|
||||||
|
new Cat('Garfield', false),
|
||||||
|
new Human('Jon')
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]),
|
||||||
|
'types' => [$CatType, $DogType]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$query = '{
|
||||||
|
pets {
|
||||||
|
name
|
||||||
|
... on Dog {
|
||||||
|
woofs
|
||||||
|
}
|
||||||
|
... on Cat {
|
||||||
|
meows
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}';
|
||||||
|
|
||||||
|
$result = GraphQL::execute($schema, $query);
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
'data' => [
|
||||||
|
'pets' => [
|
||||||
|
['name' => 'Odie', 'woofs' => true],
|
||||||
|
['name' => 'Garfield', 'meows' => false],
|
||||||
|
null
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'errors' => [
|
||||||
|
[
|
||||||
|
'message' => 'Runtime Object type "Human" is not a possible type for "Pet".',
|
||||||
|
'locations' => [['line' => 2, 'column' => 7]],
|
||||||
|
'path' => ['pets', 2]
|
||||||
|
],
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it resolveType on Union yields useful error
|
||||||
|
*/
|
||||||
|
public function testResolveTypeOnUnionYieldsUsefulError()
|
||||||
|
{
|
||||||
|
|
||||||
|
$HumanType = new ObjectType([
|
||||||
|
'name' => 'Human',
|
||||||
|
'fields' => [
|
||||||
|
'name' => ['type' => Type::string()],
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$DogType = new ObjectType([
|
||||||
|
'name' => 'Dog',
|
||||||
|
'fields' => [
|
||||||
|
'name' => ['type' => Type::string()],
|
||||||
|
'woofs' => ['type' => Type::boolean()],
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$CatType = new ObjectType([
|
||||||
|
'name' => 'Cat',
|
||||||
|
'fields' => [
|
||||||
|
'name' => ['type' => Type::string()],
|
||||||
|
'meows' => ['type' => Type::boolean()],
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$PetType = new UnionType([
|
||||||
|
'name' => 'Pet',
|
||||||
|
'resolveType' => function ($obj) use ($DogType, $CatType, $HumanType) {
|
||||||
|
return new Deferred(function () use ($obj, $DogType, $CatType, $HumanType) {
|
||||||
|
if ($obj instanceof Dog) {
|
||||||
|
return $DogType;
|
||||||
|
}
|
||||||
|
if ($obj instanceof Cat) {
|
||||||
|
return $CatType;
|
||||||
|
}
|
||||||
|
if ($obj instanceof Human) {
|
||||||
|
return $HumanType;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'types' => [$DogType, $CatType]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$schema = new Schema([
|
||||||
|
'query' => new ObjectType([
|
||||||
|
'name' => 'Query',
|
||||||
|
'fields' => [
|
||||||
|
'pets' => [
|
||||||
|
'type' => Type::listOf($PetType),
|
||||||
|
'resolve' => function () {
|
||||||
|
return [
|
||||||
|
new Dog('Odie', true),
|
||||||
|
new Cat('Garfield', false),
|
||||||
|
new Human('Jon')
|
||||||
|
];
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
|
||||||
|
$query = '{
|
||||||
|
pets {
|
||||||
|
... on Dog {
|
||||||
|
name
|
||||||
|
woofs
|
||||||
|
}
|
||||||
|
... on Cat {
|
||||||
|
name
|
||||||
|
meows
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}';
|
||||||
|
|
||||||
|
$result = GraphQL::execute($schema, $query);
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
'data' => [
|
||||||
|
'pets' => [
|
||||||
|
['name' => 'Odie', 'woofs' => true],
|
||||||
|
['name' => 'Garfield', 'meows' => false],
|
||||||
|
null
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'errors' => [
|
||||||
|
[
|
||||||
|
'message' => 'Runtime Object type "Human" is not a possible type for "Pet".',
|
||||||
|
'locations' => [['line' => 2, 'column' => 7]],
|
||||||
|
'path' => ['pets', 2]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it resolveType allows resolving with type name
|
||||||
|
*/
|
||||||
|
public function testResolveTypeAllowsResolvingWithTypeName()
|
||||||
|
{
|
||||||
|
$PetType = new InterfaceType([
|
||||||
|
'name' => 'Pet',
|
||||||
|
'resolveType' => function ($obj) {
|
||||||
|
return new Deferred(function () use ($obj) {
|
||||||
|
if ($obj instanceof Dog) {
|
||||||
|
return 'Dog';
|
||||||
|
}
|
||||||
|
if ($obj instanceof Cat) {
|
||||||
|
return 'Cat';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'fields' => [
|
||||||
|
'name' => ['type' => Type::string()]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
$DogType = new ObjectType([
|
||||||
|
'name' => 'Dog',
|
||||||
|
'interfaces' => [$PetType],
|
||||||
|
'fields' => [
|
||||||
|
'name' => ['type' => Type::string()],
|
||||||
|
'woofs' => ['type' => Type::boolean()],
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$CatType = new ObjectType([
|
||||||
|
'name' => 'Cat',
|
||||||
|
'interfaces' => [$PetType],
|
||||||
|
'fields' => [
|
||||||
|
'name' => ['type' => Type::string()],
|
||||||
|
'meows' => ['type' => Type::boolean()],
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$schema = new Schema([
|
||||||
|
'query' => new ObjectType([
|
||||||
|
'name' => 'Query',
|
||||||
|
'fields' => [
|
||||||
|
'pets' => [
|
||||||
|
'type' => Type::listOf($PetType),
|
||||||
|
'resolve' => function () {
|
||||||
|
return [
|
||||||
|
new Dog('Odie', true),
|
||||||
|
new Cat('Garfield', false)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]),
|
||||||
|
'types' => [$CatType, $DogType]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$query = '{
|
||||||
|
pets {
|
||||||
|
name
|
||||||
|
... on Dog {
|
||||||
|
woofs
|
||||||
|
}
|
||||||
|
... on Cat {
|
||||||
|
meows
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}';
|
||||||
|
|
||||||
|
$result = GraphQL::execute($schema, $query);
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
'data' => [
|
||||||
|
'pets' => [
|
||||||
|
['name' => 'Odie', 'woofs' => true],
|
||||||
|
['name' => 'Garfield', 'meows' => false],
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
$this->assertEquals($expected, $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it resolveType can be caught
|
||||||
|
*/
|
||||||
|
public function testResolveTypeCanBeCaught()
|
||||||
|
{
|
||||||
|
|
||||||
|
$PetType = new InterfaceType([
|
||||||
|
'name' => 'Pet',
|
||||||
|
'resolveType' => function () {
|
||||||
|
return new Deferred(function () {
|
||||||
|
throw new \Exception('We are testing this error');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'fields' => [
|
||||||
|
'name' => ['type' => Type::string()]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$DogType = new ObjectType([
|
||||||
|
'name' => 'Dog',
|
||||||
|
'interfaces' => [$PetType],
|
||||||
|
'fields' => [
|
||||||
|
'name' => ['type' => Type::string()],
|
||||||
|
'woofs' => ['type' => Type::boolean()],
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$CatType = new ObjectType([
|
||||||
|
'name' => 'Cat',
|
||||||
|
'interfaces' => [$PetType],
|
||||||
|
'fields' => [
|
||||||
|
'name' => ['type' => Type::string()],
|
||||||
|
'meows' => ['type' => Type::boolean()],
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$schema = new Schema([
|
||||||
|
'query' => new ObjectType([
|
||||||
|
'name' => 'Query',
|
||||||
|
'fields' => [
|
||||||
|
'pets' => [
|
||||||
|
'type' => Type::listOf($PetType),
|
||||||
|
'resolve' => function () {
|
||||||
|
return [
|
||||||
|
new Dog('Odie', true),
|
||||||
|
new Cat('Garfield', false)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]),
|
||||||
|
'types' => [$CatType, $DogType]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$query = '{
|
||||||
|
pets {
|
||||||
|
name
|
||||||
|
... on Dog {
|
||||||
|
woofs
|
||||||
|
}
|
||||||
|
... on Cat {
|
||||||
|
meows
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}';
|
||||||
|
|
||||||
|
$result = GraphQL::execute($schema, $query);
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
'data' => [
|
||||||
|
'pets' => [null, null]
|
||||||
|
],
|
||||||
|
'errors' => [
|
||||||
|
[
|
||||||
|
'message' => 'We are testing this error',
|
||||||
|
'locations' => [['line' => 2, 'column' => 7]],
|
||||||
|
'path' => ['pets', 0]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'message' => 'We are testing this error',
|
||||||
|
'locations' => [['line' => 2, 'column' => 7]],
|
||||||
|
'path' => ['pets', 1]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $result);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user