Completed
Pull Request — master (#133)
by Christoffer
02:26
created

Schema::getQuery()   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\Config\ConfigObject;
6
use Digia\GraphQL\Error\InvariantException;
7
use Digia\GraphQL\Language\Node\NameAwareInterface;
8
use Digia\GraphQL\Language\Node\NodeTrait;
9
use Digia\GraphQL\Language\Node\SchemaDefinitionNode;
10
use Digia\GraphQL\Type\Definition\AbstractTypeInterface;
11
use Digia\GraphQL\Type\Definition\Argument;
12
use Digia\GraphQL\Type\Definition\Directive;
13
use Digia\GraphQL\Type\Definition\DirectiveInterface;
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\TypeInterface;
18
use Digia\GraphQL\Type\Definition\UnionType;
19
use Digia\GraphQL\Type\Definition\WrappingTypeInterface;
20
use function Digia\GraphQL\Util\find;
21
use function Digia\GraphQL\Util\invariant;
22
23
/**
24
 * Schema Definition
25
 * A Schema is created by supplying the root types of each type of operation,
26
 * query and mutation (optional). A schema definition is then supplied to the
27
 * validator and executor.
28
 * Example:
29
 *     const MyAppSchema = new GraphQLSchema({
30
 *       query: MyAppQueryRootType,
31
 *       mutation: MyAppMutationRootType,
32
 *     })
33
 * Note: If an array of `directives` are provided to GraphQLSchema, that will be
34
 * the exact list of directives represented and allowed. If `directives` is not
35
 * provided then a default set of the specified directives (e.g. @include and
36
 * @skip) will be used. If you wish to provide *additional* directives to these
37
 * specified directives, you must explicitly declare them. Example:
38
 *     const MyAppSchema = new GraphQLSchema({
39
 *       ...
40
 *       directives: specifiedDirectives.concat([ myCustomDirective ]),
41
 *     })
42
 */
43
44
/**
45
 * Class Schema
46
 *
47
 * @package Digia\GraphQL\Type
48
 * @property SchemaDefinitionNode $astNode
49
 */
50
class Schema extends ConfigObject implements SchemaInterface
51
{
52
53
    use NodeTrait;
54
55
    /**
56
     * @var TypeInterface|null
57
     */
58
    protected $query;
59
60
    /**
61
     * @var TypeInterface|null
62
     */
63
    protected $mutation;
64
65
    /**
66
     * @var TypeInterface|null
67
     */
68
    protected $subscription;
69
70
    /**
71
     * @var TypeInterface
72
     */
73
    protected $types = [];
74
75
    /**
76
     * @var array
77
     */
78
    protected $directives = [];
79
80
    /**
81
     * @var bool
82
     */
83
    protected $assumeValid = false;
84
85
    /**
86
     * @var array
87
     */
88
    protected $typeMap = [];
89
90
    /**
91
     * @var array
92
     */
93
    protected $implementations = [];
94
95
    /**
96
     * @var array
97
     */
98
    protected $possibleTypesMap = [];
99
100
    /**
101
     * @inheritdoc
102
     */
103
    public function getQueryType(): ?TypeInterface
104
    {
105
        return $this->query;
106
    }
107
108
    /**
109
     * @inheritdoc
110
     */
111
    public function getMutationType(): ?TypeInterface
112
    {
113
        return $this->mutation;
114
    }
115
116
    /**
117
     * @inheritdoc
118
     */
119
    public function getSubscriptionType(): ?TypeInterface
120
    {
121
        return $this->subscription;
122
    }
123
124
    /**
125
     * @inheritdoc
126
     */
127
    public function getDirective(string $name): ?Directive
128
    {
129
        return find($this->directives, function (Directive $directive) use ($name) {
130
            return $directive->getName() === $name;
131
        });
132
    }
133
134
    /**
135
     * @inheritdoc
136
     */
137
    public function getDirectives(): array
138
    {
139
        return $this->directives;
140
    }
141
142
    /**
143
     * @inheritdoc
144
     */
145
    public function getTypeMap(): array
146
    {
147
        return $this->typeMap;
148
    }
149
150
    /**
151
     * @inheritdoc
152
     */
153
    public function getAssumeValid(): bool
154
    {
155
        return $this->assumeValid;
156
    }
157
158
    /**
159
     * @inheritdoc
160
     */
161
    public function isPossibleType(AbstractTypeInterface $abstractType, TypeInterface $possibleType): bool
162
    {
163
        /** @noinspection PhpUndefinedMethodInspection */
164
        $abstractTypeName = $abstractType->getName();
165
        /** @noinspection PhpUndefinedMethodInspection */
166
        $possibleTypeName = $possibleType->getName();
0 ignored issues
show
Bug introduced by
The method getName() does not exist on Digia\GraphQL\Type\Definition\TypeInterface. It seems like you code against a sub-type of said class. However, the method does not exist in Digia\GraphQL\Type\Defin...n\WrappingTypeInterface. Are you sure you never get one of those? ( Ignorable by Annotation )

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

166
        /** @scrutinizer ignore-call */ 
167
        $possibleTypeName = $possibleType->getName();
Loading history...
167
168
        if (!isset($this->possibleTypesMap[$abstractTypeName])) {
169
            $possibleTypes = $this->getPossibleTypes($abstractType);
170
171
            invariant(
172
                \is_array($possibleTypes),
173
                \sprintf(
174
                    'Could not find possible implementing types for %s ' .
175
                    'in schema. Check that schema.types is defined and is an array of ' .
176
                    'all possible types in the schema.',
177
                    $abstractTypeName
178
                )
179
            );
180
181
            $this->possibleTypesMap[$abstractTypeName] = \array_reduce($possibleTypes,
182
                function (array $map, TypeInterface $type) {
183
                    /** @var NameAwareInterface $type */
184
                    $map[$type->getName()] = true;
185
                    return $map;
186
                }, []);
187
        }
188
189
        return isset($this->possibleTypesMap[$abstractTypeName][$possibleTypeName]);
190
    }
191
192
    /**
193
     * @inheritdoc
194
     */
195
    public function getPossibleTypes(AbstractTypeInterface $abstractType): ?array
196
    {
197
        if ($abstractType instanceof UnionType) {
198
            return $abstractType->getTypes();
199
        }
200
201
        return $this->implementations[$abstractType->getName()] ?? null;
202
    }
203
204
    /**
205
     * @inheritdoc
206
     */
207
    public function getType(string $name): ?TypeInterface
208
    {
209
        return $this->typeMap[$name] ?? null;
210
    }
211
212
    /**
213
     * @inheritdoc
214
     */
215
    protected function beforeConfig(): void
216
    {
217
        $this->setDirectives(specifiedDirectives());
218
    }
219
220
    /**
221
     * @throws InvariantException
222
     */
223
    protected function afterConfig(): void
224
    {
225
        $this->buildTypeMap();
226
        $this->buildImplementations();
227
    }
228
229
    /**
230
     *
231
     */
232
    protected function buildTypeMap(): void
233
    {
234
        $initialTypes = [
235
            $this->query,
236
            $this->mutation,
237
            $this->subscription,
238
        ];
239
240
        if ($this->types) {
241
            $initialTypes = \array_merge($initialTypes, $this->types);
0 ignored issues
show
Bug introduced by
$this->types of type Digia\GraphQL\Type\Definition\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

241
            $initialTypes = \array_merge($initialTypes, /** @scrutinizer ignore-type */ $this->types);
Loading history...
242
        }
243
244
        // Keep track of all types referenced within the schema.
245
        $typeMap = [];
246
247
        // First by deeply visiting all initial types.
248
        $typeMap = \array_reduce($initialTypes, [$this, 'typeMapReducer'], $typeMap);
249
250
        // Then by deeply visiting all directive types.
251
        $typeMap = \array_reduce($this->directives, [$this, 'typeMapDirectiveReducer'], $typeMap);
252
253
        // Storing the resulting map for reference by the schema.
254
        $this->typeMap = $typeMap;
255
    }
256
257
    /**
258
     * @throws InvariantException
259
     */
260
    protected function buildImplementations()
261
    {
262
        $implementations = [];
263
264
        // Keep track of all implementations by interface name.
265
        foreach ($this->typeMap as $typeName => $type) {
266
            if ($type instanceof ObjectType) {
267
                foreach ($type->getInterfaces() as $interface) {
268
                    $interfaceName = $interface->getName();
269
270
                    if (!isset($implementations[$interfaceName])) {
271
                        $implementations[$interfaceName] = [];
272
                    }
273
274
                    $implementations[$interfaceName][] = $type;
275
                }
276
            }
277
        }
278
279
        $this->implementations = $implementations;
280
    }
281
282
    /**
283
     * @param TypeInterface|null $query
284
     * @return Schema
285
     */
286
    protected function setQuery(?TypeInterface $query): Schema
287
    {
288
        $this->query = $query;
289
        return $this;
290
    }
291
292
    /**
293
     * @param TypeInterface|null $mutation
294
     * @return Schema
295
     */
296
    protected function setMutation(?TypeInterface $mutation): Schema
297
    {
298
        $this->mutation = $mutation;
299
        return $this;
300
    }
301
302
    /**
303
     * @param TypeInterface|null $subscription
304
     * @return Schema
305
     */
306
    protected function setSubscription(?TypeInterface $subscription): Schema
307
    {
308
        $this->subscription = $subscription;
309
        return $this;
310
    }
311
312
    /**
313
     * @param array $types
314
     * @return Schema
315
     */
316
    protected function setTypes(array $types): Schema
317
    {
318
        $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\Definition\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...
319
        return $this;
320
    }
321
322
    /**
323
     * @param DirectiveInterface[] $directives
324
     * @return Schema
325
     */
326
    protected function setDirectives(array $directives): Schema
327
    {
328
        $this->directives = $directives;
329
        return $this;
330
    }
331
332
    /**
333
     * @param bool $assumeValid
334
     * @return Schema
335
     */
336
    protected function setAssumeValid(bool $assumeValid): Schema
337
    {
338
        $this->assumeValid = $assumeValid;
339
        return $this;
340
    }
341
342
    /**
343
     * @param array              $map
344
     * @param TypeInterface|NameAwareInterface|null $type
345
     * @return array
346
     * @throws InvariantException
347
     */
348
    protected function typeMapReducer(array $map, ?TypeInterface $type): array
349
    {
350
        if (null === $type) {
351
            return $map;
352
        }
353
354
        if ($type instanceof WrappingTypeInterface) {
355
            return $this->typeMapReducer($map, $type->getOfType());
356
        }
357
358
        $typeName = $type->getName();
359
360
        if (isset($map[$typeName])) {
361
            invariant(
362
                $type === $map[$typeName],
363
                \sprintf(
364
                    'Schema must contain unique named types but contains multiple types named "%s".',
365
                    $type->getName()
366
                )
367
            );
368
369
            return $map;
370
        }
371
372
        $map[$typeName] = $type;
373
374
        $reducedMap = $map;
375
376
        if ($type instanceof UnionType) {
377
            $reducedMap = \array_reduce($type->getTypes(), [$this, 'typeMapReducer'], $reducedMap);
378
        }
379
380
        if ($type instanceof ObjectType) {
381
            $reducedMap = \array_reduce($type->getInterfaces(), [$this, 'typeMapReducer'], $reducedMap);
382
        }
383
384
        if ($type instanceof ObjectType || $type instanceof InterfaceType) {
385
            foreach ($type->getFields() as $field) {
386
                if ($field->hasArguments()) {
387
                    $fieldArgTypes = \array_map(function (Argument $argument) {
388
                        return $argument->getType();
389
                    }, $field->getArguments());
390
391
                    $reducedMap = \array_reduce($fieldArgTypes, [$this, 'typeMapReducer'], $reducedMap);
392
                }
393
394
                $reducedMap = $this->typeMapReducer($reducedMap, $field->getType());
395
            }
396
        }
397
398
        if ($type instanceof InputObjectType) {
399
            foreach ($type->getFields() as $field) {
400
                $reducedMap = $this->typeMapReducer($reducedMap, $field->getType());
401
            }
402
        }
403
404
        return $reducedMap;
405
    }
406
407
    /**
408
     * Note: We do not type-hint the `$directive`, because we want the `SchemaValidator` to catch these errors.
409
     *
410
     * @param array      $map
411
     * @param mixed|null $directive
412
     * @return array
413
     */
414
    protected function typeMapDirectiveReducer(array $map, $directive): array
415
    {
416
        if (!($directive instanceof Directive) ||
417
            ($directive instanceof Directive && !$directive->hasArguments())) {
418
            return $map;
419
        }
420
421
        return \array_reduce($directive->getArguments(), function ($map, Argument $argument) {
422
            return $this->typeMapReducer($map, $argument->getType());
423
        }, $map);
424
    }
425
426
}
427