Completed
Push — master ( 5d1025...4a04fa )
by Christoffer
07:28
created

Schema::getAssumeValid()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 0
1
<?php
2
3
namespace Digia\GraphQL\Type;
4
5
use Digia\GraphQL\ConfigObject;
6
use Digia\GraphQL\Language\AST\Node\NodeTrait;
7
use Digia\GraphQL\Language\AST\Node\SchemaDefinitionNode;
8
use Digia\GraphQL\Type\Contract\SchemaInterface;
9
use Digia\GraphQL\Type\Definition\Argument;
10
use Digia\GraphQL\Type\Definition\Contract\AbstractTypeInterface;
11
use Digia\GraphQL\Type\Definition\Contract\DirectiveInterface;
12
use Digia\GraphQL\Type\Definition\Contract\TypeInterface;
13
use Digia\GraphQL\Type\Definition\Contract\WrappingTypeInterface;
14
use Digia\GraphQL\Type\Definition\InputObjectType;
15
use Digia\GraphQL\Type\Definition\InterfaceType;
16
use Digia\GraphQL\Type\Definition\ObjectType;
17
use Digia\GraphQL\Type\Definition\UnionType;
18
use function Digia\GraphQL\Util\invariant;
19
20
/**
21
 * Schema Definition
22
 * A Schema is created by supplying the root types of each type of operation,
23
 * query and mutation (optional). A schema definition is then supplied to the
24
 * validator and executor.
25
 * Example:
26
 *     const MyAppSchema = new GraphQLSchema({
27
 *       query: MyAppQueryRootType,
28
 *       mutation: MyAppMutationRootType,
29
 *     })
30
 * Note: If an array of `directives` are provided to GraphQLSchema, that will be
31
 * the exact list of directives represented and allowed. If `directives` is not
32
 * provided then a default set of the specified directives (e.g. @include and
33
 * @skip) will be used. If you wish to provide *additional* directives to these
34
 * specified directives, you must explicitly declare them. Example:
35
 *     const MyAppSchema = new GraphQLSchema({
36
 *       ...
37
 *       directives: specifiedDirectives.concat([ myCustomDirective ]),
38
 *     })
39
 */
40
41
/**
42
 * Class Schema
43
 *
44
 * @package Digia\GraphQL\Type
45
 * @property SchemaDefinitionNode $astNode
46
 */
47
class Schema extends ConfigObject implements SchemaInterface
48
{
49
50
    use NodeTrait;
51
52
    /**
53
     * @var ObjectType
54
     */
55
    private $query;
56
57
    /**
58
     * @var ObjectType
59
     */
60
    private $mutation;
61
62
    /**
63
     * @var ObjectType
64
     */
65
    private $subscription;
66
67
    /**
68
     * @var TypeInterface
69
     */
70
    private $types = [];
71
72
    /**
73
     * @var array
74
     */
75
    private $directives = [];
76
77
    /**
78
     * @var bool
79
     */
80
    private $assumeValid = false;
81
82
    /**
83
     * @var array
84
     */
85
    private $_typeMap = [];
86
87
    /**
88
     * @var array
89
     */
90
    private $_implementations = [];
91
92
    /**
93
     * @var array
94
     */
95
    private $_possibleTypeMap = [];
96
97
    /**
98
     * @inheritdoc
99
     */
100
    public function getQuery(): ObjectType
101
    {
102
        return $this->query;
103
    }
104
105
    /**
106
     * @inheritdoc
107
     */
108
    public function getMutation(): ObjectType
109
    {
110
        return $this->mutation;
111
    }
112
113
    /**
114
     * @inheritdoc
115
     */
116
    public function getSubscription(): ObjectType
117
    {
118
        return $this->subscription;
119
    }
120
121
    /**
122
     * @inheritdoc
123
     */
124
    public function getDirectives(): array
125
    {
126
        return $this->directives;
127
    }
128
129
    /**
130
     * @inheritdoc
131
     */
132
    public function getTypeMap(): array
133
    {
134
        return $this->_typeMap;
135
    }
136
137
    /**
138
     * @inheritdoc
139
     */
140
    public function getAssumeValid(): bool
141
    {
142
        return $this->assumeValid;
143
    }
144
145
    /**
146
     * @inheritdoc
147
     * @throws \Exception
148
     */
149
    public function isPossibleType(AbstractTypeInterface $abstractType, TypeInterface $possibleType): bool
150
    {
151
        $abstractTypeName = $abstractType->getName();
152
        $possibleTypeName = $possibleType->getName();
153
154
        if (!isset($this->_possibleTypeMap[$abstractTypeName])) {
155
            $possibleTypes = $this->getPossibleTypes($abstractType);
156
157
            invariant(
158
                is_array($possibleTypes),
159
                sprintf(
160
                    'Could not find possible implementing types for %s ' .
161
                    'in schema. Check that schema.types is defined and is an array of ' .
162
                    'all possible types in the schema.',
163
                    $abstractTypeName
164
                )
165
            );
166
167
            $this->_possibleTypeMap = array_reduce($possibleTypes, function (array $map, TypeInterface $type) {
168
                $map[$type->getName()] = true;
169
                return $map;
170
            });
171
        }
172
173
        return isset($this->_possibleTypeMap[$abstractTypeName][$possibleTypeName]);
174
    }
175
176
    /**
177
     * @inheritdoc
178
     * @throws \Exception
179
     */
180
    public function getPossibleTypes(AbstractTypeInterface $abstractType): ?array
181
    {
182
        if ($abstractType instanceof UnionType) {
183
            return $abstractType->getTypes();
184
        }
185
186
        return $this->_implementations[$abstractType->getName()] ?? null;
187
    }
188
189
    /**
190
     * @inheritdoc
191
     */
192
    public function getType(string $name): ?TypeInterface
193
    {
194
        return $this->_typeMap[$name] ?? null;
195
    }
196
197
    /**
198
     * @inheritdoc
199
     */
200
    protected function beforeConfig(): void
201
    {
202
        $this->setDirectives(specifiedDirectives());
203
    }
204
205
    /**
206
     * @throws \Exception
207
     */
208
    protected function afterConfig(): void
209
    {
210
        $this->buildTypeMap();
211
        $this->buildImplementations();
212
    }
213
214
    /**
215
     *
216
     */
217
    protected function buildTypeMap(): void
218
    {
219
        $initialTypes = [
220
            $this->query,
221
            $this->mutation,
222
            $this->subscription,
223
        ];
224
225
        if ($this->types) {
226
            $initialTypes = array_merge($initialTypes, $this->types);
0 ignored issues
show
Bug introduced by
$this->types of type Digia\GraphQL\Type\Defin...\Contract\TypeInterface is incompatible with the type null|array expected by parameter $array2 of array_merge(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

226
            $initialTypes = array_merge($initialTypes, /** @scrutinizer ignore-type */ $this->types);
Loading history...
227
        }
228
229
        // Keep track of all types referenced within the schema.
230
        $typeMap = [];
231
232
        // First by deeply visiting all initial types.
233
        $typeMap = array_reduce($initialTypes, 'Digia\GraphQL\Type\typeMapReducer', $typeMap);
234
235
        // Then by deeply visiting all directive types.
236
        $typeMap = array_reduce($this->directives, 'Digia\GraphQL\Type\typeMapDirectiveReducer', $typeMap);
237
238
        // Storing the resulting map for reference by the schema.
239
        $this->_typeMap = $typeMap;
240
    }
241
242
    /**
243
     * @throws \Exception
244
     */
245
    protected function buildImplementations()
246
    {
247
        $implementations = [];
248
249
        // Keep track of all implementations by interface name.
250
        foreach ($this->_typeMap as $typeName => $type) {
251
            if ($type instanceof ObjectType) {
252
                foreach ($type->getInterfaces() as $interface) {
253
                    $interfaceName = $interface->getName();
254
255
                    if (!isset($implementations[$interfaceName])) {
256
                        $implementations[$interfaceName] = [];
257
                    }
258
259
                    $implementations[$interfaceName][] = $type;
260
                }
261
            }
262
        }
263
264
        $this->_implementations = $implementations;
265
    }
266
267
    /**
268
     * @param ObjectType $query
269
     * @return Schema
270
     */
271
    protected function setQuery(ObjectType $query): Schema
272
    {
273
        $this->query = $query;
274
275
        return $this;
276
    }
277
278
    /**
279
     * @param ObjectType $mutation
280
     * @return Schema
281
     */
282
    protected function setMutation(ObjectType $mutation): Schema
283
    {
284
        $this->mutation = $mutation;
285
286
        return $this;
287
    }
288
289
    /**
290
     * @param ObjectType $subscription
291
     * @return Schema
292
     */
293
    protected function setSubscription(ObjectType $subscription): Schema
294
    {
295
        $this->subscription = $subscription;
296
297
        return $this;
298
    }
299
300
    /**
301
     * @param array $types
302
     * @return Schema
303
     */
304
    protected function setTypes(array $types): Schema
305
    {
306
        $this->types = $types;
0 ignored issues
show
Documentation Bug introduced by
It seems like $types of type array is incompatible with the declared type Digia\GraphQL\Type\Defin...\Contract\TypeInterface of property $types.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
307
308
        return $this;
309
    }
310
311
    /**
312
     * @param DirectiveInterface[] $directives
313
     * @return Schema
314
     */
315
    protected function setDirectives(array $directives): Schema
316
    {
317
        $this->directives = $directives;
318
319
        return $this;
320
    }
321
322
    /**
323
     * @param bool $assumeValid
324
     * @return Schema
325
     */
326
    protected function setAssumeValid(bool $assumeValid): Schema
327
    {
328
        $this->assumeValid = $assumeValid;
329
330
        return $this;
331
    }
332
}
333
334
/**
335
 * @param array              $map
336
 * @param null|TypeInterface $type
337
 * @return array
338
 * @throws \Exception
339
 */
340
function typeMapReducer(array $map, ?TypeInterface $type): array
341
{
342
    if (null === $type) {
343
        return $map;
344
    }
345
346
    if ($type instanceof WrappingTypeInterface) {
347
        return typeMapReducer($map, $type->getOfType());
348
    }
349
350
    $typeName = $type->getName();
351
352
    if (isset($map[$typeName])) {
353
        invariant(
354
            $map[$typeName] === $type,
355
            sprintf(
356
                'Schema must contain unique named types but contains multiple types named "%s".',
357
                $type->getName()
358
            )
359
        );
360
361
        return $map;
362
    }
363
364
    $map[$typeName] = $type;
365
366
    $reducedMap = $map;
367
368
    if ($type instanceof UnionType) {
369
        $reducedMap = array_reduce($type->getTypes(), 'Digia\GraphQL\Type\typeMapReducer', $reducedMap);
370
    }
371
372
    if ($type instanceof ObjectType) {
373
        $reducedMap = array_reduce($type->getInterfaces(), 'Digia\GraphQL\Type\typeMapReducer', $reducedMap);
374
    }
375
376
    if ($type instanceof ObjectType || $type instanceof InterfaceType) {
377
        foreach ($type->getFields() as $field) {
378
            if ($field->hasArgs()) {
379
                $fieldArgTypes = array_map(function (Argument $argument) {
380
                    return $argument->getType();
381
                }, $field->getArgs());
382
383
                $reducedMap = array_reduce($fieldArgTypes, 'Digia\GraphQL\Type\typeMapReducer', $reducedMap);
384
            }
385
386
            $reducedMap = typeMapReducer($reducedMap, $field->getType());
387
        }
388
    }
389
390
    if ($type instanceof InputObjectType) {
391
        foreach ($type->getFields() as $field) {
392
            $reducedMap = typeMapReducer($reducedMap, $field->getType());
393
        }
394
    }
395
396
    return $reducedMap;
397
}
398
399
/**
400
 * @param array                    $map
401
 * @param null| DirectiveInterface $directive
402
 * @return array
403
 */
404
function typeMapDirectiveReducer(array $map, ?DirectiveInterface $directive): array
405
{
406
    if (!$directive || !$directive->hasArgs()) {
407
        return $map;
408
    }
409
410
    return array_reduce($directive->getArgs(), function ($map, Argument $argument) {
411
        return typeMapReducer($map, $argument->getType());
412
    }, $map);
413
}
414