Completed
Push — master ( a01b08...b72ba3 )
by Vladimir
16s queued 14s
created

ValidationContext   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 264
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 33
eloc 98
c 1
b 0
f 0
dl 0
loc 264
rs 9.76
ccs 108
cts 108
cp 1

17 Methods

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