Completed
Pull Request — master (#106)
by Christoffer
02:38
created

ValidationContext::getParentInputType()   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 getParentInputType(): ?TypeInterface
128
    {
129
        return $this->typeInfo->getParentInputType();
130
    }
131
132
    /**
133
     * @inheritdoc
134
     */
135
    public function getFieldDefinition(): ?Field
136
    {
137
        return $this->typeInfo->getFieldDefinition();
138
    }
139
140
    /**
141
     * @inheritdoc
142
     */
143
    public function getSchema(): SchemaInterface
144
    {
145
        return $this->schema;
146
    }
147
148
    /**
149
     * @inheritdoc
150
     */
151
    public function getArgument(): ?Argument
152
    {
153
        return $this->typeInfo->getArgument();
154
    }
155
156
    /**
157
     * @inheritdoc
158
     */
159
    public function getDirective(): ?Directive
160
    {
161
        return $this->typeInfo->getDirective();
162
    }
163
164
    /**
165
     * @inheritdoc
166
     */
167
    public function getFragment(string $name): ?FragmentDefinitionNode
168
    {
169
        if (empty($this->fragments)) {
170
            $this->fragments = array_reduce($this->document->getDefinitions(), function ($fragments, $definition) {
171
                if ($definition instanceof FragmentDefinitionNode) {
172
                    $fragments[$definition->getNameValue()] = $definition;
173
                }
174
                return $fragments;
175
            }, []);
176
        }
177
178
        return $this->fragments[$name] ?? null;
179
    }
180
181
    /**
182
     * @inheritdoc
183
     */
184
    public function getFragmentSpreads(SelectionSetNode $selectionSet): array
185
    {
186
        $spreads = $this->fragmentSpreads[(string)$selectionSet] ?? null;
187
188
        if (null === $spreads) {
189
            $spreads = [];
190
191
            $setsToVisit = [$selectionSet];
192
193
            while (!empty($setsToVisit)) {
194
                /** @var SelectionSetNode $set */
195
                $set = array_pop($setsToVisit);
196
197
                /** @var FieldNode $selection */
198
                foreach ($set->getSelections() as $selection) {
199
                    if ($selection instanceof FragmentSpreadNode) {
200
                        $spreads[] = $selection;
201
                    } elseif ($selection->hasSelectionSet()) {
202
                        $setsToVisit[] = $selection->getSelectionSet();
203
                    }
204
                }
205
            }
206
207
            $this->fragmentSpreads[(string)$selectionSet] = $spreads;
208
        }
209
210
        return $spreads;
211
    }
212
213
    /**
214
     * @inheritdoc
215
     */
216
    public function getRecursiveVariableUsages(OperationDefinitionNode $operation): array
217
    {
218
        $usages = $this->recursiveVariableUsages[(string)$operation] ?? null;
219
220
        if (null === $usages) {
221
            $usages    = $this->getVariableUsages($operation);
222
            $fragments = $this->getRecursivelyReferencedFragments($operation);
223
224
            foreach ($fragments as $fragment) {
225
                // TODO: Figure out a more performance way to do this.
226
                /** @noinspection SlowArrayOperationsInLoopInspection */
227
                $usages = array_merge($usages, $this->getVariableUsages($fragment));
228
            }
229
230
            $this->recursiveVariableUsages[(string)$operation] = $usages;
231
        }
232
233
        return $usages;
234
    }
235
236
    /**
237
     * @param NodeInterface|OperationDefinitionNode|FragmentDefinitionNode $node
238
     * @inheritdoc
239
     */
240
    public function getVariableUsages(NodeInterface $node): array
241
    {
242
        $usages = $this->variableUsages[(string)$node] ?? null;
243
244
        if (null === $usages) {
245
            $usages   = [];
246
            $typeInfo = new TypeInfo($this->schema);
247
            $visitor  = new TypeInfoVisitor($typeInfo, new Visitor(
248
                function (NodeInterface $node) use (&$usages, $typeInfo): ?NodeInterface {
249
                    if ($node instanceof VariableDefinitionNode) {
250
                        return null;
251
                    }
252
253
                    if ($node instanceof VariableNode) {
254
                        $usages[] = ['node' => $node, 'type' => $typeInfo->getInputType()];
255
                    }
256
257
                    return $node;
258
                }
259
            ));
260
261
            $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

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