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

AcceptVisitorTrait::accept()   C

Complexity

Conditions 10
Paths 12

Size

Total Lines 52
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 23
nc 12
nop 5
dl 0
loc 52
rs 6.2553
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Digia\GraphQL\Language\AST\Visitor;
4
5
use Digia\GraphQL\Language\AST\Node\NodeInterface;
6
use Digia\GraphQL\Util\SerializationInterface;
7
8
trait AcceptVisitorTrait
9
{
10
11
    /**
12
     * @var VisitorInterface
13
     */
14
    protected $visitor;
15
16
    /**
17
     * @var string|int|null
18
     */
19
    protected $key;
20
21
    /**
22
     * @var NodeInterface|null
23
     */
24
    protected $parent;
25
26
    /**
27
     * @var array
28
     */
29
    protected $path;
30
31
    /**
32
     * @var array
33
     */
34
    protected $ancestors = [];
35
36
    /**
37
     * @var boolean
38
     */
39
    protected $isEdited = false;
40
41
    /**
42
     * @param VisitorInterface      $visitor
43
     * @param string|int|null       $key
44
     * @param NodeInterface|null    $parent
45
     * @param array                 $path
46
     * @param array|NodeInterface[] $ancestors
47
     * @return NodeInterface|AcceptVisitorTrait|SerializationInterface|null
48
     */
49
    public function accept(
50
        VisitorInterface $visitor,
51
        $key = null,
52
        ?NodeInterface $parent = null,
53
        array $path = [],
54
        array $ancestors = []
55
    ): ?NodeInterface {
56
        $this->visitor   = $visitor;
57
        $this->key       = $key;
58
        $this->parent    = $parent;
59
        $this->path      = $path;
60
        $this->ancestors = $ancestors;
61
62
        /** @var NodeInterface|AcceptVisitorTrait|null $newNode */
63
        $newNode = clone $this; // TODO: Benchmark cloning
64
65
        // If the result was null, it means that we should not traverse this branch.
66
        if (null === ($newNode = $visitor->enterNode($newNode))) {
0 ignored issues
show
Bug introduced by
It seems like $newNode can also be of type null and Digia\GraphQL\Language\A...itor\AcceptVisitorTrait; however, parameter $node of Digia\GraphQL\Language\A...rInterface::enterNode() does only seem to accept Digia\GraphQL\Language\AST\Node\NodeInterface, maybe add an additional type check? ( Ignorable by Annotation )

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

66
        if (null === ($newNode = $visitor->enterNode(/** @scrutinizer ignore-type */ $newNode))) {
Loading history...
67
            return null;
68
        }
69
70
        // If the node was edited, we want to return early
71
        // to avoid visiting its sub-tree completely.
72
        if ($newNode->determineIsEdited($this)) {
0 ignored issues
show
Bug introduced by
The method determineIsEdited() 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

72
        if ($newNode->/** @scrutinizer ignore-call */ determineIsEdited($this)) {
Loading history...
73
            return $newNode;
74
        }
75
76
        foreach (self::$kindToNodesToVisitMap[$this->kind] as $name) {
0 ignored issues
show
Bug Best Practice introduced by
The property kind does not exist on Digia\GraphQL\Language\A...itor\AcceptVisitorTrait. Did you maybe forget to declare it?
Loading history...
77
            $nodeOrNodes = $this->getNodeOrNodes($name);
78
79
            if (null === $nodeOrNodes || empty($nodeOrNodes)) {
80
                continue;
81
            }
82
83
            $newNodeOrNodes = $this->visitNodeOrNodes($nodeOrNodes, $name);
84
85
            if (null === $newNodeOrNodes || empty($newNodeOrNodes)) {
86
                continue;
87
            }
88
89
            $setter = 'set' . ucfirst($name);
90
91
            if (method_exists($newNode, $setter)) {
92
                $newNode->{$setter}($newNodeOrNodes);
93
            }
94
        }
95
96
        if (null !== $parent) {
97
            $this->removeAncestor();
98
        }
99
100
        return $visitor->leaveNode($newNode);
101
    }
102
103
    /**
104
     * @return bool
105
     */
106
    public function isEdited(): bool
107
    {
108
        return $this->isEdited;
109
    }
110
111
    /**
112
     * @param bool $isEdited
113
     * @return $this
114
     */
115
    public function setIsEdited(bool $isEdited)
116
    {
117
        $this->isEdited = $isEdited;
118
        return $this;
119
    }
120
121
    /**
122
     * @return NodeInterface|null
123
     */
124
    public function getClosestAncestor(): ?NodeInterface
125
    {
126
        return !empty($this->ancestors) ? $this->ancestors[\count($this->ancestors) - 1] : null;
127
    }
128
129
    /**
130
     * @return int|null|string
131
     */
132
    public function getKey()
133
    {
134
        return $this->key;
135
    }
136
137
    /**
138
     * @return NodeInterface|null
139
     */
140
    public function getParent(): ?NodeInterface
141
    {
142
        return $this->parent;
143
    }
144
145
    /**
146
     * @return array
147
     */
148
    public function getPath(): array
149
    {
150
        return $this->path;
151
    }
152
153
    /**
154
     * @param string|int $key
155
     * @return array|NodeInterface|NodeInterface[]|null
156
     */
157
    protected function getNodeOrNodes($key)
158
    {
159
        return $this->{$key};
160
    }
161
162
    /**
163
     * @param            $nodeOrNodes
164
     * @param string|int $key
165
     * @return array|NodeInterface|NodeInterface[]|null
166
     */
167
    protected function visitNodeOrNodes($nodeOrNodes, $key)
168
    {
169
        $this->addAncestor($this);
0 ignored issues
show
Bug introduced by
$this of type Digia\GraphQL\Language\A...itor\AcceptVisitorTrait is incompatible with the type Digia\GraphQL\Language\AST\Node\NodeInterface expected by parameter $node of Digia\GraphQL\Language\A...torTrait::addAncestor(). ( Ignorable by Annotation )

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

169
        $this->addAncestor(/** @scrutinizer ignore-type */ $this);
Loading history...
170
171
        $newNodeOrNodes = \is_array($nodeOrNodes)
172
            ? $this->visitNodes($nodeOrNodes, $key)
173
            : $this->visitNode($nodeOrNodes, $key, $this);
0 ignored issues
show
Bug introduced by
$this of type Digia\GraphQL\Language\A...itor\AcceptVisitorTrait is incompatible with the type null|Digia\GraphQL\Language\AST\Node\NodeInterface expected by parameter $parent of Digia\GraphQL\Language\A...sitorTrait::visitNode(). ( Ignorable by Annotation )

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

173
            : $this->visitNode($nodeOrNodes, $key, /** @scrutinizer ignore-type */ $this);
Loading history...
174
175
        $this->removeAncestor();
176
177
        return $newNodeOrNodes;
178
    }
179
180
    /**
181
     * @param NodeInterface[]    $nodes
182
     * @param string|int         $key
183
     * @param NodeInterface|null $parent
184
     * @return NodeInterface[]
185
     */
186
    protected function visitNodes(array $nodes, $key): array
187
    {
188
        $this->addOneToPath($key);
189
190
        $index    = 0;
191
        $newNodes = [];
192
193
        foreach ($nodes as $node) {
194
            $newNode = $this->visitNode($node, $index, null);
195
196
            if (null !== $newNode) {
197
                $newNodes[$index] = $newNode;
198
                $index++;
199
            }
200
        }
201
202
        $this->removeOneFromPath();
203
204
        return $newNodes;
205
    }
206
207
    /**
208
     * @param NodeInterface|AcceptVisitorTrait $node
209
     * @param string|int                       $key
210
     * @param NodeInterface|null               $parent
211
     * @return NodeInterface|null
212
     */
213
    protected function visitNode(NodeInterface $node, $key, ?NodeInterface $parent): ?NodeInterface
214
    {
215
        $this->addOneToPath($key);
216
217
        $newNode = $node->accept($this->visitor, $key, $parent, $this->path, $this->ancestors);
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

217
        /** @scrutinizer ignore-call */ 
218
        $newNode = $node->accept($this->visitor, $key, $parent, $this->path, $this->ancestors);
Loading history...
218
219
        // If the node was edited, we need to revisit it
220
        // to produce the expected result.
221
        if (null !== $newNode && $newNode->isEdited()) {
222
            $newNode = $newNode->accept($this->visitor, $key, $parent, $this->path, $this->ancestors);
223
        }
224
225
        $this->removeOneFromPath();
226
227
        return $newNode;
228
    }
229
230
    /**
231
     * @param NodeInterface|AcceptVisitorTrait $node
232
     * @return bool
233
     */
234
    protected function determineIsEdited(NodeInterface $node): bool
235
    {
236
        $this->isEdited = $this->isEdited || !$this->compareNode($node);
237
        return $this->isEdited;
238
    }
239
240
    /**
241
     * @param NodeInterface $other
242
     * @return bool
243
     */
244
    protected function compareNode(NodeInterface $other)
245
    {
246
        // TODO: Figure out a better way to solve this.
247
        return $this->toJSON() === $other->toJSON();
0 ignored issues
show
Bug introduced by
It seems like toJSON() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

247
        return $this->/** @scrutinizer ignore-call */ toJSON() === $other->toJSON();
Loading history...
248
    }
249
250
    /**
251
     * Appends a key to the path.
252
     * @param string $key
253
     */
254
    protected function addOneToPath(string $key)
255
    {
256
        $this->path[] = $key;
257
    }
258
259
    /**
260
     * Removes the last item from the path.
261
     */
262
    protected function removeOneFromPath()
263
    {
264
        $this->path = \array_slice($this->path, 0, -1);
265
    }
266
267
    /**
268
     * Adds an ancestor.
269
     * @param NodeInterface $node
270
     */
271
    protected function addAncestor(NodeInterface $node)
272
    {
273
        $this->ancestors[] = $node;
274
    }
275
276
    /**
277
     *  Removes the last ancestor.
278
     */
279
    protected function removeAncestor()
280
    {
281
        $this->ancestors = \array_slice($this->ancestors, 0, -1);
282
    }
283
284
    /**
285
     * @var array
286
     */
287
    protected static $kindToNodesToVisitMap = [
288
        'Name' => [],
289
290
        'Document'            => ['definitions'],
291
        'OperationDefinition' => [
292
            'name',
293
            'variableDefinitions',
294
            'directives',
295
            'selectionSet',
296
        ],
297
        'VariableDefinition'  => ['variable', 'type', 'defaultValue'],
298
        'Variable'            => ['name'],
299
        'SelectionSet'        => ['selections'],
300
        'Field'               => ['alias', 'name', 'arguments', 'directives', 'selectionSet'],
301
        'Argument'            => ['name', 'value'],
302
303
        'FragmentSpread'     => ['name', 'directives'],
304
        'InlineFragment'     => ['typeCondition', 'directives', 'selectionSet'],
305
        'FragmentDefinition' => [
306
            'name',
307
            // Note: fragment variable definitions are experimental and may be changed or removed in the future.
308
            'variableDefinitions',
309
            'typeCondition',
310
            'directives',
311
            'selectionSet',
312
        ],
313
314
        'IntValue'     => [],
315
        'FloatValue'   => [],
316
        'StringValue'  => [],
317
        'BooleanValue' => [],
318
        'NullValue'    => [],
319
        'EnumValue'    => [],
320
        'ListValue'    => ['values'],
321
        'ObjectValue'  => ['fields'],
322
        'ObjectField'  => ['name', 'value'],
323
324
        'Directive' => ['name', 'arguments'],
325
326
        'NamedType'   => ['name'],
327
        'ListType'    => ['type'],
328
        'NonNullType' => ['type'],
329
330
        'SchemaDefinition'        => ['directives', 'operationTypes'],
331
        'OperationTypeDefinition' => ['type'],
332
333
        'ScalarTypeDefinition'      => ['description', 'name', 'directives'],
334
        'ObjectTypeDefinition'      => [
335
            'description',
336
            'name',
337
            'interfaces',
338
            'directives',
339
            'fields',
340
        ],
341
        'FieldDefinition'           => ['description', 'name', 'arguments', 'type', 'directives'],
342
        'InputValueDefinition'      => [
343
            'description',
344
            'name',
345
            'type',
346
            'defaultValue',
347
            'directives',
348
        ],
349
        'InterfaceTypeDefinition'   => ['description', 'name', 'directives', 'fields'],
350
        'UnionTypeDefinition'       => ['description', 'name', 'directives', 'types'],
351
        'EnumTypeDefinition'        => ['description', 'name', 'directives', 'values'],
352
        'EnumValueDefinition'       => ['description', 'name', 'directives'],
353
        'InputObjectTypeDefinition' => ['description', 'name', 'directives', 'fields'],
354
355
        'ScalarTypeExtension'      => ['name', 'directives'],
356
        'ObjectTypeExtension'      => ['name', 'interfaces', 'directives', 'fields'],
357
        'InterfaceTypeExtension'   => ['name', 'directives', 'fields'],
358
        'UnionTypeExtension'       => ['name', 'directives', 'types'],
359
        'EnumTypeExtension'        => ['name', 'directives', 'values'],
360
        'InputObjectTypeExtension' => ['name', 'directives', 'fields'],
361
362
        'DirectiveDefinition' => ['description', 'name', 'arguments', 'locations'],
363
    ];
364
}
365