Issues (167)

src/Language/Node/AbstractNode.php (1 issue)

Labels
Severity
1
<?php
2
3
namespace Digia\GraphQL\Language\Node;
4
5
use Digia\GraphQL\GraphQL;
6
use Digia\GraphQL\Language\Location;
7
use Digia\GraphQL\Language\NodeBuilderInterface;
8
use Digia\GraphQL\Language\Visitor\VisitorBreak;
9
use Digia\GraphQL\Language\Visitor\VisitorInfo;
10
use Digia\GraphQL\Language\Visitor\VisitorResult;
11
use Digia\GraphQL\Util\ArrayToJsonTrait;
12
use Digia\GraphQL\Util\NodeComparator;
13
use Digia\GraphQL\Util\SerializationInterface;
14
15
abstract class AbstractNode implements NodeInterface, SerializationInterface
16
{
17
    use ArrayToJsonTrait;
18
19
    /**
20
     * @var string
21
     */
22
    protected $kind;
23
24
    /**
25
     * @var Location|null
26
     */
27
    protected $location;
28
29
    /**
30
     * @var VisitorInfo|null
31
     */
32
    protected $visitorInfo;
33
34
    /**
35
     * @var bool
36
     */
37
    protected $isEdited = false;
38
39
    /**
40
     * @var NodeBuilderInterface
41
     */
42
    private static $nodeBuilder;
43
44
    /**
45
     * @return array
46
     */
47
    abstract public function toAST(): array;
48
49
    /**
50
     * AbstractNode constructor.
51
     *
52
     * @param string        $kind
53
     * @param Location|null $location
54
     */
55
    public function __construct(string $kind, ?Location $location)
56
    {
57
        $this->kind     = $kind;
58
        $this->location = $location;
59
    }
60
61
    /**
62
     * @return string
63
     */
64
    public function getKind(): string
65
    {
66
        return $this->kind;
67
    }
68
69
    /**
70
     * @return bool
71
     */
72
    public function hasLocation(): bool
73
    {
74
        return null !== $this->location;
75
    }
76
77
    /**
78
     * @return Location|null
79
     */
80
    public function getLocation(): ?Location
81
    {
82
        return $this->location;
83
    }
84
85
    /**
86
     * @return array|null
87
     */
88
    public function getLocationAST(): ?array
89
    {
90
        return null !== $this->location
91
            ? $this->location->toArray()
92
            : null;
93
    }
94
95
    /**
96
     * @return array
97
     */
98
    public function toArray(): array
99
    {
100
        return $this->toAST();
101
    }
102
103
    /**
104
     * @return string
105
     */
106
    public function __toString(): string
107
    {
108
        return $this->toJSON();
109
    }
110
111
    /**
112
     * @inheritdoc
113
     */
114
    public function acceptVisitor(VisitorInfo $visitorInfo): ?NodeInterface
115
    {
116
        $this->visitorInfo = $visitorInfo;
117
118
        $visitor       = $this->visitorInfo->getVisitor();
119
        $VisitorResult = $visitor->enterNode(clone $this);
120
        $newNode       = $VisitorResult->getValue();
121
122
        // Handle early exit while entering
123
        if ($VisitorResult->getAction() === VisitorResult::ACTION_BREAK) {
124
            /** @noinspection PhpUnhandledExceptionInspection */
125
            throw new VisitorBreak();
126
        }
127
128
        // If the result was null, it means that we should not traverse this branch.
129
        if (null === $newNode) {
130
            return null;
131
        }
132
133
        // If the node was edited, we want to return early to avoid visiting its sub-tree completely.
134
        if ($newNode->determineIsEdited($this)) {
135
            return $newNode;
136
        }
137
138
        foreach (self::$kindToNodesToVisitMap[$this->kind] as $property) {
139
            $nodeOrNodes = $this->{$property};
140
141
            if (empty($nodeOrNodes)) {
142
                continue;
143
            }
144
145
            $newNodeOrNodes = $this->visitNodeOrNodes($nodeOrNodes, $property, $newNode);
146
147
            if (empty($newNodeOrNodes)) {
148
                continue;
149
            }
150
151
            $setter = 'set' . \ucfirst($property);
152
153
            if (\method_exists($newNode, $setter)) {
154
                $newNode->{$setter}($newNodeOrNodes);
155
            }
156
        }
157
158
        $VisitorResult = $visitor->leaveNode($newNode);
159
160
        // Handle early exit while leaving
161
        if ($VisitorResult->getAction() === VisitorResult::ACTION_BREAK) {
162
            /** @noinspection PhpUnhandledExceptionInspection */
163
            throw new VisitorBreak();
164
        }
165
166
        return $VisitorResult->getValue();
167
    }
168
169
    /**
170
     * @return VisitorInfo|null
171
     */
172
    public function getVisitorInfo(): ?VisitorInfo
173
    {
174
        return $this->visitorInfo;
175
    }
176
177
    /**
178
     * @inheritdoc
179
     */
180
    public function determineIsEdited(NodeInterface $node): bool
181
    {
182
        return $this->isEdited = $this->isEdited() || !NodeComparator::compare($this, $node);
183
    }
184
185
    /**
186
     * @inheritdoc
187
     */
188
    public function getAncestor(int $depth = 1): ?NodeInterface
189
    {
190
        return null !== $this->visitorInfo ? $this->visitorInfo->getAncestor($depth) : null;
191
    }
192
193
    /**
194
     * @return bool
195
     */
196
    public function isEdited(): bool
197
    {
198
        return $this->isEdited;
199
    }
200
201
    /**
202
     * @param NodeInterface|NodeInterface[] $nodeOrNodes
203
     * @param mixed                         $key
204
     * @param NodeInterface                 $parent
205
     * @return NodeInterface|NodeInterface[]|null
206
     */
207
    protected function visitNodeOrNodes($nodeOrNodes, $key, NodeInterface $parent)
208
    {
209
        $this->visitorInfo->addAncestor($parent);
0 ignored issues
show
The method addAncestor() does not exist on null. ( Ignorable by Annotation )

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

209
        $this->visitorInfo->/** @scrutinizer ignore-call */ 
210
                            addAncestor($parent);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
210
211
        $newNodeOrNodes = \is_array($nodeOrNodes)
212
            ? $this->visitNodes($nodeOrNodes, $key)
213
            : $this->visitNode($nodeOrNodes, $key, $parent);
214
215
        $this->visitorInfo->removeAncestor();
216
217
        return $newNodeOrNodes;
218
    }
219
220
    /**
221
     * @param NodeInterface[] $nodes
222
     * @param string|int      $key
223
     * @return NodeInterface[]
224
     */
225
    protected function visitNodes(array $nodes, $key): array
226
    {
227
        $this->visitorInfo->addOneToPath($key);
228
229
        $index    = 0;
230
        $newNodes = [];
231
232
        foreach ($nodes as $node) {
233
            $newNode = $this->visitNode($node, $index, null);
234
235
            if (null !== $newNode) {
236
                $newNodes[$index] = $newNode;
237
                $index++;
238
            }
239
        }
240
241
        $this->visitorInfo->removeOneFromPath();
242
243
        return $newNodes;
244
    }
245
246
    /**
247
     * @param NodeInterface      $node
248
     * @param string|int         $key
249
     * @param NodeInterface|null $parent
250
     * @return NodeInterface|null
251
     */
252
    protected function visitNode(NodeInterface $node, $key, ?NodeInterface $parent): ?NodeInterface
253
    {
254
        $this->visitorInfo->addOneToPath($key);
255
256
        $info = new VisitorInfo(
257
            $this->visitorInfo->getVisitor(),
258
            $key,
259
            $parent,
260
            $this->visitorInfo->getPath(),
261
            $this->visitorInfo->getAncestors()
262
        );
263
264
        $newNode = $node->acceptVisitor($info);
265
266
        // If the node was edited, we need to revisit it to produce the expected result.
267
        if (null !== $newNode && $newNode->isEdited()) {
268
            $newNode = $newNode->acceptVisitor($info);
269
        }
270
271
        $this->visitorInfo->removeOneFromPath();
272
273
        return $newNode;
274
    }
275
276
    /**
277
     * @param NodeInterface $other
278
     * @return bool
279
     */
280
    protected function compareNode(NodeInterface $other)
281
    {
282
        return $this->toJSON() === $other->toJSON();
283
    }
284
285
    /**
286
     * @return NodeBuilderInterface
287
     */
288
    protected function getNodeBuilder(): NodeBuilderInterface
289
    {
290
        if (null === self::$nodeBuilder) {
291
            self::$nodeBuilder = GraphQL::make(NodeBuilderInterface::class);
292
        }
293
294
        return self::$nodeBuilder;
295
    }
296
297
    /**
298
     * @var array
299
     */
300
    protected static $kindToNodesToVisitMap = [
301
        'Name' => [],
302
303
        'Document'            => ['definitions'],
304
        'OperationDefinition' => [
305
            'name',
306
            'variableDefinitions',
307
            'directives',
308
            'selectionSet',
309
        ],
310
        'VariableDefinition'  => ['variable', 'type', 'defaultValue'],
311
        'Variable'            => ['name'],
312
        'SelectionSet'        => ['selections'],
313
        'Field'               => ['alias', 'name', 'arguments', 'directives', 'selectionSet'],
314
        'Argument'            => ['name', 'value'],
315
316
        'FragmentSpread'     => ['name', 'directives'],
317
        'InlineFragment'     => ['typeCondition', 'directives', 'selectionSet'],
318
        'FragmentDefinition' => [
319
            'name',
320
            'variableDefinitions',
321
            'typeCondition',
322
            'directives',
323
            'selectionSet',
324
        ],
325
326
        'IntValue'     => [],
327
        'FloatValue'   => [],
328
        'StringValue'  => [],
329
        'BooleanValue' => [],
330
        'NullValue'    => [],
331
        'EnumValue'    => [],
332
        'ListValue'    => ['values'],
333
        'ObjectValue'  => ['fields'],
334
        'ObjectField'  => ['name', 'value'],
335
336
        'Directive' => ['name', 'arguments'],
337
338
        'NamedType'   => ['name'],
339
        'ListType'    => ['type'],
340
        'NonNullType' => ['type'],
341
342
        'SchemaDefinition'        => ['directives', 'operationTypes'],
343
        'OperationTypeDefinition' => ['type'],
344
345
        'ScalarTypeDefinition'      => ['description', 'name', 'directives'],
346
        'ObjectTypeDefinition'      => [
347
            'description',
348
            'name',
349
            'interfaces',
350
            'directives',
351
            'fields',
352
        ],
353
        'FieldDefinition'           => ['description', 'name', 'arguments', 'type', 'directives'],
354
        'InputValueDefinition'      => [
355
            'description',
356
            'name',
357
            'type',
358
            'defaultValue',
359
            'directives',
360
        ],
361
        'InterfaceTypeDefinition'   => ['description', 'name', 'directives', 'fields'],
362
        'UnionTypeDefinition'       => ['description', 'name', 'directives', 'types'],
363
        'EnumTypeDefinition'        => ['description', 'name', 'directives', 'values'],
364
        'EnumValueDefinition'       => ['description', 'name', 'directives'],
365
        'InputObjectTypeDefinition' => ['description', 'name', 'directives', 'fields'],
366
367
        'DirectiveDefinition' => ['description', 'name', 'arguments', 'locations'],
368
369
        'SchemaExtension' => ['directives', 'operationTypes'],
370
371
        'ScalarTypeExtension'      => ['name', 'directives'],
372
        'ObjectTypeExtension'      => ['name', 'interfaces', 'directives', 'fields'],
373
        'InterfaceTypeExtension'   => ['name', 'directives', 'fields'],
374
        'UnionTypeExtension'       => ['name', 'directives', 'types'],
375
        'EnumTypeExtension'        => ['name', 'directives', 'values'],
376
        'InputObjectTypeExtension' => ['name', 'directives', 'fields'],
377
    ];
378
}
379