ValidationContext   A
last analyzed

Complexity

Total Complexity 31

Size/Duplication

Total Lines 229
Duplicated Lines 0 %

Test Coverage

Coverage 99.02%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 31
eloc 95
dl 0
loc 229
ccs 101
cts 102
cp 0.9902
rs 9.92
c 1
b 0
f 0

13 Methods

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