Completed
Pull Request — master (#80)
by Christoffer
02:18
created

ValidationContext::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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

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