Completed
Push — master ( bc1dad...026574 )
by Chris
03:13
created

Node::__clone()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 0
crap 2
1
<?php declare(strict_types=1);
2
3
namespace DaveRandom\Jom;
4
5
use DaveRandom\Jom\Exceptions\InvalidNodeValueException;
6
use DaveRandom\Jom\Exceptions\InvalidPointerException;
7
use DaveRandom\Jom\Exceptions\InvalidSubjectNodeException;
8
9
abstract class Node implements \JsonSerializable
10
{
11
    protected $ownerDocument;
12
13
    /** @var string|int|null */
14
    protected $key;
15
16
    /** @var Node|null */
17
    protected $parent;
18
19
    /** @var Node|null */
20
    protected $previousSibling;
21
22
    /** @var Node|null */
23
    protected $nextSibling;
24
25
    /**
26
     * @return Node[]
27
     */
28
    private static function getNodePath(Node $node): array
29
    {
30
        $path = [$node];
31
32
        while (null !== $node = $node->parent) {
33
            $path[] = $node;
34
        }
35
36
        return $path;
37
    }
38
39
    /**
40
     * @throws InvalidPointerException
41
     */
42
    private function getAbsolutePointer(): Pointer
43
    {
44
        $current = $this;
45
        $components = [];
46
47
        while ($current->key !== null) {
48
            $components[] = $current->key;
49
            $current = $current->parent;
50
        }
51
52
        return new Pointer(\array_reverse($components), null, false);
53
    }
54
55
    /**
56
     * @throws InvalidSubjectNodeException
57
     * @throws InvalidPointerException
58
     */
59
    private function getRelativePointer(Node $base): Pointer
60
    {
61
        if ($base->ownerDocument !== $this->ownerDocument) {
62
            throw new InvalidSubjectNodeException('Base node belongs to a different document');
63
        }
64
65
        $thisPath = self::getNodePath($this);
66
        $basePath = self::getNodePath($base);
67
68
        // Find the nearest common ancestor
69
        while (\end($thisPath) === \end($basePath)) {
70
            \array_pop($thisPath);
71
            \array_pop($basePath);
72
        }
73
74
        $path = [];
75
76
        for ($i = \count($thisPath) - 1; $i >= 0; $i--) {
77
            $path[] = $thisPath[$i]->key;
78
        }
79
80
        return new Pointer($path, \count($basePath), false);
81
    }
82
83
    /**
84
     * @throws InvalidNodeValueException
85
     * @return static
86
     */
87 37
    public static function createFromValue($value, ?Document $ownerDocument = null): Node
88
    {
89 37
        static $nodeFactory;
90
91
        try {
92 37
            $result = ($nodeFactory ?? $nodeFactory = new UnsafeNodeFactory)
93 37
                ->createNodeFromValue($value, $ownerDocument);
94
95 37
            if (!($result instanceof static)) {
0 ignored issues
show
introduced by
$result is always a sub-type of static.
Loading history...
96
                throw new InvalidNodeValueException(\sprintf(
97
                    "Value of type %s parsed as %s, %s expected",
98
                    \gettype($value),
99
                    \get_class($result),
100
                    static::class
101
                ));
102
            }
103
104 37
            return $result;
105
        } catch (InvalidNodeValueException $e) {
106
            throw $e;
107
        //@codeCoverageIgnoreStart
108
        } catch (\Exception $e) {
109
            throw new \Error('Unexpected ' . \get_class($e) . ": {$e->getMessage()}", 0, $e);
110
        }
111
        //@codeCoverageIgnoreEnd
112
    }
113
114 101
    protected function __construct(?Document $ownerDocument)
115
    {
116 101
        $this->ownerDocument = $ownerDocument;
117
    }
118
119
    public function __clone()
120
    {
121
        $this->setReferences(null, null, null, null);
122
    }
123
124 87
    final protected function setReferences(?Node $parent, $key, ?Node $previousSibling, ?Node $nextSibling): void
125
    {
126 87
        $this->parent = $parent;
127 87
        $this->key = $key;
128 87
        $this->previousSibling = $previousSibling;
129 87
        $this->nextSibling = $nextSibling;
130
    }
131
132 7
    final public function getParent(): ?Node
133
    {
134 7
        return $this->parent;
135
    }
136
137 7
    final public function getPreviousSibling(): ?Node
138
    {
139 7
        return $this->previousSibling;
140
    }
141
142 7
    final public function getNextSibling(): ?Node
143
    {
144 7
        return $this->nextSibling;
145
    }
146
147
    public function hasChildren(): bool
148
    {
149
        return false;
150
    }
151
152
    public function getFirstChild(): ?Node
153
    {
154
        return null;
155
    }
156
157
    public function getLastChild(): ?Node
158
    {
159
        return null;
160
    }
161
162
    final public function getOwnerDocument(): ?Document
163
    {
164
        return $this->ownerDocument;
165
    }
166
167
    /**
168
     * @return string|int|null
169
     */
170 9
    final public function getKey()
171
    {
172 9
        return $this->key;
173
    }
174
175
    /**
176
     * @throws InvalidPointerException
177
     * @throws InvalidSubjectNodeException
178
     */
179
    final public function getPointer(Node $base = null): Pointer
180
    {
181
        return $base === null
182
            ? $this->getAbsolutePointer()
183
            : $this->getRelativePointer($base);
184
    }
185
186
    abstract public function getValue();
187
    abstract public function jsonSerialize();
188
}
189