Schema::__construct()   A
last analyzed

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