GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Schema::getMutationType()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 0
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Type;
6
7
use Generator;
8
use GraphQL\Error\Error;
9
use GraphQL\Error\InvariantViolation;
10
use GraphQL\GraphQL;
11
use GraphQL\Language\AST\SchemaDefinitionNode;
12
use GraphQL\Language\AST\SchemaTypeExtensionNode;
13
use GraphQL\Type\Definition\AbstractType;
14
use GraphQL\Type\Definition\Directive;
15
use GraphQL\Type\Definition\InterfaceType;
16
use GraphQL\Type\Definition\ObjectType;
17
use GraphQL\Type\Definition\Type;
18
use GraphQL\Type\Definition\UnionType;
19
use GraphQL\Utils\TypeInfo;
20
use GraphQL\Utils\Utils;
21
use Traversable;
22
use function array_values;
23
use function implode;
24
use function is_array;
25
use function is_callable;
26
use function sprintf;
27
28
/**
29
 * Schema Definition (see [related docs](type-system/schema.md))
30
 *
31
 * A Schema is created by supplying the root types of each type of operation:
32
 * query, mutation (optional) and subscription (optional). A schema definition is
33
 * then supplied to the validator and executor. Usage Example:
34
 *
35
 *     $schema = new GraphQL\Type\Schema([
36
 *       'query' => $MyAppQueryRootType,
37
 *       'mutation' => $MyAppMutationRootType,
38
 *     ]);
39
 *
40
 * Or using Schema Config instance:
41
 *
42
 *     $config = GraphQL\Type\SchemaConfig::create()
43
 *         ->setQuery($MyAppQueryRootType)
44
 *         ->setMutation($MyAppMutationRootType);
45
 *
46
 *     $schema = new GraphQL\Type\Schema($config);
47
 */
48
class Schema
49
{
50
    /** @var SchemaConfig */
51
    private $config;
52
53
    /**
54
     * Contains currently resolved schema types
55
     *
56
     * @var Type[]
57
     */
58
    private $resolvedTypes = [];
59
60
    /** @var array<string, array<string, ObjectType>>|null */
61
    private $possibleTypeMap;
62
63
    /**
64
     * True when $resolvedTypes contain all possible schema types
65
     *
66
     * @var bool
67
     */
68
    private $fullyLoaded = false;
69
70
    /** @var Error[] */
71
    private $validationErrors;
72
73
    /** @var SchemaTypeExtensionNode[] */
74
    public $extensionASTNodes = [];
75
76
    /**
77
     * @param mixed[]|SchemaConfig $config
78
     *
79
     * @api
80
     */
81 891
    public function __construct($config)
82
    {
83 891
        if (is_array($config)) {
84 891
            $config = SchemaConfig::create($config);
85
        }
86
87
        // If this schema was built from a source known to be valid, then it may be
88
        // marked with assumeValid to avoid an additional type system validation.
89 890
        if ($config->getAssumeValid()) {
90
            $this->validationErrors = [];
91
        } else {
92
            // Otherwise check for common mistakes during construction to produce
93
            // clear and early error messages.
94 890
            Utils::invariant(
95 890
                $config instanceof SchemaConfig,
96 890
                'Schema constructor expects instance of GraphQL\Type\SchemaConfig or an array with keys: %s; but got: %s',
97 890
                implode(
98 890
                    ', ',
99
                    [
100 890
                        'query',
101
                        'mutation',
102
                        'subscription',
103
                        'types',
104
                        'directives',
105
                        'typeLoader',
106
                    ]
107
                ),
108 890
                Utils::getVariableType($config)
109
            );
110 890
            Utils::invariant(
111 890
                ! $config->types || is_array($config->types) || is_callable($config->types),
112 890
                '"types" must be array or callable if provided but got: ' . Utils::getVariableType($config->types)
113
            );
114 890
            Utils::invariant(
115 890
                $config->directives === null || is_array($config->directives),
116 890
                '"directives" must be Array if provided but got: ' . Utils::getVariableType($config->directives)
117
            );
118
        }
119
120 890
        $this->config            = $config;
121 890
        $this->extensionASTNodes = $config->extensionASTNodes;
122
123 890
        if ($config->query !== null) {
124 873
            $this->resolvedTypes[$config->query->name] = $config->query;
125
        }
126 890
        if ($config->mutation !== null) {
127 83
            $this->resolvedTypes[$config->mutation->name] = $config->mutation;
128
        }
129 890
        if ($config->subscription !== null) {
130 35
            $this->resolvedTypes[$config->subscription->name] = $config->subscription;
131
        }
132 890
        if (is_array($this->config->types)) {
133 157
            foreach ($this->resolveAdditionalTypes() as $type) {
134 157
                if (isset($this->resolvedTypes[$type->name])) {
135 57
                    Utils::invariant(
136 57
                        $type === $this->resolvedTypes[$type->name],
137 57
                        sprintf(
138 57
                            'Schema must contain unique named types but contains multiple types named "%s" (see http://webonyx.github.io/graphql-php/type-system/#type-registry).',
139 57
                            $type
140
                        )
141
                    );
142
                }
143 157
                $this->resolvedTypes[$type->name] = $type;
144
            }
145
        }
146 889
        $this->resolvedTypes += Type::getStandardTypes() + Introspection::getTypes();
147
148 889
        if ($this->config->typeLoader) {
149 161
            return;
150
        }
151
152
        // Perform full scan of the schema
153 777
        $this->getTypeMap();
154 773
    }
155
156
    /**
157
     * @return Generator
158
     */
159 254
    private function resolveAdditionalTypes()
160
    {
161 254
        $types = $this->config->types ?: [];
162
163 254
        if (is_callable($types)) {
164 112
            $types = $types();
165
        }
166
167 254
        if (! is_array($types) && ! $types instanceof Traversable) {
168
            throw new InvariantViolation(sprintf(
169
                'Schema types callable must return array or instance of Traversable but got: %s',
170
                Utils::getVariableType($types)
171
            ));
172
        }
173
174 254
        foreach ($types as $index => $type) {
175 254
            if (! $type instanceof Type) {
176
                throw new InvariantViolation(sprintf(
177
                    'Each entry of schema types must be instance of GraphQL\Type\Definition\Type but entry at %s is %s',
178
                    $index,
179
                    Utils::printSafe($type)
180
                ));
181
            }
182 254
            yield $type;
183
        }
184 253
    }
185
186
    /**
187
     * Returns array of all types in this schema. Keys of this array represent type names, values are instances
188
     * of corresponding type definitions
189
     *
190
     * This operation requires full schema scan. Do not use in production environment.
191
     *
192
     * @return Type[]
193
     *
194
     * @api
195
     */
196 854
    public function getTypeMap()
197
    {
198 854
        if (! $this->fullyLoaded) {
199 854
            $this->resolvedTypes = $this->collectAllTypes();
200 847
            $this->fullyLoaded   = true;
201
        }
202
203 847
        return $this->resolvedTypes;
204
    }
205
206
    /**
207
     * @return Type[]
208
     */
209 854
    private function collectAllTypes()
210
    {
211 854
        $typeMap = [];
212 854
        foreach ($this->resolvedTypes as $type) {
213 854
            $typeMap = TypeInfo::extractTypes($type, $typeMap);
214
        }
215 847
        foreach ($this->getDirectives() as $directive) {
216 847
            if (! ($directive instanceof Directive)) {
217 1
                continue;
218
            }
219
220 846
            $typeMap = TypeInfo::extractTypesFromDirectives($directive, $typeMap);
0 ignored issues
show
Bug introduced by
It seems like $typeMap can also be of type null; however, parameter $typeMap of GraphQL\Utils\TypeInfo::...ctTypesFromDirectives() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

220
            $typeMap = TypeInfo::extractTypesFromDirectives($directive, /** @scrutinizer ignore-type */ $typeMap);
Loading history...
221
        }
222
        // When types are set as array they are resolved in constructor
223 847
        if (is_callable($this->config->types)) {
224 112
            foreach ($this->resolveAdditionalTypes() as $type) {
225 112
                $typeMap = TypeInfo::extractTypes($type, $typeMap);
226
            }
227
        }
228
229 847
        return $typeMap;
230
    }
231
232
    /**
233
     * Returns a list of directives supported by this schema
234
     *
235
     * @return Directive[]
236
     *
237
     * @api
238
     */
239 868
    public function getDirectives()
240
    {
241 868
        return $this->config->directives ?? GraphQL::getStandardDirectives();
242
    }
243
244
    /**
245
     * @param string $operation
246
     *
247
     * @return ObjectType|null
248
     */
249
    public function getOperationType($operation)
250
    {
251
        switch ($operation) {
252
            case 'query':
253
                return $this->getQueryType();
254
            case 'mutation':
255
                return $this->getMutationType();
256
            case 'subscription':
257
                return $this->getSubscriptionType();
258
            default:
259
                return null;
260
        }
261
    }
262
263
    /**
264
     * Returns schema query type
265
     *
266
     * @return ObjectType
267
     *
268
     * @api
269
     */
270 771
    public function getQueryType() : ?Type
271
    {
272 771
        return $this->config->query;
273
    }
274
275
    /**
276
     * Returns schema mutation type
277
     *
278
     * @return ObjectType|null
279
     *
280
     * @api
281
     */
282 215
    public function getMutationType() : ?Type
283
    {
284 215
        return $this->config->mutation;
285
    }
286
287
    /**
288
     * Returns schema subscription
289
     *
290
     * @return ObjectType|null
291
     *
292
     * @api
293
     */
294 208
    public function getSubscriptionType() : ?Type
295
    {
296 208
        return $this->config->subscription;
297
    }
298
299
    /**
300
     * @return SchemaConfig
301
     *
302
     * @api
303
     */
304 8
    public function getConfig()
305
    {
306 8
        return $this->config;
307
    }
308
309
    /**
310
     * Returns type by its name
311
     *
312
     * @api
313
     */
314 559
    public function getType(string $name) : ?Type
315
    {
316 559
        if (! isset($this->resolvedTypes[$name])) {
317 97
            $type = $this->loadType($name);
318 92
            if (! $type) {
319 70
                return null;
320
            }
321 22
            $this->resolvedTypes[$name] = $type;
322
        }
323
324 512
        return $this->resolvedTypes[$name];
325
    }
326
327 35
    public function hasType(string $name) : bool
328
    {
329 35
        return $this->getType($name) !== null;
330
    }
331
332 98
    private function loadType(string $typeName) : ?Type
333
    {
334 98
        $typeLoader = $this->config->typeLoader;
335
336 98
        if (! $typeLoader) {
337 70
            return $this->defaultTypeLoader($typeName);
338
        }
339
340 28
        $type = $typeLoader($typeName);
341
342 26
        if (! $type instanceof Type) {
343 2
            throw new InvariantViolation(
344 2
                sprintf(
345 2
                    'Type loader is expected to return valid type "%s", but it returned %s',
346 2
                    $typeName,
347 2
                    Utils::printSafe($type)
348
                )
349
            );
350
        }
351 24
        if ($type->name !== $typeName) {
352 1
            throw new InvariantViolation(
353 1
                sprintf('Type loader is expected to return type "%s", but it returned "%s"', $typeName, $type->name)
354
            );
355
        }
356
357 23
        return $type;
358
    }
359
360 70
    private function defaultTypeLoader(string $typeName) : ?Type
361
    {
362
        // Default type loader simply fallbacks to collecting all types
363 70
        $typeMap = $this->getTypeMap();
364
365 70
        return $typeMap[$typeName] ?? null;
366
    }
367
368
    /**
369
     * Returns all possible concrete types for given abstract type
370
     * (implementations for interfaces and members of union type for unions)
371
     *
372
     * This operation requires full schema scan. Do not use in production environment.
373
     *
374
     * @param InterfaceType|UnionType $abstractType
375
     *
376
     * @return array<Type&ObjectType>
377
     *
378
     * @api
379
     */
380 18
    public function getPossibleTypes(Type $abstractType) : array
381
    {
382 18
        $possibleTypeMap = $this->getPossibleTypeMap();
383
384 18
        return array_values($possibleTypeMap[$abstractType->name] ?? []);
385
    }
386
387
    /**
388
     * @return array<string, array<string, ObjectType>>
389
     */
390 18
    private function getPossibleTypeMap()
391
    {
392 18
        if ($this->possibleTypeMap === null) {
393 18
            $this->possibleTypeMap = [];
394 18
            foreach ($this->getTypeMap() as $type) {
395 18
                if ($type instanceof ObjectType) {
396 18
                    foreach ($type->getInterfaces() as $interface) {
397 16
                        if (! ($interface instanceof InterfaceType)) {
398
                            continue;
399
                        }
400
401 18
                        $this->possibleTypeMap[$interface->name][$type->name] = $type;
402
                    }
403 18
                } elseif ($type instanceof UnionType) {
404 12
                    foreach ($type->getTypes() as $innerType) {
405 18
                        $this->possibleTypeMap[$type->name][$innerType->name] = $innerType;
406
                    }
407
                }
408
            }
409
        }
410
411 18
        return $this->possibleTypeMap;
412
    }
413
414
    /**
415
     * Returns true if object type is concrete type of given abstract type
416
     * (implementation for interfaces and members of union type for unions)
417
     *
418
     * @api
419
     */
420 49
    public function isPossibleType(AbstractType $abstractType, ObjectType $possibleType) : bool
421
    {
422 49
        if ($abstractType instanceof InterfaceType) {
423 33
            return $possibleType->implementsInterface($abstractType);
424
        }
425
426 17
        if ($abstractType instanceof UnionType) {
427 17
            return $abstractType->isPossibleType($possibleType);
428
        }
429
430
        throw InvariantViolation::shouldNotHappen();
431
    }
432
433
    /**
434
     * Returns instance of directive by name
435
     *
436
     * @api
437
     */
438 65
    public function getDirective(string $name) : ?Directive
439
    {
440 65
        foreach ($this->getDirectives() as $directive) {
441 65
            if ($directive->name === $name) {
442 65
                return $directive;
443
            }
444
        }
445
446 28
        return null;
447
    }
448
449
    public function getAstNode() : ?SchemaDefinitionNode
450
    {
451
        return $this->config->getAstNode();
452 147
    }
453
454 147
    /**
455
     * Validates schema.
456
     *
457
     * This operation requires full schema scan. Do not use in production environment.
458
     *
459
     * @throws InvariantViolation
460
     *
461
     * @api
462
     */
463
    public function assertValid()
464
    {
465
        $errors = $this->validate();
466 11
467
        if ($errors) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $errors of type GraphQL\Error\Error[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
468 11
            throw new InvariantViolation(implode("\n\n", $this->validationErrors));
469
        }
470 11
471
        $internalTypes = Type::getStandardTypes() + Introspection::getTypes();
472
        foreach ($this->getTypeMap() as $name => $type) {
473
            if (isset($internalTypes[$name])) {
474 11
                continue;
475 11
            }
476 11
477 1
            $type->assertValid();
478
479
            // Make sure type loader returns the same instance as registered in other places of schema
480 11
            if (! $this->config->typeLoader) {
481
                continue;
482
            }
483 11
484 10
            Utils::invariant(
485
                $this->loadType($name) === $type,
486
                sprintf(
487 1
                    'Type loader returns different instance for %s than field/argument definitions. Make sure you always return the same instance for the same type name.',
488 1
                    $name
489 1
                )
490 1
            );
491 1
        }
492
    }
493
494
    /**
495 1
     * Validates schema.
496
     *
497
     * This operation requires full schema scan. Do not use in production environment.
498
     *
499
     * @return InvariantViolation[]|Error[]
500
     *
501
     * @api
502
     */
503
    public function validate()
504
    {
505
        // If this Schema has already been validated, return the previous results.
506 89
        if ($this->validationErrors !== null) {
507
            return $this->validationErrors;
508
        }
509 89
        // Validate the schema, producing a list of errors.
510
        $context = new SchemaValidationContext($this);
511
        $context->validateRootTypes();
512
        $context->validateDirectives();
513 89
        $context->validateTypes();
514 89
515 89
        // Persist the results of validation before returning to ensure validation
516 89
        // does not run multiple times for this schema.
517
        $this->validationErrors = $context->getErrors();
518
519
        return $this->validationErrors;
520 89
    }
521
}
522