Passed
Pull Request — master (#351)
by Kirill
02:29
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 ObjectTypeInterface|null
60
     */
61
    protected $queryType;
62
63
    /**
64
     * @var ObjectTypeInterface|null
65
     */
66
    protected $mutationType;
67
68
    /**
69
     * @var ObjectTypeInterface|null
70
     */
71
    protected $subscriptionType;
72
73
    /**
74
     * @var array|TypeInterface[]
75
     */
76
    protected $types = [];
77
78
    /**
79
     * @var array|DirectiveInterface[]
80
     */
81
    protected $directives = [];
82
83
    /**
84
     * @var bool
85
     */
86
    protected $assumeValid = false;
87
88
    /**
89
     * @var array|NamedTypeInterface[]
90
     */
91
    protected $typeMap = [];
92
93
    /**
94
     * @var array|ObjectTypeInterface[][]
95
     */
96
    protected $implementations = [];
97
98
    /**
99
     * @var array|NamedTypeInterface[]
100
     */
101
    protected $possibleTypesMap = [];
102
103
    /**
104
     * Schema constructor.
105
     *
106
     * @param ObjectTypeInterface|null                               $queryType
107
     * @param ObjectTypeInterface|null                               $mutationType
108
     * @param ObjectTypeInterface|null                               $subscriptionType
109
     * @param TypeInterface[]                                        $types
110
     * @param DirectiveInterface[]                                   $directives
111
     * @param bool                                                   $assumeValid
112
     * @param SchemaDefinitionNode|null                              $astNode
113
     * @param ObjectTypeExtensionNode[]|InterfaceTypeExtensionNode[] $extensionASTNodes
114
     * @throws InvariantException
115
     */
116
    public function __construct(
117
        ?ObjectTypeInterface $queryType,
118
        ?ObjectTypeInterface $mutationType,
119
        ?ObjectTypeInterface $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 ObjectTypeInterface|null
143
     */
144
    public function getQueryType(): ?ObjectTypeInterface
145
    {
146
        return $this->queryType;
147
    }
148
149
    /**
150
     * @return ObjectTypeInterface|null
151
     */
152
    public function getMutationType(): ?ObjectTypeInterface
153
    {
154
        return $this->mutationType;
155
    }
156
157
    /**
158
     * @return ObjectTypeInterface|null
159
     */
160
    public function getSubscriptionType(): ?ObjectTypeInterface
161
    {
162
        return $this->subscriptionType;
163
    }
164
165
    /**
166
     * @param string $name
167
     * @return DirectiveInterface|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 DirectiveInterface[]
178
     */
179
    public function getDirectives(): array
180
    {
181
        return $this->directives;
182
    }
183
184
    /**
185
     * @return NamedTypeInterface[]
186
     */
187
    public function getTypeMap(): array
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
                static function (array $map, NamedTypeInterface $type) {
228
                    $map[$type->getName()] = true;
229
230
                    return $map;
231
                },
232
                []
233
            );
234
        }
235
236
        return isset($this->possibleTypesMap[$abstractTypeName][$possibleTypeName]);
237
    }
238
239
    /**
240
     * @param AbstractTypeContract $abstractType
241
     * @return array|ObjectTypeInterface[]
242
     * @throws InvariantException
243
     */
244
    public function getPossibleTypes(AbstractTypeContract $abstractType): array
245
    {
246
        assert($abstractType instanceof NamedTypeInterface);
247
248
        if ($abstractType instanceof UnionType) {
249
            return $abstractType->getTypes();
250
        }
251
252
        return $this->implementations[$abstractType->getName()] ?? [];
253
    }
254
255
    /**
256
     * @param string $name
257
     * @return NamedTypeInterface|null
258
     */
259
    public function getType(string $name): ?NamedTypeInterface
260
    {
261
        return $this->typeMap[$name] ?? null;
262
    }
263
264
    /**
265
     * @return void
266
     */
267
    protected function buildTypeMap(): void
268
    {
269
        $initialTypes = [
270
            $this->queryType,
271
            $this->mutationType,
272
            $this->subscriptionType,
273
            __Schema(), // Introspection schema
274
        ];
275
276
        if (!empty($this->types)) {
277
            $initialTypes = \array_merge($initialTypes, $this->types);
278
        }
279
280
        // Keep track of all types referenced within the schema.
281
        $typeMap = [];
282
283
        // First by deeply visiting all initial types.
284
        $typeMap = \array_reduce($initialTypes, [$this, 'typeMapReducer'], $typeMap);
285
286
        // Then by deeply visiting all directive types.
287
        $typeMap = \array_reduce($this->directives, [$this, 'typeMapDirectiveReducer'], $typeMap);
288
289
        // Storing the resulting map for reference by the schema.
290
        $this->typeMap = $typeMap;
291
    }
292
293
    /**
294
     * @throws InvariantException
295
     */
296
    protected function buildImplementations(): void
297
    {
298
        $implementations = [];
299
300
        // Keep track of all implementations by interface name.
301
        foreach ($this->typeMap as $typeName => $type) {
302
            if ($type instanceof ObjectType) {
303
                foreach ($type->getInterfaces() as $interface) {
304
                    if (!($interface instanceof InterfaceType)) {
305
                        continue;
306
                    }
307
308
                    $interfaceName = $interface->getName();
309
310
                    if (!isset($implementations[$interfaceName])) {
311
                        $implementations[$interfaceName] = [];
312
                    }
313
314
                    $implementations[$interfaceName][] = $type;
315
                }
316
            }
317
        }
318
319
        $this->implementations = $implementations;
320
    }
321
322
    /**
323
     * @param array              $map
324
     * @param TypeInterface|null $type
325
     * @return array
326
     * @throws InvariantException
327
     */
328
    protected function typeMapReducer(array $map, ?TypeInterface $type): array
329
    {
330
        if (null === $type) {
331
            return $map;
332
        }
333
334
        if ($type instanceof WrappingTypeInterface) {
335
            return $this->typeMapReducer($map, $type->getOfType());
336
        }
337
338
        if ($type instanceof NamedTypeInterface) {
339
            $typeName = $type->getName();
340
341
            if (isset($map[$typeName])) {
342
                if ($type !== $map[$typeName]) {
343
                    throw new InvariantException(\sprintf(
344
                        'Schema must contain unique named types but contains multiple types named "%s".',
345
                        $type->getName()
346
                    ));
347
                }
348
349
                return $map;
350
            }
351
352
            $map[$typeName] = $type;
353
354
            $reducedMap = $map;
355
356
            if ($type instanceof UnionType) {
357
                $reducedMap = \array_reduce($type->getTypes(), [$this, 'typeMapReducer'], $reducedMap);
358
            }
359
360
            if ($type instanceof ObjectType) {
361
                $reducedMap = \array_reduce($type->getInterfaces(), [$this, 'typeMapReducer'], $reducedMap);
362
            }
363
364
            if ($type instanceof ObjectType || $type instanceof InterfaceType) {
365
                foreach ($type->getFields() as $field) {
366
                    if ($field->hasArguments()) {
367
                        $fieldArgTypes = \array_map(function (Argument $argument) {
368
                            return $argument->getNullableType();
369
                        }, $field->getArguments());
370
371
                        $reducedMap = \array_reduce($fieldArgTypes, [$this, 'typeMapReducer'], $reducedMap);
372
                    }
373
374
                    $reducedMap = $this->typeMapReducer($reducedMap, $field->getNullableType());
375
                }
376
            }
377
378
            if ($type instanceof InputObjectType) {
379
                foreach ($type->getFields() as $field) {
380
                    $reducedMap = $this->typeMapReducer($reducedMap, $field->getNullableType());
381
                }
382
            }
383
384
            return $reducedMap;
385
        }
386
387
        return $map;
388
    }
389
390
    /**
391
     * @param array     $map
392
     * @param Directive $directive
393
     * @return array
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