mirror of
https://github.com/retailcrm/graphql-php.git
synced 2024-11-22 12:56:05 +03:00
Separate utility for extractTypes
This commit is contained in:
parent
dd4acfd3b6
commit
3f909e3e11
@ -36,10 +36,11 @@ class EagerResolution implements Resolution
|
|||||||
*/
|
*/
|
||||||
public function __construct(array $initialTypes)
|
public function __construct(array $initialTypes)
|
||||||
{
|
{
|
||||||
|
$typeMap = [];
|
||||||
foreach ($initialTypes as $type) {
|
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
|
// Keep track of all possible types for abstract types
|
||||||
foreach ($this->typeMap as $typeName => $type) {
|
foreach ($this->typeMap as $typeName => $type) {
|
||||||
@ -112,50 +113,4 @@ class EagerResolution implements Resolution
|
|||||||
'possibleTypeMap' => $possibleTypesMap
|
'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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,8 @@ use GraphQL\Type\Definition\ListOfType;
|
|||||||
use GraphQL\Type\Definition\NonNull;
|
use GraphQL\Type\Definition\NonNull;
|
||||||
use GraphQL\Type\Definition\ObjectType;
|
use GraphQL\Type\Definition\ObjectType;
|
||||||
use GraphQL\Type\Definition\Type;
|
use GraphQL\Type\Definition\Type;
|
||||||
|
use GraphQL\Type\Definition\UnionType;
|
||||||
|
use GraphQL\Type\Definition\WrappingType;
|
||||||
use GraphQL\Type\Introspection;
|
use GraphQL\Type\Introspection;
|
||||||
use GraphQL\Utils;
|
use GraphQL\Utils;
|
||||||
|
|
||||||
@ -162,6 +164,66 @@ class TypeInfo
|
|||||||
return $schema->getType($inputTypeNode->name->value);
|
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
|
* Not exactly the same as the executor's definition of getFieldDef, in this
|
||||||
* statically evaluated environment we do not always have an Object type,
|
* statically evaluated environment we do not always have an Object type,
|
||||||
|
338
tests/Utils/ExtractTypesTest.php
Normal file
338
tests/Utils/ExtractTypesTest.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user