Completed
Push — master ( 974258...005b1a )
by Vladimir
19:57 queued 16:19
created

ValidationContext::getFragmentSpreads()   A

Complexity

Conditions 6
Paths 2

Size

Total Lines 23
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 23
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\SelectionSetNode;
15
use GraphQL\Language\AST\VariableNode;
16
use GraphQL\Language\Visitor;
17
use GraphQL\Type\Definition\FieldDefinition;
18
use GraphQL\Type\Definition\InputType;
19
use GraphQL\Type\Definition\Type;
20
use GraphQL\Type\Schema;
21
use GraphQL\Utils\TypeInfo;
22
use SplObjectStorage;
23
use function array_pop;
24
use function call_user_func_array;
25
use function count;
26
27
/**
28
 * An instance of this class is passed as the "this" context to all validators,
29
 * allowing access to commonly useful contextual information from within a
30
 * validation rule.
31
 */
32
class ValidationContext
33
{
34
    /** @var Schema */
35
    private $schema;
36
37
    /** @var DocumentNode */
38
    private $ast;
39
40
    /** @var TypeInfo */
41
    private $typeInfo;
42
43
    /** @var Error[] */
44
    private $errors;
45
46
    /** @var FragmentDefinitionNode[] */
47
    private $fragments;
48
49
    /** @var SplObjectStorage */
50
    private $fragmentSpreads;
51
52
    /** @var SplObjectStorage */
53
    private $recursivelyReferencedFragments;
54
55
    /** @var SplObjectStorage */
56
    private $variableUsages;
57
58
    /** @var SplObjectStorage */
59
    private $recursiveVariableUsages;
60
61 189
    public function __construct(Schema $schema, DocumentNode $ast, TypeInfo $typeInfo)
62
    {
63 189
        $this->schema                         = $schema;
64 189
        $this->ast                            = $ast;
65 189
        $this->typeInfo                       = $typeInfo;
66 189
        $this->errors                         = [];
67 189
        $this->fragmentSpreads                = new SplObjectStorage();
68 189
        $this->recursivelyReferencedFragments = new SplObjectStorage();
69 189
        $this->variableUsages                 = new SplObjectStorage();
70 189
        $this->recursiveVariableUsages        = new SplObjectStorage();
71 189
    }
72
73 46
    public function reportError(Error $error)
74
    {
75 46
        $this->errors[] = $error;
76 46
    }
77
78
    /**
79
     * @return Error[]
80
     */
81 189
    public function getErrors()
82
    {
83 189
        return $this->errors;
84
    }
85
86
    /**
87
     * @return Schema
88
     */
89 150
    public function getSchema()
90
    {
91 150
        return $this->schema;
92
    }
93
94
    /**
95
     * @return mixed[][] List of ['node' => VariableNode, 'type' => ?InputObjectType]
96
     */
97 103
    public function getRecursiveVariableUsages(OperationDefinitionNode $operation)
98
    {
99 103
        $usages = $this->recursiveVariableUsages[$operation] ?? null;
100
101 103
        if ($usages === null) {
102 103
            $usages    = $this->getVariableUsages($operation);
103 103
            $fragments = $this->getRecursivelyReferencedFragments($operation);
104
105 103
            $tmp = [$usages];
106 103
            foreach ($fragments as $i => $fragment) {
107 10
                $tmp[] = $this->getVariableUsages($fragments[$i]);
108
            }
109 103
            $usages                                    = call_user_func_array('array_merge', $tmp);
110 103
            $this->recursiveVariableUsages[$operation] = $usages;
111
        }
112
113 103
        return $usages;
114
    }
115
116
    /**
117
     * @return mixed[][] List of ['node' => VariableNode, 'type' => ?InputObjectType]
118
     */
119 103
    private function getVariableUsages(HasSelectionSet $node)
120
    {
121 103
        $usages = $this->variableUsages[$node] ?? null;
122
123 103
        if ($usages === null) {
124 103
            $newUsages = [];
125 103
            $typeInfo  = new TypeInfo($this->schema);
126 103
            Visitor::visit(
127 103
                $node,
128 103
                Visitor::visitWithTypeInfo(
129 103
                    $typeInfo,
130
                    [
131
                        NodeKind::VARIABLE_DEFINITION => static function () {
132 11
                            return false;
133 103
                        },
134
                        NodeKind::VARIABLE            => static function (VariableNode $variable) use (
135 11
                            &$newUsages,
136 11
                            $typeInfo
137
                        ) {
138 11
                            $newUsages[] = ['node' => $variable, 'type' => $typeInfo->getInputType()];
139 103
                        },
140
                    ]
141
                )
142
            );
143 103
            $usages                      = $newUsages;
144 103
            $this->variableUsages[$node] = $usages;
145
        }
146
147 103
        return $usages;
148
    }
149
150
    /**
151
     * @return FragmentDefinitionNode[]
152
     */
153 103
    public function getRecursivelyReferencedFragments(OperationDefinitionNode $operation)
154
    {
155 103
        $fragments = $this->recursivelyReferencedFragments[$operation] ?? null;
156
157 103
        if ($fragments === null) {
158 103
            $fragments      = [];
159 103
            $collectedNames = [];
160 103
            $nodesToVisit   = [$operation];
161 103
            while (! empty($nodesToVisit)) {
162 103
                $node    = array_pop($nodesToVisit);
163 103
                $spreads = $this->getFragmentSpreads($node);
164 103
                foreach ($spreads as $spread) {
165 10
                    $fragName = $spread->name->value;
166
167 10
                    if (! empty($collectedNames[$fragName])) {
168 4
                        continue;
169
                    }
170
171 10
                    $collectedNames[$fragName] = true;
172 10
                    $fragment                  = $this->getFragment($fragName);
173 10
                    if (! $fragment) {
174
                        continue;
175
                    }
176
177 10
                    $fragments[]    = $fragment;
178 10
                    $nodesToVisit[] = $fragment;
179
                }
180
            }
181 103
            $this->recursivelyReferencedFragments[$operation] = $fragments;
182
        }
183
184 103
        return $fragments;
185
    }
186
187
    /**
188
     * @return FragmentSpreadNode[]
189
     */
190 104
    public function getFragmentSpreads(HasSelectionSet $node)
191
    {
192 104
        $spreads = $this->fragmentSpreads[$node] ?? null;
193 104
        if ($spreads === null) {
194 104
            $spreads = [];
195
            /** @var SelectionSetNode[] $setsToVisit */
196 104
            $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...
197 104
            while (! empty($setsToVisit)) {
198 104
                $set = array_pop($setsToVisit);
199
200 104
                for ($i = 0, $selectionCount = count($set->selections); $i < $selectionCount; $i++) {
201 104
                    $selection = $set->selections[$i];
202 104
                    if ($selection instanceof FragmentSpreadNode) {
203 10
                        $spreads[] = $selection;
204 104
                    } elseif ($selection->selectionSet) {
205 58
                        $setsToVisit[] = $selection->selectionSet;
206
                    }
207
                }
208
            }
209 104
            $this->fragmentSpreads[$node] = $spreads;
210
        }
211
212 104
        return $spreads;
213
    }
214
215
    /**
216
     * @param string $name
217
     *
218
     * @return FragmentDefinitionNode|null
219
     */
220 14
    public function getFragment($name)
221
    {
222 14
        $fragments = $this->fragments;
223 14
        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...
224 14
            $fragments = [];
225 14
            foreach ($this->getDocument()->definitions as $statement) {
226 14
                if (! ($statement instanceof FragmentDefinitionNode)) {
227 14
                    continue;
228
                }
229
230 14
                $fragments[$statement->name->value] = $statement;
231
            }
232 14
            $this->fragments = $fragments;
233
        }
234
235 14
        return $fragments[$name] ?? null;
236
    }
237
238
    /**
239
     * @return DocumentNode
240
     */
241 173
    public function getDocument()
242
    {
243 173
        return $this->ast;
244
    }
245
246
    /**
247
     * Returns OutputType
248
     *
249
     * @return Type
250
     */
251 104
    public function getType()
252
    {
253 104
        return $this->typeInfo->getType();
254
    }
255
256
    /**
257
     * @return Type
258
     */
259 128
    public function getParentType()
260
    {
261 128
        return $this->typeInfo->getParentType();
262
    }
263
264
    /**
265
     * @return InputType
266
     */
267 53
    public function getInputType()
268
    {
269 53
        return $this->typeInfo->getInputType();
270
    }
271
272
    /**
273
     * @return InputType
274
     */
275
    public function getParentInputType()
276
    {
277
        return $this->typeInfo->getParentInputType();
278
    }
279
280
    /**
281
     * @return FieldDefinition
282
     */
283 104
    public function getFieldDef()
284
    {
285 104
        return $this->typeInfo->getFieldDef();
286
    }
287
288
    public function getDirective()
289
    {
290
        return $this->typeInfo->getDirective();
291
    }
292
293 52
    public function getArgument()
294
    {
295 52
        return $this->typeInfo->getArgument();
296
    }
297
}
298