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