Completed
Push — master ( 75194b...9afc27 )
by Chris
02:30
created

Document::importScalarNode()   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 0
cts 4
cp 0
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 6
nc 3
nop 1
crap 30
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
use DaveRandom\Jom\Exceptions\ParseFailureException;
9
use DaveRandom\Jom\Exceptions\PointerReferenceNotFoundException;
10
use DaveRandom\Jom\Exceptions\WriteOperationForbiddenException;
11
use ExceptionalJSON\DecodeErrorException;
12
13
final class Document implements \JsonSerializable
14
{
15
    public const IGNORE_INVALID_VALUES = Node::IGNORE_INVALID_VALUES;
16
17
    /** @var Node */
18
    private $rootNode;
19
20
    /**
21
     * @throws PointerReferenceNotFoundException
22
     */
23
    private function evaluatePointerPath(Pointer $pointer, Node $current): Node
24
    {
25
        foreach ($pointer->getPath() as $component) {
26
            if (!($current instanceof VectorNode)) {
27
                throw new PointerReferenceNotFoundException(
28
                    "Pointer '{$pointer}' does not indicate a valid path in the document"
29
                );
30
            }
31
32
            if (!$current->offsetExists($component)) {
33
                throw new PointerReferenceNotFoundException("The referenced property or index '{$component}' does not exist");
34
            }
35
36
            $current = $current->offsetGet($component);
37
        }
38
39
        return $current;
40
    }
41
42
    /**
43
     * @throws PointerReferenceNotFoundException
44
     * @throws InvalidSubjectNodeException
45
     */
46
    private function evaluateRelativePointer(Pointer $pointer, Node $current): Node
47
    {
48
        if ($current->getOwnerDocument() !== $this) {
49
            throw new InvalidSubjectNodeException('Base node belongs to a different document');
50
        }
51
52
        for ($i = $pointer->getRelativeLevels(); $i > 0; $i--) {
53
            $current = $current->getParent();
54
55
            if ($current === null) {
56
                throw new PointerReferenceNotFoundException(
57
                    "Pointer '{$pointer}' does not indicate a valid path in the document relative to the supplied node"
58
                );
59
            }
60
        }
61
62
        return $this->evaluatePointerPath($pointer, $current);
63
    }
64
65
    /**
66
     * @throws InvalidNodeValueException
67
     * @throws InvalidSubjectNodeException
68
     * @throws WriteOperationForbiddenException
69
     */
70
    private function importVectorNode(VectorNode $node): VectorNode
71
    {
72
        if (!($node instanceof ArrayNode || $node instanceof ObjectNode)) {
73
            throw new InvalidSubjectNodeException('Source node is of unknown type ' . \get_class($node));
74
        }
75
76
        $newNode = new $node(null, $this);
77
78
        foreach ($node as $key => $value) {
79
            $newNode[$key] = $this->import($value);
80
        }
81
82
        return $newNode;
83
    }
84
85
    /**
86
     * @throws InvalidSubjectNodeException
87
     */
88
    private function importScalarNode(Node $node): Node
89
    {
90
        if (!($node instanceof BooleanNode || $node instanceof NumberNode || $node instanceof StringNode)) {
91
            throw new InvalidSubjectNodeException('Source node is of unknown type ' . \get_class($node));
92
        }
93
94
        try {
95
            return Node::createFromValue($node->getValue(), $this);
96
        //@codeCoverageIgnoreStart
97
        } catch (\Exception $e) {
98
            throw new \Error('Unexpected ' . \get_class($e) . ": {$e->getMessage()}", 0, $e);
99
        }
100
        //@codeCoverageIgnoreEnd
101
    }
102
103
    private function __construct() { }
104
105
    public function __clone()
106
    {
107
        try {
108
            $this->rootNode = $this->import($this->rootNode);
109
        //@codeCoverageIgnoreStart
110
        } catch (\Exception $e) {
111
            throw new \Error('Unexpected ' . \get_class($e) . ": {$e->getMessage()}", 0, $e);
112
        }
113
        //@codeCoverageIgnoreEnd
114
    }
115
116
    /**
117
     * @throws ParseFailureException
118
     */
119
    public static function parse(string $json, int $depthLimit = 512, int $options = 0): Document
120
    {
121
        static $nodeFactory;
122
123
        try {
124
            $data = \ExceptionalJSON\decode($json, false, $depthLimit, $options & ~\JSON_OBJECT_AS_ARRAY);
125
126
            $doc = new self();
127
            $doc->rootNode = ($nodeFactory ?? $nodeFactory = new SafeNodeFactory)
128
                ->createNodeFromValue($data, $doc, 0);
129
130
            return $doc;
131
        } catch (DecodeErrorException $e) {
132
            throw new ParseFailureException("Decoding JSON string failed: {$e->getMessage()}", $e);
133
        //@codeCoverageIgnoreStart
134
        } catch (\Exception $e) {
135
            throw new \Error('Unexpected ' . \get_class($e) . ": {$e->getMessage()}", 0, $e);
136
        }
137
        //@codeCoverageIgnoreEnd
138
    }
139
140
    /**
141
     * @throws InvalidNodeValueException
142
     */
143 21
    public static function createFromValue($value, int $flags = 0): Document
144
    {
145
        try {
146 21
            $doc = new self();
147 21
            $doc->rootNode = Node::createFromValue($value, $doc, $flags);
148
149 21
            return $doc;
150
        } catch (InvalidNodeValueException $e) {
151
            throw $e;
152
        //@codeCoverageIgnoreStart
153
        } catch (\Exception $e) {
154
            throw new \Error('Unexpected ' . \get_class($e) . ": {$e->getMessage()}", 0, $e);
155
        }
156
        //@codeCoverageIgnoreEnd
157
    }
158
159
    public static function createFromNode(Node $node): Document
160
    {
161
        try {
162
            $doc = new self();
163
            $doc->rootNode = $doc->import($node);
164
165
            return $doc;
166
        //@codeCoverageIgnoreStart
167
        } catch (\Exception $e) {
168
            throw new \Error('Unexpected ' . \get_class($e) . ": {$e->getMessage()}", 0, $e);
169
        }
170
        //@codeCoverageIgnoreEnd
171
    }
172
173 20
    public function getRootNode(): ?Node
174
    {
175 20
        return $this->rootNode;
176
    }
177
178
    /**
179
     * @throws InvalidSubjectNodeException
180
     * @throws WriteOperationForbiddenException
181
     * @throws InvalidNodeValueException
182
     * @throws InvalidSubjectNodeException
183
     */
184
    public function import(Node $node): Node
185
    {
186
        if ($node->getOwnerDocument() === $this) {
187
            throw new InvalidSubjectNodeException('Cannot import tne supplied node, already owned by this document');
188
        }
189
190
        if ($node instanceof NullNode) {
191
            return new NullNode($this);
192
        }
193
194
        return $node instanceof VectorNode
195
            ? $this->importVectorNode($node)
196
            : $this->importScalarNode($node);
197
    }
198
199
    /**
200
     * @param Pointer|string $pointer
201
     * @return Node|int|string
202
     * @throws InvalidPointerException
203
     * @throws PointerReferenceNotFoundException
204
     * @throws InvalidSubjectNodeException
205
     */
206
    public function evaluatePointer($pointer, Node $base = null)
207
    {
208
        if (!($pointer instanceof Pointer)) {
209
            $pointer = Pointer::createFromString((string)$pointer);
210
        }
211
212
        if (!$pointer->isRelative()) {
213
            return $this->evaluatePointerPath($pointer, $this->rootNode);
214
        }
215
216
        $target = $this->evaluateRelativePointer($pointer, $base ?? $this->rootNode);
217
218
        return $pointer->isKeyLookup()
219
            ? $target->getKey()
220
            : $target;
221
    }
222
223
    public function jsonSerialize()
224
    {
225
        return $this->rootNode !== null
226
            ? $this->rootNode->jsonSerialize()
227
            : null;
228
    }
229
}
230