Completed
Push — master ( e6d18b...825aa7 )
by Chris
03:21
created

Node::createFromValue()   B

Complexity

Conditions 4
Paths 5

Size

Total Lines 23
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 6.4984

Importance

Changes 0
Metric Value
dl 0
loc 23
ccs 6
cts 13
cp 0.4615
rs 8.7972
c 0
b 0
f 0
cc 4
eloc 15
nc 5
nop 2
crap 6.4984
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 21
    public static function createFromValue($value, ?Document $ownerDocument = null): Node
88
    {
89 21
        static $nodeFactory;
90
91
        try {
92 21
            $result = ($nodeFactory ?? $nodeFactory = new UnsafeNodeFactory)
93 21
                ->createNodeFromValue($value, $ownerDocument);
94
95 21
            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 21
            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 85
    protected function __construct(?Document $ownerDocument)
115
    {
116 85
        $this->ownerDocument = $ownerDocument;
117
    }
118
119 74
    final protected function setReferences(?Node $parent, $key, ?Node $previousSibling, ?Node $nextSibling): void
120
    {
121 74
        $this->parent = $parent;
122 74
        $this->key = $key;
123 74
        $this->previousSibling = $previousSibling;
124 74
        $this->nextSibling = $nextSibling;
125
    }
126
127 7
    final public function getParent(): ?Node
128
    {
129 7
        return $this->parent;
130
    }
131
132 7
    final public function getPreviousSibling(): ?Node
133
    {
134 7
        return $this->previousSibling;
135
    }
136
137 7
    final public function getNextSibling(): ?Node
138
    {
139 7
        return $this->nextSibling;
140
    }
141
142
    public function hasChildren(): bool
143
    {
144
        return false;
145
    }
146
147
    public function getFirstChild(): ?Node
148
    {
149
        return null;
150
    }
151
152
    public function getLastChild(): ?Node
153
    {
154
        return null;
155
    }
156
157
    final public function getOwnerDocument(): ?Document
158
    {
159
        return $this->ownerDocument;
160
    }
161
162
    /**
163
     * @return string|int|null
164
     */
165 7
    final public function getKey()
166
    {
167 7
        return $this->key;
168
    }
169
170
    /**
171
     * @throws InvalidPointerException
172
     * @throws InvalidSubjectNodeException
173
     */
174
    final public function getPointer(Node $base = null): Pointer
175
    {
176
        return $base === null
177
            ? $this->getAbsolutePointer()
178
            : $this->getRelativePointer($base);
179
    }
180
181
    abstract public function getValue();
182
    abstract public function jsonSerialize();
183
}
184