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