mirror of
https://github.com/retailcrm/graphql-php.git
synced 2024-11-22 12:56:05 +03:00
Readd type decorator and fix lazy type loading
This commit is contained in:
parent
f9a366e69a
commit
3e067cc60f
@ -134,7 +134,7 @@ class Schema
|
|||||||
if ($config->subscription) {
|
if ($config->subscription) {
|
||||||
$this->resolvedTypes[$config->subscription->name] = $config->subscription;
|
$this->resolvedTypes[$config->subscription->name] = $config->subscription;
|
||||||
}
|
}
|
||||||
if ($this->config->types) {
|
if (is_array($this->config->types)) {
|
||||||
foreach ($this->resolveAdditionalTypes() as $type) {
|
foreach ($this->resolveAdditionalTypes() as $type) {
|
||||||
if (isset($this->resolvedTypes[$type->name])) {
|
if (isset($this->resolvedTypes[$type->name])) {
|
||||||
Utils::invariant(
|
Utils::invariant(
|
||||||
|
@ -38,6 +38,11 @@ class ASTDefinitionBuilder
|
|||||||
*/
|
*/
|
||||||
private $typeDefintionsMap;
|
private $typeDefintionsMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var callable
|
||||||
|
*/
|
||||||
|
private $typeConfigDecorator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
@ -53,9 +58,10 @@ class ASTDefinitionBuilder
|
|||||||
*/
|
*/
|
||||||
private $cache;
|
private $cache;
|
||||||
|
|
||||||
public function __construct(array $typeDefintionsMap, $options, callable $resolveType)
|
public function __construct(array $typeDefintionsMap, $options, callable $resolveType, callable $typeConfigDecorator = null)
|
||||||
{
|
{
|
||||||
$this->typeDefintionsMap = $typeDefintionsMap;
|
$this->typeDefintionsMap = $typeDefintionsMap;
|
||||||
|
$this->typeConfigDecorator = $typeConfigDecorator;
|
||||||
$this->options = $options;
|
$this->options = $options;
|
||||||
$this->resolveType = $resolveType;
|
$this->resolveType = $resolveType;
|
||||||
|
|
||||||
@ -101,7 +107,41 @@ class ASTDefinitionBuilder
|
|||||||
private function internalBuildType($typeName, $typeNode = null) {
|
private function internalBuildType($typeName, $typeNode = null) {
|
||||||
if (!isset($this->cache[$typeName])) {
|
if (!isset($this->cache[$typeName])) {
|
||||||
if (isset($this->typeDefintionsMap[$typeName])) {
|
if (isset($this->typeDefintionsMap[$typeName])) {
|
||||||
$this->cache[$typeName] = $this->makeSchemaDef($this->typeDefintionsMap[$typeName]);
|
$type = $this->makeSchemaDef($this->typeDefintionsMap[$typeName]);
|
||||||
|
if ($this->typeConfigDecorator) {
|
||||||
|
$fn = $this->typeConfigDecorator;
|
||||||
|
try {
|
||||||
|
$config = $fn($type->config, $this->typeDefintionsMap[$typeName], $this->typeDefintionsMap);
|
||||||
|
} 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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$type = $this->makeSchemaDefFromConfig($this->typeDefintionsMap[$typeName], $config);
|
||||||
|
}
|
||||||
|
$this->cache[$typeName] = $type;
|
||||||
} else {
|
} else {
|
||||||
$fn = $this->resolveType;
|
$fn = $this->resolveType;
|
||||||
$this->cache[$typeName] = $fn($typeName, $typeNode);
|
$this->cache[$typeName] = $fn($typeName, $typeNode);
|
||||||
@ -186,6 +226,29 @@ class ASTDefinitionBuilder
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function makeSchemaDefFromConfig($def, array $config)
|
||||||
|
{
|
||||||
|
if (!$def) {
|
||||||
|
throw new Error('def must be defined.');
|
||||||
|
}
|
||||||
|
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 makeTypeDef(ObjectTypeDefinitionNode $def)
|
private function makeTypeDef(ObjectTypeDefinitionNode $def)
|
||||||
{
|
{
|
||||||
$typeName = $def->name->value;
|
$typeName = $def->name->value;
|
||||||
|
@ -26,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 second argument:
|
* Accepts options as a third argument:
|
||||||
*
|
*
|
||||||
* - commentDescriptions:
|
* - commentDescriptions:
|
||||||
* Provide true to use preceding comments as the description.
|
* Provide true to use preceding comments as the description.
|
||||||
@ -34,25 +34,26 @@ class BuildSchema
|
|||||||
*
|
*
|
||||||
* @api
|
* @api
|
||||||
* @param DocumentNode $ast
|
* @param DocumentNode $ast
|
||||||
|
* @param callable $typeConfigDecorator
|
||||||
* @param array $options
|
* @param array $options
|
||||||
* @return Schema
|
* @return Schema
|
||||||
* @throws Error
|
* @throws Error
|
||||||
*/
|
*/
|
||||||
public static function buildAST(DocumentNode $ast, array $options = [])
|
public static function buildAST(DocumentNode $ast, callable $typeConfigDecorator = null, array $options = [])
|
||||||
{
|
{
|
||||||
$builder = new self($ast, $options);
|
$builder = new self($ast, $typeConfigDecorator, $options);
|
||||||
return $builder->buildSchema();
|
return $builder->buildSchema();
|
||||||
}
|
}
|
||||||
|
|
||||||
private $ast;
|
private $ast;
|
||||||
private $nodeMap;
|
private $nodeMap;
|
||||||
private $loadedTypeDefs;
|
private $typeConfigDecorator;
|
||||||
private $options;
|
private $options;
|
||||||
|
|
||||||
public function __construct(DocumentNode $ast, array $options = [])
|
public function __construct(DocumentNode $ast, callable $typeConfigDecorator = null, array $options = [])
|
||||||
{
|
{
|
||||||
$this->ast = $ast;
|
$this->ast = $ast;
|
||||||
$this->loadedTypeDefs = [];
|
$this->typeConfigDecorator = $typeConfigDecorator;
|
||||||
$this->options = $options;
|
$this->options = $options;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,7 +102,8 @@ class BuildSchema
|
|||||||
$defintionBuilder = new ASTDefinitionBuilder(
|
$defintionBuilder = new ASTDefinitionBuilder(
|
||||||
$this->nodeMap,
|
$this->nodeMap,
|
||||||
$this->options,
|
$this->options,
|
||||||
function($typeName) { throw new Error('Type "'. $typeName . '" not found in document.'); }
|
function($typeName) { throw new Error('Type "'. $typeName . '" not found in document.'); },
|
||||||
|
$this->typeConfigDecorator
|
||||||
);
|
);
|
||||||
|
|
||||||
$directives = array_map(function($def) use ($defintionBuilder) {
|
$directives = array_map(function($def) use ($defintionBuilder) {
|
||||||
@ -152,10 +154,8 @@ class BuildSchema
|
|||||||
'types' => function () use ($defintionBuilder) {
|
'types' => function () use ($defintionBuilder) {
|
||||||
$types = [];
|
$types = [];
|
||||||
foreach ($this->nodeMap as $name => $def) {
|
foreach ($this->nodeMap as $name => $def) {
|
||||||
if (!isset($this->loadedTypeDefs[$name])) {
|
|
||||||
$types[] = $defintionBuilder->buildType($def->name->value);
|
$types[] = $defintionBuilder->buildType($def->name->value);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return $types;
|
return $types;
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
@ -196,12 +196,13 @@ class BuildSchema
|
|||||||
*
|
*
|
||||||
* @api
|
* @api
|
||||||
* @param DocumentNode|Source|string $source
|
* @param DocumentNode|Source|string $source
|
||||||
|
* @param callable $typeConfigDecorator
|
||||||
* @param array $options
|
* @param array $options
|
||||||
* @return Schema
|
* @return Schema
|
||||||
*/
|
*/
|
||||||
public static function build($source, array $options = [])
|
public static function build($source, callable $typeConfigDecorator = null, array $options = [])
|
||||||
{
|
{
|
||||||
$doc = $source instanceof DocumentNode ? $source : Parser::parse($source);
|
$doc = $source instanceof DocumentNode ? $source : Parser::parse($source);
|
||||||
return self::buildAST($doc, $options);
|
return self::buildAST($doc, $typeConfigDecorator, $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, $options);
|
$schema = BuildSchema::buildAST($ast, null, $options);
|
||||||
return "\n" . SchemaPrinter::doPrint($schema, $options);
|
return "\n" . SchemaPrinter::doPrint($schema, $options);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1140,4 +1140,132 @@ 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…
Reference in New Issue
Block a user