Completed
Pull Request — master (#45)
by Christoffer
02:06
created

ValidationContext::getFragmentSpreads()   B

Complexity

Conditions 6
Paths 2

Size

Total Lines 27
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 13
nc 2
nop 1
dl 0
loc 27
rs 8.439
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\SelectionNodeInterface;
13
use Digia\GraphQL\Language\AST\Node\SelectionSetNode;
14
use Digia\GraphQL\Language\AST\Node\VariableDefinitionNode;
15
use Digia\GraphQL\Language\AST\Node\VariableNode;
16
use Digia\GraphQL\Language\AST\Visitor\AcceptVisitorTrait;
17
use Digia\GraphQL\Language\AST\Visitor\TypeInfoVisitor;
18
use Digia\GraphQL\Language\AST\Visitor\Visitor;
19
use Digia\GraphQL\Type\Definition\Argument;
20
use Digia\GraphQL\Type\Definition\Directive;
21
use Digia\GraphQL\Type\Definition\Field;
22
use Digia\GraphQL\Type\Definition\TypeInterface;
23
use Digia\GraphQL\Type\SchemaInterface;
24
use Digia\GraphQL\Util\TypeInfo;
25
26
class ValidationContext
27
{
28
    /**
29
     * @var SchemaInterface
30
     */
31
    protected $schema;
32
33
    /**
34
     * @var DocumentNode
35
     */
36
    protected $documentNode;
37
38
    /**
39
     * @var TypeInfo
40
     */
41
    protected $typeInfo;
42
43
    /**
44
     * @var array|ValidationException[]
45
     */
46
    protected $errors = [];
47
48
    /**
49
     * @var array|FragmentDefinitionNode[]
50
     */
51
    protected $fragments = [];
52
53
    /**
54
     * @var array
55
     */
56
    protected $fragmentSpreads = [];
57
58
    /**
59
     * @var array
60
     */
61
    protected $variableUsages = [];
62
63
    /**
64
     * @var array
65
     */
66
    protected $recursiveVariableUsages = [];
67
68
    /**
69
     * @var array
70
     */
71
    protected $recursivelyReferencedFragment = [];
72
73
    /**
74
     * ValidationContext constructor.
75
     * @param SchemaInterface $schema
76
     * @param DocumentNode    $documentNode
77
     * @param TypeInfo        $typeInfo
78
     */
79
    public function __construct(SchemaInterface $schema, DocumentNode $documentNode, TypeInfo $typeInfo)
80
    {
81
        $this->schema       = $schema;
82
        $this->documentNode = $documentNode;
83
        $this->typeInfo     = $typeInfo;
84
    }
85
86
    /**
87
     * @param ValidationException $error
88
     */
89
    public function reportError(ValidationException $error): void
90
    {
91
        $this->errors[] = $error;
92
    }
93
94
    /**
95
     * @return array|ValidationException[]
96
     */
97
    public function getErrors(): array
98
    {
99
        return $this->errors;
100
    }
101
102
    /**
103
     * @return TypeInterface|null
104
     */
105
    public function getParentType(): ?TypeInterface
106
    {
107
        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...
108
    }
109
110
    /**
111
     * @return Field|null
112
     */
113
    public function getFieldDefinition(): ?Field
114
    {
115
        return $this->typeInfo->getFieldDefinition();
116
    }
117
118
    /**
119
     * @return SchemaInterface
120
     */
121
    public function getSchema(): SchemaInterface
122
    {
123
        return $this->schema;
124
    }
125
126
    /**
127
     * @return Argument|null
128
     */
129
    public function getArgument(): ?Argument
130
    {
131
        return $this->typeInfo->getArgument();
132
    }
133
134
    /**
135
     * @return Directive|null
136
     */
137
    public function getDirective(): ?Directive
138
    {
139
        return $this->typeInfo->getDirective();
140
    }
141
142
    /**
143
     * @param string $name
144
     * @return FragmentDefinitionNode|null
145
     */
146
    public function getFragment(string $name): ?FragmentDefinitionNode
147
    {
148
        if (empty($this->fragments)) {
149
            $this->fragments = array_reduce($this->documentNode->getDefinitions(), function ($fragments, $definition) {
150
                if ($definition instanceof FragmentDefinitionNode) {
151
                    $fragments[$definition->getNameValue()] = $definition;
152
                }
153
                return $fragments;
154
            }, []);
155
        }
156
157
        return $this->fragments[$name] ?? null;
158
    }
159
160
    /**
161
     * @param SelectionSetNode $selectionSet
162
     * @return array|FragmentSpreadNode[]
163
     */
164
    public function getFragmentSpreads(SelectionSetNode $selectionSet): array
165
    {
166
        $spreads = $this->fragmentSpreads[(string)$selectionSet] ?? null;
167
168
        if (null === $spreads) {
169
            $spreads = [];
170
171
            $setsToVisit = [$selectionSet];
172
173
            while (!empty($setsToVisit)) {
174
                /** @var SelectionSetNode $set */
175
                $set = array_pop($setsToVisit);
176
177
                /** @var FieldNode $selection */
178
                foreach ($set->getSelections() as $selection) {
179
                    if ($selection instanceof FragmentSpreadNode) {
180
                        $spreads[] = $selection;
181
                    } elseif ($selection->hasSelectionSet()) {
182
                        $setsToVisit[] = $selection->getSelectionSet();
183
                    }
184
                }
185
            }
186
187
            $this->fragmentSpreads[(string)$selectionSet] = $spreads;
188
        }
189
190
        return $spreads;
191
    }
192
193
    /**
194
     * @param OperationDefinitionNode $operation
195
     * @return array
196
     */
197
    public function getRecursiveVariableUsages(OperationDefinitionNode $operation): array
198
    {
199
        $usages = $this->recursiveVariableUsages[(string)$operation] ?? null;
200
201
        if (null === $usages) {
202
            $usages = $this->getVariableUsages($operation);
203
            $fragments = $this->getRecursivelyReferencedFragments($operation);
204
205
            foreach ($fragments as $fragment) {
206
                $usages[] = $this->getVariableUsages($fragment);
207
            }
208
209
            $this->recursiveVariableUsages[(string)$operation] = $usages;
210
        }
211
212
        return $usages;
213
    }
214
215
    /**
216
     * @param OperationDefinitionNode|FragmentDefinitionNode $node
217
     * @return array
218
     */
219
    public function getVariableUsages(NodeInterface $node): array
220
    {
221
        $usages = $this->variableUsages[(string)$node] ?? null;
222
223
        if (null === $usages) {
224
            $usages = [];
225
            $typeInfo = new TypeInfo($this->schema);
226
            $visitor = new TypeInfoVisitor($typeInfo, new Visitor(
227
                function (NodeInterface $node) use (&$usages, $typeInfo): ?NodeInterface
228
                {
229
                    if ($node instanceof VariableDefinitionNode) {
230
                        return null;
231
                    }
232
233
                    if ($node instanceof VariableNode) {
234
                        $usages[] = ['node' => $node, 'type' => $typeInfo->getInputType()];
235
                    }
236
237
                    return $node;
238
                }
239
            ));
240
241
            $node->accept($visitor);
0 ignored issues
show
Bug introduced by
The method accept() 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

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