Failed Conditions
Push — master ( 387f41...747cb4 )
by Vladimir
10:09 queued 12s
created

src/Utils/BuildSchema.php (1 issue)

Labels
Severity
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Utils;
6
7
use GraphQL\Error\Error;
8
use GraphQL\Language\AST\DirectiveDefinitionNode;
9
use GraphQL\Language\AST\DocumentNode;
10
use GraphQL\Language\AST\EnumTypeDefinitionNode;
11
use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
12
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
13
use GraphQL\Language\AST\Node;
14
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
15
use GraphQL\Language\AST\ScalarTypeDefinitionNode;
16
use GraphQL\Language\AST\SchemaDefinitionNode;
17
use GraphQL\Language\AST\UnionTypeDefinitionNode;
18
use GraphQL\Language\Parser;
19
use GraphQL\Language\Source;
20
use GraphQL\Type\Definition\Directive;
21
use GraphQL\Type\Schema;
22
use function array_map;
23
use function array_reduce;
24
use function sprintf;
25
26
/**
27
 * Build instance of `GraphQL\Type\Schema` out of type language definition (string or parsed AST)
28
 * See [section in docs](type-system/type-language.md) for details.
29
 */
30
class BuildSchema
31
{
32
    /** @var DocumentNode */
33
    private $ast;
34
35
    /** @var Node[] */
36
    private $nodeMap;
37
38
    /** @var callable|null */
39
    private $typeConfigDecorator;
40
41
    /** @var bool[] */
42
    private $options;
43
44
    /**
45
     * @param bool[] $options
46
     */
47 120
    public function __construct(DocumentNode $ast, ?callable $typeConfigDecorator = null, array $options = [])
48
    {
49 120
        $this->ast                 = $ast;
50 120
        $this->typeConfigDecorator = $typeConfigDecorator;
51 120
        $this->options             = $options;
52 120
    }
53
54
    /**
55
     * A helper function to build a GraphQLSchema directly from a source
56
     * document.
57
     *
58
     * @param DocumentNode|Source|string $source
59
     * @param bool[]                     $options
60
     *
61
     * @return Schema
62
     *
63
     * @api
64
     */
65 75
    public static function build($source, ?callable $typeConfigDecorator = null, array $options = [])
66
    {
67 75
        $doc = $source instanceof DocumentNode ? $source : Parser::parse($source);
68
69 75
        return self::buildAST($doc, $typeConfigDecorator, $options);
70
    }
71
72
    /**
73
     * This takes the ast of a schema document produced by the parse function in
74
     * GraphQL\Language\Parser.
75
     *
76
     * If no schema definition is provided, then it will look for types named Query
77
     * and Mutation.
78
     *
79
     * Given that AST it constructs a GraphQL\Type\Schema. The resulting schema
80
     * has no resolve methods, so execution will use default resolvers.
81
     *
82
     * Accepts options as a third argument:
83
     *
84
     *    - commentDescriptions:
85
     *        Provide true to use preceding comments as the description.
86
     *
87
     * @param bool[] $options
88
     *
89
     * @return Schema
90
     *
91
     * @throws Error
92
     *
93
     * @api
94
     */
95 120
    public static function buildAST(DocumentNode $ast, ?callable $typeConfigDecorator = null, array $options = [])
96
    {
97 120
        $builder = new self($ast, $typeConfigDecorator, $options);
98
99 120
        return $builder->buildSchema();
100
    }
101
102 120
    public function buildSchema()
103
    {
104 120
        $schemaDef     = null;
105 120
        $typeDefs      = [];
106 120
        $this->nodeMap = [];
107 120
        $directiveDefs = [];
108 120
        foreach ($this->ast->definitions as $definition) {
109
            switch (true) {
110 120
                case $definition instanceof SchemaDefinitionNode:
111 26
                    if ($schemaDef !== null) {
112 1
                        throw new Error('Must provide only one schema definition.');
113
                    }
114 26
                    $schemaDef = $definition;
115 26
                    break;
116 119
                case $definition instanceof ScalarTypeDefinitionNode:
117 119
                case $definition instanceof ObjectTypeDefinitionNode:
118 64
                case $definition instanceof InterfaceTypeDefinitionNode:
119 41
                case $definition instanceof EnumTypeDefinitionNode:
120 32
                case $definition instanceof UnionTypeDefinitionNode:
121 21
                case $definition instanceof InputObjectTypeDefinitionNode:
122 117
                    $typeName = $definition->name->value;
0 ignored issues
show
Accessing name on the interface GraphQL\Language\AST\DefinitionNode suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
123 117
                    if (! empty($this->nodeMap[$typeName])) {
124 1
                        throw new Error(sprintf('Type "%s" was defined more than once.', $typeName));
125
                    }
126 117
                    $typeDefs[]               = $definition;
127 117
                    $this->nodeMap[$typeName] = $definition;
128 117
                    break;
129 9
                case $definition instanceof DirectiveDefinitionNode:
130 7
                    $directiveDefs[] = $definition;
131 120
                    break;
132
            }
133
        }
134
135 118
        $operationTypes = $schemaDef !== null
136 24
            ? $this->getOperationTypes($schemaDef)
137
            : [
138 110
                'query'        => isset($this->nodeMap['Query']) ? 'Query' : null,
139 101
                'mutation'     => isset($this->nodeMap['Mutation']) ? 'Mutation' : null,
140 101
                'subscription' => isset($this->nodeMap['Subscription']) ? 'Subscription' : null,
141
            ];
142
143 110
        $DefinitionBuilder = new ASTDefinitionBuilder(
144 110
            $this->nodeMap,
145 110
            $this->options,
146
            static function ($typeName) {
147 4
                throw new Error('Type "' . $typeName . '" not found in document.');
148 110
            },
149 110
            $this->typeConfigDecorator
150
        );
151
152 110
        $directives = array_map(
153
            static function ($def) use ($DefinitionBuilder) {
154 7
                return $DefinitionBuilder->buildDirective($def);
155 110
            },
156 110
            $directiveDefs
157
        );
158
159
        // If specified directives were not explicitly declared, add them.
160 110
        $skip = array_reduce(
161 110
            $directives,
162
            static function ($hasSkip, $directive) {
163 7
                return $hasSkip || $directive->name === 'skip';
164 110
            }
165
        );
166 110
        if (! $skip) {
167 109
            $directives[] = Directive::skipDirective();
168
        }
169
170 110
        $include = array_reduce(
171 110
            $directives,
172
            static function ($hasInclude, $directive) {
173 110
                return $hasInclude || $directive->name === 'include';
174 110
            }
175
        );
176 110
        if (! $include) {
177 109
            $directives[] = Directive::includeDirective();
178
        }
179
180 110
        $deprecated = array_reduce(
181 110
            $directives,
182
            static function ($hasDeprecated, $directive) {
183 110
                return $hasDeprecated || $directive->name === 'deprecated';
184 110
            }
185
        );
186 110
        if (! $deprecated) {
187 109
            $directives[] = Directive::deprecatedDirective();
188
        }
189
190
        // Note: While this could make early assertions to get the correctly
191
        // typed values below, that would throw immediately while type system
192
        // validation with validateSchema() will produce more actionable results.
193
194 110
        return new Schema([
195 110
            'query'        => isset($operationTypes['query'])
196 106
                ? $DefinitionBuilder->buildType($operationTypes['query'])
197
                : null,
198 110
            'mutation'     => isset($operationTypes['mutation'])
199 7
                ? $DefinitionBuilder->buildType($operationTypes['mutation'])
200
                : null,
201 110
            'subscription' => isset($operationTypes['subscription'])
202 5
                ? $DefinitionBuilder->buildType($operationTypes['subscription'])
203
                : null,
204
            'typeLoader'   => static function ($name) use ($DefinitionBuilder) {
205 6
                return $DefinitionBuilder->buildType($name);
206 110
            },
207 110
            'directives'   => $directives,
208 110
            'astNode'      => $schemaDef,
209
            'types'        => function () use ($DefinitionBuilder) {
210 98
                $types = [];
211 98
                foreach ($this->nodeMap as $name => $def) {
212 98
                    $types[] = $DefinitionBuilder->buildType($def->name->value);
213
                }
214
215 98
                return $types;
216 110
            },
217
        ]);
218
    }
219
220
    /**
221
     * @param SchemaDefinitionNode $schemaDef
222
     *
223
     * @return string[]
224
     *
225
     * @throws Error
226
     */
227 24
    private function getOperationTypes($schemaDef)
228
    {
229 24
        $opTypes = [];
230
231 24
        foreach ($schemaDef->operationTypes as $operationType) {
232 24
            $typeName  = $operationType->type->name->value;
233 24
            $operation = $operationType->operation;
234
235 24
            if (isset($opTypes[$operation])) {
236 3
                throw new Error(sprintf('Must provide only one %s type in schema.', $operation));
237
            }
238
239 24
            if (! isset($this->nodeMap[$typeName])) {
240 5
                throw new Error(sprintf('Specified %s type "%s" not found in document.', $operation, $typeName));
241
            }
242
243 21
            $opTypes[$operation] = $typeName;
244
        }
245
246 16
        return $opTypes;
247
    }
248
}
249