Completed
Pull Request — master (#23)
by Christoffer
01:59
created

AcceptVisitorTrait::determineIsEdited()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 2
nc 2
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
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 array
18
     */
19
    protected $path;
20
21
    /**
22
     * @var boolean
23
     */
24
    protected $isEdited = false;
25
26
    /**
27
     * @param VisitorInterface $visitor
28
     * @param string|null $key
29
     * @param array $path
30
     * @return NodeInterface|AcceptVisitorTrait|SerializationInterface|null
31
     */
32
    public function accept(VisitorInterface $visitor, ?string $key = null, array $path = []): ?NodeInterface
33
    {
34
        $this->visitor = $visitor;
35
        $this->path = $path;
36
37
        /** @var NodeInterface|AcceptVisitorTrait|null $newNode */
38
        $newNode = clone $this; // TODO: Benchmark cloning
39
40
        if (null === ($newNode = $visitor->enterNode($newNode, $key, $this->path))) {
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

40
        if (null === ($newNode = $visitor->enterNode(/** @scrutinizer ignore-type */ $newNode, $key, $this->path))) {
Loading history...
41
            return null;
42
        }
43
44
        // If the node has been edited, we have to return early, because otherwise
45
        // the edited AST won't be what we'd expect.
46
        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

46
        if ($newNode->/** @scrutinizer ignore-call */ determineIsEdited($this)) {
Loading history...
47
            return $newNode;
48
        }
49
50
        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...
51
            $nodeOrNodes = $this->getNodeOrNodes($name);
52
53
            if (null === $nodeOrNodes || empty($nodeOrNodes)) {
54
                continue;
55
            }
56
57
            $newNodeOrNodes = $this->visitNodeOrNodes($nodeOrNodes, $name);
58
59
            if (null === $newNodeOrNodes || empty($newNodeOrNodes)) {
60
                continue;
61
            }
62
63
            $setter = 'set' . ucfirst($name);
64
65
            if (method_exists($newNode, $setter)) {
66
                $newNode->{$setter}($newNodeOrNodes);
67
            }
68
        }
69
70
        if (null === ($newNode = $visitor->leaveNode($newNode, $key, $this->path))) {
71
            return null;
72
        }
73
74
        $newNode->determineIsEdited($this);
75
76
        return $newNode;
77
    }
78
79
    /**
80
     * @return bool
81
     */
82
    public function isEdited(): bool
83
    {
84
        return $this->isEdited;
85
    }
86
87
    /**
88
     * @param bool $isEdited
89
     * @return $this
90
     */
91
    public function setIsEdited(bool $isEdited)
92
    {
93
        $this->isEdited = $isEdited;
94
        return $this;
95
    }
96
97
    /**
98
     * @param string $key
99
     * @return array|NodeInterface|NodeInterface[]|null
100
     */
101
    protected function getNodeOrNodes(string $key)
102
    {
103
        return $this->{$key};
104
    }
105
106
    /**
107
     * @param $nodeOrNodes
108
     * @param string $key
109
     * @return array|NodeInterface|NodeInterface[]|null
110
     */
111
    protected function visitNodeOrNodes($nodeOrNodes, string $key)
112
    {
113
        return \is_array($nodeOrNodes) ? $this->visitNodes($nodeOrNodes, $key) : $this->visitNode($nodeOrNodes, $key);
114
    }
115
116
    /**
117
     * @param NodeInterface[] $nodes
118
     * @param string $key
119
     * @return NodeInterface[]
120
     */
121
    protected function visitNodes(array $nodes, string $key): array
122
    {
123
        $this->addOneToPath($key);
124
125
        $index = 0;
126
        $newNodes = [];
127
128
        foreach ($nodes as $node) {
129
            if (null !== ($newNode = $this->visitNode($node, $index))) {
130
                $newNodes[$index] = $newNode;
131
                $index++;
132
            }
133
        }
134
135
        $this->removeOneFromPath();
136
137
        return $newNodes;
138
    }
139
140
    /**
141
     * @param NodeInterface|AcceptVisitorTrait $node
142
     * @param string $key
143
     * @return NodeInterface|null
144
     */
145
    protected function visitNode(NodeInterface $node, string $key): ?NodeInterface
146
    {
147
        $this->addOneToPath($key);
148
149
        $newNode = $node->accept($this->visitor, $key, $this->path);
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

149
        /** @scrutinizer ignore-call */ 
150
        $newNode = $node->accept($this->visitor, $key, $this->path);
Loading history...
150
151
        if (null !== $newNode && $newNode->isEdited()) {
152
            $newNode = $newNode->accept($this->visitor, $key, $this->path);
153
        }
154
155
        $this->removeOneFromPath();
156
157
        return $newNode;
158
    }
159
160
    /**
161
     * @param NodeInterface|AcceptVisitorTrait $node
162
     * @return bool
163
     */
164
    protected function determineIsEdited(NodeInterface $node): bool
165
    {
166
        $this->isEdited = $this->isEdited || !$this->compareNode($node);
167
        return $this->isEdited;
168
    }
169
170
    /**
171
     * @param NodeInterface $other
172
     * @return bool
173
     */
174
    protected function compareNode(NodeInterface $other)
175
    {
176
        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

176
        return $this->/** @scrutinizer ignore-call */ toJSON() === $other->toJSON();
Loading history...
177
    }
178
179
    /**
180
     * Appends a key to the path.
181
     * @param string $key
182
     */
183
    protected function addOneToPath(string $key)
184
    {
185
        $this->path[] = $key;
186
    }
187
188
    /**
189
     * Removes the last item from the path.
190
     */
191
    protected function removeOneFromPath()
192
    {
193
        $this->path = \array_slice($this->path, 0, count($this->path) - 1);
194
    }
195
196
    /**
197
     * @var array
198
     */
199
    protected static $kindToNodesToVisitMap = [
200
        'Name' => [],
201
202
        'Document' => ['definitions'],
203
        'OperationDefinition' => [
204
            'name',
205
            'variableDefinitions',
206
            'directives',
207
            'selectionSet',
208
        ],
209
        'VariableDefinition' => ['variable', 'type', 'defaultValue'],
210
        'Variable' => ['name'],
211
        'SelectionSet' => ['selections'],
212
        'Field' => ['alias', 'name', 'arguments', 'directives', 'selectionSet'],
213
        'Argument' => ['name', 'value'],
214
215
        'FragmentSpread' => ['name', 'directives'],
216
        'InlineFragment' => ['typeCondition', 'directives', 'selectionSet'],
217
        'FragmentDefinition' => [
218
            'name',
219
            // Note: fragment variable definitions are experimental and may be changed or removed in the future.
220
            'variableDefinitions',
221
            'typeCondition',
222
            'directives',
223
            'selectionSet',
224
        ],
225
226
        'IntValue' => [],
227
        'FloatValue' => [],
228
        'StringValue' => [],
229
        'BooleanValue' => [],
230
        'NullValue' => [],
231
        'EnumValue' => [],
232
        'ListValue' => ['values'],
233
        'ObjectValue' => ['fields'],
234
        'ObjectField' => ['name', 'value'],
235
236
        'Directive' => ['name', 'arguments'],
237
238
        'NamedType' => ['name'],
239
        'ListType' => ['type'],
240
        'NonNullType' => ['type'],
241
242
        'SchemaDefinition' => ['directives', 'operationTypes'],
243
        'OperationTypeDefinition' => ['type'],
244
245
        'ScalarTypeDefinition' => ['description', 'name', 'directives'],
246
        'ObjectTypeDefinition' => [
247
            'description',
248
            'name',
249
            'interfaces',
250
            'directives',
251
            'fields',
252
        ],
253
        'FieldDefinition' => ['description', 'name', 'arguments', 'type', 'directives'],
254
        'InputValueDefinition' => [
255
            'description',
256
            'name',
257
            'type',
258
            'defaultValue',
259
            'directives',
260
        ],
261
        'InterfaceTypeDefinition' => ['description', 'name', 'directives', 'fields'],
262
        'UnionTypeDefinition' => ['description', 'name', 'directives', 'types'],
263
        'EnumTypeDefinition' => ['description', 'name', 'directives', 'values'],
264
        'EnumValueDefinition' => ['description', 'name', 'directives'],
265
        'InputObjectTypeDefinition' => ['description', 'name', 'directives', 'fields'],
266
267
        'ScalarTypeExtension' => ['name', 'directives'],
268
        'ObjectTypeExtension' => ['name', 'interfaces', 'directives', 'fields'],
269
        'InterfaceTypeExtension' => ['name', 'directives', 'fields'],
270
        'UnionTypeExtension' => ['name', 'directives', 'types'],
271
        'EnumTypeExtension' => ['name', 'directives', 'values'],
272
        'InputObjectTypeExtension' => ['name', 'directives', 'fields'],
273
274
        'DirectiveDefinition' => ['description', 'name', 'arguments', 'locations'],
275
    ];
276
}
277