Completed
Pull Request — master (#102)
by Christoffer
04:14 queued 01:07
created

ValidationContext::getInputType()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 0
1
<?php
2
3
namespace Digia\GraphQL\Validation;
4
5
use Digia\GraphQL\Error\ValidationException;
6
use Digia\GraphQL\Language\Node\DocumentNode;
7
use Digia\GraphQL\Language\Node\FieldNode;
8
use Digia\GraphQL\Language\Node\FragmentDefinitionNode;
9
use Digia\GraphQL\Language\Node\FragmentSpreadNode;
10
use Digia\GraphQL\Language\Node\NodeInterface;
11
use Digia\GraphQL\Language\Node\OperationDefinitionNode;
12
use Digia\GraphQL\Language\Node\SelectionSetNode;
13
use Digia\GraphQL\Language\Node\VariableDefinitionNode;
14
use Digia\GraphQL\Language\Node\VariableNode;
15
use Digia\GraphQL\Language\Visitor\TypeInfoVisitor;
16
use Digia\GraphQL\Language\Visitor\Visitor;
17
use Digia\GraphQL\Type\Definition\Argument;
18
use Digia\GraphQL\Type\Definition\Directive;
19
use Digia\GraphQL\Type\Definition\Field;
20
use Digia\GraphQL\Type\Definition\TypeInterface;
21
use Digia\GraphQL\Type\SchemaInterface;
22
use Digia\GraphQL\Util\TypeInfo;
23
24
class ValidationContext implements ValidationContextInterface
25
{
26
    /**
27
     * @var SchemaInterface
28
     */
29
    protected $schema;
30
31
    /**
32
     * @var DocumentNode
33
     */
34
    protected $document;
35
36
    /**
37
     * @var TypeInfo
38
     */
39
    protected $typeInfo;
40
41
    /**
42
     * @var array|ValidationException[]
43
     */
44
    protected $errors = [];
45
46
    /**
47
     * @var array|FragmentDefinitionNode[]
48
     */
49
    protected $fragments = [];
50
51
    /**
52
     * @var array
53
     */
54
    protected $fragmentSpreads = [];
55
56
    /**
57
     * @var array
58
     */
59
    protected $variableUsages = [];
60
61
    /**
62
     * @var array
63
     */
64
    protected $recursiveVariableUsages = [];
65
66
    /**
67
     * @var array
68
     */
69
    protected $recursivelyReferencedFragment = [];
70
71
    /**
72
     * ValidationContext constructor.
73
     * @param SchemaInterface $schema
74
     * @param DocumentNode    $document
75
     * @param TypeInfo        $typeInfo
76
     */
77
    public function __construct(SchemaInterface $schema, DocumentNode $document, TypeInfo $typeInfo)
78
    {
79
        $this->schema   = $schema;
80
        $this->document = $document;
81
        $this->typeInfo = $typeInfo;
82
    }
83
84
    /**
85
     * @inheritdoc
86
     */
87
    public function reportError(ValidationException $error): void
88
    {
89
        $this->errors[] = $error;
90
    }
91
92
    /**
93
     * @inheritdoc
94
     */
95
    public function getErrors(): array
96
    {
97
        return $this->errors;
98
    }
99
100
    /**
101
     * @return TypeInterface|null
102
     */
103
    public function getType(): ?TypeInterface
104
    {
105
        return $this->typeInfo->getType();
106
    }
107
108
    /**
109
     * @inheritdoc
110
     */
111
    public function getParentType(): ?TypeInterface
112
    {
113
        return $this->typeInfo->getParentType();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->typeInfo->getParentType() could return the type Digia\GraphQL\Type\Defin...\CompositeTypeInterface which is incompatible with the type-hinted return null|Digia\GraphQL\Type\Definition\TypeInterface. Consider adding an additional type-check to rule them out.
Loading history...
114
    }
115
116
    /**
117
     * @inheritdoc
118
     */
119
    public function getInputType(): ?TypeInterface
120
    {
121
        return $this->typeInfo->getInputType();
122
    }
123
124
    /**
125
     * @inheritdoc
126
     */
127
    public function getFieldDefinition(): ?Field
128
    {
129
        return $this->typeInfo->getFieldDefinition();
130
    }
131
132
    /**
133
     * @inheritdoc
134
     */
135
    public function getSchema(): SchemaInterface
136
    {
137
        return $this->schema;
138
    }
139
140
    /**
141
     * @inheritdoc
142
     */
143
    public function getArgument(): ?Argument
144
    {
145
        return $this->typeInfo->getArgument();
146
    }
147
148
    /**
149
     * @inheritdoc
150
     */
151
    public function getDirective(): ?Directive
152
    {
153
        return $this->typeInfo->getDirective();
154
    }
155
156
    /**
157
     * @inheritdoc
158
     */
159
    public function getFragment(string $name): ?FragmentDefinitionNode
160
    {
161
        if (empty($this->fragments)) {
162
            $this->fragments = array_reduce($this->document->getDefinitions(), function ($fragments, $definition) {
163
                if ($definition instanceof FragmentDefinitionNode) {
164
                    $fragments[$definition->getNameValue()] = $definition;
165
                }
166
                return $fragments;
167
            }, []);
168
        }
169
170
        return $this->fragments[$name] ?? null;
171
    }
172
173
    /**
174
     * @inheritdoc
175
     */
176
    public function getFragmentSpreads(SelectionSetNode $selectionSet): array
177
    {
178
        $spreads = $this->fragmentSpreads[(string)$selectionSet] ?? null;
179
180
        if (null === $spreads) {
181
            $spreads = [];
182
183
            $setsToVisit = [$selectionSet];
184
185
            while (!empty($setsToVisit)) {
186
                /** @var SelectionSetNode $set */
187
                $set = array_pop($setsToVisit);
188
189
                /** @var FieldNode $selection */
190
                foreach ($set->getSelections() as $selection) {
191
                    if ($selection instanceof FragmentSpreadNode) {
192
                        $spreads[] = $selection;
193
                    } elseif ($selection->hasSelectionSet()) {
194
                        $setsToVisit[] = $selection->getSelectionSet();
195
                    }
196
                }
197
            }
198
199
            $this->fragmentSpreads[(string)$selectionSet] = $spreads;
200
        }
201
202
        return $spreads;
203
    }
204
205
    /**
206
     * @inheritdoc
207
     */
208
    public function getRecursiveVariableUsages(OperationDefinitionNode $operation): array
209
    {
210
        $usages = $this->recursiveVariableUsages[(string)$operation] ?? null;
211
212
        if (null === $usages) {
213
            $usages    = $this->getVariableUsages($operation);
214
            $fragments = $this->getRecursivelyReferencedFragments($operation);
215
216
            foreach ($fragments as $fragment) {
217
                // TODO: Figure out a more performance way to do this.
218
                /** @noinspection SlowArrayOperationsInLoopInspection */
219
                $usages = array_merge($usages, $this->getVariableUsages($fragment));
220
            }
221
222
            $this->recursiveVariableUsages[(string)$operation] = $usages;
223
        }
224
225
        return $usages;
226
    }
227
228
    /**
229
     * @param NodeInterface|OperationDefinitionNode|FragmentDefinitionNode $node
230
     * @inheritdoc
231
     */
232
    public function getVariableUsages(NodeInterface $node): array
233
    {
234
        $usages = $this->variableUsages[(string)$node] ?? null;
235
236
        if (null === $usages) {
237
            $usages   = [];
238
            $typeInfo = new TypeInfo($this->schema);
239
            $visitor  = new TypeInfoVisitor($typeInfo, new Visitor(
240
                function (NodeInterface $node) use (&$usages, $typeInfo): ?NodeInterface {
241
                    if ($node instanceof VariableDefinitionNode) {
242
                        return null;
243
                    }
244
245
                    if ($node instanceof VariableNode) {
246
                        $usages[] = ['node' => $node, 'type' => $typeInfo->getInputType()];
247
                    }
248
249
                    return $node;
250
                }
251
            ));
252
253
            $node->acceptVisitor($visitor);
0 ignored issues
show
Bug introduced by
The method acceptVisitor() does not exist on Digia\GraphQL\Language\Node\NodeInterface. It seems like you code against a sub-type of said class. However, the method does not exist in Digia\GraphQL\Language\Node\SelectionNodeInterface or Digia\GraphQL\Language\N...DefinitionNodeInterface or Digia\GraphQL\Language\Node\ValueNodeInterface or Digia\GraphQL\Language\Node\TypeNodeInterface or Digia\GraphQL\Language\N...eExtensionNodeInterface or Digia\GraphQL\Language\N...DefinitionNodeInterface or Digia\GraphQL\Language\N...DefinitionNodeInterface or Digia\GraphQL\Language\N...DefinitionNodeInterface. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

253
            $node->/** @scrutinizer ignore-call */ 
254
                   acceptVisitor($visitor);
Loading history...
254
255
            $this->variableUsages[(string)$node] = $usages;
256
        }
257
258
        return $usages;
259
    }
260
261
    /**
262
     * @inheritdoc
263
     */
264
    public function getRecursivelyReferencedFragments(OperationDefinitionNode $operation): array
265
    {
266
        $fragments = $this->recursivelyReferencedFragment[(string)$operation] ?? null;
267
268
        if (null === $fragments) {
269
            $fragments      = [];
270
            $collectedNames = [];
271
            $nodesToVisit   = [$operation->getSelectionSet()];
272
273
            while (!empty($nodesToVisit)) {
274
                $node    = array_pop($nodesToVisit);
275
                $spreads = $this->getFragmentSpreads($node);
276
277
                foreach ($spreads as $spread) {
278
                    $fragmentName = $spread->getNameValue();
279
280
                    if (!isset($collectedNames[$fragmentName])) {
281
                        $collectedNames[$fragmentName] = true;
282
                        $fragment                      = $this->getFragment($fragmentName);
283
284
                        if (null !== $fragment) {
285
                            $fragments[]    = $fragment;
286
                            $nodesToVisit[] = $fragment->getSelectionSet();
287
                        }
288
                    }
289
                }
290
            }
291
292
            $this->recursivelyReferencedFragment[(string)$operation] = $fragments;
293
        }
294
295
        return $fragments;
296
    }
297
}
298