*/ private $nodeMap; /** @var callable|null */ private $typeConfigDecorator; /** @var array */ private $options; /** * @param array $options */ public function __construct(DocumentNode $ast, ?callable $typeConfigDecorator = null, array $options = []) { $this->ast = $ast; $this->typeConfigDecorator = $typeConfigDecorator; $this->options = $options; } /** * A helper function to build a GraphQLSchema directly from a source * document. * * @param DocumentNode|Source|string $source * @param array $options * * @return Schema * * @api */ public static function build($source, ?callable $typeConfigDecorator = null, array $options = []) { $doc = $source instanceof DocumentNode ? $source : Parser::parse($source); return self::buildAST($doc, $typeConfigDecorator, $options); } /** * 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. * * 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 third argument: * * - commentDescriptions: * Provide true to use preceding comments as the description. * This option is provided to ease adoption and will be removed in v16. * * @param array $options * * @return Schema * * @throws Error * * @api */ public static function buildAST(DocumentNode $ast, ?callable $typeConfigDecorator = null, array $options = []) { $builder = new self($ast, $typeConfigDecorator, $options); return $builder->buildSchema(); } public function buildSchema() { $options = $this->options; if (! ($options['assumeValid'] ?? false) && ! ($options['assumeValidSDL'] ?? false)) { DocumentValidator::assertValidSDL($this->ast); } $schemaDef = null; $typeDefs = []; $this->nodeMap = []; /** @var array $directiveDefs */ $directiveDefs = []; foreach ($this->ast->definitions as $definition) { switch (true) { case $definition instanceof SchemaDefinitionNode: $schemaDef = $definition; break; case $definition instanceof TypeDefinitionNode: $typeName = $definition->name->value; if (isset($this->nodeMap[$typeName])) { throw new Error(sprintf('Type "%s" was defined more than once.', $typeName)); } $typeDefs[] = $definition; $this->nodeMap[$typeName] = $definition; break; case $definition instanceof DirectiveDefinitionNode: $directiveDefs[] = $definition; break; } } $operationTypes = $schemaDef !== null ? $this->getOperationTypes($schemaDef) : [ 'query' => isset($this->nodeMap['Query']) ? 'Query' : null, 'mutation' => isset($this->nodeMap['Mutation']) ? 'Mutation' : null, 'subscription' => isset($this->nodeMap['Subscription']) ? 'Subscription' : null, ]; $DefinitionBuilder = new ASTDefinitionBuilder( $this->nodeMap, $this->options, static function ($typeName) : void { throw new Error('Type "' . $typeName . '" not found in document.'); }, $this->typeConfigDecorator ); $directives = array_map( static function (DirectiveDefinitionNode $def) use ($DefinitionBuilder) : Directive { return $DefinitionBuilder->buildDirective($def); }, $directiveDefs ); // If specified directives were not explicitly declared, add them. $directivesByName = Utils::groupBy( $directives, static function (Directive $directive) : string { return $directive->name; } ); if (! isset($directivesByName['skip'])) { $directives[] = Directive::skipDirective(); } if (! isset($directivesByName['include'])) { $directives[] = Directive::includeDirective(); } if (! isset($directivesByName['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. return new Schema([ 'query' => isset($operationTypes['query']) ? $DefinitionBuilder->buildType($operationTypes['query']) : null, 'mutation' => isset($operationTypes['mutation']) ? $DefinitionBuilder->buildType($operationTypes['mutation']) : null, 'subscription' => isset($operationTypes['subscription']) ? $DefinitionBuilder->buildType($operationTypes['subscription']) : null, 'typeLoader' => static function ($name) use ($DefinitionBuilder) : Type { return $DefinitionBuilder->buildType($name); }, 'directives' => $directives, 'astNode' => $schemaDef, 'types' => function () use ($DefinitionBuilder) : array { $types = []; /** @var ScalarTypeDefinitionNode|ObjectTypeDefinitionNode|InterfaceTypeDefinitionNode|UnionTypeDefinitionNode|EnumTypeDefinitionNode|InputObjectTypeDefinitionNode $def */ foreach ($this->nodeMap as $name => $def) { $types[] = $DefinitionBuilder->buildType($def->name->value); } return $types; }, ]); } /** * @param SchemaDefinitionNode $schemaDef * * @return string[] * * @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(sprintf('Must provide only one %s type in schema.', $operation)); } if (! isset($this->nodeMap[$typeName])) { throw new Error(sprintf('Specified %s type "%s" not found in document.', $operation, $typeName)); } $opTypes[$operation] = $typeName; } return $opTypes; } }