ExtensionContext   B
last analyzed

Complexity

Total Complexity 46

Size/Duplication

Total Lines 401
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 146
c 1
b 0
f 0
dl 0
loc 401
rs 8.72
wmc 46

19 Methods

Rating   Name   Duplication   Size   Complexity  
A getExtendedTypes() 0 9 1
A getExtendedQueryType() 0 7 2
A getExtendedDirectives() 0 13 2
A extendFieldMap() 0 42 5
A isSchemaExtended() 0 7 4
A getExtendedMutationType() 0 7 2
A extendImplementedInterfaces() 0 21 3
A getExtendedOperationTypes() 0 22 4
A setDefinitionBuilder() 0 4 1
A extendType() 0 22 5
A extendObjectType() 0 21 2
A extendUnionType() 0 10 1
A extendInterfaceType() 0 18 2
A extendFieldType() 0 11 3
A __construct() 0 3 1
A extendExtensionASTNodes() 0 4 2
A resolveType() 0 16 2
A getExtendedSubscriptionType() 0 7 2
A getExtendedType() 0 9 2

How to fix   Complexity   

Complex Class

Complex classes like ExtensionContext 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 ExtensionContext, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Digia\GraphQL\Schema\Extension;
4
5
use Digia\GraphQL\Error\InvalidTypeException;
6
use Digia\GraphQL\Error\InvariantException;
7
use Digia\GraphQL\Language\Node\DirectiveDefinitionNode;
8
use Digia\GraphQL\Language\Node\NamedTypeNode;
9
use Digia\GraphQL\Schema\DefinitionBuilderInterface;
10
use Digia\GraphQL\Type\Definition\Directive;
11
use Digia\GraphQL\Type\Definition\FieldsAwareInterface;
12
use Digia\GraphQL\Type\Definition\InterfaceType;
13
use Digia\GraphQL\Type\Definition\ListType;
14
use Digia\GraphQL\Type\Definition\NamedTypeInterface;
15
use Digia\GraphQL\Type\Definition\NonNullType;
16
use Digia\GraphQL\Type\Definition\ObjectType;
17
use Digia\GraphQL\Type\Definition\TypeInterface;
18
use Digia\GraphQL\Type\Definition\UnionType;
19
use function Digia\GraphQL\Type\isIntrospectionType;
20
use function Digia\GraphQL\Type\newInterfaceType;
21
use function Digia\GraphQL\Type\newList;
22
use function Digia\GraphQL\Type\newNonNull;
23
use function Digia\GraphQL\Type\newObjectType;
24
use function Digia\GraphQL\Type\newUnionType;
25
26
class ExtensionContext implements ExtensionContextInterface
27
{
28
    /**
29
     * @var ExtendInfo
30
     */
31
    protected $info;
32
33
    /**
34
     * @var DefinitionBuilderInterface
35
     */
36
    protected $definitionBuilder;
37
38
    /**
39
     * @var TypeInterface[]
40
     */
41
    protected $extendTypeCache = [];
42
43
    /**
44
     * ExtensionContext constructor.
45
     * @param ExtendInfo $info
46
     */
47
    public function __construct(ExtendInfo $info)
48
    {
49
        $this->info = $info;
50
    }
51
52
    /**
53
     * @return bool
54
     */
55
    public function isSchemaExtended(): bool
56
    {
57
        return
58
            $this->info->hasTypeExtensionsMap() ||
59
            $this->info->hasTypeDefinitionMap() ||
60
            $this->info->hasDirectiveDefinitions() ||
61
            $this->info->hasSchemaExtensions();
62
    }
63
64
    /**
65
     * @return ObjectType[]
66
     * @throws SchemaExtensionException
67
     * @throws InvariantException
68
     */
69
    public function getExtendedOperationTypes(): array
70
    {
71
        /** @noinspection PhpUnhandledExceptionInspection */
72
        $operationTypes = [
73
            'query'        => $this->getExtendedQueryType(),
74
            'mutation'     => $this->getExtendedMutationType(),
75
            'subscription' => $this->getExtendedSubscriptionType(),
76
        ];
77
78
        foreach ($this->info->getSchemaExtensions() as $schemaExtension) {
79
            foreach ($schemaExtension->getOperationTypes() as $operationType) {
80
                $operation = $operationType->getOperation();
81
82
                if (isset($operationTypes[$operation])) {
83
                    throw new SchemaExtensionException(\sprintf('Must provide only one %s type in schema.', $operation));
84
                }
85
86
                $operationTypes[$operation] = $this->definitionBuilder->buildType($operationType->getType());
87
            }
88
        }
89
90
        return $operationTypes;
91
    }
92
93
    /**
94
     * @return TypeInterface|null
95
     * @throws InvariantException
96
     */
97
    protected function getExtendedQueryType(): ?TypeInterface
98
    {
99
        $existingQueryType = $this->info->getSchema()->getQueryType();
100
101
        return null !== $existingQueryType
102
            ? $this->getExtendedType($existingQueryType)
103
            : null;
104
    }
105
106
    /**
107
     * @return TypeInterface|null
108
     * @throws InvariantException
109
     */
110
    protected function getExtendedMutationType(): ?TypeInterface
111
    {
112
        $existingMutationType = $this->info->getSchema()->getMutationType();
113
114
        return null !== $existingMutationType
115
            ? $this->getExtendedType($existingMutationType)
116
            : null;
117
    }
118
119
    /**
120
     * @return TypeInterface|null
121
     * @throws InvariantException
122
     */
123
    protected function getExtendedSubscriptionType(): ?TypeInterface
124
    {
125
        $existingSubscriptionType = $this->info->getSchema()->getSubscriptionType();
126
127
        return null !== $existingSubscriptionType
128
            ? $this->getExtendedType($existingSubscriptionType)
129
            : null;
130
    }
131
132
    /**
133
     * @return TypeInterface[]
134
     */
135
    public function getExtendedTypes(): array
136
    {
137
        $extendedTypes = \array_map(function ($type) {
138
            return $this->getExtendedType($type);
139
        }, $this->info->getSchema()->getTypeMap());
140
141
        return \array_merge(
142
            $extendedTypes,
143
            $this->definitionBuilder->buildTypes($this->info->getTypeDefinitionMap())
144
        );
145
    }
146
147
    /**
148
     * @return Directive[]
149
     * @throws InvariantException
150
     */
151
    public function getExtendedDirectives(): array
152
    {
153
        $existingDirectives = $this->info->getSchema()->getDirectives();
154
155
        if (empty($existingDirectives)) {
156
            throw new InvariantException('schema must have default directives');
157
        }
158
159
        return \array_merge(
160
            $existingDirectives,
161
            \array_map(function (DirectiveDefinitionNode $node) {
162
                return $this->definitionBuilder->buildDirective($node);
163
            }, $this->info->getDirectiveDefinitions())
164
        );
165
    }
166
167
    /**
168
     * @param DefinitionBuilderInterface $definitionBuilder
169
     * @return ExtensionContext
170
     */
171
    public function setDefinitionBuilder(DefinitionBuilderInterface $definitionBuilder): ExtensionContext
172
    {
173
        $this->definitionBuilder = $definitionBuilder;
174
        return $this;
175
    }
176
177
    /**
178
     * @param NamedTypeNode $node
179
     * @return TypeInterface|null
180
     * @throws SchemaExtensionException
181
     * @throws InvariantException
182
     */
183
    public function resolveType(NamedTypeNode $node): ?TypeInterface
184
    {
185
        $typeName     = $node->getNameValue();
186
        $existingType = $this->info->getSchema()->getType($typeName);
0 ignored issues
show
Bug introduced by
It seems like $typeName can also be of type null; however, parameter $name of Digia\GraphQL\Schema\Schema::getType() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

186
        $existingType = $this->info->getSchema()->getType(/** @scrutinizer ignore-type */ $typeName);
Loading history...
187
188
        if ($existingType instanceof NamedTypeInterface) {
189
            return $this->getExtendedType($existingType);
190
        }
191
192
        throw new SchemaExtensionException(
193
            \sprintf(
194
                'Unknown type: "%s". Ensure that this type exists ' .
195
                'either in the original schema, or is added in a type definition.',
196
                $typeName
197
            ),
198
            [$node]
199
        );
200
    }
201
202
    /**
203
     * @param NamedTypeInterface $type
204
     * @return TypeInterface
205
     * @throws InvariantException
206
     */
207
    protected function getExtendedType(NamedTypeInterface $type): TypeInterface
208
    {
209
        $typeName = $type->getName();
210
211
        if (isset($this->extendTypeCache[$typeName])) {
212
            return $this->extendTypeCache[$typeName];
213
        }
214
215
        return $this->extendTypeCache[$typeName] = $this->extendType($type);
216
    }
217
218
    /**
219
     * @param TypeInterface $type
220
     * @return TypeInterface
221
     * @throws InvariantException
222
     */
223
    protected function extendType(TypeInterface $type): TypeInterface
224
    {
225
        /** @noinspection PhpParamsInspection */
226
        if (isIntrospectionType($type)) {
227
            // Introspection types are not extended.
228
            return $type;
229
        }
230
231
        if ($type instanceof ObjectType) {
232
            return $this->extendObjectType($type);
233
        }
234
235
        if ($type instanceof InterfaceType) {
236
            return $this->extendInterfaceType($type);
237
        }
238
239
        if ($type instanceof UnionType) {
240
            return $this->extendUnionType($type);
241
        }
242
243
        // This type is not yet extendable.
244
        return $type;
245
    }
246
247
    /**
248
     * @param ObjectType $type
249
     * @return ObjectType
250
     * @throws InvariantException
251
     */
252
    protected function extendObjectType(ObjectType $type): ObjectType
253
    {
254
        $typeName          = $type->getName();
255
        $extensionASTNodes = $type->getExtensionAstNodes();
256
257
        if ($this->info->hasTypeExtensions($typeName)) {
258
            $extensionASTNodes = $this->extendExtensionASTNodes($typeName, $extensionASTNodes);
259
        }
260
261
        return newObjectType([
262
            'name'              => $typeName,
263
            'description'       => $type->getDescription(),
264
            'interfaces'        => function () use ($type) {
265
                return $this->extendImplementedInterfaces($type);
266
            },
267
            'fields'            => function () use ($type) {
268
                return $this->extendFieldMap($type);
269
            },
270
            'astNode'           => $type->getAstNode(),
271
            'extensionASTNodes' => $extensionASTNodes,
272
            'isTypeOf'          => $type->getIsTypeOfCallback(),
273
        ]);
274
    }
275
276
    /**
277
     * @param InterfaceType $type
278
     * @return InterfaceType
279
     * @throws InvariantException
280
     */
281
    protected function extendInterfaceType(InterfaceType $type): InterfaceType
282
    {
283
        $typeName          = $type->getName();
284
        $extensionASTNodes = $this->info->getTypeExtensions($typeName);
285
286
        if ($this->info->hasTypeExtensions($typeName)) {
287
            $extensionASTNodes = $this->extendExtensionASTNodes($typeName, $extensionASTNodes);
288
        }
289
290
        return newInterfaceType([
291
            'name'              => $typeName,
292
            'description'       => $type->getDescription(),
293
            'fields'            => function () use ($type) {
294
                return $this->extendFieldMap($type);
295
            },
296
            'astNode'           => $type->getAstNode(),
297
            'extensionASTNodes' => $extensionASTNodes,
298
            'resolveType'       => $type->getResolveTypeCallback(),
299
        ]);
300
    }
301
302
    /**
303
     * @param string $typeName
304
     * @param array  $nodes
305
     * @return array
306
     */
307
    protected function extendExtensionASTNodes(string $typeName, array $nodes): array
308
    {
309
        $typeExtensions = $this->info->getTypeExtensions($typeName);
310
        return !empty($nodes) ? \array_merge($typeExtensions, $nodes) : $typeExtensions;
311
    }
312
313
    /**
314
     * @param UnionType $type
315
     * @return UnionType
316
     * @throws InvariantException
317
     */
318
    protected function extendUnionType(UnionType $type): UnionType
319
    {
320
        return newUnionType([
321
            'name'        => $type->getName(),
322
            'description' => $type->getDescription(),
323
            'types'       => \array_map(function ($unionType) {
324
                return $this->getExtendedType($unionType);
325
            }, $type->getTypes()),
326
            'astNode'     => $type->getAstNode(),
327
            'resolveType' => $type->getResolveTypeCallback(),
328
        ]);
329
    }
330
331
    /**
332
     * @param ObjectType $type
333
     * @return array
334
     * @throws InvariantException
335
     */
336
    protected function extendImplementedInterfaces(ObjectType $type): array
337
    {
338
        $typeName = $type->getName();
339
340
        $interfaces = \array_map(function (InterfaceType $interface) {
341
            return $this->getExtendedType($interface);
342
        }, $type->getInterfaces());
343
344
        // If there are any extensions to the interfaces, apply those here.
345
        $extensions = $this->info->getTypeExtensions($typeName);
346
347
        foreach ($extensions as $extension) {
348
            foreach ($extension->getInterfaces() as $namedType) {
349
                // Note: While this could make early assertions to get the correctly
350
                // typed values, that would throw immediately while type system
351
                // validation with validateSchema() will produce more actionable results.
352
                $interfaces[] = $this->definitionBuilder->buildType($namedType);
353
            }
354
        }
355
356
        return $interfaces;
357
    }
358
359
    /**
360
     * @param FieldsAwareInterface $type
361
     * @return array
362
     * @throws InvalidTypeException
363
     * @throws InvariantException
364
     * @throws SchemaExtensionException
365
     */
366
    protected function extendFieldMap(FieldsAwareInterface $type): array
367
    {
368
        $typeName    = $type->getName();
369
        $newFieldMap = [];
370
        $oldFieldMap = $type->getFields();
371
372
        foreach (\array_keys($oldFieldMap) as $fieldName) {
373
            $field = $oldFieldMap[$fieldName];
374
375
            $newFieldMap[$fieldName] = [
376
                'description'       => $field->getDescription(),
377
                'deprecationReason' => $field->getDeprecationReason(),
378
                'type'              => $this->extendFieldType($field->getType()),
0 ignored issues
show
Bug introduced by
It seems like $field->getType() can also be of type null; however, parameter $typeDefinition of Digia\GraphQL\Schema\Ext...text::extendFieldType() does only seem to accept Digia\GraphQL\Type\Definition\TypeInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

378
                'type'              => $this->extendFieldType(/** @scrutinizer ignore-type */ $field->getType()),
Loading history...
379
                'args'              => $field->getRawArguments(),
380
                'astNode'           => $field->getAstNode(),
381
                'resolve'           => $field->getResolveCallback(),
382
            ];
383
        }
384
385
        // If there are any extensions to the fields, apply those here.
386
        $extensions = $this->info->getTypeExtensions($typeName);
387
388
        foreach ($extensions as $extension) {
389
            foreach ($extension->getFields() as $field) {
390
                $fieldName = $field->getNameValue();
391
392
                if (isset($oldFieldMap[$fieldName])) {
393
                    throw new SchemaExtensionException(
394
                        \sprintf(
395
                            'Field "%s.%s" already exists in the schema. ' .
396
                            'It cannot also be defined in this type extension.',
397
                            $typeName, $fieldName
398
                        ),
399
                        [$field]
400
                    );
401
                }
402
403
                $newFieldMap[$fieldName] = $this->definitionBuilder->buildField($field);
404
            }
405
        }
406
407
        return $newFieldMap;
408
    }
409
410
    /**
411
     * @param TypeInterface $typeDefinition
412
     * @return TypeInterface
413
     * @throws InvalidTypeException
414
     * @throws InvariantException
415
     */
416
    protected function extendFieldType(TypeInterface $typeDefinition): TypeInterface
417
    {
418
        if ($typeDefinition instanceof ListType) {
419
            return newList($this->extendFieldType($typeDefinition->getOfType()));
420
        }
421
422
        if ($typeDefinition instanceof NonNullType) {
423
            return newNonNull($this->extendFieldType($typeDefinition->getOfType()));
424
        }
425
426
        return $this->getExtendedType($typeDefinition);
427
    }
428
}
429