graphql-php/src/Utils/BuildSchema.php

208 lines
7.0 KiB
PHP
Raw Normal View History

2017-02-19 22:26:56 +03:00
<?php
namespace GraphQL\Utils;
use GraphQL\Error\Error;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\SchemaDefinitionNode;
2017-02-19 22:26:56 +03:00
use GraphQL\Language\Parser;
use GraphQL\Language\Source;
use GraphQL\Type\Schema;
2017-02-19 22:26:56 +03:00
use GraphQL\Type\Definition\Directive;
/**
2017-08-20 18:10:13 +03:00
* Build instance of `GraphQL\Type\Schema` out of type language definition (string or parsed AST)
* See [section in docs](type-system/type-language.md) for details.
2017-02-19 22:26:56 +03:00
*/
class BuildSchema
{
/**
* This takes the ast of a schema document produced by the parse function in
* GraphQL\Language\Parser.
*
* If no schema definition is provided, then it will look for types named Query
* and Mutation.
*
2017-08-20 18:10:13 +03:00
* Given that AST it constructs a GraphQL\Type\Schema. The resulting schema
2017-02-19 22:26:56 +03:00
* has no resolve methods, so execution will use default resolvers.
*
* Accepts options as a second argument:
*
* - commentDescriptions:
* Provide true to use preceding comments as the description.
*
*
2017-08-20 18:10:13 +03:00
* @api
2017-02-19 22:26:56 +03:00
* @param DocumentNode $ast
* @param array $options
2017-02-19 22:26:56 +03:00
* @return Schema
* @throws Error
*/
public static function buildAST(DocumentNode $ast, array $options = [])
2017-02-19 22:26:56 +03:00
{
$builder = new self($ast, $options);
2017-02-19 22:26:56 +03:00
return $builder->buildSchema();
}
private $ast;
private $nodeMap;
private $loadedTypeDefs;
private $options;
2017-02-19 22:26:56 +03:00
public function __construct(DocumentNode $ast, array $options = [])
2017-02-19 22:26:56 +03:00
{
$this->ast = $ast;
$this->loadedTypeDefs = [];
$this->options = $options;
2017-02-19 22:26:56 +03:00
}
2017-02-19 22:26:56 +03:00
public function buildSchema()
{
/** @var SchemaDefinitionNode $schemaDef */
2017-02-19 22:26:56 +03:00
$schemaDef = null;
$typeDefs = [];
$this->nodeMap = [];
$directiveDefs = [];
foreach ($this->ast->definitions as $d) {
switch ($d->kind) {
case NodeKind::SCHEMA_DEFINITION:
if ($schemaDef) {
throw new Error('Must provide only one schema definition.');
}
$schemaDef = $d;
break;
case NodeKind::SCALAR_TYPE_DEFINITION:
case NodeKind::OBJECT_TYPE_DEFINITION:
case NodeKind::INTERFACE_TYPE_DEFINITION:
case NodeKind::ENUM_TYPE_DEFINITION:
case NodeKind::UNION_TYPE_DEFINITION:
case NodeKind::INPUT_OBJECT_TYPE_DEFINITION:
2017-07-04 09:59:46 +03:00
$typeName = $d->name->value;
if (!empty($this->nodeMap[$typeName])) {
throw new Error("Type \"$typeName\" was defined more than once.");
}
2017-02-19 22:26:56 +03:00
$typeDefs[] = $d;
2017-07-04 09:59:46 +03:00
$this->nodeMap[$typeName] = $d;
2017-02-19 22:26:56 +03:00
break;
case NodeKind::DIRECTIVE_DEFINITION:
$directiveDefs[] = $d;
break;
}
}
$operationTypes = $schemaDef
? $this->getOperationTypes($schemaDef)
: [
'query' => isset($this->nodeMap['Query']) ? 'Query' : null,
'mutation' => isset($this->nodeMap['Mutation']) ? 'Mutation' : null,
'subscription' => isset($this->nodeMap['Subscription']) ? 'Subscription' : null,
];
2017-02-19 22:26:56 +03:00
$defintionBuilder = new ASTDefinitionBuilder(
$this->nodeMap,
$this->options,
function($typeName) { throw new Error('Type "'. $typeName . '" not found in document.'); }
);
2017-02-19 22:26:56 +03:00
$directives = array_map(function($def) use ($defintionBuilder) {
return $defintionBuilder->buildDirective($def);
}, $directiveDefs);
2017-02-19 22:26:56 +03:00
// If specified directives were not explicitly declared, add them.
$skip = array_reduce($directives, function ($hasSkip, $directive) {
2017-02-19 22:26:56 +03:00
return $hasSkip || $directive->name == 'skip';
});
if (!$skip) {
$directives[] = Directive::skipDirective();
}
$include = array_reduce($directives, function ($hasInclude, $directive) {
2017-02-19 22:26:56 +03:00
return $hasInclude || $directive->name == 'include';
});
if (!$include) {
$directives[] = Directive::includeDirective();
}
$deprecated = array_reduce($directives, function ($hasDeprecated, $directive) {
2017-02-19 22:26:56 +03:00
return $hasDeprecated || $directive->name == 'deprecated';
});
if (!$deprecated) {
$directives[] = Directive::deprecatedDirective();
}
// Note: While this could make early assertions to get the correctly
// typed values below, that would throw immediately while type system
// validation with validateSchema() will produce more actionable results.
$schema = new Schema([
'query' => isset($operationTypes['query'])
? $defintionBuilder->buildType($operationTypes['query'])
: null,
'mutation' => isset($operationTypes['mutation'])
? $defintionBuilder->buildType($operationTypes['mutation'])
: null,
'subscription' => isset($operationTypes['subscription'])
? $defintionBuilder->buildType($operationTypes['subscription'])
: null,
'typeLoader' => function ($name) use ($defintionBuilder) {
return $defintionBuilder->buildType($name);
},
2017-02-19 22:26:56 +03:00
'directives' => $directives,
'astNode' => $schemaDef,
'types' => function () use ($defintionBuilder) {
$types = [];
foreach ($this->nodeMap as $name => $def) {
if (!isset($this->loadedTypeDefs[$name])) {
$types[] = $defintionBuilder->buildType($def->name->value);
}
}
return $types;
}
2017-02-19 22:26:56 +03:00
]);
return $schema;
2017-02-19 22:26:56 +03:00
}
/**
* @param SchemaDefinitionNode $schemaDef
* @return array
* @throws Error
*/
private function getOperationTypes($schemaDef)
{
$opTypes = [];
foreach ($schemaDef->operationTypes as $operationType) {
$typeName = $operationType->type->name->value;
$operation = $operationType->operation;
if (isset($opTypes[$operation])) {
throw new Error("Must provide only one $operation type in schema.");
}
if (!isset($this->nodeMap[$typeName])) {
throw new Error("Specified $operation type \"$typeName\" not found in document.");
}
$opTypes[$operation] = $typeName;
}
return $opTypes;
}
2017-02-19 22:26:56 +03:00
/**
* A helper function to build a GraphQLSchema directly from a source
* document.
*
2017-08-20 18:10:13 +03:00
* @api
* @param DocumentNode|Source|string $source
* @param array $options
* @return Schema
2017-02-19 22:26:56 +03:00
*/
public static function build($source, array $options = [])
2017-02-19 22:26:56 +03:00
{
$doc = $source instanceof DocumentNode ? $source : Parser::parse($source);
return self::buildAST($doc, $options);
2017-02-19 22:26:56 +03:00
}
}