buildSchema(); } private $ast; private $nodeMap; private $loadedTypeDefs; private $options; public function __construct(DocumentNode $ast, array $options = []) { $this->ast = $ast; $this->loadedTypeDefs = []; $this->options = $options; } public function buildSchema() { /** @var SchemaDefinitionNode $schemaDef */ $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: $typeName = $d->name->value; if (!empty($this->nodeMap[$typeName])) { throw new Error("Type \"$typeName\" was defined more than once."); } $typeDefs[] = $d; $this->nodeMap[$typeName] = $d; 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, ]; $defintionBuilder = new ASTDefinitionBuilder( $this->nodeMap, $this->options, function($typeName) { throw new Error('Type "'. $typeName . '" not found in document.'); } ); $directives = array_map(function($def) use ($defintionBuilder) { return $defintionBuilder->buildDirective($def); }, $directiveDefs); // If specified directives were not explicitly declared, add them. $skip = array_reduce($directives, function ($hasSkip, $directive) { return $hasSkip || $directive->name == 'skip'; }); if (!$skip) { $directives[] = Directive::skipDirective(); } $include = array_reduce($directives, function ($hasInclude, $directive) { return $hasInclude || $directive->name == 'include'; }); if (!$include) { $directives[] = Directive::includeDirective(); } $deprecated = array_reduce($directives, function ($hasDeprecated, $directive) { 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); }, '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; } ]); return $schema; } /** * @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; } /** * A helper function to build a GraphQLSchema directly from a source * document. * * @api * @param DocumentNode|Source|string $source * @param array $options * @return Schema */ public static function build($source, array $options = []) { $doc = $source instanceof DocumentNode ? $source : Parser::parse($source); return self::buildAST($doc, $options); } }