mirror of
https://github.com/retailcrm/graphql-php.git
synced 2024-11-22 04:46:04 +03:00
commit
29eba82093
239
src/Type/Definition/QueryPlan.php
Normal file
239
src/Type/Definition/QueryPlan.php
Normal file
@ -0,0 +1,239 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Type\Definition;
|
||||
|
||||
use GraphQL\Error\Error;
|
||||
use GraphQL\Executor\Values;
|
||||
use GraphQL\Language\AST\FieldNode;
|
||||
use GraphQL\Language\AST\FragmentDefinitionNode;
|
||||
use GraphQL\Language\AST\FragmentSpreadNode;
|
||||
use GraphQL\Language\AST\InlineFragmentNode;
|
||||
use GraphQL\Language\AST\SelectionSetNode;
|
||||
use GraphQL\Type\Schema;
|
||||
use function array_filter;
|
||||
use function array_key_exists;
|
||||
use function array_keys;
|
||||
use function array_merge;
|
||||
use function array_merge_recursive;
|
||||
use function array_unique;
|
||||
use function array_values;
|
||||
use function count;
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
use function is_numeric;
|
||||
|
||||
class QueryPlan
|
||||
{
|
||||
/** @var string[][] */
|
||||
private $types = [];
|
||||
|
||||
/** @var Schema */
|
||||
private $schema;
|
||||
|
||||
/** @var mixed[] */
|
||||
private $queryPlan = [];
|
||||
|
||||
/** @var mixed[] */
|
||||
private $variableValues;
|
||||
|
||||
/** @var FragmentDefinitionNode[] */
|
||||
private $fragments;
|
||||
|
||||
/**
|
||||
* @param FieldNode[] $fieldNodes
|
||||
* @param mixed[] $variableValues
|
||||
* @param FragmentDefinitionNode[] $fragments
|
||||
*/
|
||||
public function __construct(ObjectType $parentType, Schema $schema, iterable $fieldNodes, array $variableValues, array $fragments)
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
@ -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[]
|
||||
*/
|
||||
|
@ -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]);
|
||||
|
588
tests/Type/QueryPlanTest.php
Normal file
588
tests/Type/QueryPlanTest.php
Normal file
@ -0,0 +1,588 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GraphQL\Tests\Type;
|
||||
|
||||
use GraphQL\GraphQL;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\QueryPlan;
|
||||
use GraphQL\Type\Definition\ResolveInfo;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Schema;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class QueryPlanTest extends TestCase
|
||||
{
|
||||
public function testQueryPlan() : 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
|
||||
}
|
||||
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'));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user