2016-12-17 01:14:51 +03:00
|
|
|
<?php
|
|
|
|
namespace GraphQL\Tests\Executor;
|
|
|
|
|
|
|
|
|
|
|
|
use GraphQL\Deferred;
|
|
|
|
use GraphQL\Executor\Executor;
|
|
|
|
use GraphQL\Language\Parser;
|
|
|
|
use GraphQL\Schema;
|
|
|
|
use GraphQL\Type\Definition\ObjectType;
|
|
|
|
use GraphQL\Type\Definition\ResolveInfo;
|
|
|
|
use GraphQL\Type\Definition\Type;
|
2017-07-10 15:50:26 +03:00
|
|
|
use GraphQL\Utils\Utils;
|
2016-12-17 01:14:51 +03:00
|
|
|
|
|
|
|
class DeferredFieldsTest extends \PHPUnit_Framework_TestCase
|
|
|
|
{
|
|
|
|
private $userType;
|
|
|
|
|
|
|
|
private $storyType;
|
|
|
|
|
|
|
|
private $categoryType;
|
|
|
|
|
|
|
|
private $path;
|
|
|
|
|
|
|
|
private $storyDataSource;
|
|
|
|
|
|
|
|
private $userDataSource;
|
|
|
|
|
|
|
|
private $categoryDataSource;
|
|
|
|
|
|
|
|
private $queryType;
|
|
|
|
|
|
|
|
public function setUp()
|
|
|
|
{
|
|
|
|
$this->storyDataSource = [
|
|
|
|
['id' => 1, 'authorId' => 1, 'title' => 'Story #1', 'categoryIds' => [2, 3]],
|
|
|
|
['id' => 2, 'authorId' => 2, 'title' => 'Story #2', 'categoryIds' => [1, 2]],
|
|
|
|
['id' => 3, 'authorId' => 3, 'title' => 'Story #3', 'categoryIds' => [2]],
|
|
|
|
['id' => 4, 'authorId' => 3, 'title' => 'Story #4', 'categoryIds' => [1]],
|
|
|
|
['id' => 5, 'authorId' => 1, 'title' => 'Story #5', 'categoryIds' => [3]],
|
|
|
|
['id' => 6, 'authorId' => 2, 'title' => 'Story #6', 'categoryIds' => [1]],
|
|
|
|
['id' => 7, 'authorId' => 3, 'title' => 'Story #7', 'categoryIds' => [2]],
|
|
|
|
['id' => 8, 'authorId' => 1, 'title' => 'Story #8', 'categoryIds' => [1, 2, 3]],
|
|
|
|
['id' => 9, 'authorId' => 2, 'title' => 'Story #9', 'categoryIds' => [2, 3]],
|
|
|
|
];
|
|
|
|
|
|
|
|
$this->userDataSource = [
|
|
|
|
['id' => 1, 'name' => 'John', 'bestFriendId' => 4],
|
|
|
|
['id' => 2, 'name' => 'Jane', 'bestFriendId' => 3],
|
|
|
|
['id' => 3, 'name' => 'Joe', 'bestFriendId' => 2],
|
|
|
|
['id' => 4, 'name' => 'Dirk', 'bestFriend' => 1],
|
|
|
|
];
|
|
|
|
|
|
|
|
$this->categoryDataSource = [
|
|
|
|
['id' => 1, 'name' => 'Category #1', 'topStoryId' => 8],
|
|
|
|
['id' => 2, 'name' => 'Category #2', 'topStoryId' => 3],
|
|
|
|
['id' => 3, 'name' => 'Category #3', 'topStoryId' => 9]
|
|
|
|
];
|
|
|
|
|
|
|
|
$this->path = [];
|
|
|
|
$this->userType = new ObjectType([
|
|
|
|
'name' => 'User',
|
|
|
|
'fields' => function() {
|
|
|
|
return [
|
|
|
|
'name' => [
|
|
|
|
'type' => Type::string(),
|
|
|
|
'resolve' => function ($user, $args, $context, ResolveInfo $info) {
|
|
|
|
$this->path[] = $info->path;
|
|
|
|
return $user['name'];
|
|
|
|
}
|
|
|
|
],
|
|
|
|
'bestFriend' => [
|
|
|
|
'type' => $this->userType,
|
|
|
|
'resolve' => function($user, $args, $context, ResolveInfo $info) {
|
|
|
|
$this->path[] = $info->path;
|
|
|
|
|
|
|
|
return new Deferred(function() use ($user) {
|
|
|
|
$this->path[] = 'deferred-for-best-friend-of-' . $user['id'];
|
|
|
|
return Utils::find($this->userDataSource, function($entry) use ($user) {
|
|
|
|
return $entry['id'] === $user['bestFriendId'];
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
]
|
|
|
|
];
|
|
|
|
}
|
|
|
|
]);
|
|
|
|
|
|
|
|
$this->storyType = new ObjectType([
|
|
|
|
'name' => 'Story',
|
|
|
|
'fields' => [
|
|
|
|
'title' => [
|
|
|
|
'type' => Type::string(),
|
|
|
|
'resolve' => function($entry, $args, $context, ResolveInfo $info) {
|
|
|
|
$this->path[] = $info->path;
|
|
|
|
return $entry['title'];
|
|
|
|
}
|
|
|
|
],
|
|
|
|
'author' => [
|
|
|
|
'type' => $this->userType,
|
|
|
|
'resolve' => function($story, $args, $context, ResolveInfo $info) {
|
|
|
|
$this->path[] = $info->path;
|
|
|
|
|
|
|
|
return new Deferred(function() use ($story) {
|
|
|
|
$this->path[] = 'deferred-for-story-' . $story['id'] . '-author';
|
|
|
|
return Utils::find($this->userDataSource, function($entry) use ($story) {
|
|
|
|
return $entry['id'] === $story['authorId'];
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
]
|
|
|
|
]
|
|
|
|
]);
|
|
|
|
|
|
|
|
$this->categoryType = new ObjectType([
|
|
|
|
'name' => 'Category',
|
|
|
|
'fields' => [
|
|
|
|
'name' => [
|
|
|
|
'type' => Type::string(),
|
|
|
|
'resolve' => function($category, $args, $context, ResolveInfo $info) {
|
|
|
|
$this->path[] = $info->path;
|
|
|
|
return $category['name'];
|
|
|
|
}
|
|
|
|
],
|
|
|
|
|
|
|
|
'stories' => [
|
|
|
|
'type' => Type::listOf($this->storyType),
|
|
|
|
'resolve' => function($category, $args, $context, ResolveInfo $info) {
|
|
|
|
$this->path[] = $info->path;
|
|
|
|
return Utils::filter($this->storyDataSource, function($story) use ($category) {
|
|
|
|
return in_array($category['id'], $story['categoryIds']);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
],
|
|
|
|
'topStory' => [
|
|
|
|
'type' => $this->storyType,
|
|
|
|
'resolve' => function($category, $args, $context, ResolveInfo $info) {
|
|
|
|
$this->path[] = $info->path;
|
|
|
|
|
|
|
|
return new Deferred(function () use ($category) {
|
|
|
|
$this->path[] = 'deferred-for-category-' . $category['id'] . '-topStory';
|
|
|
|
return Utils::find($this->storyDataSource, function($story) use ($category) {
|
|
|
|
return $story['id'] === $category['topStoryId'];
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
]
|
|
|
|
]
|
|
|
|
]);
|
|
|
|
|
|
|
|
$this->queryType = new ObjectType([
|
|
|
|
'name' => 'Query',
|
|
|
|
'fields' => [
|
|
|
|
'topStories' => [
|
|
|
|
'type' => Type::listOf($this->storyType),
|
|
|
|
'resolve' => function($val, $args, $context, ResolveInfo $info) {
|
|
|
|
$this->path[] = $info->path;
|
|
|
|
return Utils::filter($this->storyDataSource, function($story) {
|
|
|
|
return $story['id'] % 2 === 1;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
],
|
|
|
|
'featuredCategory' => [
|
|
|
|
'type' => $this->categoryType,
|
|
|
|
'resolve' => function($val, $args, $context, ResolveInfo $info) {
|
|
|
|
$this->path[] = $info->path;
|
|
|
|
return $this->categoryDataSource[0];
|
|
|
|
}
|
|
|
|
],
|
|
|
|
'categories' => [
|
|
|
|
'type' => Type::listOf($this->categoryType),
|
|
|
|
'resolve' => function($val, $args, $context, ResolveInfo $info) {
|
|
|
|
$this->path[] = $info->path;
|
|
|
|
return $this->categoryDataSource;
|
|
|
|
}
|
|
|
|
]
|
|
|
|
]
|
|
|
|
]);
|
|
|
|
|
|
|
|
parent::setUp();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testDeferredFields()
|
|
|
|
{
|
|
|
|
$query = Parser::parse('
|
|
|
|
{
|
|
|
|
topStories {
|
|
|
|
title
|
|
|
|
author {
|
|
|
|
name
|
|
|
|
}
|
|
|
|
}
|
|
|
|
featuredCategory {
|
|
|
|
stories {
|
|
|
|
title
|
|
|
|
author {
|
|
|
|
name
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
');
|
|
|
|
|
|
|
|
$schema = new Schema([
|
|
|
|
'query' => $this->queryType
|
|
|
|
]);
|
|
|
|
|
|
|
|
$expected = [
|
|
|
|
'data' => [
|
|
|
|
'topStories' => [
|
|
|
|
['title' => 'Story #1', 'author' => ['name' => 'John']],
|
|
|
|
['title' => 'Story #3', 'author' => ['name' => 'Joe']],
|
|
|
|
['title' => 'Story #5', 'author' => ['name' => 'John']],
|
|
|
|
['title' => 'Story #7', 'author' => ['name' => 'Joe']],
|
|
|
|
['title' => 'Story #9', 'author' => ['name' => 'Jane']],
|
|
|
|
],
|
|
|
|
'featuredCategory' => [
|
|
|
|
'stories' => [
|
|
|
|
['title' => 'Story #2', 'author' => ['name' => 'Jane']],
|
|
|
|
['title' => 'Story #4', 'author' => ['name' => 'Joe']],
|
|
|
|
['title' => 'Story #6', 'author' => ['name' => 'Jane']],
|
|
|
|
['title' => 'Story #8', 'author' => ['name' => 'John']],
|
|
|
|
]
|
|
|
|
]
|
|
|
|
]
|
|
|
|
];
|
|
|
|
|
|
|
|
$result = Executor::execute($schema, $query);
|
|
|
|
$this->assertEquals($expected, $result->toArray());
|
|
|
|
|
|
|
|
$expectedPath = [
|
|
|
|
['topStories'],
|
|
|
|
['topStories', 0, 'title'],
|
|
|
|
['topStories', 0, 'author'],
|
|
|
|
['topStories', 1, 'title'],
|
|
|
|
['topStories', 1, 'author'],
|
|
|
|
['topStories', 2, 'title'],
|
|
|
|
['topStories', 2, 'author'],
|
|
|
|
['topStories', 3, 'title'],
|
|
|
|
['topStories', 3, 'author'],
|
|
|
|
['topStories', 4, 'title'],
|
|
|
|
['topStories', 4, 'author'],
|
|
|
|
['featuredCategory'],
|
|
|
|
['featuredCategory', 'stories'],
|
|
|
|
['featuredCategory', 'stories', 0, 'title'],
|
|
|
|
['featuredCategory', 'stories', 0, 'author'],
|
|
|
|
['featuredCategory', 'stories', 1, 'title'],
|
|
|
|
['featuredCategory', 'stories', 1, 'author'],
|
|
|
|
['featuredCategory', 'stories', 2, 'title'],
|
|
|
|
['featuredCategory', 'stories', 2, 'author'],
|
|
|
|
['featuredCategory', 'stories', 3, 'title'],
|
|
|
|
['featuredCategory', 'stories', 3, 'author'],
|
|
|
|
'deferred-for-story-1-author',
|
|
|
|
'deferred-for-story-3-author',
|
|
|
|
'deferred-for-story-5-author',
|
|
|
|
'deferred-for-story-7-author',
|
|
|
|
'deferred-for-story-9-author',
|
|
|
|
'deferred-for-story-2-author',
|
|
|
|
'deferred-for-story-4-author',
|
|
|
|
'deferred-for-story-6-author',
|
|
|
|
'deferred-for-story-8-author',
|
|
|
|
['topStories', 0, 'author', 'name'],
|
|
|
|
['topStories', 1, 'author', 'name'],
|
|
|
|
['topStories', 2, 'author', 'name'],
|
|
|
|
['topStories', 3, 'author', 'name'],
|
|
|
|
['topStories', 4, 'author', 'name'],
|
|
|
|
['featuredCategory', 'stories', 0, 'author', 'name'],
|
|
|
|
['featuredCategory', 'stories', 1, 'author', 'name'],
|
|
|
|
['featuredCategory', 'stories', 2, 'author', 'name'],
|
|
|
|
['featuredCategory', 'stories', 3, 'author', 'name'],
|
|
|
|
];
|
|
|
|
$this->assertEquals($expectedPath, $this->path);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testNestedDeferredFields()
|
|
|
|
{
|
|
|
|
$query = Parser::parse('
|
|
|
|
{
|
|
|
|
categories {
|
|
|
|
name
|
|
|
|
topStory {
|
|
|
|
title
|
|
|
|
author {
|
|
|
|
name
|
|
|
|
bestFriend {
|
|
|
|
name
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
');
|
|
|
|
|
|
|
|
$schema = new Schema([
|
|
|
|
'query' => $this->queryType
|
|
|
|
]);
|
|
|
|
|
|
|
|
$author1 = ['name' => 'John', 'bestFriend' => ['name' => 'Dirk']];
|
|
|
|
$author2 = ['name' => 'Jane', 'bestFriend' => ['name' => 'Joe']];
|
|
|
|
$author3 = ['name' => 'Joe', 'bestFriend' => ['name' => 'Jane']];
|
|
|
|
$author4 = ['name' => 'Dirk', 'bestFriend' => ['name' => 'John']];
|
|
|
|
|
|
|
|
$expected = [
|
|
|
|
'data' => [
|
|
|
|
'categories' => [
|
|
|
|
['name' => 'Category #1', 'topStory' => ['title' => 'Story #8', 'author' => $author1]],
|
|
|
|
['name' => 'Category #2', 'topStory' => ['title' => 'Story #3', 'author' => $author3]],
|
|
|
|
['name' => 'Category #3', 'topStory' => ['title' => 'Story #9', 'author' => $author2]],
|
|
|
|
]
|
|
|
|
]
|
|
|
|
];
|
|
|
|
|
|
|
|
$result = Executor::execute($schema, $query);
|
|
|
|
$this->assertEquals($expected, $result->toArray());
|
|
|
|
|
|
|
|
$expectedPath = [
|
|
|
|
['categories'],
|
|
|
|
['categories', 0, 'name'],
|
|
|
|
['categories', 0, 'topStory'],
|
|
|
|
['categories', 1, 'name'],
|
|
|
|
['categories', 1, 'topStory'],
|
|
|
|
['categories', 2, 'name'],
|
|
|
|
['categories', 2, 'topStory'],
|
|
|
|
'deferred-for-category-1-topStory',
|
|
|
|
'deferred-for-category-2-topStory',
|
|
|
|
'deferred-for-category-3-topStory',
|
|
|
|
['categories', 0, 'topStory', 'title'],
|
|
|
|
['categories', 0, 'topStory', 'author'],
|
|
|
|
['categories', 1, 'topStory', 'title'],
|
|
|
|
['categories', 1, 'topStory', 'author'],
|
|
|
|
['categories', 2, 'topStory', 'title'],
|
|
|
|
['categories', 2, 'topStory', 'author'],
|
|
|
|
'deferred-for-story-8-author',
|
|
|
|
'deferred-for-story-3-author',
|
|
|
|
'deferred-for-story-9-author',
|
|
|
|
['categories', 0, 'topStory', 'author', 'name'],
|
|
|
|
['categories', 0, 'topStory', 'author', 'bestFriend'],
|
|
|
|
['categories', 1, 'topStory', 'author', 'name'],
|
|
|
|
['categories', 1, 'topStory', 'author', 'bestFriend'],
|
|
|
|
['categories', 2, 'topStory', 'author', 'name'],
|
|
|
|
['categories', 2, 'topStory', 'author', 'bestFriend'],
|
|
|
|
'deferred-for-best-friend-of-1',
|
|
|
|
'deferred-for-best-friend-of-3',
|
|
|
|
'deferred-for-best-friend-of-2',
|
|
|
|
['categories', 0, 'topStory', 'author', 'bestFriend', 'name'],
|
|
|
|
['categories', 1, 'topStory', 'author', 'bestFriend', 'name'],
|
|
|
|
['categories', 2, 'topStory', 'author', 'bestFriend', 'name'],
|
|
|
|
];
|
|
|
|
$this->assertEquals($expectedPath, $this->path);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testComplexRecursiveDeferredFields()
|
|
|
|
{
|
|
|
|
$complexType = new ObjectType([
|
|
|
|
'name' => 'ComplexType',
|
|
|
|
'fields' => function() use (&$complexType) {
|
|
|
|
return [
|
|
|
|
'sync' => [
|
|
|
|
'type' => Type::string(),
|
|
|
|
'resolve' => function($v, $a, $c, ResolveInfo $info) {
|
|
|
|
$this->path[] = $info->path;
|
|
|
|
return 'sync';
|
|
|
|
}
|
|
|
|
],
|
|
|
|
'deferred' => [
|
|
|
|
'type' => Type::string(),
|
|
|
|
'resolve' => function($v, $a, $c, ResolveInfo $info) {
|
|
|
|
$this->path[] = $info->path;
|
|
|
|
|
|
|
|
return new Deferred(function() use ($info) {
|
|
|
|
$this->path[] = ['!dfd for: ', $info->path];
|
|
|
|
return 'deferred';
|
|
|
|
});
|
|
|
|
}
|
|
|
|
],
|
|
|
|
'nest' => [
|
|
|
|
'type' => $complexType,
|
|
|
|
'resolve' => function($v, $a, $c, ResolveInfo $info) {
|
|
|
|
$this->path[] = $info->path;
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
],
|
|
|
|
'deferredNest' => [
|
|
|
|
'type' => $complexType,
|
|
|
|
'resolve' => function($v, $a, $c, ResolveInfo $info) {
|
|
|
|
$this->path[] = $info->path;
|
|
|
|
|
|
|
|
return new Deferred(function() use ($info) {
|
|
|
|
$this->path[] = ['!dfd nest for: ', $info->path];
|
|
|
|
return [];
|
|
|
|
});
|
|
|
|
}
|
|
|
|
]
|
|
|
|
];
|
|
|
|
}
|
|
|
|
]);
|
|
|
|
|
|
|
|
$schema = new Schema([
|
|
|
|
'query' => $complexType
|
|
|
|
]);
|
|
|
|
|
|
|
|
$query = Parser::parse('
|
|
|
|
{
|
|
|
|
nest {
|
|
|
|
sync
|
|
|
|
deferred
|
|
|
|
nest {
|
|
|
|
sync
|
|
|
|
deferred
|
|
|
|
}
|
|
|
|
deferredNest {
|
|
|
|
sync
|
|
|
|
deferred
|
|
|
|
}
|
|
|
|
}
|
|
|
|
deferredNest {
|
|
|
|
sync
|
|
|
|
deferred
|
|
|
|
nest {
|
|
|
|
sync
|
|
|
|
deferred
|
|
|
|
}
|
|
|
|
deferredNest {
|
|
|
|
sync
|
|
|
|
deferred
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
');
|
|
|
|
$result = Executor::execute($schema, $query);
|
|
|
|
$expected = [
|
|
|
|
'data' => [
|
|
|
|
'nest' => [
|
|
|
|
'sync' => 'sync',
|
|
|
|
'deferred' => 'deferred',
|
|
|
|
'nest' => [
|
|
|
|
'sync' => 'sync',
|
|
|
|
'deferred' => 'deferred'
|
|
|
|
],
|
|
|
|
'deferredNest' => [
|
|
|
|
'sync' => 'sync',
|
|
|
|
'deferred' => 'deferred'
|
|
|
|
]
|
|
|
|
],
|
|
|
|
'deferredNest' => [
|
|
|
|
'sync' => 'sync',
|
|
|
|
'deferred' => 'deferred',
|
|
|
|
'nest' => [
|
|
|
|
'sync' => 'sync',
|
|
|
|
'deferred' => 'deferred'
|
|
|
|
],
|
|
|
|
'deferredNest' => [
|
|
|
|
'sync' => 'sync',
|
|
|
|
'deferred' => 'deferred'
|
|
|
|
]
|
|
|
|
]
|
|
|
|
]
|
|
|
|
];
|
|
|
|
|
|
|
|
$this->assertEquals($expected, $result->toArray());
|
|
|
|
|
|
|
|
$expectedPath = [
|
|
|
|
['nest'],
|
|
|
|
['nest', 'sync'],
|
|
|
|
['nest', 'deferred'],
|
|
|
|
['nest', 'nest'],
|
|
|
|
['nest', 'nest', 'sync'],
|
|
|
|
['nest', 'nest', 'deferred'],
|
|
|
|
['nest', 'deferredNest'],
|
|
|
|
['deferredNest'],
|
|
|
|
|
|
|
|
['!dfd for: ', ['nest', 'deferred']],
|
|
|
|
['!dfd for: ', ['nest', 'nest', 'deferred']],
|
|
|
|
['!dfd nest for: ', ['nest', 'deferredNest']],
|
|
|
|
['!dfd nest for: ', ['deferredNest']],
|
|
|
|
|
|
|
|
['nest', 'deferredNest', 'sync'],
|
|
|
|
['nest', 'deferredNest', 'deferred'],
|
|
|
|
['deferredNest', 'sync'],
|
|
|
|
['deferredNest', 'deferred'],
|
|
|
|
['deferredNest', 'nest'],
|
|
|
|
['deferredNest', 'nest', 'sync'],
|
|
|
|
['deferredNest', 'nest', 'deferred'],
|
|
|
|
['deferredNest', 'deferredNest'],
|
|
|
|
|
|
|
|
['!dfd for: ', ['nest', 'deferredNest', 'deferred']],
|
|
|
|
['!dfd for: ', ['deferredNest', 'deferred']],
|
|
|
|
['!dfd for: ', ['deferredNest', 'nest', 'deferred']],
|
|
|
|
['!dfd nest for: ', ['deferredNest', 'deferredNest']],
|
|
|
|
|
|
|
|
['deferredNest', 'deferredNest', 'sync'],
|
|
|
|
['deferredNest', 'deferredNest', 'deferred'],
|
|
|
|
['!dfd for: ', ['deferredNest', 'deferredNest', 'deferred']],
|
|
|
|
];
|
|
|
|
|
|
|
|
$this->assertEquals($expectedPath, $this->path);
|
|
|
|
}
|
|
|
|
}
|