Passed
Push — master ( 5ac3ee...654419 )
by Vladimir
13:40 queued 04:49
created

Collector::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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