1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Cerbero\JsonParser; |
4
|
|
|
|
5
|
|
|
use Cerbero\JsonParser\Decoders\DecodedValue; |
6
|
|
|
use Cerbero\JsonParser\Decoders\Decoder; |
7
|
|
|
use Cerbero\JsonParser\Exceptions\SyntaxException; |
8
|
|
|
use Cerbero\JsonParser\Pointers\Pointer; |
9
|
|
|
use Cerbero\JsonParser\Sources\AnySource; |
10
|
|
|
use Closure; |
11
|
|
|
use IteratorAggregate; |
12
|
|
|
use Traversable; |
13
|
|
|
|
14
|
|
|
/** |
15
|
|
|
* The JSON parser entry-point. |
16
|
|
|
* |
17
|
|
|
* @implements IteratorAggregate<string|int, mixed> |
18
|
|
|
*/ |
19
|
|
|
final class JsonParser implements IteratorAggregate |
20
|
|
|
{ |
21
|
|
|
/** |
22
|
|
|
* The configuration. |
23
|
|
|
* |
24
|
|
|
* @var Config |
25
|
|
|
*/ |
26
|
|
|
private Config $config; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* The parser. |
30
|
|
|
* |
31
|
|
|
* @var Parser |
32
|
|
|
*/ |
33
|
|
|
private Parser $parser; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* Instantiate the class. |
37
|
|
|
* |
38
|
|
|
* @param mixed $source |
39
|
|
|
*/ |
40
|
152 |
|
public function __construct(mixed $source) |
41
|
|
|
{ |
42
|
152 |
|
$this->config = new Config(); |
43
|
152 |
|
$this->parser = Parser::for(new AnySource($source, $this->config)); |
44
|
|
|
} |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* Statically instantiate the class |
48
|
|
|
* |
49
|
|
|
* @param mixed $source |
50
|
|
|
* @return self |
51
|
|
|
*/ |
52
|
140 |
|
public static function parse(mixed $source): self |
53
|
|
|
{ |
54
|
140 |
|
return new self($source); |
55
|
|
|
} |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* Retrieve the lazily iterable JSON |
59
|
|
|
* |
60
|
|
|
* @return Traversable<string|int, mixed> |
61
|
|
|
*/ |
62
|
140 |
|
public function getIterator(): Traversable |
63
|
|
|
{ |
64
|
|
|
try { |
65
|
140 |
|
yield from $this->parser; |
66
|
15 |
|
} catch (SyntaxException $e) { |
|
|
|
|
67
|
11 |
|
$e->setPosition($this->parser->position()); |
68
|
11 |
|
($this->config->onSyntaxError)($e); |
69
|
|
|
} |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
/** |
73
|
|
|
* Set the JSON pointers |
74
|
|
|
* |
75
|
|
|
* @param string[]|array<string, Closure> $pointers |
76
|
|
|
* @return self |
77
|
|
|
*/ |
78
|
39 |
|
public function pointers(array $pointers): self |
79
|
|
|
{ |
80
|
39 |
|
foreach ($pointers as $pointer => $callback) { |
81
|
39 |
|
$callback instanceof Closure ? $this->pointer($pointer, $callback) : $this->pointer($callback); |
82
|
|
|
} |
83
|
|
|
|
84
|
31 |
|
return $this; |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* Set a JSON pointer |
89
|
|
|
* |
90
|
|
|
* @param string $pointer |
91
|
|
|
* @param Closure|null $callback |
92
|
|
|
* @return self |
93
|
|
|
*/ |
94
|
107 |
|
public function pointer(string $pointer, Closure $callback = null): self |
95
|
|
|
{ |
96
|
107 |
|
$this->config->pointers->add(new Pointer($pointer, false, $callback)); |
97
|
|
|
|
98
|
103 |
|
return $this; |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* Set the lazy JSON pointers |
103
|
|
|
* |
104
|
|
|
* @param string[]|array<string, Closure> $pointers |
105
|
|
|
* @return self |
106
|
|
|
*/ |
107
|
4 |
|
public function lazyPointers(array $pointers): self |
108
|
|
|
{ |
109
|
4 |
|
foreach ($pointers as $pointer => $callback) { |
110
|
4 |
|
$callback instanceof Closure ? $this->lazyPointer($pointer, $callback) : $this->lazyPointer($callback); |
111
|
|
|
} |
112
|
|
|
|
113
|
4 |
|
return $this; |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* Set a lazy JSON pointer |
118
|
|
|
* |
119
|
|
|
* @param string $pointer |
120
|
|
|
* @param Closure|null $callback |
121
|
|
|
* @return self |
122
|
|
|
*/ |
123
|
9 |
|
public function lazyPointer(string $pointer, Closure $callback = null): self |
124
|
|
|
{ |
125
|
9 |
|
$this->config->pointers->add(new Pointer($pointer, true, $callback)); |
126
|
|
|
|
127
|
9 |
|
return $this; |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* Traverse the lazily iterable JSON |
132
|
|
|
* |
133
|
|
|
* @param Closure|null $callback |
134
|
|
|
* @return void |
135
|
|
|
*/ |
136
|
16 |
|
public function traverse(Closure $callback = null): void |
137
|
|
|
{ |
138
|
16 |
|
$callback ??= fn () => true; |
139
|
|
|
|
140
|
16 |
|
foreach ($this as $key => $value) { |
141
|
9 |
|
$callback($value, $key, $this); |
142
|
|
|
} |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* Set the JSON decoder |
147
|
|
|
* |
148
|
|
|
* @param Decoder $decoder |
149
|
|
|
* @return self |
150
|
|
|
*/ |
151
|
|
|
public function decoder(Decoder $decoder): self |
152
|
|
|
{ |
153
|
|
|
$this->config->decoder = $decoder; |
154
|
|
|
|
155
|
|
|
return $this; |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
/** |
159
|
|
|
* Retrieve the parsing progress |
160
|
|
|
* |
161
|
|
|
* @return Progress |
162
|
|
|
*/ |
163
|
|
|
public function progress(): Progress |
164
|
|
|
{ |
165
|
|
|
return $this->parser->progress(); |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
/** |
169
|
|
|
* The number of bytes to read in each chunk |
170
|
|
|
* |
171
|
|
|
* @param int<1, max> $bytes |
172
|
|
|
* @return self |
173
|
|
|
*/ |
174
|
|
|
public function bytes(int $bytes): self |
175
|
|
|
{ |
176
|
|
|
$this->config->bytes = $bytes; |
177
|
|
|
|
178
|
|
|
return $this; |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* Set the patch to apply during a decoding error |
183
|
|
|
* |
184
|
|
|
* @param mixed $patch |
185
|
|
|
* @return self |
186
|
|
|
*/ |
187
|
4 |
|
public function patchDecodingError(mixed $patch = null): self |
188
|
|
|
{ |
189
|
4 |
|
return $this->onDecodingError(function (DecodedValue $decoded) use ($patch) { |
190
|
4 |
|
$decoded->value = is_callable($patch) ? $patch($decoded) : $patch; |
191
|
4 |
|
}); |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
/** |
195
|
|
|
* Set the logic to run during a decoding error |
196
|
|
|
* |
197
|
|
|
* @param Closure $callback |
198
|
|
|
* @return self |
199
|
|
|
*/ |
200
|
5 |
|
public function onDecodingError(Closure $callback): self |
201
|
|
|
{ |
202
|
5 |
|
$this->config->onDecodingError = $callback; |
203
|
|
|
|
204
|
5 |
|
return $this; |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
/** |
208
|
|
|
* Set the logic to run during a syntax error |
209
|
|
|
* |
210
|
|
|
* @param Closure $callback |
211
|
|
|
* @return self |
212
|
|
|
*/ |
213
|
1 |
|
public function onSyntaxError(Closure $callback): self |
214
|
|
|
{ |
215
|
1 |
|
$this->config->onSyntaxError = $callback; |
216
|
|
|
|
217
|
1 |
|
return $this; |
218
|
|
|
} |
219
|
|
|
} |
220
|
|
|
|
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.
Unreachable code is most often the result of
return
,die
orexit
statements that have been added for debug purposes.In the above example, the last
return false
will never be executed, because a return statement has already been met in every possible execution path.