Passed
Pull Request — master (#351)
by Kirill
02:28
created

Schema::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 23
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 12
nc 2
nop 8
dl 0
loc 23
rs 9.8666
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
namespace Digia\GraphQL\Schema;
4
5
use Digia\GraphQL\Error\InvariantException;
6
use Digia\GraphQL\Language\Node\ASTNodeTrait;
7
use Digia\GraphQL\Language\Node\InterfaceTypeExtensionNode;
8
use Digia\GraphQL\Language\Node\ObjectTypeExtensionNode;
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\ExtensionASTNodesTrait;
14
use Digia\GraphQL\Type\Definition\InputObjectType;
15
use Digia\GraphQL\Type\Definition\InterfaceType;
16
use GraphQL\Contracts\TypeSystem\DirectiveInterface;
17
use GraphQL\Contracts\TypeSystem\SchemaInterface;
18
use GraphQL\Contracts\TypeSystem\Type\NamedTypeInterface;
19
use Digia\GraphQL\Type\Definition\ObjectType;
20
use GraphQL\Contracts\TypeSystem\Type\ObjectTypeInterface;
21
use GraphQL\Contracts\TypeSystem\Type\TypeInterface;
22
use Digia\GraphQL\Type\Definition\UnionType;
23
use GraphQL\Contracts\TypeSystem\Type\WrappingTypeInterface;
24
use function Digia\GraphQL\Type\__Schema;
25
use function Digia\GraphQL\Util\find;
26
use GraphQL\Contracts\TypeSystem\Type\AbstractTypeInterface as AbstractTypeContract;
27
28
/**
29
 * Schema Definition
30
 *
31
 * A Schema is created by supplying the root types of each type of operation,
32
 * query and mutation (optional). A schema definition is then supplied to the
33
 * validator and executor.
34
 *
35
 * Example:
36
 *
37
 *     $MyAppSchema = GraphQLSchema([
38
 *       'query'    => $MyAppQueryRootType,
39
 *       'mutation' => $MyAppMutationRootType,
40
 *     ])
41
 *
42
 * Note: If an array of `directives` are provided to GraphQLSchema, that will be
43
 * the exact list of directives represented and allowed. If `directives` is not
44
 * provided then a default set of the specified directives (e.g. @include and
45
 * @skip) will be used. If you wish to provide *additional* directives to these
46
 * specified directives, you must explicitly declare them. Example:
47
 *
48
 *     $MyAppSchema = GraphQLSchema([
49
 *       ...
50
 *       'directives' => \array_merge(specifiedDirectives(), [$myCustomDirective]),
51
 *     ])
52
 */
53
class Schema extends Definition implements SchemaInterface
54
{
55
    use ExtensionASTNodesTrait;
56
    use ASTNodeTrait;
57
58
    /**
59
     * @var ObjectType|null
60
     */
61
    protected $queryType;
62
63
    /**
64
     * @var ObjectType|null
65
     */
66
    protected $mutationType;
67
68
    /**
69
     * @var ObjectType|null
70
     */
71
    protected $subscriptionType;
72
73
    /**
74
     * @var TypeInterface[]
75
     */
76
    protected $types = [];
77
78
    /**
79
     * @var array
80
     */
81
    protected $directives = [];
82
83
    /**
84
     * @var bool
85
     */
86
    protected $assumeValid = false;
87
88
    /**
89
     * @var TypeInterface[]
90
     */
91
    protected $typeMap = [];
92
93
    /**
94
     * @var array
95
     */
96
    protected $implementations = [];
97
98
    /**
99
     * @var NamedTypeInterface[]
100
     */
101
    protected $possibleTypesMap = [];
102
103
    /**
104
     * Schema constructor.
105
     *
106
     * @param ObjectType|null                                        $queryType
107
     * @param ObjectType|null                                        $mutationType
108
     * @param ObjectType|null                                        $subscriptionType
109
     * @param TypeInterface[]                                        $types
110
     * @param Directive[]                                            $directives
111
     * @param bool                                                   $assumeValid
112
     * @param SchemaDefinitionNode|null                              $astNode
113
     * @param ObjectTypeExtensionNode[]|InterfaceTypeExtensionNode[] $extensionASTNodes
114
     * @throws InvariantException
115
     */
116
    public function __construct(
117
        ?ObjectType $queryType,
118
        ?ObjectType $mutationType,
119
        ?ObjectType $subscriptionType,
120
        array $types,
121
        array $directives,
122
        bool $assumeValid,
123
        ?SchemaDefinitionNode $astNode,
124
        array $extensionASTNodes
125
    ) {
126
        $this->queryType         = $queryType;
127
        $this->mutationType      = $mutationType;
128
        $this->subscriptionType  = $subscriptionType;
129
        $this->types             = $types;
130
        $this->directives        = !empty($directives)
131
            ? $directives
132
            : specifiedDirectives();
133
        $this->assumeValid       = $assumeValid;
134
        $this->astNode           = $astNode;
135
        $this->extensionAstNodes = $extensionASTNodes;
136
137
        $this->buildTypeMap();
138
        $this->buildImplementations();
139
    }
140
141
    /**
142
     * @return ObjectType|null
143
     */
144
    public function getQueryType(): ?ObjectTypeInterface
145
    {
146
        return $this->queryType;
147
    }
148
149
    /**
150
     * @return ObjectType|null
151
     */
152
    public function getMutationType(): ?ObjectTypeInterface
153
    {
154
        return $this->mutationType;
155
    }
156
157
    /**
158
     * @return ObjectType|null
159
     */
160
    public function getSubscriptionType(): ?ObjectTypeInterface
161
    {
162
        return $this->subscriptionType;
163
    }
164
165
    /**
166
     * @param string $name
167
     * @return Directive|null
168
     */
169
    public function getDirective(string $name): ?DirectiveInterface
170
    {
171
        return find($this->directives, static function (Directive $directive) use ($name) {
172
            return $directive->getName() === $name;
173
        });
174
    }
175
176
    /**
177
     * @return array
178
     */
179
    public function getDirectives(): array
180
    {
181
        return $this->directives;
182
    }
183
184
    /**
185
     * @return array
186
     */
187
    public function getTypeMap(): iterable
188
    {
189
        return $this->typeMap;
190
    }
191
192
    /**
193
     * @return bool
194
     */
195
    public function getAssumeValid(): bool
196
    {
197
        return $this->assumeValid;
198
    }
199
200
    /**
201
     * @param AbstractTypeContract|AbstractTypeInterface $abstractType
202
     * @param ObjectTypeInterface $possibleType
203
     * @return bool
204
     * @throws InvariantException
205
     */
206
    public function isPossibleType(AbstractTypeContract $abstractType, ObjectTypeInterface $possibleType): bool
207
    {
208
        assert($abstractType instanceof NamedTypeInterface);
209
210
        $abstractTypeName = $abstractType->getName();
211
        $possibleTypeName = $possibleType->getName();
212
213
        if (!isset($this->possibleTypesMap[$abstractTypeName])) {
214
            $possibleTypes = $this->getPossibleTypes($abstractType);
215
216
            if ($possibleTypes === []) {
217
                throw new InvariantException(\sprintf(
218
                    'Could not find possible implementing types for %s ' .
219
                    'in schema. Check that schema.types is defined and is an array of ' .
220
                    'all possible types in the schema.',
221
                    $abstractTypeName
222
                ));
223
            }
224
225
            $this->possibleTypesMap[$abstractTypeName] = \array_reduce(
226
                $possibleTypes,
227
                function (array $map, NamedTypeInterface $type) {
228
                    $map[$type->getName()] = true;
229
                    return $map;
230
                },
231
                []
232
            );
233
        }
234
235
        return isset($this->possibleTypesMap[$abstractTypeName][$possibleTypeName]);
236
    }
237
238
    /**
239
     * @param AbstractTypeContract $abstractType
240
     * @return NamedTypeInterface[]|null
241
     * @throws InvariantException
242
     */
243
    public function getPossibleTypes(AbstractTypeContract $abstractType): iterable
244
    {
245
        if ($abstractType instanceof UnionType) {
246
            return $abstractType->getTypes();
247
        }
248
249
        return $this->implementations[$abstractType->getName()] ?? [];
250
    }
251
252
    /**
253
     * @param string $name
254
     * @return TypeInterface|null
255
     */
256
    public function getType(string $name): ?NamedTypeInterface
257
    {
258
        return $this->typeMap[$name] ?? null;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->typeMap[$name] ?? null could return the type GraphQL\Contracts\TypeSystem\Type\TypeInterface which includes types incompatible with the type-hinted return GraphQL\Contracts\TypeSy...NamedTypeInterface|null. Consider adding an additional type-check to rule them out.
Loading history...
259
    }
260
261
    /**
262
     *
263
     */
264
    protected function buildTypeMap(): void
265
    {
266
        $initialTypes = [
267
            $this->queryType,
268
            $this->mutationType,
269
            $this->subscriptionType,
270
            __Schema(), // Introspection schema
271
        ];
272
273
        if (!empty($this->types)) {
274
            $initialTypes = \array_merge($initialTypes, $this->types);
275
        }
276
277
        // Keep track of all types referenced within the schema.
278
        $typeMap = [];
279
280
        // First by deeply visiting all initial types.
281
        $typeMap = \array_reduce($initialTypes, [$this, 'typeMapReducer'], $typeMap);
282
283
        // Then by deeply visiting all directive types.
284
        $typeMap = \array_reduce($this->directives, [$this, 'typeMapDirectiveReducer'], $typeMap);
285
286
        // Storing the resulting map for reference by the schema.
287
        $this->typeMap = $typeMap;
288
    }
289
290
    /**
291
     * @throws InvariantException
292
     */
293
    protected function buildImplementations(): void
294
    {
295
        $implementations = [];
296
297
        // Keep track of all implementations by interface name.
298
        foreach ($this->typeMap as $typeName => $type) {
299
            if ($type instanceof ObjectType) {
300
                foreach ($type->getInterfaces() as $interface) {
301
                    if (!($interface instanceof InterfaceType)) {
302
                        continue;
303
                    }
304
305
                    $interfaceName = $interface->getName();
306
307
                    if (!isset($implementations[$interfaceName])) {
308
                        $implementations[$interfaceName] = [];
309
                    }
310
311
                    $implementations[$interfaceName][] = $type;
312
                }
313
            }
314
        }
315
316
        $this->implementations = $implementations;
317
    }
318
319
    /**
320
     * @param array              $map
321
     * @param TypeInterface|null $type
322
     * @return array
323
     * @throws InvariantException
324
     */
325
    protected function typeMapReducer(array $map, ?TypeInterface $type): array
326
    {
327
        if (null === $type) {
328
            return $map;
329
        }
330
331
        if ($type instanceof WrappingTypeInterface) {
332
            return $this->typeMapReducer($map, $type->getOfType());
333
        }
334
335
        if ($type instanceof NamedTypeInterface) {
336
            $typeName = $type->getName();
337
338
            if (isset($map[$typeName])) {
339
                if ($type !== $map[$typeName]) {
340
                    throw new InvariantException(\sprintf(
341
                        'Schema must contain unique named types but contains multiple types named "%s".',
342
                        $type->getName()
343
                    ));
344
                }
345
346
                return $map;
347
            }
348
349
            $map[$typeName] = $type;
350
351
            $reducedMap = $map;
352
353
            if ($type instanceof UnionType) {
354
                $reducedMap = \array_reduce($type->getTypes(), [$this, 'typeMapReducer'], $reducedMap);
355
            }
356
357
            if ($type instanceof ObjectType) {
358
                $reducedMap = \array_reduce($type->getInterfaces(), [$this, 'typeMapReducer'], $reducedMap);
359
            }
360
361
            if ($type instanceof ObjectType || $type instanceof InterfaceType) {
362
                foreach ($type->getFields() as $field) {
363
                    if ($field->hasArguments()) {
364
                        $fieldArgTypes = \array_map(function (Argument $argument) {
365
                            return $argument->getNullableType();
366
                        }, $field->getArguments());
367
368
                        $reducedMap = \array_reduce($fieldArgTypes, [$this, 'typeMapReducer'], $reducedMap);
369
                    }
370
371
                    $reducedMap = $this->typeMapReducer($reducedMap, $field->getNullableType());
372
                }
373
            }
374
375
            if ($type instanceof InputObjectType) {
376
                foreach ($type->getFields() as $field) {
377
                    $reducedMap = $this->typeMapReducer($reducedMap, $field->getNullableType());
378
                }
379
            }
380
381
            return $reducedMap;
382
        }
383
384
        return $map;
385
    }
386
387
    /**
388
     * @param array     $map
389
     * @param Directive $directive
390
     * @return array
391
     * @throws InvariantException
392
     */
393
    protected function typeMapDirectiveReducer(array $map, Directive $directive): array
394
    {
395
        if (!$directive->hasArguments()) {
396
            return $map;
397
        }
398
399
        return \array_reduce($directive->getArguments(), function ($map, Argument $argument) {
400
            return $this->typeMapReducer($map, $argument->getNullableType());
401
        }, $map);
402
    }
403
404
    /**
405
     * @return string
406
     */
407
    public function __toString(): string
408
    {
409
        return 'schema';
410
    }
411
}
412