Passed
Pull Request — master (#141)
by Christoffer
02:56
created

Schema   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 375
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 43
dl 0
loc 375
rs 8.3157
c 0
b 0
f 0

22 Methods

Rating   Name   Duplication   Size   Complexity  
A afterConfig() 0 4 1
A getQueryType() 0 3 1
A getType() 0 3 1
A getPossibleTypes() 0 7 2
A getAssumeValid() 0 3 1
B isPossibleType() 0 29 2
A getDirectives() 0 3 1
A beforeConfig() 0 3 1
A getTypeMap() 0 3 1
A getSubscriptionType() 0 3 1
A getMutationType() 0 3 1
A getDirective() 0 4 1
A typeMapDirectiveReducer() 0 10 4
B buildTypeMap() 0 24 2
A setSubscription() 0 4 1
C typeMapReducer() 0 57 12
A setTypes() 0 4 1
B buildImplementations() 0 20 5
A setQuery() 0 4 1
A setMutation() 0 4 1
A setAssumeValid() 0 4 1
A setDirectives() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Schema often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Schema, and based on these observations, apply Extract Interface, too.

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();
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

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

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
            __Schema(), // Introspection
239
        ];
240
241
        if ($this->types) {
242
            $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

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