mirror of
https://github.com/retailcrm/graphql-php.git
synced 2024-11-22 12:56:05 +03:00
Refactored executor logic related to isTypeOf
This commit is contained in:
parent
29c1132554
commit
34bd378c7e
@ -968,81 +968,86 @@ class Executor
|
|||||||
$exeContext = $this->exeContext;
|
$exeContext = $this->exeContext;
|
||||||
$runtimeType = $returnType->resolveType($result, $exeContext->contextValue, $info);
|
$runtimeType = $returnType->resolveType($result, $exeContext->contextValue, $info);
|
||||||
|
|
||||||
if (null === $runtimeType) {
|
|
||||||
$runtimeType = self::defaultTypeResolver($result, $exeContext->contextValue, $info, $returnType);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->promises->isThenable($runtimeType)) {
|
if ($this->promises->isThenable($runtimeType)) {
|
||||||
$runtimeType = $this->promises->convertThenable($runtimeType);
|
$runtimeType = $this->promises->convertThenable($runtimeType);
|
||||||
Utils::invariant($runtimeType instanceof Promise);
|
Utils::invariant($runtimeType instanceof Promise);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (null === $runtimeType) {
|
||||||
|
$runtimeType = self::defaultTypeResolver($result, $exeContext->contextValue, $info, $returnType);
|
||||||
|
}
|
||||||
|
|
||||||
if ($runtimeType instanceof Promise) {
|
if ($runtimeType instanceof Promise) {
|
||||||
return $runtimeType->then(function($resolvedRuntimeType) use ($returnType, $fieldNodes, $info, $path, &$result) {
|
return $runtimeType->then(function($resolvedRuntimeType) use ($returnType, $fieldNodes, $info, $path, &$result) {
|
||||||
return $this->validateRuntimeTypeAndCompleteObjectValue(
|
return $this->completeObjectValue(
|
||||||
$returnType,
|
$this->ensureValidRuntimeType(
|
||||||
|
$resolvedRuntimeType,
|
||||||
|
$returnType,
|
||||||
|
$fieldNodes,
|
||||||
|
$info,
|
||||||
|
$result
|
||||||
|
),
|
||||||
$fieldNodes,
|
$fieldNodes,
|
||||||
$info,
|
$info,
|
||||||
$path,
|
$path,
|
||||||
$resolvedRuntimeType,
|
|
||||||
$result
|
$result
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->validateRuntimeTypeAndCompleteObjectValue(
|
return $this->completeObjectValue(
|
||||||
$returnType,
|
$this->ensureValidRuntimeType(
|
||||||
|
$runtimeType,
|
||||||
|
$returnType,
|
||||||
|
$fieldNodes,
|
||||||
|
$info,
|
||||||
|
$result
|
||||||
|
),
|
||||||
$fieldNodes,
|
$fieldNodes,
|
||||||
$info,
|
$info,
|
||||||
$path,
|
$path,
|
||||||
$runtimeType,
|
|
||||||
$result
|
$result
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param string|ObjectType|null $runtimeTypeOrName
|
||||||
* @param AbstractType $returnType
|
* @param AbstractType $returnType
|
||||||
* @param FieldNode[] $fieldNodes
|
* @param $fieldNodes
|
||||||
* @param ResolveInfo $info
|
* @param ResolveInfo $info
|
||||||
* @param array $path
|
|
||||||
* @param mixed $returnedRuntimeType
|
|
||||||
* @param $result
|
* @param $result
|
||||||
* @return array|Promise|\stdClass
|
* @return ObjectType
|
||||||
* @throws Error
|
* @throws Error
|
||||||
*/
|
*/
|
||||||
private function validateRuntimeTypeAndCompleteObjectValue(
|
private function ensureValidRuntimeType(
|
||||||
|
$runtimeTypeOrName,
|
||||||
AbstractType $returnType,
|
AbstractType $returnType,
|
||||||
$fieldNodes,
|
$fieldNodes,
|
||||||
ResolveInfo $info,
|
ResolveInfo $info,
|
||||||
$path,
|
|
||||||
$returnedRuntimeType,
|
|
||||||
&$result
|
&$result
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
$exeContext = $this->exeContext;
|
$runtimeType = is_string($runtimeTypeOrName) ?
|
||||||
$runtimeType = $returnedRuntimeType;
|
$this->exeContext->schema->getType($runtimeTypeOrName) :
|
||||||
|
$runtimeTypeOrName;
|
||||||
|
|
||||||
// If resolveType returns a string, we assume it's a ObjectType name.
|
if (!$runtimeType instanceof ObjectType) {
|
||||||
if (is_string($runtimeType)) {
|
|
||||||
$runtimeType = $exeContext->schema->getType($runtimeType);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!($runtimeType instanceof ObjectType)) {
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Abstract type {$returnType} must resolve to an Object type at runtime " .
|
"Abstract type {$returnType} must resolve to an Object type at runtime " .
|
||||||
"for field {$info->parentType}.{$info->fieldName} with value: " . Utils::printSafe($result) . "," .
|
"for field {$info->parentType}.{$info->fieldName} with " .
|
||||||
"received \"$runtimeType\".",
|
'value "' . Utils::printSafe($result) . '", received "'. Utils::printSafe($runtimeType) . '".',
|
||||||
$fieldNodes
|
$fieldNodes
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$exeContext->schema->isPossibleType($returnType, $runtimeType)) {
|
if (!$this->exeContext->schema->isPossibleType($returnType, $runtimeType)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Runtime Object type \"$runtimeType\" is not a possible type for \"$returnType\".",
|
"Runtime Object type \"$runtimeType\" is not a possible type for \"$returnType\".",
|
||||||
$fieldNodes
|
$fieldNodes
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return $this->completeObjectValue($runtimeType, $fieldNodes, $info, $path, $result);
|
|
||||||
|
return $runtimeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1114,67 +1119,81 @@ class Executor
|
|||||||
*/
|
*/
|
||||||
private function completeObjectValue(ObjectType $returnType, $fieldNodes, ResolveInfo $info, $path, &$result)
|
private function completeObjectValue(ObjectType $returnType, $fieldNodes, ResolveInfo $info, $path, &$result)
|
||||||
{
|
{
|
||||||
|
// If there is an isTypeOf predicate function, call it with the
|
||||||
|
// current result. If isTypeOf returns false, then raise an error rather
|
||||||
|
// than continuing execution.
|
||||||
$isTypeOf = $returnType->isTypeOf($result, $this->exeContext->contextValue, $info);
|
$isTypeOf = $returnType->isTypeOf($result, $this->exeContext->contextValue, $info);
|
||||||
|
|
||||||
if (null === $isTypeOf) {
|
if (null !== $isTypeOf) {
|
||||||
return $this->validateResultTypeAndExecuteFields(
|
if ($this->promises->isThenable($isTypeOf)) {
|
||||||
$returnType,
|
/** @var Promise $isTypeOf */
|
||||||
$fieldNodes,
|
$isTypeOf = $this->promises->convertThenable($isTypeOf);
|
||||||
$info,
|
Utils::invariant($isTypeOf instanceof Promise);
|
||||||
$path,
|
|
||||||
$result,
|
return $isTypeOf->then(function($isTypeOfResult) use ($returnType, $fieldNodes, $info, $path, &$result) {
|
||||||
true
|
if (!$isTypeOfResult) {
|
||||||
);
|
throw $this->invalidReturnTypeError($returnType, $result, $fieldNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->collectAndExecuteSubfields(
|
||||||
|
$returnType,
|
||||||
|
$fieldNodes,
|
||||||
|
$info,
|
||||||
|
$path,
|
||||||
|
$result
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!$isTypeOf) {
|
||||||
|
throw $this->invalidReturnTypeError($returnType, $result, $fieldNodes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->promises->isThenable($isTypeOf)) {
|
return $this->collectAndExecuteSubfields(
|
||||||
/** @var Promise $isTypeOf */
|
$returnType,
|
||||||
$isTypeOf = $this->promises->convertThenable($isTypeOf);
|
$fieldNodes,
|
||||||
Utils::invariant($isTypeOf instanceof Promise);
|
$info,
|
||||||
|
$path,
|
||||||
return $isTypeOf->then(function($isTypeOfResult) use ($returnType, $fieldNodes, $info, $path, &$result) {
|
$result
|
||||||
return $this->validateResultTypeAndExecuteFields(
|
);
|
||||||
$returnType,
|
|
||||||
$fieldNodes,
|
|
||||||
$info,
|
|
||||||
$path,
|
|
||||||
$result,
|
|
||||||
$isTypeOfResult
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->validateResultTypeAndExecuteFields($returnType, $fieldNodes, $info, $path, $result, $isTypeOf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ObjectType $returnType
|
||||||
|
* @param array $result
|
||||||
|
* @param FieldNode[] $fieldNodes
|
||||||
|
* @return Error
|
||||||
|
*/
|
||||||
|
private function invalidReturnTypeError(
|
||||||
|
ObjectType $returnType,
|
||||||
|
$result,
|
||||||
|
$fieldNodes
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return new Error(
|
||||||
|
'Expected value of type "' . $returnType->name . '" but got: ' . Utils::printSafe($result) . '.',
|
||||||
|
$fieldNodes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param ObjectType $returnType
|
* @param ObjectType $returnType
|
||||||
* @param FieldNode[] $fieldNodes
|
* @param FieldNode[] $fieldNodes
|
||||||
* @param ResolveInfo $info
|
* @param ResolveInfo $info
|
||||||
* @param array $path
|
* @param array $path
|
||||||
* @param array $result
|
* @param array $result
|
||||||
* @param bool $isTypeOfResult
|
|
||||||
* @return array|Promise|\stdClass
|
* @return array|Promise|\stdClass
|
||||||
* @throws Error
|
* @throws Error
|
||||||
*/
|
*/
|
||||||
private function validateResultTypeAndExecuteFields(
|
private function collectAndExecuteSubfields(
|
||||||
ObjectType $returnType,
|
ObjectType $returnType,
|
||||||
$fieldNodes,
|
$fieldNodes,
|
||||||
ResolveInfo $info,
|
ResolveInfo $info,
|
||||||
$path,
|
$path,
|
||||||
&$result,
|
&$result
|
||||||
$isTypeOfResult
|
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
// If isTypeOf returns false, then raise an error
|
|
||||||
// rather than continuing execution.
|
|
||||||
if (false === $isTypeOfResult) {
|
|
||||||
throw new Error(
|
|
||||||
"Expected value of type $returnType but got: " . Utils::getVariableType($result),
|
|
||||||
$fieldNodes
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect sub-fields to execute to complete this value.
|
// Collect sub-fields to execute to complete this value.
|
||||||
$subFieldNodes = new \ArrayObject();
|
$subFieldNodes = new \ArrayObject();
|
||||||
$visitedFragmentNames = new \ArrayObject();
|
$visitedFragmentNames = new \ArrayObject();
|
||||||
@ -1208,15 +1227,13 @@ class Executor
|
|||||||
{
|
{
|
||||||
$possibleTypes = $info->schema->getPossibleTypes($abstractType);
|
$possibleTypes = $info->schema->getPossibleTypes($abstractType);
|
||||||
$promisedIsTypeOfResults = [];
|
$promisedIsTypeOfResults = [];
|
||||||
$promisedIsTypeOfResultTypes = [];
|
|
||||||
|
|
||||||
foreach ($possibleTypes as $type) {
|
foreach ($possibleTypes as $index => $type) {
|
||||||
$isTypeOfResult = $type->isTypeOf($value, $context, $info);
|
$isTypeOfResult = $type->isTypeOf($value, $context, $info);
|
||||||
|
|
||||||
if (null !== $isTypeOfResult) {
|
if (null !== $isTypeOfResult) {
|
||||||
if ($this->promises->isThenable($isTypeOfResult)) {
|
if ($this->promises->isThenable($isTypeOfResult)) {
|
||||||
$promisedIsTypeOfResults[] = $this->promises->convertThenable($isTypeOfResult);
|
$promisedIsTypeOfResults[$index] = $this->promises->convertThenable($isTypeOfResult);
|
||||||
$promisedIsTypeOfResultTypes[] = $type;
|
|
||||||
} else if ($isTypeOfResult) {
|
} else if ($isTypeOfResult) {
|
||||||
return $type;
|
return $type;
|
||||||
}
|
}
|
||||||
@ -1225,10 +1242,10 @@ class Executor
|
|||||||
|
|
||||||
if (!empty($promisedIsTypeOfResults)) {
|
if (!empty($promisedIsTypeOfResults)) {
|
||||||
return $this->promises->all($promisedIsTypeOfResults)
|
return $this->promises->all($promisedIsTypeOfResults)
|
||||||
->then(function($isTypeOfResults) use ($promisedIsTypeOfResultTypes) {
|
->then(function($isTypeOfResults) use ($possibleTypes) {
|
||||||
foreach ($isTypeOfResults as $index => $result) {
|
foreach ($isTypeOfResults as $index => $result) {
|
||||||
if ($result) {
|
if ($result) {
|
||||||
return $promisedIsTypeOfResultTypes[$index];
|
return $possibleTypes[$index];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -934,9 +934,10 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
|
|||||||
], $result->data);
|
], $result->data);
|
||||||
|
|
||||||
$this->assertEquals(1, count($result->errors));
|
$this->assertEquals(1, count($result->errors));
|
||||||
$this->assertArraySubset([
|
$this->assertEquals([
|
||||||
'message' => 'Expected value of type SpecialType but got: GraphQL\Tests\Executor\NotSpecial',
|
'message' => 'Expected value of type "SpecialType" but got: instance of GraphQL\Tests\Executor\NotSpecial.',
|
||||||
'locations' => [['line' => 1, 'column' => 3]]
|
'locations' => [['line' => 1, 'column' => 3]],
|
||||||
|
'path' => ['specials', 1]
|
||||||
], $result->errors[0]->toSerializableArray());
|
], $result->errors[0]->toSerializableArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,11 +243,19 @@ class ValidationTest extends \PHPUnit_Framework_TestCase
|
|||||||
// TODO: does not allow isDeprecated without deprecationReason on field
|
// TODO: does not allow isDeprecated without deprecationReason on field
|
||||||
// TODO: does not allow isDeprecated without deprecationReason on enum
|
// TODO: does not allow isDeprecated without deprecationReason on enum
|
||||||
|
|
||||||
// Type System: Object fields must have valid resolve values
|
// DESCRIBE: Type System: Object fields must have valid resolve values
|
||||||
// TODO: accepts a lambda as an Object field resolver
|
// TODO: accepts a lambda as an Object field resolver
|
||||||
// TODO: rejects an empty Object field resolver
|
// TODO: rejects an empty Object field resolver
|
||||||
// TODO: rejects a constant scalar value resolver
|
// TODO: rejects a constant scalar value resolver
|
||||||
|
|
||||||
|
// DESCRIBE: Type System: Input Object fields must not have resolvers
|
||||||
|
// TODO: accepts an Input Object type with no resolver
|
||||||
|
// TODO: accepts an Input Object type with null resolver
|
||||||
|
// TODO: accepts an Input Object type with undefined resolver
|
||||||
|
// TODO: rejects an Input Object type with resolver function
|
||||||
|
// TODO: rejects an Input Object type with resolver constant
|
||||||
|
|
||||||
|
|
||||||
private function assertEachCallableThrows($closures, $expectedError)
|
private function assertEachCallableThrows($closures, $expectedError)
|
||||||
{
|
{
|
||||||
foreach ($closures as $index => $factory) {
|
foreach ($closures as $index => $factory) {
|
||||||
|
Loading…
Reference in New Issue
Block a user