Completed
Push — master ( 0f7b0a...750008 )
by Christoffer
02:09
created

ValidationContext::getDirective()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
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\AST\Node\DocumentNode;
7
use Digia\GraphQL\Language\AST\Node\FieldNode;
8
use Digia\GraphQL\Language\AST\Node\FragmentDefinitionNode;
9
use Digia\GraphQL\Language\AST\Node\FragmentSpreadNode;
10
use Digia\GraphQL\Language\AST\Node\NodeInterface;
11
use Digia\GraphQL\Language\AST\Node\OperationDefinitionNode;
12
use Digia\GraphQL\Language\AST\Node\SelectionSetNode;
13
use Digia\GraphQL\Language\AST\Node\VariableDefinitionNode;
14
use Digia\GraphQL\Language\AST\Node\VariableNode;
15
use Digia\GraphQL\Language\AST\Visitor\TypeInfoVisitor;
16
use Digia\GraphQL\Language\AST\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
     * @inheritdoc
102
     */
103
    public function getParentType(): ?TypeInterface
104
    {
105
        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...
106
    }
107
108
    /**
109
     * @inheritdoc
110
     */
111
    public function getFieldDefinition(): ?Field
112
    {
113
        return $this->typeInfo->getFieldDefinition();
114
    }
115
116
    /**
117
     * @inheritdoc
118
     */
119
    public function getSchema(): SchemaInterface
120
    {
121
        return $this->schema;
122
    }
123
124
    /**
125
     * @inheritdoc
126
     */
127
    public function getArgument(): ?Argument
128
    {
129
        return $this->typeInfo->getArgument();
130
    }
131
132
    /**
133
     * @inheritdoc
134
     */
135
    public function getDirective(): ?Directive
136
    {
137
        return $this->typeInfo->getDirective();
138
    }
139
140
    /**
141
     * @inheritdoc
142
     */
143
    public function getFragment(string $name): ?FragmentDefinitionNode
144
    {
145
        if (empty($this->fragments)) {
146
            $this->fragments = array_reduce($this->document->getDefinitions(), function ($fragments, $definition) {
147
                if ($definition instanceof FragmentDefinitionNode) {
148
                    $fragments[$definition->getNameValue()] = $definition;
149
                }
150
                return $fragments;
151
            }, []);
152
        }
153
154
        return $this->fragments[$name] ?? null;
155
    }
156
157
    /**
158
     * @inheritdoc
159
     */
160
    public function getFragmentSpreads(SelectionSetNode $selectionSet): array
161
    {
162
        $spreads = $this->fragmentSpreads[(string)$selectionSet] ?? null;
163
164
        if (null === $spreads) {
165
            $spreads = [];
166
167
            $setsToVisit = [$selectionSet];
168
169
            while (!empty($setsToVisit)) {
170
                /** @var SelectionSetNode $set */
171
                $set = array_pop($setsToVisit);
172
173
                /** @var FieldNode $selection */
174
                foreach ($set->getSelections() as $selection) {
175
                    if ($selection instanceof FragmentSpreadNode) {
176
                        $spreads[] = $selection;
177
                    } elseif ($selection->hasSelectionSet()) {
178
                        $setsToVisit[] = $selection->getSelectionSet();
179
                    }
180
                }
181
            }
182
183
            $this->fragmentSpreads[(string)$selectionSet] = $spreads;
184
        }
185
186
        return $spreads;
187
    }
188
189
    /**
190
     * @inheritdoc
191
     */
192
    public function getRecursiveVariableUsages(OperationDefinitionNode $operation): array
193
    {
194
        $usages = $this->recursiveVariableUsages[(string)$operation] ?? null;
195
196
        if (null === $usages) {
197
            $usages    = $this->getVariableUsages($operation);
198
            $fragments = $this->getRecursivelyReferencedFragments($operation);
199
200
            foreach ($fragments as $fragment) {
201
                // TODO: Figure out a more performance way to do this.
202
                /** @noinspection SlowArrayOperationsInLoopInspection */
203
                $usages = array_merge($usages, $this->getVariableUsages($fragment));
204
            }
205
206
            $this->recursiveVariableUsages[(string)$operation] = $usages;
207
        }
208
209
        return $usages;
210
    }
211
212
    /**
213
     * @param NodeInterface|OperationDefinitionNode|FragmentDefinitionNode $node
214
     * @inheritdoc
215
     */
216
    public function getVariableUsages(NodeInterface $node): array
217
    {
218
        $usages = $this->variableUsages[(string)$node] ?? null;
219
220
        if (null === $usages) {
221
            $usages   = [];
222
            $typeInfo = new TypeInfo($this->schema);
223
            $visitor  = new TypeInfoVisitor($typeInfo, new Visitor(
224
                function (NodeInterface $node) use (&$usages, $typeInfo): ?NodeInterface {
225
                    if ($node instanceof VariableDefinitionNode) {
226
                        return null;
227
                    }
228
229
                    if ($node instanceof VariableNode) {
230
                        $usages[] = ['node' => $node, 'type' => $typeInfo->getInputType()];
231
                    }
232
233
                    return $node;
234
                }
235
            ));
236
237
            $node->acceptVisitor($visitor);
0 ignored issues
show
Bug introduced by
The method acceptVisitor() does not exist on Digia\GraphQL\Language\AST\Node\NodeInterface. It seems like you code against a sub-type of said class. However, the method does not exist in Digia\GraphQL\Language\A...DefinitionNodeInterface or Digia\GraphQL\Language\A...eExtensionNodeInterface or Digia\GraphQL\Language\AST\Node\ValueNodeInterface or Digia\GraphQL\Language\A...\SelectionNodeInterface or Digia\GraphQL\Language\AST\Node\TypeNodeInterface or Digia\GraphQL\Language\A...DefinitionNodeInterface or Digia\GraphQL\Language\A...DefinitionNodeInterface or Digia\GraphQL\Language\A...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

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