Collector   D
last analyzed

Complexity

Total Complexity 58

Size/Duplication

Total Lines 240
Duplicated Lines 0 %

Test Coverage

Coverage 80.8%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 58
eloc 129
c 1
b 0
f 0
dl 0
loc 240
ccs 101
cts 125
cp 0.808
rs 4.5599

4 Methods

Rating   Name   Duplication   Size   Complexity  
B collectFields() 0 28 9
C initialize() 0 45 15
A __construct() 0 4 1
D doCollectFields() 0 129 33

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Experimental\Executor;
6
7
use Generator;
8
use GraphQL\Error\Error;
9
use GraphQL\Language\AST\BooleanValueNode;
10
use GraphQL\Language\AST\DefinitionNode;
11
use GraphQL\Language\AST\DocumentNode;
12
use GraphQL\Language\AST\EnumValueNode;
13
use GraphQL\Language\AST\FieldNode;
14
use GraphQL\Language\AST\FloatValueNode;
15
use GraphQL\Language\AST\FragmentDefinitionNode;
16
use GraphQL\Language\AST\FragmentSpreadNode;
17
use GraphQL\Language\AST\InlineFragmentNode;
18
use GraphQL\Language\AST\IntValueNode;
19
use GraphQL\Language\AST\ListValueNode;
20
use GraphQL\Language\AST\Node;
21
use GraphQL\Language\AST\NullValueNode;
22
use GraphQL\Language\AST\ObjectValueNode;
23
use GraphQL\Language\AST\OperationDefinitionNode;
24
use GraphQL\Language\AST\SelectionSetNode;
25
use GraphQL\Language\AST\StringValueNode;
26
use GraphQL\Language\AST\VariableNode;
27
use GraphQL\Type\Definition\AbstractType;
28
use GraphQL\Type\Definition\Directive;
29
use GraphQL\Type\Definition\ObjectType;
30
use GraphQL\Type\Definition\Type;
31
use GraphQL\Type\Introspection;
32
use GraphQL\Type\Schema;
33
use function sprintf;
34
35
/**
36
 * @internal
37
 */
38
class Collector
39
{
40
    /** @var Schema */
41
    private $schema;
42
43
    /** @var Runtime */
44
    private $runtime;
45
46
    /** @var OperationDefinitionNode|null */
47
    public $operation = null;
48
49
    /** @var FragmentDefinitionNode[] */
50
    public $fragments = [];
51
52
    /** @var ObjectType|null */
53
    public $rootType;
54
55
    /** @var FieldNode[][] */
56
    private $fields;
57
58
    /** @var array<string, bool> */
59
    private $visitedFragments;
60
61 253
    public function __construct(Schema $schema, Runtime $runtime)
62
    {
63 253
        $this->schema  = $schema;
64 253
        $this->runtime = $runtime;
65 253
    }
66
67 253
    public function initialize(DocumentNode $documentNode, ?string $operationName = null)
68
    {
69 253
        $hasMultipleAssumedOperations = false;
70
71 253
        foreach ($documentNode->definitions as $definitionNode) {
72
            /** @var DefinitionNode|Node $definitionNode */
73
74 253
            if ($definitionNode instanceof OperationDefinitionNode) {
75 251
                if ($operationName === null && $this->operation !== null) {
76 1
                    $hasMultipleAssumedOperations = true;
77
                }
78 251
                if ($operationName === null ||
79 251
                    (isset($definitionNode->name) && $definitionNode->name->value === $operationName)
80
                ) {
81 251
                    $this->operation = $definitionNode;
82
                }
83 26
            } elseif ($definitionNode instanceof FragmentDefinitionNode) {
84 253
                $this->fragments[$definitionNode->name->value] = $definitionNode;
85
            }
86
        }
87
88 253
        if ($this->operation === null) {
89 3
            if ($operationName !== null) {
90 1
                $this->runtime->addError(new Error(sprintf('Unknown operation named "%s".', $operationName)));
91
            } else {
92 2
                $this->runtime->addError(new Error('Must provide an operation.'));
93
            }
94
95 3
            return;
96
        }
97
98 250
        if ($hasMultipleAssumedOperations) {
99 1
            $this->runtime->addError(new Error('Must provide operation name if query contains multiple operations.'));
100
101 1
            return;
102
        }
103
104 249
        if ($this->operation->operation === 'query') {
105 241
            $this->rootType = $this->schema->getQueryType();
106 8
        } elseif ($this->operation->operation === 'mutation') {
107 6
            $this->rootType = $this->schema->getMutationType();
108 2
        } elseif ($this->operation->operation === 'subscription') {
109 2
            $this->rootType = $this->schema->getSubscriptionType();
110
        } else {
111
            $this->runtime->addError(new Error(sprintf('Cannot initialize collector with operation type "%s".', $this->operation->operation)));
112
        }
113 249
    }
114
115
    /**
116
     * @return Generator
117
     */
118 238
    public function collectFields(ObjectType $runtimeType, ?SelectionSetNode $selectionSet)
119
    {
120 238
        $this->fields           = [];
121 238
        $this->visitedFragments = [];
122
123 238
        $this->doCollectFields($runtimeType, $selectionSet);
124
125 238
        foreach ($this->fields as $resultName => $fieldNodes) {
126 229
            $fieldNode = $fieldNodes[0];
127 229
            $fieldName = $fieldNode->name->value;
128
129 229
            $argumentValueMap = null;
130 229
            if (! empty($fieldNode->arguments)) {
131 229
                foreach ($fieldNode->arguments as $argumentNode) {
132 91
                    $argumentValueMap                             = $argumentValueMap ?? [];
133 91
                    $argumentValueMap[$argumentNode->name->value] = $argumentNode->value;
134
                }
135
            }
136
137 229
            if ($fieldName !== Introspection::TYPE_NAME_FIELD_NAME &&
138 229
                ! ($runtimeType === $this->schema->getQueryType() && ($fieldName === Introspection::SCHEMA_FIELD_NAME || $fieldName === Introspection::TYPE_FIELD_NAME)) &&
139 229
                ! $runtimeType->hasField($fieldName)
140
            ) {
141
                // do not emit error
142 7
                continue;
143
            }
144
145 225
            yield new CoroutineContextShared($fieldNodes, $fieldName, $resultName, $argumentValueMap);
146
        }
147 238
    }
148
149 238
    private function doCollectFields(ObjectType $runtimeType, ?SelectionSetNode $selectionSet)
150
    {
151 238
        if ($selectionSet === null) {
152
            return;
153
        }
154
155 238
        foreach ($selectionSet->selections as $selection) {
156
            /** @var FieldNode|FragmentSpreadNode|InlineFragmentNode $selection */
157 238
            if (! empty($selection->directives)) {
158 238
                foreach ($selection->directives as $directiveNode) {
159 21
                    if ($directiveNode->name->value === Directive::SKIP_NAME) {
160
                        /** @var VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode|null $condition */
161 13
                        $condition = null;
162 13
                        foreach ($directiveNode->arguments as $argumentNode) {
163 13
                            if ($argumentNode->name->value === Directive::IF_ARGUMENT_NAME) {
164 13
                                $condition = $argumentNode->value;
165 13
                                break;
166
                            }
167
                        }
168
169 13
                        if ($condition === null) {
170
                            $this->runtime->addError(new Error(
171
                                sprintf('@%s directive is missing "%s" argument.', Directive::SKIP_NAME, Directive::IF_ARGUMENT_NAME),
172
                                $selection
173
                            ));
174
                        } else {
175 13
                            if ($this->runtime->evaluate($condition, Type::boolean()) === true) {
176 13
                                continue 2; // !!! advances outer loop
177
                            }
178
                        }
179 15
                    } elseif ($directiveNode->name->value === Directive::INCLUDE_NAME) {
180
                        /** @var VariableNode|NullValueNode|IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|EnumValueNode|ListValueNode|ObjectValueNode|null $condition */
181 15
                        $condition = null;
182 15
                        foreach ($directiveNode->arguments as $argumentNode) {
183 15
                            if ($argumentNode->name->value === Directive::IF_ARGUMENT_NAME) {
184 15
                                $condition = $argumentNode->value;
185 15
                                break;
186
                            }
187
                        }
188
189 15
                        if ($condition === null) {
190
                            $this->runtime->addError(new Error(
191
                                sprintf('@%s directive is missing "%s" argument.', Directive::INCLUDE_NAME, Directive::IF_ARGUMENT_NAME),
192
                                $selection
193
                            ));
194
                        } else {
195 15
                            if ($this->runtime->evaluate($condition, Type::boolean()) !== true) {
196 18
                                continue 2; // !!! advances outer loop
197
                            }
198
                        }
199
                    }
200
                }
201
            }
202
203 229
            if ($selection instanceof FieldNode) {
204 229
                $resultName = $selection->alias === null ? $selection->name->value : $selection->alias->value;
205
206 229
                if (! isset($this->fields[$resultName])) {
207 229
                    $this->fields[$resultName] = [];
208
                }
209
210 229
                $this->fields[$resultName][] = $selection;
211 36
            } elseif ($selection instanceof FragmentSpreadNode) {
212 14
                $fragmentName = $selection->name->value;
213
214 14
                if (isset($this->visitedFragments[$fragmentName])) {
215 1
                    continue;
216
                }
217
218 14
                if (! isset($this->fragments[$fragmentName])) {
219
                    $this->runtime->addError(new Error(
220
                        sprintf('Fragment "%s" does not exist.', $fragmentName),
221
                        $selection
222
                    ));
223
                    continue;
224
                }
225
226 14
                $this->visitedFragments[$fragmentName] = true;
227
228 14
                $fragmentDefinition = $this->fragments[$fragmentName];
229 14
                $conditionTypeName  = $fragmentDefinition->typeCondition->name->value;
230
231 14
                if (! $this->schema->hasType($conditionTypeName)) {
232
                    $this->runtime->addError(new Error(
233
                        sprintf('Cannot spread fragment "%s", type "%s" does not exist.', $fragmentName, $conditionTypeName),
234
                        $selection
235
                    ));
236
                    continue;
237
                }
238
239 14
                $conditionType = $this->schema->getType($conditionTypeName);
240
241 14
                if ($conditionType instanceof ObjectType) {
242 13
                    if ($runtimeType->name !== $conditionType->name) {
243 13
                        continue;
244
                    }
245 1
                } elseif ($conditionType instanceof AbstractType) {
246 1
                    if (! $this->schema->isPossibleType($conditionType, $runtimeType)) {
247
                        continue;
248
                    }
249
                }
250
251 14
                $this->doCollectFields($runtimeType, $fragmentDefinition->selectionSet);
252 24
            } elseif ($selection instanceof InlineFragmentNode) {
253 24
                if ($selection->typeCondition !== null) {
254 23
                    $conditionTypeName = $selection->typeCondition->name->value;
255
256 23
                    if (! $this->schema->hasType($conditionTypeName)) {
257
                        $this->runtime->addError(new Error(
258
                            sprintf('Cannot spread inline fragment, type "%s" does not exist.', $conditionTypeName),
259
                            $selection
260
                        ));
261
                        continue;
262
                    }
263
264 23
                    $conditionType = $this->schema->getType($conditionTypeName);
265
266 23
                    if ($conditionType instanceof ObjectType) {
267 23
                        if ($runtimeType->name !== $conditionType->name) {
268 23
                            continue;
269
                        }
270
                    } elseif ($conditionType instanceof AbstractType) {
271
                        if (! $this->schema->isPossibleType($conditionType, $runtimeType)) {
272
                            continue;
273
                        }
274
                    }
275
                }
276
277 229
                $this->doCollectFields($runtimeType, $selection->selectionSet);
278
            }
279
        }
280 238
    }
281
}
282