1 | <?php declare(strict_types=1); |
||
2 | |||
3 | namespace DaveRandom\Jom; |
||
4 | |||
5 | use DaveRandom\Jom\Exceptions\InvalidNodeValueException; |
||
6 | use DaveRandom\Jom\Exceptions\InvalidSubjectNodeException; |
||
7 | use DaveRandom\Jom\Exceptions\ParseFailureException; |
||
8 | use DaveRandom\Jom\Exceptions\WriteOperationForbiddenException; |
||
9 | use ExceptionalJSON\DecodeErrorException; |
||
10 | |||
11 | final class Document implements \JsonSerializable |
||
12 | { |
||
13 | public const IGNORE_INVALID_VALUES = Node::IGNORE_INVALID_VALUES; |
||
14 | |||
15 | /** @var Node */ |
||
16 | private $rootNode; |
||
17 | |||
18 | /** |
||
19 | * @throws InvalidNodeValueException |
||
20 | * @throws InvalidSubjectNodeException |
||
21 | * @throws WriteOperationForbiddenException |
||
22 | */ |
||
23 | private function importVectorNode(VectorNode $node): VectorNode |
||
24 | { |
||
25 | if (!\in_array(\get_class($node), [ArrayNode::class, ObjectNode::class])) { |
||
26 | throw new InvalidSubjectNodeException('Source node is of unknown type: ' . \get_class($node)); |
||
27 | } |
||
28 | |||
29 | $newNode = new $node(null, $this); |
||
1 ignored issue
–
show
|
|||
30 | |||
31 | foreach ($node as $key => $value) { |
||
32 | $newNode[$key] = $this->import($value); |
||
33 | } |
||
34 | |||
35 | return $newNode; |
||
36 | } |
||
37 | |||
38 | /** |
||
39 | * @throws InvalidSubjectNodeException |
||
40 | */ |
||
41 | private function importScalarNode(Node $node): Node |
||
42 | { |
||
43 | if (!\in_array(\get_class($node), [BooleanNode::class, NumberNode::class, StringNode::class])) { |
||
44 | throw new InvalidSubjectNodeException('Source node is of unknown type: ' . \get_class($node)); |
||
45 | } |
||
46 | |||
47 | try { |
||
48 | return Node::createFromValue($node->getValue(), $this); |
||
49 | //@codeCoverageIgnoreStart |
||
50 | } catch (\Exception $e) { |
||
51 | /** @noinspection PhpInternalEntityUsedInspection */ |
||
52 | throw unexpected($e); |
||
53 | } |
||
54 | //@codeCoverageIgnoreEnd |
||
55 | } |
||
56 | |||
57 | private function __construct() { } |
||
58 | |||
59 | public function __clone() |
||
60 | { |
||
61 | try { |
||
62 | $this->rootNode = $this->import($this->rootNode); |
||
63 | //@codeCoverageIgnoreStart |
||
64 | } catch (\Exception $e) { |
||
65 | /** @noinspection PhpInternalEntityUsedInspection */ |
||
66 | throw unexpected($e); |
||
67 | } |
||
68 | //@codeCoverageIgnoreEnd |
||
69 | } |
||
70 | |||
71 | /** |
||
72 | * @throws ParseFailureException |
||
73 | */ |
||
74 | 64 | public static function parse(string $json, ?int $depthLimit = 512, ?int $options = 0): Document |
|
75 | { |
||
76 | 64 | static $nodeFactory; |
|
77 | |||
78 | 64 | $depthLimit = $depthLimit ?? 512; |
|
79 | 64 | $options = ($options ?? 0) & ~\JSON_OBJECT_AS_ARRAY; |
|
80 | |||
81 | try { |
||
82 | 64 | $data = \ExceptionalJSON\decode($json, false, $depthLimit, $options); |
|
83 | |||
84 | 64 | $doc = new self(); |
|
85 | 64 | $doc->rootNode = ($nodeFactory ?? $nodeFactory = new SafeNodeFactory) |
|
86 | 64 | ->createNodeFromValue($data, $doc, 0); |
|
87 | |||
88 | 64 | return $doc; |
|
89 | } catch (DecodeErrorException $e) { |
||
90 | throw new ParseFailureException("Decoding JSON string failed: {$e->getMessage()}", $e); |
||
91 | //@codeCoverageIgnoreStart |
||
92 | } catch (\Exception $e) { |
||
93 | /** @noinspection PhpInternalEntityUsedInspection */ |
||
94 | throw unexpected($e); |
||
95 | } |
||
96 | //@codeCoverageIgnoreEnd |
||
97 | } |
||
98 | |||
99 | /** |
||
100 | * @throws InvalidNodeValueException |
||
101 | */ |
||
102 | 21 | public static function createFromValue($value, ?int $flags = 0): Document |
|
103 | { |
||
104 | try { |
||
105 | 21 | $doc = new self(); |
|
106 | 21 | $doc->rootNode = Node::createFromValue($value, $doc, $flags); |
|
107 | |||
108 | 21 | return $doc; |
|
109 | } catch (InvalidNodeValueException $e) { |
||
110 | throw $e; |
||
111 | //@codeCoverageIgnoreStart |
||
112 | } catch (\Exception $e) { |
||
113 | /** @noinspection PhpInternalEntityUsedInspection */ |
||
114 | throw unexpected($e); |
||
115 | } |
||
116 | //@codeCoverageIgnoreEnd |
||
117 | } |
||
118 | |||
119 | public static function createFromNode(Node $node): Document |
||
120 | { |
||
121 | try { |
||
122 | $doc = new self(); |
||
123 | $doc->rootNode = $doc->import($node); |
||
124 | |||
125 | return $doc; |
||
126 | //@codeCoverageIgnoreStart |
||
127 | } catch (\Exception $e) { |
||
128 | /** @noinspection PhpInternalEntityUsedInspection */ |
||
129 | throw unexpected($e); |
||
130 | } |
||
131 | //@codeCoverageIgnoreEnd |
||
132 | } |
||
133 | |||
134 | 84 | public function getRootNode(): Node |
|
135 | { |
||
136 | 84 | return $this->rootNode; |
|
137 | } |
||
138 | |||
139 | /** |
||
140 | * @throws InvalidSubjectNodeException |
||
141 | * @throws WriteOperationForbiddenException |
||
142 | * @throws InvalidNodeValueException |
||
143 | * @throws InvalidSubjectNodeException |
||
144 | */ |
||
145 | public function import(Node $node): Node |
||
146 | { |
||
147 | if ($node->getOwnerDocument() === $this) { |
||
148 | throw new InvalidSubjectNodeException('Cannot import tne supplied node, already owned by this document'); |
||
149 | } |
||
150 | |||
151 | if ($node instanceof NullNode) { |
||
152 | return new NullNode($this); |
||
153 | } |
||
154 | |||
155 | return $node instanceof VectorNode |
||
156 | ? $this->importVectorNode($node) |
||
157 | : $this->importScalarNode($node); |
||
158 | } |
||
159 | |||
160 | public function jsonSerialize() |
||
161 | { |
||
162 | return $this->rootNode !== null |
||
163 | ? $this->rootNode->jsonSerialize() |
||
164 | : null; |
||
165 | } |
||
166 | } |
||
167 |
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.