Failed Conditions
Push — master ( be61cd...7cc72b )
by Vladimir
18s queued 14s
created

ValidationContext::getFragmentSpreads()   B

Complexity

Conditions 8
Paths 3

Size

Total Lines 27
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 8.013

Importance

Changes 0
Metric Value
eloc 17
c 0
b 0
f 0
dl 0
loc 27
ccs 16
cts 17
cp 0.9412
rs 8.4444
cc 8
nc 3
nop 1
crap 8.013
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\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 532
    public function __construct(Schema $schema, DocumentNode $ast, TypeInfo $typeInfo)
60
    {
61 532
        parent::__construct($ast, $schema);
62 532
        $this->typeInfo                       = $typeInfo;
63 532
        $this->fragmentSpreads                = new SplObjectStorage();
64 532
        $this->recursivelyReferencedFragments = new SplObjectStorage();
65 532
        $this->variableUsages                 = new SplObjectStorage();
66 532
        $this->recursiveVariableUsages        = new SplObjectStorage();
67 532
    }
68
69
    /**
70
     * @return mixed[][] List of ['node' => VariableNode, 'type' => ?InputObjectType]
71
     */
72 169
    public function getRecursiveVariableUsages(OperationDefinitionNode $operation)
73
    {
74 169
        $usages = $this->recursiveVariableUsages[$operation] ?? null;
75
76 169
        if ($usages === null) {
77 169
            $usages    = $this->getVariableUsages($operation);
78 169
            $fragments = $this->getRecursivelyReferencedFragments($operation);
79
80 169
            $tmp = [$usages];
81 169
            foreach ($fragments as $i => $fragment) {
82 35
                $tmp[] = $this->getVariableUsages($fragments[$i]);
83
            }
84 169
            $usages                                    = call_user_func_array('array_merge', $tmp);
85 169
            $this->recursiveVariableUsages[$operation] = $usages;
86
        }
87
88 169
        return $usages;
89
    }
90
91
    /**
92
     * @return mixed[][] List of ['node' => VariableNode, 'type' => ?InputObjectType]
93
     */
94 169
    private function getVariableUsages(HasSelectionSet $node)
95
    {
96 169
        $usages = $this->variableUsages[$node] ?? null;
97
98 169
        if ($usages === null) {
99 169
            $newUsages = [];
100 169
            $typeInfo  = new TypeInfo($this->schema);
101 169
            Visitor::visit(
102 169
                $node,
103 169
                Visitor::visitWithTypeInfo(
104 169
                    $typeInfo,
105
                    [
106
                        NodeKind::VARIABLE_DEFINITION => static function () {
107 62
                            return false;
108 169
                        },
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 169
                        },
119
                    ]
120
                )
121
            );
122 169
            $usages                      = $newUsages;
123 169
            $this->variableUsages[$node] = $usages;
124
        }
125
126 169
        return $usages;
127
    }
128
129
    /**
130
     * @return FragmentDefinitionNode[]
131
     */
132 174
    public function getRecursivelyReferencedFragments(OperationDefinitionNode $operation)
133
    {
134 174
        $fragments = $this->recursivelyReferencedFragments[$operation] ?? null;
135
136 174
        if ($fragments === null) {
137 174
            $fragments      = [];
138 174
            $collectedNames = [];
139 174
            $nodesToVisit   = [$operation];
140 174
            while (! empty($nodesToVisit)) {
141 174
                $node    = array_pop($nodesToVisit);
142 174
                $spreads = $this->getFragmentSpreads($node);
143 174
                foreach ($spreads as $spread) {
144 40
                    $fragName = $spread->name->value;
145
146 40
                    if (! empty($collectedNames[$fragName])) {
147 7
                        continue;
148
                    }
149
150 40
                    $collectedNames[$fragName] = true;
151 40
                    $fragment                  = $this->getFragment($fragName);
152 40
                    if (! $fragment) {
153 1
                        continue;
154
                    }
155
156 39
                    $fragments[]    = $fragment;
157 39
                    $nodesToVisit[] = $fragment;
158
                }
159
            }
160 174
            $this->recursivelyReferencedFragments[$operation] = $fragments;
161
        }
162
163 174
        return $fragments;
164
    }
165
166
    /**
167
     * @param OperationDefinitionNode|FragmentDefinitionNode $node
168
     *
169
     * @return FragmentSpreadNode[]
170
     */
171 190
    public function getFragmentSpreads(HasSelectionSet $node) : array
172
    {
173 190
        $spreads = $this->fragmentSpreads[$node] ?? null;
174 190
        if ($spreads === null) {
175 190
            $spreads = [];
176
            /** @var SelectionSetNode[] $setsToVisit */
177 190
            $setsToVisit = [$node->selectionSet];
178 190
            while (! empty($setsToVisit)) {
179 190
                $set = array_pop($setsToVisit);
180
181 190
                for ($i = 0, $selectionCount = count($set->selections); $i < $selectionCount; $i++) {
182 190
                    $selection = $set->selections[$i];
183 190
                    if ($selection instanceof FragmentSpreadNode) {
184 55
                        $spreads[] = $selection;
185 182
                    } elseif ($selection instanceof FieldNode || $selection instanceof InlineFragmentNode) {
186 182
                        if ($selection->selectionSet) {
187 182
                            $setsToVisit[] = $selection->selectionSet;
188
                        }
189
                    } else {
190
                        throw InvariantViolation::shouldNotHappen();
191
                    }
192
                }
193
            }
194 190
            $this->fragmentSpreads[$node] = $spreads;
195
        }
196
197 190
        return $spreads;
198
    }
199
200
    /**
201
     * @param string $name
202
     *
203
     * @return FragmentDefinitionNode|null
204
     */
205 86
    public function getFragment($name)
206
    {
207 86
        $fragments = $this->fragments;
208 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...
209 86
            $fragments = [];
210 86
            foreach ($this->getDocument()->definitions as $statement) {
211 86
                if (! ($statement instanceof FragmentDefinitionNode)) {
212 51
                    continue;
213
                }
214
215 86
                $fragments[$statement->name->value] = $statement;
216
            }
217 86
            $this->fragments = $fragments;
218
        }
219
220 86
        return $fragments[$name] ?? null;
221
    }
222
223
    /**
224
     * Returns OutputType
225
     *
226
     * @return Type
227
     */
228 131
    public function getType()
229
    {
230 131
        return $this->typeInfo->getType();
231
    }
232
233
    /**
234
     * @return Type
235
     */
236 224
    public function getParentType()
237
    {
238 224
        return $this->typeInfo->getParentType();
239
    }
240
241
    /**
242
     * @return ScalarType|EnumType|InputObjectType|ListOfType|NonNull
243
     */
244 118
    public function getInputType() : ?InputType
245
    {
246 118
        return $this->typeInfo->getInputType();
247
    }
248
249
    /**
250
     * @return ScalarType|EnumType|InputObjectType|ListOfType|NonNull
251
     */
252 19
    public function getParentInputType() : ?InputType
253
    {
254 19
        return $this->typeInfo->getParentInputType();
255
    }
256
257
    /**
258
     * @return FieldDefinition
259
     */
260 159
    public function getFieldDef()
261
    {
262 159
        return $this->typeInfo->getFieldDef();
263
    }
264
265 5
    public function getDirective()
266
    {
267 5
        return $this->typeInfo->getDirective();
268
    }
269
270 106
    public function getArgument()
271
    {
272 106
        return $this->typeInfo->getArgument();
273
    }
274
}
275