Passed
Push — master ( cea2fe...bda88f )
by Christoffer
02:15
created

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

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

163
        /** @scrutinizer ignore-call */ 
164
        $abstractTypeName = $abstractType->getName();
Loading history...
164
        /** @noinspection PhpUndefinedMethodInspection */
165
        $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\AbstractTypeInterface or 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

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

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