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) {
|
||||
$this->resolvedTypes[$config->subscription->name] = $config->subscription;
|
||||
}
|
||||
if ($this->config->types) {
|
||||
if (is_array($this->config->types)) {
|
||||
foreach ($this->resolveAdditionalTypes() as $type) {
|
||||
if (isset($this->resolvedTypes[$type->name])) {
|
||||
Utils::invariant(
|
||||
|
@ -38,6 +38,11 @@ class ASTDefinitionBuilder
|
||||
*/
|
||||
private $typeDefintionsMap;
|
||||
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
private $typeConfigDecorator;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
@ -53,9 +58,10 @@ class ASTDefinitionBuilder
|
||||
*/
|
||||
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->typeConfigDecorator = $typeConfigDecorator;
|
||||
$this->options = $options;
|
||||
$this->resolveType = $resolveType;
|
||||
|
||||
@ -101,7 +107,41 @@ class ASTDefinitionBuilder
|
||||
private function internalBuildType($typeName, $typeNode = null) {
|
||||
if (!isset($this->cache[$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 {
|
||||
$fn = $this->resolveType;
|
||||
$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)
|
||||
{
|
||||
$typeName = $def->name->value;
|
||||
|
@ -26,7 +26,7 @@ class BuildSchema
|
||||
* Given that AST it constructs a GraphQL\Type\Schema. The resulting schema
|
||||
* has no resolve methods, so execution will use default resolvers.
|
||||
*
|
||||
* Accepts options as a second argument:
|
||||
* Accepts options as a third argument:
|
||||
*
|
||||
* - commentDescriptions:
|
||||
* Provide true to use preceding comments as the description.
|
||||
@ -34,25 +34,26 @@ class BuildSchema
|
||||
*
|
||||
* @api
|
||||
* @param DocumentNode $ast
|
||||
* @param callable $typeConfigDecorator
|
||||
* @param array $options
|
||||
* @return Schema
|
||||
* @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();
|
||||
}
|
||||
|
||||
private $ast;
|
||||
private $nodeMap;
|
||||
private $loadedTypeDefs;
|
||||
private $typeConfigDecorator;
|
||||
private $options;
|
||||
|
||||
public function __construct(DocumentNode $ast, array $options = [])
|
||||
public function __construct(DocumentNode $ast, callable $typeConfigDecorator = null, array $options = [])
|
||||
{
|
||||
$this->ast = $ast;
|
||||
$this->loadedTypeDefs = [];
|
||||
$this->typeConfigDecorator = $typeConfigDecorator;
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
@ -101,7 +102,8 @@ class BuildSchema
|
||||
$defintionBuilder = new ASTDefinitionBuilder(
|
||||
$this->nodeMap,
|
||||
$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) {
|
||||
@ -152,9 +154,7 @@ class BuildSchema
|
||||
'types' => function () use ($defintionBuilder) {
|
||||
$types = [];
|
||||
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;
|
||||
}
|
||||
@ -196,12 +196,13 @@ class BuildSchema
|
||||
*
|
||||
* @api
|
||||
* @param DocumentNode|Source|string $source
|
||||
* @param callable $typeConfigDecorator
|
||||
* @param array $options
|
||||
* @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);
|
||||
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 = [])
|
||||
{
|
||||
$ast = Parser::parse($body);
|
||||
$schema = BuildSchema::buildAST($ast, $options);
|
||||
$schema = BuildSchema::buildAST($ast, null, $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.');
|
||||
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