Separate utility for extractTypes

This commit is contained in:
Vladimir Razuvaev 2017-02-24 16:22:33 +07:00
parent dd4acfd3b6
commit 3f909e3e11
3 changed files with 403 additions and 48 deletions

View File

@ -36,10 +36,11 @@ class EagerResolution implements Resolution
*/
public function __construct(array $initialTypes)
{
$typeMap = [];
foreach ($initialTypes as $type) {
$this->extractTypes($type);
$typeMap = Utils\TypeInfo::extractTypes($type, $typeMap);
}
$this->typeMap += Type::getInternalTypes();
$this->typeMap = $typeMap + Type::getInternalTypes();
// Keep track of all possible types for abstract types
foreach ($this->typeMap as $typeName => $type) {
@ -112,50 +113,4 @@ class EagerResolution implements Resolution
'possibleTypeMap' => $possibleTypesMap
];
}
/**
* @param $type
* @return array
*/
private function extractTypes($type)
{
if (!$type) {
return $this->typeMap;
}
if ($type instanceof WrappingType) {
return $this->extractTypes($type->getWrappedType(true));
}
if (!empty($this->typeMap[$type->name])) {
Utils::invariant(
$this->typeMap[$type->name] === $type,
"Schema must contain unique named types but contains multiple types named \"$type\"."
);
return $this->typeMap;
}
$this->typeMap[$type->name] = $type;
$nestedTypes = [];
if ($type instanceof UnionType) {
$nestedTypes = $type->getTypes();
}
if ($type instanceof ObjectType) {
$nestedTypes = array_merge($nestedTypes, $type->getInterfaces());
}
if ($type instanceof ObjectType || $type instanceof InterfaceType || $type instanceof InputObjectType) {
foreach ((array) $type->getFields() as $fieldName => $field) {
if (isset($field->args)) {
$fieldArgTypes = array_map(function(FieldArgument $arg) { return $arg->getType(); }, $field->args);
$nestedTypes = array_merge($nestedTypes, $fieldArgTypes);
}
$nestedTypes[] = $field->getType();
}
}
foreach ($nestedTypes as $type) {
$this->extractTypes($type);
}
return $this->typeMap;
}
}

View File

@ -20,6 +20,8 @@ use GraphQL\Type\Definition\ListOfType;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Definition\WrappingType;
use GraphQL\Type\Introspection;
use GraphQL\Utils;
@ -162,6 +164,66 @@ class TypeInfo
return $schema->getType($inputTypeNode->name->value);
}
/**
* Given root type scans through all fields to find nested types. Returns array where keys are for type name
* and value contains corresponding type instance.
*
* Example output:
* [
* 'String' => $instanceOfStringType,
* 'MyType' => $instanceOfMyType,
* ...
* ]
*
* @param Type $type
* @param array|null $typeMap
* @return array
*/
public static function extractTypes($type, array $typeMap = null)
{
if (!$typeMap) {
$typeMap = [];
}
if (!$type) {
return $typeMap;
}
if ($type instanceof WrappingType) {
return self::extractTypes($type->getWrappedType(true), $typeMap);
}
if (!empty($typeMap[$type->name])) {
Utils::invariant(
$typeMap[$type->name] === $type,
"Schema must contain unique named types but contains multiple types named \"$type\"."
);
return $typeMap;
}
$typeMap[$type->name] = $type;
$nestedTypes = [];
if ($type instanceof UnionType) {
$nestedTypes = $type->getTypes();
}
if ($type instanceof ObjectType) {
$nestedTypes = array_merge($nestedTypes, $type->getInterfaces());
}
if ($type instanceof ObjectType || $type instanceof InterfaceType || $type instanceof InputObjectType) {
foreach ((array) $type->getFields() as $fieldName => $field) {
if (isset($field->args)) {
$fieldArgTypes = array_map(function(FieldArgument $arg) { return $arg->getType(); }, $field->args);
$nestedTypes = array_merge($nestedTypes, $fieldArgTypes);
}
$nestedTypes[] = $field->getType();
}
}
foreach ($nestedTypes as $type) {
$typeMap = self::extractTypes($type, $typeMap);
}
return $typeMap;
}
/**
* Not exactly the same as the executor's definition of getFieldDef, in this
* statically evaluated environment we do not always have an Object type,

View File

@ -0,0 +1,338 @@
<?php
namespace Utils;
use GraphQL\Type\Definition\Config;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType;
use GraphQL\Utils\TypeInfo;
class ExtractTypesTest extends \PHPUnit_Framework_TestCase
{
/**
* @var ObjectType
*/
private $query;
/**
* @var ObjectType
*/
private $mutation;
/**
* @var InterfaceType
*/
private $node;
/**
* @var InterfaceType
*/
private $content;
/**
* @var ObjectType
*/
private $blogStory;
/**
* @var ObjectType
*/
private $link;
/**
* @var ObjectType
*/
private $video;
/**
* @var ObjectType
*/
private $videoMetadata;
/**
* @var ObjectType
*/
private $comment;
/**
* @var ObjectType
*/
private $user;
/**
* @var ObjectType
*/
private $category;
/**
* @var UnionType
*/
private $mention;
private $postStoryMutation;
private $postStoryMutationInput;
private $postCommentMutation;
private $postCommentMutationInput;
public function setUp()
{
Config::enableValidation(false);
$this->node = new InterfaceType([
'name' => 'Node',
'fields' => [
'id' => Type::string()
]
]);
$this->content = new InterfaceType([
'name' => 'Content',
'fields' => function() {
return [
'title' => Type::string(),
'body' => Type::string(),
'author' => $this->user,
'comments' => Type::listOf($this->comment),
'categories' => Type::listOf($this->category)
];
}
]);
$this->blogStory = new ObjectType([
'name' => 'BlogStory',
'interfaces' => [
$this->node,
$this->content
],
'fields' => function() {
return [
$this->node->getField('id'),
$this->content->getField('title'),
$this->content->getField('body'),
$this->content->getField('author'),
$this->content->getField('comments'),
$this->content->getField('categories')
];
},
]);
$this->link = new ObjectType([
'name' => 'Link',
'interfaces' => [
$this->node,
$this->content
],
'fields' => function() {
return [
$this->node->getField('id'),
$this->content->getField('title'),
$this->content->getField('body'),
$this->content->getField('author'),
$this->content->getField('comments'),
$this->content->getField('categories'),
'url' => Type::string()
];
},
]);
$this->video = new ObjectType([
'name' => 'Video',
'interfaces' => [
$this->node,
$this->content
],
'fields' => function() {
return [
$this->node->getField('id'),
$this->content->getField('title'),
$this->content->getField('body'),
$this->content->getField('author'),
$this->content->getField('comments'),
$this->content->getField('categories'),
'streamUrl' => Type::string(),
'downloadUrl' => Type::string(),
'metadata' => $this->videoMetadata = new ObjectType([
'name' => 'VideoMetadata',
'fields' => [
'lat' => Type::float(),
'lng' => Type::float()
]
])
];
}
]);
$this->comment = new ObjectType([
'name' => 'Comment',
'interfaces' => [
$this->node
],
'fields' => function() {
return [
$this->node->getField('id'),
'author' => $this->user,
'text' => Type::string(),
'replies' => Type::listOf($this->comment),
'parent' => $this->comment,
'content' => $this->content
];
}
]);
$this->user = new ObjectType([
'name' => 'User',
'interfaces' => [
$this->node
],
'fields' => function() {
return [
$this->node->getField('id'),
'name' => Type::string(),
];
}
]);
$this->category = new ObjectType([
'name' => 'Category',
'interfaces' => [
$this->node
],
'fields' => function() {
return [
$this->node->getField('id'),
'name' => Type::string()
];
}
]);
$this->mention = new UnionType([
'name' => 'Mention',
'types' => [
$this->user,
$this->category
]
]);
$this->query = new ObjectType([
'name' => 'Query',
'fields' => [
'viewer' => $this->user,
'latestContent' => $this->content,
'node' => $this->node,
'mentions' => Type::listOf($this->mention)
]
]);
$this->mutation = new ObjectType([
'name' => 'Mutation',
'fields' => [
'postStory' => [
'type' => $this->postStoryMutation = new ObjectType([
'name' => 'PostStoryMutation',
'fields' => [
'story' => $this->blogStory
]
]),
'args' => [
'input' => Type::nonNull($this->postStoryMutationInput = new InputObjectType([
'name' => 'PostStoryMutationInput',
'fields' => [
'title' => Type::string(),
'body' => Type::string(),
'author' => Type::id(),
'category' => Type::id()
]
])),
'clientRequestId' => Type::string()
]
],
'postComment' => [
'type' => $this->postCommentMutation = new ObjectType([
'name' => 'PostCommentMutation',
'fields' => [
'comment' => $this->comment
]
]),
'args' => [
'input' => Type::nonNull($this->postCommentMutationInput = new InputObjectType([
'name' => 'PostCommentMutationInput',
'fields' => [
'text' => Type::nonNull(Type::string()),
'author' => Type::nonNull(Type::id()),
'content' => Type::id(),
'parent' => Type::id()
]
])),
'clientRequestId' => Type::string()
]
]
]
]);
}
public function testExtractTypesFromQuery()
{
$expectedTypeMap = [
'Query' => $this->query,
'User' => $this->user,
'Node' => $this->node,
'String' => Type::string(),
'Content' => $this->content,
'Comment' => $this->comment,
'Mention' => $this->mention,
'Category' => $this->category,
];
$actualTypeMap = TypeInfo::extractTypes($this->query);
$this->assertEquals($expectedTypeMap, $actualTypeMap);
}
public function testExtractTypesFromMutation()
{
$expectedTypeMap = [
'Mutation' => $this->mutation,
'User' => $this->user,
'Node' => $this->node,
'String' => Type::string(),
'Content' => $this->content,
'Comment' => $this->comment,
'BlogStory' => $this->blogStory,
'Category' => $this->category,
'PostStoryMutationInput' => $this->postStoryMutationInput,
'ID' => Type::id(),
'PostStoryMutation' => $this->postStoryMutation,
'PostCommentMutationInput' => $this->postCommentMutationInput,
'PostCommentMutation' => $this->postCommentMutation,
];
$actualTypeMap = TypeInfo::extractTypes($this->mutation);
$this->assertEquals($expectedTypeMap, $actualTypeMap);
}
public function testThrowsOnMultipleTypesWithSameName()
{
$otherUserType = new ObjectType([
'name' => 'User',
'fields' => []
]);
$queryType = new ObjectType([
'name' => 'Test',
'fields' => [
'otherUser' => $otherUserType,
'user' => $this->user
]
]);
$this->setExpectedException(
'\GraphQL\Error\InvariantViolation',
"Schema must contain unique named types but contains multiple types named \"User\""
);
TypeInfo::extractTypes($queryType);
}
}