Completed
Push — master ( f400d6...2675c1 )
by Chris
02:17
created

Node::createFromValue()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 4.125

Importance

Changes 0
Metric Value
dl 0
loc 11
c 0
b 0
f 0
ccs 4
cts 8
cp 0.5
rs 9.4285
cc 3
eloc 8
nc 3
nop 2
crap 4.125
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 15
    public static function createFromValue($value, ?Document $ownerDocument = null): Node
88
    {
89 15
        static $nodeFactory;
90
91
        try {
92 15
            return ($nodeFactory ?? $nodeFactory = new UnsafeNodeFactory)
93 15
                ->createNodeFromValue($value, $ownerDocument);
94
        } catch (InvalidNodeValueException $e) {
95
            throw $e;
96
        } catch (\Exception $e) {
97
            throw new \Error('Unexpected ' . \get_class($e) . ": {$e->getMessage()}", 0, $e);
98
        }
99
    }
100
101 68
    protected function __construct(?Document $ownerDocument)
102
    {
103 68
        $this->ownerDocument = $ownerDocument;
104
    }
105
106 6
    final public function getParent(): ?Node
107
    {
108 6
        return $this->parent;
109
    }
110
111 6
    final public function getPreviousSibling(): ?Node
112
    {
113 6
        return $this->previousSibling;
114
    }
115
116 6
    final public function getNextSibling(): ?Node
117
    {
118 6
        return $this->nextSibling;
119
    }
120
121
    public function hasChildren(): bool
122
    {
123
        return false;
124
    }
125
126
    public function getFirstChild(): ?Node
127
    {
128
        return null;
129
    }
130
131
    public function getLastChild(): ?Node
132
    {
133
        return null;
134
    }
135
136
    final public function getOwnerDocument(): ?Document
137
    {
138
        return $this->ownerDocument;
139
    }
140
141
    /**
142
     * @return string|int|null
143
     */
144 6
    final public function getKey()
145
    {
146 6
        return $this->key;
147
    }
148
149
    /**
150
     * @throws InvalidPointerException
151
     * @throws InvalidSubjectNodeException
152
     */
153
    final public function getPointer(Node $base = null): Pointer
154
    {
155
        return $base === null
156
            ? $this->getAbsolutePointer()
157
            : $this->getRelativePointer($base);
158
    }
159
160
    abstract public function getValue();
161
    abstract public function jsonSerialize();
162
}
163