Failed Conditions
Pull Request — master (#321)
by Šimon
04:11
created

ValidationContext::getFragmentSpreads()   A

Complexity

Conditions 6
Paths 2

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 14
dl 0
loc 22
ccs 15
cts 15
cp 1
rs 9.2222
c 0
b 0
f 0
cc 6
nc 2
nop 1
crap 6
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Validator;
6
7
use GraphQL\Error\Error;
8
use GraphQL\Language\AST\DocumentNode;
9
use GraphQL\Language\AST\FragmentDefinitionNode;
10
use GraphQL\Language\AST\FragmentSpreadNode;
11
use GraphQL\Language\AST\HasSelectionSet;
12
use GraphQL\Language\AST\NodeKind;
13
use GraphQL\Language\AST\OperationDefinitionNode;
14
use GraphQL\Language\AST\VariableNode;
15
use GraphQL\Language\Visitor;
16
use GraphQL\Type\Definition\FieldDefinition;
17
use GraphQL\Type\Definition\InputType;
18
use GraphQL\Type\Definition\Type;
19
use GraphQL\Type\Schema;
20
use GraphQL\Utils\TypeInfo;
21
use SplObjectStorage;
22
use function array_pop;
23
use function call_user_func_array;
24
use function count;
25
26
/**
27
 * An instance of this class is passed as the "this" context to all validators,
28
 * allowing access to commonly useful contextual information from within a
29
 * validation rule.
30
 */
31
class ValidationContext
32
{
33
    /** @var Schema */
34
    private $schema;
35
36
    /** @var DocumentNode */
37
    private $ast;
38
39
    /** @var TypeInfo */
40
    private $typeInfo;
41
42
    /** @var Error[] */
43
    private $errors;
44
45
    /** @var FragmentDefinitionNode[] */
46
    private $fragments;
47
48
    /** @var SplObjectStorage */
49
    private $fragmentSpreads;
50
51
    /** @var SplObjectStorage */
52
    private $recursivelyReferencedFragments;
53
54
    /** @var SplObjectStorage */
55
    private $variableUsages;
56
57
    /** @var SplObjectStorage */
58
    private $recursiveVariableUsages;
59
60 514
    public function __construct(Schema $schema, DocumentNode $ast, TypeInfo $typeInfo)
61
    {
62 514
        $this->schema                         = $schema;
63 514
        $this->ast                            = $ast;
64 514
        $this->typeInfo                       = $typeInfo;
65 514
        $this->errors                         = [];
66 514
        $this->fragmentSpreads                = new SplObjectStorage();
67 514
        $this->recursivelyReferencedFragments = new SplObjectStorage();
68 514
        $this->variableUsages                 = new SplObjectStorage();
69 514
        $this->recursiveVariableUsages        = new SplObjectStorage();
70 514
    }
71
72 213
    public function reportError(Error $error)
73
    {
74 213
        $this->errors[] = $error;
75 213
    }
76
77
    /**
78
     * @return Error[]
79
     */
80 514
    public function getErrors()
81
    {
82 514
        return $this->errors;
83
    }
84
85
    /**
86
     * @return Schema
87
     */
88 148
    public function getSchema()
89
    {
90 148
        return $this->schema;
91
    }
92
93
    /**
94
     * @return mixed[][] List of ['node' => VariableNode, 'type' => ?InputObjectType]
95
     */
96 151
    public function getRecursiveVariableUsages(OperationDefinitionNode $operation)
97
    {
98 151
        $usages = $this->recursiveVariableUsages[$operation] ?? null;
99
100 151
        if (! $usages) {
101 151
            $usages    = $this->getVariableUsages($operation);
102 151
            $fragments = $this->getRecursivelyReferencedFragments($operation);
103
104 151
            $tmp = [$usages];
105 151
            for ($i = 0; $i < count($fragments); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
106 30
                $tmp[] = $this->getVariableUsages($fragments[$i]);
107
            }
108 151
            $usages                                    = call_user_func_array('array_merge', $tmp);
109 151
            $this->recursiveVariableUsages[$operation] = $usages;
110
        }
111
112 151
        return $usages;
113
    }
114
115
    /**
116
     * @return mixed[][] List of ['node' => VariableNode, 'type' => ?InputObjectType]
117
     */
118 151
    private function getVariableUsages(HasSelectionSet $node)
119
    {
120 151
        $usages = $this->variableUsages[$node] ?? null;
121
122 151
        if (! $usages) {
123 151
            $newUsages = [];
124 151
            $typeInfo  = new TypeInfo($this->schema);
125 151
            Visitor::visit(
126 151
                $node,
127 151
                Visitor::visitWithTypeInfo(
128 151
                    $typeInfo,
129
                    [
130
                        NodeKind::VARIABLE_DEFINITION => function () {
131 58
                            return false;
132 151
                        },
133
                        NodeKind::VARIABLE            => function (VariableNode $variable) use (
134 60
                            &$newUsages,
135 60
                            $typeInfo
136
                        ) {
137 60
                            $newUsages[] = ['node' => $variable, 'type' => $typeInfo->getInputType()];
138 151
                        },
139
                    ]
140
                )
141
            );
142 151
            $usages                      = $newUsages;
143 151
            $this->variableUsages[$node] = $usages;
144
        }
145
146 151
        return $usages;
147
    }
148
149
    /**
150
     * @return FragmentDefinitionNode[]
151
     */
152 156
    public function getRecursivelyReferencedFragments(OperationDefinitionNode $operation)
153
    {
154 156
        $fragments = $this->recursivelyReferencedFragments[$operation] ?? null;
155
156 156
        if (! $fragments) {
157 156
            $fragments      = [];
158 156
            $collectedNames = [];
159 156
            $nodesToVisit   = [$operation];
160 156
            while (! empty($nodesToVisit)) {
161 156
                $node    = array_pop($nodesToVisit);
162 156
                $spreads = $this->getFragmentSpreads($node);
163 156
                for ($i = 0; $i < count($spreads); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
164 35
                    $fragName = $spreads[$i]->name->value;
165
166 35
                    if (! empty($collectedNames[$fragName])) {
167 6
                        continue;
168
                    }
169
170 35
                    $collectedNames[$fragName] = true;
171 35
                    $fragment                  = $this->getFragment($fragName);
172 35
                    if (! $fragment) {
173 1
                        continue;
174
                    }
175
176 34
                    $fragments[]    = $fragment;
177 34
                    $nodesToVisit[] = $fragment;
178
                }
179
            }
180 156
            $this->recursivelyReferencedFragments[$operation] = $fragments;
181
        }
182
183 156
        return $fragments;
184
    }
185
186
    /**
187
     * @return FragmentSpreadNode[]
188
     */
189 171
    public function getFragmentSpreads(HasSelectionSet $node)
190
    {
191 171
        $spreads = $this->fragmentSpreads[$node] ?? null;
192 171
        if (! $spreads) {
193 171
            $spreads     = [];
194 171
            $setsToVisit = [$node->selectionSet];
0 ignored issues
show
Bug introduced by
Accessing selectionSet on the interface GraphQL\Language\AST\HasSelectionSet suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
195 171
            while (! empty($setsToVisit)) {
196 171
                $set = array_pop($setsToVisit);
197
198 171
                for ($i = 0; $i < count($set->selections); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
199 171
                    $selection = $set->selections[$i];
200 171
                    if ($selection->kind === NodeKind::FRAGMENT_SPREAD) {
201 50
                        $spreads[] = $selection;
202 163
                    } elseif ($selection->selectionSet) {
203 93
                        $setsToVisit[] = $selection->selectionSet;
204
                    }
205
                }
206
            }
207 171
            $this->fragmentSpreads[$node] = $spreads;
208
        }
209
210 171
        return $spreads;
211
    }
212
213
    /**
214
     * @param string $name
215
     * @return FragmentDefinitionNode|null
216
     */
217 80
    public function getFragment($name)
218
    {
219 80
        $fragments = $this->fragments;
220 80
        if (! $fragments) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $fragments of type GraphQL\Language\AST\FragmentDefinitionNode[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
221 80
            $fragments = [];
222 80
            foreach ($this->getDocument()->definitions as $statement) {
223 80
                if ($statement->kind !== NodeKind::FRAGMENT_DEFINITION) {
0 ignored issues
show
Bug introduced by
Accessing kind on the interface GraphQL\Language\AST\DefinitionNode suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
224 46
                    continue;
225
                }
226
227 80
                $fragments[$statement->name->value] = $statement;
0 ignored issues
show
Bug introduced by
Accessing name on the interface GraphQL\Language\AST\DefinitionNode suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
228
            }
229 80
            $this->fragments = $fragments;
230
        }
231
232 80
        return $fragments[$name] ?? null;
233
    }
234
235
    /**
236
     * @return DocumentNode
237
     */
238 131
    public function getDocument()
239
    {
240 131
        return $this->ast;
241
    }
242
243
    /**
244
     * Returns OutputType
245
     *
246
     * @return Type
247
     */
248 115
    public function getType()
249
    {
250 115
        return $this->typeInfo->getType();
251
    }
252
253
    /**
254
     * @return Type
255
     */
256 205
    public function getParentType()
257
    {
258 205
        return $this->typeInfo->getParentType();
259
    }
260
261
    /**
262
     * @return InputType
263
     */
264 129
    public function getInputType()
265
    {
266 129
        return $this->typeInfo->getInputType();
267
    }
268
269
    /**
270
     * @return InputType
271
     */
272 18
    public function getParentInputType()
273
    {
274 18
        return $this->typeInfo->getParentInputType();
275
    }
276
277
    /**
278
     * @return FieldDefinition
279
     */
280 142
    public function getFieldDef()
281
    {
282 142
        return $this->typeInfo->getFieldDef();
283
    }
284
285 5
    public function getDirective()
286
    {
287 5
        return $this->typeInfo->getDirective();
288
    }
289
290 61
    public function getArgument()
291
    {
292 61
        return $this->typeInfo->getArgument();
293
    }
294
}
295