TypeInfo   D
last analyzed

Complexity

Total Complexity 65

Size/Duplication

Total Lines 276
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 14

Test Coverage

Coverage 5.81%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 65
c 1
b 0
f 0
lcom 1
cbo 14
dl 0
loc 276
ccs 9
cts 155
cp 0.0581
rs 4.7692

8 Methods

Rating   Name   Duplication   Size   Complexity  
B typeFromAST() 0 18 6
C getFieldDefinition() 0 25 12
A __construct() 0 8 1
A getType() 0 8 2
A getParentType() 0 7 2
A getInputType() 0 7 2
D enter() 0 96 29
C leave() 0 30 11

How to fix   Complexity   

Complex Class

Complex classes like TypeInfo 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 TypeInfo, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Fubhy\GraphQL\Utility;
4
5
use Fubhy\GraphQL\Language\Node;
6
use Fubhy\GraphQL\Language\Node\Field;
7
use Fubhy\GraphQL\Language\Node\ListType;
8
use Fubhy\GraphQL\Language\Node\NamedType;
9
use Fubhy\GraphQL\Language\Node\NonNullType;
10
use Fubhy\GraphQL\Schema;
11
use Fubhy\GraphQL\Type\Definition\FieldDefinition;
12
use Fubhy\GraphQL\Type\Definition\Types\InputObjectType;
13
use Fubhy\GraphQL\Type\Definition\Types\InterfaceType;
14
use Fubhy\GraphQL\Type\Definition\Types\ListModifier;
15
use Fubhy\GraphQL\Type\Definition\Types\NonNullModifier;
16
use Fubhy\GraphQL\Type\Definition\Types\ObjectType;
17
use Fubhy\GraphQL\Type\Definition\Types\Type;
18
use Fubhy\GraphQL\Type\Definition\Types\UnionType;
19
use Fubhy\GraphQL\Type\Introspection;
20
21
class TypeInfo
22
{
23
    /**
24
     * @var Schema
25
     */
26
    protected $_schema;
27
28
    /**
29
     * @var \Fubhy\GraphQL\Type\Directives\DirectiveInterface
30
     */
31
    protected $directive;
32
33
    /**
34
     * @var \SplStack<OutputType>
35
     */
36
    protected $typeStack;
37
38
    /**
39
     * @var \SplStack<CompositeType>
40
     */
41
    protected $parentTypeStack;
42
43
    /**
44
     * @var \SplStack<InputType>
45
     */
46
    protected $inputTypeStack;
47
48
    /**
49
     * @var \SplStack<FieldDefinition>
50
     */
51
    protected $fieldDefinitionStack;
52
53
    /**
54
     * @param Schema $schema
55
     * @param $inputType
56
     *
57
     * @return ListModifier|NonNullModifier|\Fubhy\GraphQL\Type\Definition\Types\TypeInterface|null
58
     */
59 105
    public static function typeFromAST(Schema $schema, $inputType)
60
    {
61 105
        if ($inputType instanceof ListType) {
62 36
            $innerType = self::typeFromAST($schema, $inputType->get('type'));
63 36
            return $innerType ? new ListModifier($innerType) : NULL;
64
        }
65
66 105
        if ($inputType instanceof NonNullType) {
67 45
            $innerType = self::typeFromAST($schema, $inputType->get('type'));
68 45
            return $innerType ? new NonNullModifier($innerType) : NULL;
69
        }
70
71 105
        if (!($inputType instanceof NamedType)) {
72
            throw new \LogicException('Must be a type name.');
73
        }
74
75 105
        return $schema->getType($inputType->get('name')->get('value'));
76
    }
77
78
    /**
79
     * Not exactly the same as the executor's definition of getFieldDef, in this
80
     * statically evaluated environment we do not always have an Object type,
81
     * and need to handle Interface and Union types.
82
     *
83
     * @param Schema $schema
84
     * @param Type $parentType
85
     * @param Field $fieldAST
86
     *
87
     * @return FieldDefinition
88
     */
89
    protected static function getFieldDefinition(Schema $schema, Type $parentType, Field $fieldAST)
90
    {
91
        $name = $fieldAST->get('name')->get('value');
92
        $schemaMeta = Introspection::schemaMetaFieldDefinition();
93
        if ($name === $schemaMeta->getName() && $schema->getQueryType() === $parentType) {
94
            return $schemaMeta;
95
        }
96
97
        $typeMeta = Introspection::typeMetaFieldDefinition();
98
        if ($name === $typeMeta->getName() && $schema->getQueryType() === $parentType) {
99
            return $typeMeta;
100
        }
101
102
        $typeNameMeta = Introspection::typeNameMetaFieldDefinition();
103
        if ($name === $typeNameMeta->getName() && ($parentType instanceof ObjectType || $parentType instanceof InterfaceType || $parentType instanceof UnionType)) {
104
            return $typeNameMeta;
105
        }
106
107
        if ($parentType instanceof ObjectType || $parentType instanceof InterfaceType) {
108
            $fields = $parentType->getFields();
109
            return isset($fields[$name]) ? $fields[$name] : NULL;
110
        }
111
112
        return NULL;
113
    }
114
115
    /**
116
     * Constructor.
117
     *
118
     * @param Schema $schema
119
     */
120
    public function __construct(Schema $schema)
121
    {
122
        $this->_schema = $schema;
123
        $this->typeStack = [];
124
        $this->parentTypeStack = [];
125
        $this->inputTypeStack = [];
126
        $this->fieldDefinitionStack = [];
127
    }
128
129
    /**
130
     * @return Type
131
     */
132
    protected function getType()
133
    {
134
        if (!empty($this->typeStack)) {
135
            return $this->typeStack[count($this->typeStack) - 1];
136
        }
137
138
        return NULL;
139
    }
140
141
    /**
142
     * @return Type
143
     */
144
    protected function getParentType()
145
    {
146
        if (!empty($this->parentTypeStack)) {
147
            return $this->parentTypeStack[count($this->parentTypeStack) - 1];
148
        }
149
        return NULL;
150
    }
151
152
    /**
153
     * @return mixed|null
154
     */
155
    protected function getInputType()
156
    {
157
        if (!empty($this->inputTypeStack)) {
158
            return $this->inputTypeStack[count($this->inputTypeStack) - 1];
159
        }
160
        return NULL;
161
    }
162
163
    /**
164
     * @param Node $node
165
     */
166
    protected function enter(Node $node)
167
    {
168
        $schema = $this->_schema;
169
170
        switch ($node::KIND) {
171
            case Node::KIND_SELECTION_SET:
172
                $rawType = $this->getType();
173
                $rawType = isset($rawType) ? $rawType->getUnmodifiedType() : NULL;
174
                $compositeType = NULL;
175
                if (isset($rawType) && $rawType->isCompositeType()) {
176
                    $compositeType = $rawType;
177
                }
178
                array_push($this->parentTypeStack, $compositeType);
179
                break;
180
181
            case Node::KIND_DIRECTIVE:
182
                $this->directive = $schema->getDirective($node->get('name')->get('value'));
183
                break;
184
185
            case Node::KIND_FIELD:
186
                $parentType = $this->getParentType();
187
                $fieldDefinition = NULL;
188
                if (isset($parentType)) {
189
                    $fieldDefinition = self::getFieldDefinition($schema, $parentType, $node);
0 ignored issues
show
Compatibility introduced by
$node of type object<Fubhy\GraphQL\Language\Node> is not a sub-type of object<Fubhy\GraphQL\Language\Node\Field>. It seems like you assume a child class of the class Fubhy\GraphQL\Language\Node to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
190
                }
191
                array_push($this->fieldDefinitionStack, $fieldDefinition);
192
                array_push($this->typeStack, $fieldDefinition ? $fieldDefinition->getType() : NULL);
193
                break;
194
195
            case Node::KIND_OPERATION_DEFINITION:
196
                $type = NULL;
197
                if ($node->get('operation') === 'query') {
198
                    $type = $schema->getQueryType();
199
                } else if ($node->get('operation') === 'mutation') {
200
                    $type = $schema->getMutationType();
201
                }
202
                array_push($this->typeStack, $type);
203
                break;
204
205
            case Node::KIND_INLINE_FRAGMENT:
206
            case Node::KIND_FRAGMENT_DEFINITION:
207
                $type = $schema->getType($node->get('typeCondition')->get('value'));
208
                array_push($this->typeStack, $type);
209
                break;
210
211
            case Node::KIND_VARIABLE_DEFINITION:
212
                array_push($this->inputTypeStack, self::typeFromAST($schema, $node->get('type')));
213
                break;
214
215
            case Node::KIND_ARGUMENT:
216
                if (!empty($this->fieldDefinitionStack)) {
217
                    $field = $this->fieldDefinitionStack[count($this->fieldDefinitionStack) - 1];
218
                }
219
                else {
220
                    $field = NULL;
221
                }
222
223
                $argType = NULL;
224
                if ($field) {
225
                    $argDefinition = NULL;
226
                    foreach ($field->getArguments() as $arg) {
227
                        if ($arg->getName() === $node->get('name')->get('value')) {
228
                            $argDefinition = $arg;
229
                        }
230
                        break;
231
                    }
232
233
                    if ($argDefinition) {
234
                        $argType = $argDefinition->getType();
235
                    }
236
                }
237
                array_push($this->inputTypeStack, $argType);
238
                break;
239
240
            case Node::KIND_ARRAY_VALUE:
241
                $arrayType = $this->getInputType();
242
                $arrayType = isset($arrayType) ? $arrayType->getNullableType() : NULL;
243
                array_push(
244
                    $this->inputTypeStack,
245
                    $arrayType instanceof ListModifier ? $arrayType->getWrappedType() : NULL
246
                );
247
                break;
248
249
            case Node::KIND_OBJECT_FIELD:
250
                $objectType = $this->getInputType();
251
                $objectType = isset($objectType) ? $objectType->getUnmodifiedType() : NULL;
252
                $fieldType = NULL;
253
                if ($objectType instanceof InputObjectType) {
254
                    $tmp = $objectType->getFields();
255
                    $inputField = isset($tmp[$node->get('name')->get('value')]) ? $tmp[$node->get('name')->get('value')] : NULL;
256
                    $fieldType = $inputField ? $inputField->getType() : NULL;
257
                }
258
                array_push($this->inputTypeStack, $fieldType);
259
            break;
260
        }
261
    }
262
263
    /**
264
     * @param Node $node
265
     */
266
    protected function leave(Node $node)
267
    {
268
        switch ($node::KIND) {
269
            case Node::KIND_SELECTION_SET:
270
                array_pop($this->parentTypeStack);
271
                break;
272
            case Node::KIND_FIELD:
273
                array_pop($this->fieldDefinitionStack);
274
                array_pop($this->typeStack);
275
                break;
276
            case Node::KIND_DIRECTIVE:
277
                $this->directive = NULL;
278
                break;
279
            case Node::KIND_OPERATION_DEFINITION:
280
            case Node::KIND_INLINE_FRAGMENT:
281
            case Node::KIND_FRAGMENT_DEFINITION:
282
                array_pop($this->typeStack);
283
                break;
284
            case Node::KIND_VARIABLE_DEFINITION:
285
                array_pop($this->inputTypeStack);
286
                break;
287
            case Node::KIND_ARGUMENT:
288
                array_pop($this->inputTypeStack);
289
                break;
290
            case Node::KIND_ARRAY_VALUE:
291
            case Node::KIND_OBJECT_FIELD:
292
                array_pop($this->inputTypeStack);
293
                break;
294
        }
295
    }
296
}
297