mirror of
https://github.com/retailcrm/graphql-php.git
synced 2025-02-11 10:09:24 +03:00
Remove duplicated code from buildASTSchema and extendSchema
ref: graphql/graphql-js#1000 BREAKING CHANGE: SchemaBuilder::build() and buildAST() and constructor removed the typedecorator, as not needed anymore as library can now resolve union and interfaces from generated schemas.
This commit is contained in:
parent
48c33302a8
commit
2cbccb87db
437
src/Utils/ASTDefinitionBuilder.php
Normal file
437
src/Utils/ASTDefinitionBuilder.php
Normal file
@ -0,0 +1,437 @@
|
|||||||
|
<?php
|
||||||
|
namespace GraphQL\Utils;
|
||||||
|
|
||||||
|
use GraphQL\Error\Error;
|
||||||
|
use GraphQL\Executor\Values;
|
||||||
|
use GraphQL\Language\AST\DirectiveDefinitionNode;
|
||||||
|
use GraphQL\Language\AST\EnumTypeDefinitionNode;
|
||||||
|
use GraphQL\Language\AST\EnumValueDefinitionNode;
|
||||||
|
use GraphQL\Language\AST\FieldDefinitionNode;
|
||||||
|
use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
|
||||||
|
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
|
||||||
|
use GraphQL\Language\AST\ListTypeNode;
|
||||||
|
use GraphQL\Language\AST\NamedTypeNode;
|
||||||
|
use GraphQL\Language\AST\NodeKind;
|
||||||
|
use GraphQL\Language\AST\NonNullTypeNode;
|
||||||
|
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
|
||||||
|
use GraphQL\Language\AST\ScalarTypeDefinitionNode;
|
||||||
|
use GraphQL\Language\AST\TypeNode;
|
||||||
|
use GraphQL\Language\AST\UnionTypeDefinitionNode;
|
||||||
|
use GraphQL\Language\Token;
|
||||||
|
use GraphQL\Type\Definition\CustomScalarType;
|
||||||
|
use GraphQL\Type\Definition\EnumType;
|
||||||
|
use GraphQL\Type\Definition\InputObjectType;
|
||||||
|
use GraphQL\Type\Definition\InputType;
|
||||||
|
use GraphQL\Type\Definition\OutputType;
|
||||||
|
use GraphQL\Type\Definition\Directive;
|
||||||
|
use GraphQL\Type\Definition\InterfaceType;
|
||||||
|
use GraphQL\Type\Definition\FieldArgument;
|
||||||
|
use GraphQL\Type\Definition\NonNull;
|
||||||
|
use GraphQL\Type\Definition\ObjectType;
|
||||||
|
use GraphQL\Type\Definition\Type;
|
||||||
|
use GraphQL\Type\Definition\UnionType;
|
||||||
|
use GraphQL\Type\Introspection;
|
||||||
|
|
||||||
|
class ASTDefinitionBuilder
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $typeDefintionsMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $options;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var callable
|
||||||
|
*/
|
||||||
|
private $resolveType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $cache;
|
||||||
|
|
||||||
|
public function __construct(array $typeDefintionsMap, $options, callable $resolveType)
|
||||||
|
{
|
||||||
|
$this->typeDefintionsMap = $typeDefintionsMap;
|
||||||
|
$this->options = $options;
|
||||||
|
$this->resolveType = $resolveType;
|
||||||
|
|
||||||
|
$this->cache = [
|
||||||
|
'String' => Type::string(),
|
||||||
|
'Int' => Type::int(),
|
||||||
|
'Float' => Type::float(),
|
||||||
|
'Boolean' => Type::boolean(),
|
||||||
|
'ID' => Type::id(),
|
||||||
|
'__Schema' => Introspection::_schema(),
|
||||||
|
'__Directive' => Introspection::_directive(),
|
||||||
|
'__DirectiveLocation' => Introspection::_directiveLocation(),
|
||||||
|
'__Type' => Introspection::_type(),
|
||||||
|
'__Field' => Introspection::_field(),
|
||||||
|
'__InputValue' => Introspection::_inputValue(),
|
||||||
|
'__EnumValue' => Introspection::_enumValue(),
|
||||||
|
'__TypeKind' => Introspection::_typeKind(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Type $innerType
|
||||||
|
* @param TypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode
|
||||||
|
* @return Type
|
||||||
|
*/
|
||||||
|
private function buildWrappedType(Type $innerType, TypeNode $inputTypeNode)
|
||||||
|
{
|
||||||
|
if ($inputTypeNode->kind == NodeKind::LIST_TYPE) {
|
||||||
|
return Type::listOf($this->buildWrappedType($innerType, $inputTypeNode->type));
|
||||||
|
}
|
||||||
|
if ($inputTypeNode->kind == NodeKind::NON_NULL_TYPE) {
|
||||||
|
$wrappedType = $this->buildWrappedType($innerType, $inputTypeNode->type);
|
||||||
|
Utils::invariant(!($wrappedType instanceof NonNull), 'No nesting nonnull.');
|
||||||
|
return Type::nonNull($wrappedType);
|
||||||
|
}
|
||||||
|
return $innerType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param TypeNode|ListTypeNode|NonNullTypeNode $typeNode
|
||||||
|
* @return TypeNode
|
||||||
|
*/
|
||||||
|
private function getNamedTypeNode(TypeNode $typeNode)
|
||||||
|
{
|
||||||
|
$namedType = $typeNode;
|
||||||
|
while ($namedType->kind === NodeKind::LIST_TYPE || $namedType->kind === NodeKind::NON_NULL_TYPE) {
|
||||||
|
$namedType = $namedType->type;
|
||||||
|
}
|
||||||
|
return $namedType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $typeName
|
||||||
|
* @param NamedTypeNode|null $typeNode
|
||||||
|
* @return Type
|
||||||
|
* @throws Error
|
||||||
|
*/
|
||||||
|
private function internalBuildType($typeName, $typeNode = null) {
|
||||||
|
if (!isset($this->cache[$typeName])) {
|
||||||
|
if (isset($this->typeDefintionsMap[$typeName])) {
|
||||||
|
$this->cache[$typeName] = $this->makeSchemaDef($this->typeDefintionsMap[$typeName]);
|
||||||
|
} else {
|
||||||
|
$fn = $this->resolveType;
|
||||||
|
$this->cache[$typeName] = $fn($typeName, $typeNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->cache[$typeName];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|NamedTypeNode $ref
|
||||||
|
* @return Type
|
||||||
|
* @throws Error
|
||||||
|
*/
|
||||||
|
public function buildType($ref)
|
||||||
|
{
|
||||||
|
if (is_string($ref)) {
|
||||||
|
return $this->internalBuildType($ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->internalBuildType($ref->name->value, $ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param TypeNode $typeNode
|
||||||
|
* @return InputType|Type
|
||||||
|
* @throws Error
|
||||||
|
*/
|
||||||
|
public function buildInputType(TypeNode $typeNode)
|
||||||
|
{
|
||||||
|
$type = $this->internalBuildWrappedType($typeNode);
|
||||||
|
Utils::invariant(Type::isInputType($type), 'Expected Input type.');
|
||||||
|
return $type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param TypeNode $typeNode
|
||||||
|
* @return OutputType|Type
|
||||||
|
* @throws Error
|
||||||
|
*/
|
||||||
|
public function buildOutputType(TypeNode $typeNode)
|
||||||
|
{
|
||||||
|
$type = $this->internalBuildWrappedType($typeNode);
|
||||||
|
Utils::invariant(Type::isOutputType($type), 'Expected Output type.');
|
||||||
|
return $type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param TypeNode|string $typeNode
|
||||||
|
* @return ObjectType|Type
|
||||||
|
* @throws Error
|
||||||
|
*/
|
||||||
|
public function buildObjectType($typeNode)
|
||||||
|
{
|
||||||
|
$type = $this->buildType($typeNode);
|
||||||
|
Utils::invariant($type instanceof ObjectType, 'Expected Object type.' . get_class($type));
|
||||||
|
return $type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param TypeNode|string $typeNode
|
||||||
|
* @return InterfaceType|Type
|
||||||
|
* @throws Error
|
||||||
|
*/
|
||||||
|
public function buildInterfaceType($typeNode)
|
||||||
|
{
|
||||||
|
$type = $this->buildType($typeNode);
|
||||||
|
Utils::invariant($type instanceof InterfaceType, 'Expected Interface type.');
|
||||||
|
return $type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param TypeNode $typeNode
|
||||||
|
* @return Type
|
||||||
|
* @throws Error
|
||||||
|
*/
|
||||||
|
private function internalBuildWrappedType(TypeNode $typeNode)
|
||||||
|
{
|
||||||
|
$typeDef = $this->buildType($this->getNamedTypeNode($typeNode));
|
||||||
|
return $this->buildWrappedType($typeDef, $typeNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildDirective(DirectiveDefinitionNode $directiveNode)
|
||||||
|
{
|
||||||
|
return new Directive([
|
||||||
|
'name' => $directiveNode->name->value,
|
||||||
|
'description' => $this->getDescription($directiveNode),
|
||||||
|
'locations' => Utils::map($directiveNode->locations, function ($node) {
|
||||||
|
return $node->value;
|
||||||
|
}),
|
||||||
|
'args' => $directiveNode->arguments ? FieldArgument::createMap($this->makeInputValues($directiveNode->arguments)) : null,
|
||||||
|
'astNode' => $directiveNode,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildField(FieldDefinitionNode $field)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'type' => $this->buildOutputType($field->type),
|
||||||
|
'description' => $this->getDescription($field),
|
||||||
|
'args' => $this->makeInputValues($field->arguments),
|
||||||
|
'deprecationReason' => $this->getDeprecationReason($field),
|
||||||
|
'astNode' => $field
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function makeSchemaDef($def)
|
||||||
|
{
|
||||||
|
if (!$def) {
|
||||||
|
throw new Error('def must be defined.');
|
||||||
|
}
|
||||||
|
switch ($def->kind) {
|
||||||
|
case NodeKind::OBJECT_TYPE_DEFINITION:
|
||||||
|
return $this->makeTypeDef($def);
|
||||||
|
case NodeKind::INTERFACE_TYPE_DEFINITION:
|
||||||
|
return $this->makeInterfaceDef($def);
|
||||||
|
case NodeKind::ENUM_TYPE_DEFINITION:
|
||||||
|
return $this->makeEnumDef($def);
|
||||||
|
case NodeKind::UNION_TYPE_DEFINITION:
|
||||||
|
return $this->makeUnionDef($def);
|
||||||
|
case NodeKind::SCALAR_TYPE_DEFINITION:
|
||||||
|
return $this->makeScalarDef($def);
|
||||||
|
case NodeKind::INPUT_OBJECT_TYPE_DEFINITION:
|
||||||
|
return $this->makeInputObjectDef($def);
|
||||||
|
default:
|
||||||
|
throw new Error("Type kind of {$def->kind} not supported.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function makeTypeDef(ObjectTypeDefinitionNode $def)
|
||||||
|
{
|
||||||
|
$typeName = $def->name->value;
|
||||||
|
return new ObjectType([
|
||||||
|
'name' => $typeName,
|
||||||
|
'description' => $this->getDescription($def),
|
||||||
|
'fields' => function () use ($def) {
|
||||||
|
return $this->makeFieldDefMap($def);
|
||||||
|
},
|
||||||
|
'interfaces' => function () use ($def) {
|
||||||
|
return $this->makeImplementedInterfaces($def);
|
||||||
|
},
|
||||||
|
'astNode' => $def
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function makeFieldDefMap($def)
|
||||||
|
{
|
||||||
|
return Utils::keyValMap(
|
||||||
|
$def->fields,
|
||||||
|
function ($field) {
|
||||||
|
return $field->name->value;
|
||||||
|
},
|
||||||
|
function ($field) {
|
||||||
|
return $this->buildField($field);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function makeImplementedInterfaces(ObjectTypeDefinitionNode $def)
|
||||||
|
{
|
||||||
|
if (isset($def->interfaces)) {
|
||||||
|
return Utils::map($def->interfaces, function ($iface) {
|
||||||
|
return $this->buildInterfaceType($iface);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function makeInputValues($values)
|
||||||
|
{
|
||||||
|
return Utils::keyValMap(
|
||||||
|
$values,
|
||||||
|
function ($value) {
|
||||||
|
return $value->name->value;
|
||||||
|
},
|
||||||
|
function ($value) {
|
||||||
|
$type = $this->buildInputType($value->type);
|
||||||
|
$config = [
|
||||||
|
'name' => $value->name->value,
|
||||||
|
'type' => $type,
|
||||||
|
'description' => $this->getDescription($value),
|
||||||
|
'astNode' => $value
|
||||||
|
];
|
||||||
|
if (isset($value->defaultValue)) {
|
||||||
|
$config['defaultValue'] = AST::valueFromAST($value->defaultValue, $type);
|
||||||
|
}
|
||||||
|
return $config;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function makeInterfaceDef(InterfaceTypeDefinitionNode $def)
|
||||||
|
{
|
||||||
|
$typeName = $def->name->value;
|
||||||
|
return new InterfaceType([
|
||||||
|
'name' => $typeName,
|
||||||
|
'description' => $this->getDescription($def),
|
||||||
|
'fields' => function () use ($def) {
|
||||||
|
return $this->makeFieldDefMap($def);
|
||||||
|
},
|
||||||
|
'astNode' => $def
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function makeEnumDef(EnumTypeDefinitionNode $def)
|
||||||
|
{
|
||||||
|
return new EnumType([
|
||||||
|
'name' => $def->name->value,
|
||||||
|
'description' => $this->getDescription($def),
|
||||||
|
'astNode' => $def,
|
||||||
|
'values' => Utils::keyValMap(
|
||||||
|
$def->values,
|
||||||
|
function ($enumValue) {
|
||||||
|
return $enumValue->name->value;
|
||||||
|
},
|
||||||
|
function ($enumValue) {
|
||||||
|
return [
|
||||||
|
'description' => $this->getDescription($enumValue),
|
||||||
|
'deprecationReason' => $this->getDeprecationReason($enumValue),
|
||||||
|
'astNode' => $enumValue
|
||||||
|
];
|
||||||
|
}
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function makeUnionDef(UnionTypeDefinitionNode $def)
|
||||||
|
{
|
||||||
|
return new UnionType([
|
||||||
|
'name' => $def->name->value,
|
||||||
|
'description' => $this->getDescription($def),
|
||||||
|
'types' => Utils::map($def->types, function ($typeNode) {
|
||||||
|
return $this->buildObjectType($typeNode);
|
||||||
|
}),
|
||||||
|
'astNode' => $def
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function makeScalarDef(ScalarTypeDefinitionNode $def)
|
||||||
|
{
|
||||||
|
return new CustomScalarType([
|
||||||
|
'name' => $def->name->value,
|
||||||
|
'description' => $this->getDescription($def),
|
||||||
|
'astNode' => $def,
|
||||||
|
'serialize' => function($value) {
|
||||||
|
return $value;
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function makeInputObjectDef(InputObjectTypeDefinitionNode $def)
|
||||||
|
{
|
||||||
|
return new InputObjectType([
|
||||||
|
'name' => $def->name->value,
|
||||||
|
'description' => $this->getDescription($def),
|
||||||
|
'fields' => function () use ($def) {
|
||||||
|
return $this->makeInputValues($def->fields);
|
||||||
|
},
|
||||||
|
'astNode' => $def,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a collection of directives, returns the string value for the
|
||||||
|
* deprecation reason.
|
||||||
|
*
|
||||||
|
* @param EnumValueDefinitionNode | FieldDefinitionNode $node
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function getDeprecationReason($node)
|
||||||
|
{
|
||||||
|
$deprecated = Values::getDirectiveValues(Directive::deprecatedDirective(), $node);
|
||||||
|
return isset($deprecated['reason']) ? $deprecated['reason'] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an ast node, returns its string description.
|
||||||
|
*/
|
||||||
|
private function getDescription($node)
|
||||||
|
{
|
||||||
|
if ($node->description) {
|
||||||
|
return $node->description->value;
|
||||||
|
}
|
||||||
|
if (isset($this->options['commentDescriptions'])) {
|
||||||
|
$rawValue = $this->getLeadingCommentBlock($node);
|
||||||
|
if ($rawValue !== null) {
|
||||||
|
return BlockString::value("\n" . $rawValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getLeadingCommentBlock($node)
|
||||||
|
{
|
||||||
|
$loc = $node->loc;
|
||||||
|
if (!$loc || !$loc->startToken) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$comments = [];
|
||||||
|
$token = $loc->startToken->prev;
|
||||||
|
while (
|
||||||
|
$token &&
|
||||||
|
$token->kind === Token::COMMENT &&
|
||||||
|
$token->next && $token->prev &&
|
||||||
|
$token->line + 1 === $token->next->line &&
|
||||||
|
$token->line !== $token->prev->line
|
||||||
|
) {
|
||||||
|
$value = $token->value;
|
||||||
|
$comments[] = $value;
|
||||||
|
$token = $token->prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode("\n", array_reverse($comments));
|
||||||
|
}
|
||||||
|
}
|
@ -2,35 +2,13 @@
|
|||||||
namespace GraphQL\Utils;
|
namespace GraphQL\Utils;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
use GraphQL\Executor\Values;
|
|
||||||
use GraphQL\Language\AST\DirectiveDefinitionNode;
|
|
||||||
use GraphQL\Language\AST\DocumentNode;
|
use GraphQL\Language\AST\DocumentNode;
|
||||||
use GraphQL\Language\AST\EnumTypeDefinitionNode;
|
|
||||||
use GraphQL\Language\AST\EnumValueDefinitionNode;
|
|
||||||
use GraphQL\Language\AST\FieldDefinitionNode;
|
|
||||||
use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
|
|
||||||
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
|
|
||||||
use GraphQL\Language\AST\NodeKind;
|
use GraphQL\Language\AST\NodeKind;
|
||||||
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
|
|
||||||
use GraphQL\Language\AST\ScalarTypeDefinitionNode;
|
|
||||||
use GraphQL\Language\AST\SchemaDefinitionNode;
|
use GraphQL\Language\AST\SchemaDefinitionNode;
|
||||||
use GraphQL\Language\AST\TypeNode;
|
|
||||||
use GraphQL\Language\AST\UnionTypeDefinitionNode;
|
|
||||||
use GraphQL\Language\Parser;
|
use GraphQL\Language\Parser;
|
||||||
use GraphQL\Language\Source;
|
use GraphQL\Language\Source;
|
||||||
use GraphQL\Language\Token;
|
|
||||||
use GraphQL\Type\Schema;
|
use GraphQL\Type\Schema;
|
||||||
use GraphQL\Type\Definition\Directive;
|
use GraphQL\Type\Definition\Directive;
|
||||||
use GraphQL\Type\Definition\EnumType;
|
|
||||||
use GraphQL\Type\Definition\InputObjectType;
|
|
||||||
use GraphQL\Type\Definition\InterfaceType;
|
|
||||||
use GraphQL\Type\Definition\FieldArgument;
|
|
||||||
use GraphQL\Type\Definition\NonNull;
|
|
||||||
use GraphQL\Type\Definition\ObjectType;
|
|
||||||
use GraphQL\Type\Definition\CustomScalarType;
|
|
||||||
use GraphQL\Type\Definition\Type;
|
|
||||||
use GraphQL\Type\Definition\UnionType;
|
|
||||||
use GraphQL\Type\Introspection;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build instance of `GraphQL\Type\Schema` out of type language definition (string or parsed AST)
|
* Build instance of `GraphQL\Type\Schema` out of type language definition (string or parsed AST)
|
||||||
@ -38,33 +16,6 @@ use GraphQL\Type\Introspection;
|
|||||||
*/
|
*/
|
||||||
class BuildSchema
|
class BuildSchema
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @param Type $innerType
|
|
||||||
* @param TypeNode $inputTypeNode
|
|
||||||
* @return Type
|
|
||||||
*/
|
|
||||||
private function buildWrappedType(Type $innerType, TypeNode $inputTypeNode)
|
|
||||||
{
|
|
||||||
if ($inputTypeNode->kind == NodeKind::LIST_TYPE) {
|
|
||||||
return Type::listOf($this->buildWrappedType($innerType, $inputTypeNode->type));
|
|
||||||
}
|
|
||||||
if ($inputTypeNode->kind == NodeKind::NON_NULL_TYPE) {
|
|
||||||
$wrappedType = $this->buildWrappedType($innerType, $inputTypeNode->type);
|
|
||||||
Utils::invariant(!($wrappedType instanceof NonNull), 'No nesting nonnull.');
|
|
||||||
return Type::nonNull($wrappedType);
|
|
||||||
}
|
|
||||||
return $innerType;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getNamedTypeNode(TypeNode $typeNode)
|
|
||||||
{
|
|
||||||
$namedType = $typeNode;
|
|
||||||
while ($namedType->kind === NodeKind::LIST_TYPE || $namedType->kind === NodeKind::NON_NULL_TYPE) {
|
|
||||||
$namedType = $namedType->type;
|
|
||||||
}
|
|
||||||
return $namedType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This takes the ast of a schema document produced by the parse function in
|
* This takes the ast of a schema document produced by the parse function in
|
||||||
* GraphQL\Language\Parser.
|
* GraphQL\Language\Parser.
|
||||||
@ -75,7 +26,7 @@ class BuildSchema
|
|||||||
* Given that AST it constructs a GraphQL\Type\Schema. The resulting schema
|
* Given that AST it constructs a GraphQL\Type\Schema. The resulting schema
|
||||||
* has no resolve methods, so execution will use default resolvers.
|
* has no resolve methods, so execution will use default resolvers.
|
||||||
*
|
*
|
||||||
* Accepts options as a third argument:
|
* Accepts options as a second argument:
|
||||||
*
|
*
|
||||||
* - commentDescriptions:
|
* - commentDescriptions:
|
||||||
* Provide true to use preceding comments as the description.
|
* Provide true to use preceding comments as the description.
|
||||||
@ -83,27 +34,24 @@ class BuildSchema
|
|||||||
*
|
*
|
||||||
* @api
|
* @api
|
||||||
* @param DocumentNode $ast
|
* @param DocumentNode $ast
|
||||||
* @param callable $typeConfigDecorator
|
* @param array $options
|
||||||
* @return Schema
|
* @return Schema
|
||||||
* @throws Error
|
* @throws Error
|
||||||
*/
|
*/
|
||||||
public static function buildAST(DocumentNode $ast, callable $typeConfigDecorator = null, array $options = [])
|
public static function buildAST(DocumentNode $ast, array $options = [])
|
||||||
{
|
{
|
||||||
$builder = new self($ast, $typeConfigDecorator, $options);
|
$builder = new self($ast, $options);
|
||||||
return $builder->buildSchema();
|
return $builder->buildSchema();
|
||||||
}
|
}
|
||||||
|
|
||||||
private $ast;
|
private $ast;
|
||||||
private $innerTypeMap;
|
|
||||||
private $nodeMap;
|
private $nodeMap;
|
||||||
private $typeConfigDecorator;
|
|
||||||
private $loadedTypeDefs;
|
private $loadedTypeDefs;
|
||||||
private $options;
|
private $options;
|
||||||
|
|
||||||
public function __construct(DocumentNode $ast, callable $typeConfigDecorator = null, array $options = [])
|
public function __construct(DocumentNode $ast, array $options = [])
|
||||||
{
|
{
|
||||||
$this->ast = $ast;
|
$this->ast = $ast;
|
||||||
$this->typeConfigDecorator = $typeConfigDecorator;
|
|
||||||
$this->loadedTypeDefs = [];
|
$this->loadedTypeDefs = [];
|
||||||
$this->options = $options;
|
$this->options = $options;
|
||||||
}
|
}
|
||||||
@ -150,23 +98,15 @@ class BuildSchema
|
|||||||
'subscription' => isset($this->nodeMap['Subscription']) ? 'Subscription' : null,
|
'subscription' => isset($this->nodeMap['Subscription']) ? 'Subscription' : null,
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->innerTypeMap = [
|
$defintionBuilder = new ASTDefinitionBuilder(
|
||||||
'String' => Type::string(),
|
$this->nodeMap,
|
||||||
'Int' => Type::int(),
|
$this->options,
|
||||||
'Float' => Type::float(),
|
function($typeName) { throw new Error('Type "'. $typeName . '" not found in document.'); }
|
||||||
'Boolean' => Type::boolean(),
|
);
|
||||||
'ID' => Type::id(),
|
|
||||||
'__Schema' => Introspection::_schema(),
|
|
||||||
'__Directive' => Introspection::_directive(),
|
|
||||||
'__DirectiveLocation' => Introspection::_directiveLocation(),
|
|
||||||
'__Type' => Introspection::_type(),
|
|
||||||
'__Field' => Introspection::_field(),
|
|
||||||
'__InputValue' => Introspection::_inputValue(),
|
|
||||||
'__EnumValue' => Introspection::_enumValue(),
|
|
||||||
'__TypeKind' => Introspection::_typeKind(),
|
|
||||||
];
|
|
||||||
|
|
||||||
$directives = array_map([$this, 'getDirective'], $directiveDefs);
|
$directives = array_map(function($def) use ($defintionBuilder) {
|
||||||
|
return $defintionBuilder->buildDirective($def);
|
||||||
|
}, $directiveDefs);
|
||||||
|
|
||||||
// If specified directives were not explicitly declared, add them.
|
// If specified directives were not explicitly declared, add them.
|
||||||
$skip = array_reduce($directives, function ($hasSkip, $directive) {
|
$skip = array_reduce($directives, function ($hasSkip, $directive) {
|
||||||
@ -197,23 +137,23 @@ class BuildSchema
|
|||||||
}
|
}
|
||||||
|
|
||||||
$schema = new Schema([
|
$schema = new Schema([
|
||||||
'query' => $this->getObjectType($operationTypes['query']),
|
'query' => $defintionBuilder->buildObjectType($operationTypes['query']),
|
||||||
'mutation' => isset($operationTypes['mutation']) ?
|
'mutation' => isset($operationTypes['mutation']) ?
|
||||||
$this->getObjectType($operationTypes['mutation']) :
|
$defintionBuilder->buildObjectType($operationTypes['mutation']) :
|
||||||
null,
|
null,
|
||||||
'subscription' => isset($operationTypes['subscription']) ?
|
'subscription' => isset($operationTypes['subscription']) ?
|
||||||
$this->getObjectType($operationTypes['subscription']) :
|
$defintionBuilder->buildObjectType($operationTypes['subscription']) :
|
||||||
null,
|
null,
|
||||||
'typeLoader' => function ($name) {
|
'typeLoader' => function ($name) use ($defintionBuilder) {
|
||||||
return $this->typeDefNamed($name);
|
return $defintionBuilder->buildType($name);
|
||||||
},
|
},
|
||||||
'directives' => $directives,
|
'directives' => $directives,
|
||||||
'astNode' => $schemaDef,
|
'astNode' => $schemaDef,
|
||||||
'types' => function () {
|
'types' => function () use ($defintionBuilder) {
|
||||||
$types = [];
|
$types = [];
|
||||||
foreach ($this->nodeMap as $name => $def) {
|
foreach ($this->nodeMap as $name => $def) {
|
||||||
if (!isset($this->loadedTypeDefs[$name])) {
|
if (!isset($this->loadedTypeDefs[$name])) {
|
||||||
$types[] = $this->typeDefNamed($def->name->value);
|
$types[] = $defintionBuilder->buildType($def->name->value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $types;
|
return $types;
|
||||||
@ -250,377 +190,18 @@ class BuildSchema
|
|||||||
return $opTypes;
|
return $opTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getDirective(DirectiveDefinitionNode $directiveNode)
|
|
||||||
{
|
|
||||||
return new Directive([
|
|
||||||
'name' => $directiveNode->name->value,
|
|
||||||
'description' => $this->getDescription($directiveNode),
|
|
||||||
'locations' => Utils::map($directiveNode->locations, function ($node) {
|
|
||||||
return $node->value;
|
|
||||||
}),
|
|
||||||
'args' => $directiveNode->arguments ? FieldArgument::createMap($this->makeInputValues($directiveNode->arguments)) : null,
|
|
||||||
'astNode' => $directiveNode
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $name
|
|
||||||
* @return CustomScalarType|EnumType|InputObjectType|UnionType
|
|
||||||
* @throws Error
|
|
||||||
*/
|
|
||||||
private function getObjectType($name)
|
|
||||||
{
|
|
||||||
$type = $this->typeDefNamed($name);
|
|
||||||
Utils::invariant(
|
|
||||||
$type instanceof ObjectType,
|
|
||||||
'AST must provide object type.'
|
|
||||||
);
|
|
||||||
return $type;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function produceType(TypeNode $typeNode)
|
|
||||||
{
|
|
||||||
$typeName = $this->getNamedTypeNode($typeNode)->name->value;
|
|
||||||
$typeDef = $this->typeDefNamed($typeName);
|
|
||||||
return $this->buildWrappedType($typeDef, $typeNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function produceInputType(TypeNode $typeNode)
|
|
||||||
{
|
|
||||||
$type = $this->produceType($typeNode);
|
|
||||||
Utils::invariant(Type::isInputType($type), 'Expected Input type.');
|
|
||||||
return $type;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function produceOutputType(TypeNode $typeNode)
|
|
||||||
{
|
|
||||||
$type = $this->produceType($typeNode);
|
|
||||||
Utils::invariant(Type::isOutputType($type), 'Expected Input type.');
|
|
||||||
return $type;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function produceObjectType(TypeNode $typeNode)
|
|
||||||
{
|
|
||||||
$type = $this->produceType($typeNode);
|
|
||||||
Utils::invariant($type instanceof ObjectType, 'Expected Object type.');
|
|
||||||
return $type;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function produceInterfaceType(TypeNode $typeNode)
|
|
||||||
{
|
|
||||||
$type = $this->produceType($typeNode);
|
|
||||||
Utils::invariant($type instanceof InterfaceType, 'Expected Interface type.');
|
|
||||||
return $type;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function typeDefNamed($typeName)
|
|
||||||
{
|
|
||||||
if (isset($this->innerTypeMap[$typeName])) {
|
|
||||||
return $this->innerTypeMap[$typeName];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isset($this->nodeMap[$typeName])) {
|
|
||||||
throw new Error('Type "' . $typeName . '" not found in document.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->loadedTypeDefs[$typeName] = true;
|
|
||||||
|
|
||||||
$config = $this->makeSchemaDefConfig($this->nodeMap[$typeName]);
|
|
||||||
|
|
||||||
if ($this->typeConfigDecorator) {
|
|
||||||
$fn = $this->typeConfigDecorator;
|
|
||||||
try {
|
|
||||||
$config = $fn($config, $this->nodeMap[$typeName], $this->nodeMap);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
throw new Error(
|
|
||||||
"Type config decorator passed to " . (static::class) . " threw an error " .
|
|
||||||
"when building $typeName type: {$e->getMessage()}",
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
$e
|
|
||||||
);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
throw new Error(
|
|
||||||
"Type config decorator passed to " . (static::class) . " threw an error " .
|
|
||||||
"when building $typeName type: {$e->getMessage()}",
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
$e
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!is_array($config) || isset($config[0])) {
|
|
||||||
throw new Error(
|
|
||||||
"Type config decorator passed to " . (static::class) . " is expected to return an array, but got " .
|
|
||||||
Utils::getVariableType($config)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$innerTypeDef = $this->makeSchemaDef($this->nodeMap[$typeName], $config);
|
|
||||||
|
|
||||||
if (!$innerTypeDef) {
|
|
||||||
throw new Error("Nothing constructed for $typeName.");
|
|
||||||
}
|
|
||||||
$this->innerTypeMap[$typeName] = $innerTypeDef;
|
|
||||||
return $innerTypeDef;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function makeSchemaDefConfig($def)
|
|
||||||
{
|
|
||||||
if (!$def) {
|
|
||||||
throw new Error('def must be defined.');
|
|
||||||
}
|
|
||||||
switch ($def->kind) {
|
|
||||||
case NodeKind::OBJECT_TYPE_DEFINITION:
|
|
||||||
return $this->makeTypeDefConfig($def);
|
|
||||||
case NodeKind::INTERFACE_TYPE_DEFINITION:
|
|
||||||
return $this->makeInterfaceDefConfig($def);
|
|
||||||
case NodeKind::ENUM_TYPE_DEFINITION:
|
|
||||||
return $this->makeEnumDefConfig($def);
|
|
||||||
case NodeKind::UNION_TYPE_DEFINITION:
|
|
||||||
return $this->makeUnionDefConfig($def);
|
|
||||||
case NodeKind::SCALAR_TYPE_DEFINITION:
|
|
||||||
return $this->makeScalarDefConfig($def);
|
|
||||||
case NodeKind::INPUT_OBJECT_TYPE_DEFINITION:
|
|
||||||
return $this->makeInputObjectDefConfig($def);
|
|
||||||
default:
|
|
||||||
throw new Error("Type kind of {$def->kind} not supported.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function makeSchemaDef($def, array $config = null)
|
|
||||||
{
|
|
||||||
if (!$def) {
|
|
||||||
throw new Error('def must be defined.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$config = $config ?: $this->makeSchemaDefConfig($def);
|
|
||||||
|
|
||||||
switch ($def->kind) {
|
|
||||||
case NodeKind::OBJECT_TYPE_DEFINITION:
|
|
||||||
return new ObjectType($config);
|
|
||||||
case NodeKind::INTERFACE_TYPE_DEFINITION:
|
|
||||||
return new InterfaceType($config);
|
|
||||||
case NodeKind::ENUM_TYPE_DEFINITION:
|
|
||||||
return new EnumType($config);
|
|
||||||
case NodeKind::UNION_TYPE_DEFINITION:
|
|
||||||
return new UnionType($config);
|
|
||||||
case NodeKind::SCALAR_TYPE_DEFINITION:
|
|
||||||
return new CustomScalarType($config);
|
|
||||||
case NodeKind::INPUT_OBJECT_TYPE_DEFINITION:
|
|
||||||
return new InputObjectType($config);
|
|
||||||
default:
|
|
||||||
throw new Error("Type kind of {$def->kind} not supported.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function makeTypeDefConfig(ObjectTypeDefinitionNode $def)
|
|
||||||
{
|
|
||||||
$typeName = $def->name->value;
|
|
||||||
return [
|
|
||||||
'name' => $typeName,
|
|
||||||
'description' => $this->getDescription($def),
|
|
||||||
'fields' => function () use ($def) {
|
|
||||||
return $this->makeFieldDefMap($def);
|
|
||||||
},
|
|
||||||
'interfaces' => function () use ($def) {
|
|
||||||
return $this->makeImplementedInterfaces($def);
|
|
||||||
},
|
|
||||||
'astNode' => $def
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function makeFieldDefMap($def)
|
|
||||||
{
|
|
||||||
return Utils::keyValMap(
|
|
||||||
$def->fields,
|
|
||||||
function ($field) {
|
|
||||||
return $field->name->value;
|
|
||||||
},
|
|
||||||
function ($field) {
|
|
||||||
return [
|
|
||||||
'type' => $this->produceOutputType($field->type),
|
|
||||||
'description' => $this->getDescription($field),
|
|
||||||
'args' => $this->makeInputValues($field->arguments),
|
|
||||||
'deprecationReason' => $this->getDeprecationReason($field),
|
|
||||||
'astNode' => $field
|
|
||||||
];
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function makeImplementedInterfaces(ObjectTypeDefinitionNode $def)
|
|
||||||
{
|
|
||||||
if (isset($def->interfaces)) {
|
|
||||||
return Utils::map($def->interfaces, function ($iface) {
|
|
||||||
return $this->produceInterfaceType($iface);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function makeInputValues($values)
|
|
||||||
{
|
|
||||||
return Utils::keyValMap(
|
|
||||||
$values,
|
|
||||||
function ($value) {
|
|
||||||
return $value->name->value;
|
|
||||||
},
|
|
||||||
function ($value) {
|
|
||||||
$type = $this->produceInputType($value->type);
|
|
||||||
$config = [
|
|
||||||
'name' => $value->name->value,
|
|
||||||
'type' => $type,
|
|
||||||
'description' => $this->getDescription($value),
|
|
||||||
'astNode' => $value
|
|
||||||
];
|
|
||||||
if (isset($value->defaultValue)) {
|
|
||||||
$config['defaultValue'] = AST::valueFromAST($value->defaultValue, $type);
|
|
||||||
}
|
|
||||||
return $config;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function makeInterfaceDefConfig(InterfaceTypeDefinitionNode $def)
|
|
||||||
{
|
|
||||||
$typeName = $def->name->value;
|
|
||||||
return [
|
|
||||||
'name' => $typeName,
|
|
||||||
'description' => $this->getDescription($def),
|
|
||||||
'fields' => function () use ($def) {
|
|
||||||
return $this->makeFieldDefMap($def);
|
|
||||||
},
|
|
||||||
'astNode' => $def
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function makeEnumDefConfig(EnumTypeDefinitionNode $def)
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'name' => $def->name->value,
|
|
||||||
'description' => $this->getDescription($def),
|
|
||||||
'astNode' => $def,
|
|
||||||
'values' => Utils::keyValMap(
|
|
||||||
$def->values,
|
|
||||||
function ($enumValue) {
|
|
||||||
return $enumValue->name->value;
|
|
||||||
},
|
|
||||||
function ($enumValue) {
|
|
||||||
return [
|
|
||||||
'description' => $this->getDescription($enumValue),
|
|
||||||
'deprecationReason' => $this->getDeprecationReason($enumValue),
|
|
||||||
'astNode' => $enumValue
|
|
||||||
];
|
|
||||||
}
|
|
||||||
)
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function makeUnionDefConfig(UnionTypeDefinitionNode $def)
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'name' => $def->name->value,
|
|
||||||
'description' => $this->getDescription($def),
|
|
||||||
'types' => Utils::map($def->types, function ($typeNode) {
|
|
||||||
return $this->produceObjectType($typeNode);
|
|
||||||
}),
|
|
||||||
'astNode' => $def
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function makeScalarDefConfig(ScalarTypeDefinitionNode $def)
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'name' => $def->name->value,
|
|
||||||
'description' => $this->getDescription($def),
|
|
||||||
'astNode' => $def,
|
|
||||||
'serialize' => function($value) {
|
|
||||||
return $value;
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function makeInputObjectDefConfig(InputObjectTypeDefinitionNode $def)
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'name' => $def->name->value,
|
|
||||||
'description' => $this->getDescription($def),
|
|
||||||
'fields' => function () use ($def) {
|
|
||||||
return $this->makeInputValues($def->fields);
|
|
||||||
},
|
|
||||||
'astNode' => $def,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a collection of directives, returns the string value for the
|
|
||||||
* deprecation reason.
|
|
||||||
*
|
|
||||||
* @param EnumValueDefinitionNode | FieldDefinitionNode $node
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
private function getDeprecationReason($node)
|
|
||||||
{
|
|
||||||
$deprecated = Values::getDirectiveValues(Directive::deprecatedDirective(), $node);
|
|
||||||
return isset($deprecated['reason']) ? $deprecated['reason'] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given an ast node, returns its string description.
|
|
||||||
*/
|
|
||||||
public function getDescription($node)
|
|
||||||
{
|
|
||||||
if ($node->description) {
|
|
||||||
return $node->description->value;
|
|
||||||
}
|
|
||||||
if (isset($this->options['commentDescriptions'])) {
|
|
||||||
$rawValue = $this->getLeadingCommentBlock($node);
|
|
||||||
if ($rawValue !== null) {
|
|
||||||
return BlockString::value("\n" . $rawValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLeadingCommentBlock($node)
|
|
||||||
{
|
|
||||||
$loc = $node->loc;
|
|
||||||
if (!$loc || !$loc->startToken) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$comments = [];
|
|
||||||
$token = $loc->startToken->prev;
|
|
||||||
while (
|
|
||||||
$token &&
|
|
||||||
$token->kind === Token::COMMENT &&
|
|
||||||
$token->next && $token->prev &&
|
|
||||||
$token->line + 1 === $token->next->line &&
|
|
||||||
$token->line !== $token->prev->line
|
|
||||||
) {
|
|
||||||
$value = $token->value;
|
|
||||||
$comments[] = $value;
|
|
||||||
$token = $token->prev;
|
|
||||||
}
|
|
||||||
|
|
||||||
return implode("\n", array_reverse($comments));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A helper function to build a GraphQLSchema directly from a source
|
* A helper function to build a GraphQLSchema directly from a source
|
||||||
* document.
|
* document.
|
||||||
*
|
*
|
||||||
* @api
|
* @api
|
||||||
* @param DocumentNode|Source|string $source
|
* @param DocumentNode|Source|string $source
|
||||||
* @param callable $typeConfigDecorator
|
* @param array $options
|
||||||
* @return Schema
|
* @return Schema
|
||||||
*/
|
*/
|
||||||
public static function build($source, callable $typeConfigDecorator = null)
|
public static function build($source, array $options = [])
|
||||||
{
|
{
|
||||||
$doc = $source instanceof DocumentNode ? $source : Parser::parse($source);
|
$doc = $source instanceof DocumentNode ? $source : Parser::parse($source);
|
||||||
return self::buildAST($doc, $typeConfigDecorator);
|
return self::buildAST($doc, $options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ class BuildSchemaTest extends \PHPUnit_Framework_TestCase
|
|||||||
private function cycleOutput($body, $options = [])
|
private function cycleOutput($body, $options = [])
|
||||||
{
|
{
|
||||||
$ast = Parser::parse($body);
|
$ast = Parser::parse($body);
|
||||||
$schema = BuildSchema::buildAST($ast, null, $options);
|
$schema = BuildSchema::buildAST($ast, $options);
|
||||||
return "\n" . SchemaPrinter::doPrint($schema, $options);
|
return "\n" . SchemaPrinter::doPrint($schema, $options);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1172,131 +1172,4 @@ type Repeated {
|
|||||||
$this->setExpectedException('GraphQL\Error\Error', 'Type "Repeated" was defined more than once.');
|
$this->setExpectedException('GraphQL\Error\Error', 'Type "Repeated" was defined more than once.');
|
||||||
BuildSchema::buildAST($doc);
|
BuildSchema::buildAST($doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSupportsTypeConfigDecorator()
|
|
||||||
{
|
|
||||||
$body = '
|
|
||||||
schema {
|
|
||||||
query: Query
|
|
||||||
}
|
|
||||||
|
|
||||||
type Query {
|
|
||||||
str: String
|
|
||||||
color: Color
|
|
||||||
hello: Hello
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Color {
|
|
||||||
RED
|
|
||||||
GREEN
|
|
||||||
BLUE
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Hello {
|
|
||||||
world: String
|
|
||||||
}
|
|
||||||
';
|
|
||||||
$doc = Parser::parse($body);
|
|
||||||
|
|
||||||
$decorated = [];
|
|
||||||
$calls = [];
|
|
||||||
|
|
||||||
$typeConfigDecorator = function($defaultConfig, $node, $allNodesMap) use (&$decorated, &$calls) {
|
|
||||||
$decorated[] = $defaultConfig['name'];
|
|
||||||
$calls[] = [$defaultConfig, $node, $allNodesMap];
|
|
||||||
return ['description' => 'My description of ' . $node->name->value] + $defaultConfig;
|
|
||||||
};
|
|
||||||
|
|
||||||
$schema = BuildSchema::buildAST($doc, $typeConfigDecorator);
|
|
||||||
$schema->getTypeMap();
|
|
||||||
$this->assertEquals(['Query', 'Color', 'Hello'], $decorated);
|
|
||||||
|
|
||||||
list($defaultConfig, $node, $allNodesMap) = $calls[0];
|
|
||||||
$this->assertInstanceOf(ObjectTypeDefinitionNode::class, $node);
|
|
||||||
$this->assertEquals('Query', $defaultConfig['name']);
|
|
||||||
$this->assertInstanceOf(\Closure::class, $defaultConfig['fields']);
|
|
||||||
$this->assertInstanceOf(\Closure::class, $defaultConfig['interfaces']);
|
|
||||||
$this->assertArrayHasKey('description', $defaultConfig);
|
|
||||||
$this->assertCount(5, $defaultConfig);
|
|
||||||
$this->assertEquals(array_keys($allNodesMap), ['Query', 'Color', 'Hello']);
|
|
||||||
$this->assertEquals('My description of Query', $schema->getType('Query')->description);
|
|
||||||
|
|
||||||
|
|
||||||
list($defaultConfig, $node, $allNodesMap) = $calls[1];
|
|
||||||
$this->assertInstanceOf(EnumTypeDefinitionNode::class, $node);
|
|
||||||
$this->assertEquals('Color', $defaultConfig['name']);
|
|
||||||
$enumValue = [
|
|
||||||
'description' => '',
|
|
||||||
'deprecationReason' => ''
|
|
||||||
];
|
|
||||||
$this->assertArraySubset([
|
|
||||||
'RED' => $enumValue,
|
|
||||||
'GREEN' => $enumValue,
|
|
||||||
'BLUE' => $enumValue,
|
|
||||||
], $defaultConfig['values']);
|
|
||||||
$this->assertCount(4, $defaultConfig); // 3 + astNode
|
|
||||||
$this->assertEquals(array_keys($allNodesMap), ['Query', 'Color', 'Hello']);
|
|
||||||
$this->assertEquals('My description of Color', $schema->getType('Color')->description);
|
|
||||||
|
|
||||||
list($defaultConfig, $node, $allNodesMap) = $calls[2];
|
|
||||||
$this->assertInstanceOf(InterfaceTypeDefinitionNode::class, $node);
|
|
||||||
$this->assertEquals('Hello', $defaultConfig['name']);
|
|
||||||
$this->assertInstanceOf(\Closure::class, $defaultConfig['fields']);
|
|
||||||
$this->assertArrayHasKey('description', $defaultConfig);
|
|
||||||
$this->assertCount(4, $defaultConfig);
|
|
||||||
$this->assertEquals(array_keys($allNodesMap), ['Query', 'Color', 'Hello']);
|
|
||||||
$this->assertEquals('My description of Hello', $schema->getType('Hello')->description);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testCreatesTypesLazily()
|
|
||||||
{
|
|
||||||
$body = '
|
|
||||||
schema {
|
|
||||||
query: Query
|
|
||||||
}
|
|
||||||
|
|
||||||
type Query {
|
|
||||||
str: String
|
|
||||||
color: Color
|
|
||||||
hello: Hello
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Color {
|
|
||||||
RED
|
|
||||||
GREEN
|
|
||||||
BLUE
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Hello {
|
|
||||||
world: String
|
|
||||||
}
|
|
||||||
|
|
||||||
type World implements Hello {
|
|
||||||
world: String
|
|
||||||
}
|
|
||||||
';
|
|
||||||
$doc = Parser::parse($body);
|
|
||||||
$created = [];
|
|
||||||
|
|
||||||
$typeConfigDecorator = function($config, $node) use (&$created) {
|
|
||||||
$created[] = $node->name->value;
|
|
||||||
return $config;
|
|
||||||
};
|
|
||||||
|
|
||||||
$schema = BuildSchema::buildAST($doc, $typeConfigDecorator);
|
|
||||||
$this->assertEquals(['Query'], $created);
|
|
||||||
|
|
||||||
$schema->getType('Color');
|
|
||||||
$this->assertEquals(['Query', 'Color'], $created);
|
|
||||||
|
|
||||||
$schema->getType('Hello');
|
|
||||||
$this->assertEquals(['Query', 'Color', 'Hello'], $created);
|
|
||||||
|
|
||||||
$types = $schema->getTypeMap();
|
|
||||||
$this->assertEquals(['Query', 'Color', 'Hello', 'World'], $created);
|
|
||||||
$this->assertArrayHasKey('Query', $types);
|
|
||||||
$this->assertArrayHasKey('Color', $types);
|
|
||||||
$this->assertArrayHasKey('Hello', $types);
|
|
||||||
$this->assertArrayHasKey('World', $types);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user