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
|
134 |
|
public function __construct(mixed $source) |
41
|
|
|
{ |
42
|
134 |
|
$this->config = new Config(); |
43
|
134 |
|
$this->parser = Parser::for(new AnySource($source, $this->config)); |
44
|
|
|
} |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* Statically instantiate the class |
48
|
|
|
* |
49
|
|
|
* @param mixed $source |
50
|
|
|
* @return static |
51
|
|
|
*/ |
52
|
122 |
|
public static function parse(mixed $source): static |
53
|
|
|
{ |
54
|
122 |
|
return new static($source); |
55
|
|
|
} |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* Retrieve the lazily iterable JSON |
59
|
|
|
* |
60
|
|
|
* @return Traversable<string|int, mixed> |
61
|
|
|
*/ |
62
|
130 |
|
public function getIterator(): Traversable |
63
|
|
|
{ |
64
|
|
|
try { |
65
|
130 |
|
yield from $this->parser; |
66
|
15 |
|
} catch (SyntaxException $e) { |
|
|
|
|
67
|
11 |
|
call_user_func($this->config->onSyntaxError, $e); |
68
|
|
|
} |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* Set the JSON pointers |
73
|
|
|
* |
74
|
|
|
* @param string[]|array<string, Closure> $pointers |
75
|
|
|
* @return static |
76
|
|
|
*/ |
77
|
28 |
|
public function pointers(array $pointers): static |
78
|
|
|
{ |
79
|
28 |
|
foreach ($pointers as $pointer => $callback) { |
80
|
28 |
|
$callback instanceof Closure ? $this->pointer($pointer, $callback) : $this->pointer($callback); |
81
|
|
|
} |
82
|
|
|
|
83
|
28 |
|
return $this; |
84
|
|
|
} |
85
|
|
|
|
86
|
|
|
/** |
87
|
|
|
* Set a JSON pointer |
88
|
|
|
* |
89
|
|
|
* @param string $pointer |
90
|
|
|
* @param Closure|null $callback |
91
|
|
|
* @return static |
92
|
|
|
*/ |
93
|
96 |
|
public function pointer(string $pointer, Closure $callback = null): static |
94
|
|
|
{ |
95
|
96 |
|
$this->config->pointers[] = new Pointer($pointer, $callback); |
96
|
|
|
|
97
|
92 |
|
return $this; |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
/** |
101
|
|
|
* Traverse the lazily iterable JSON |
102
|
|
|
* |
103
|
|
|
* @param Closure|null $callback |
104
|
|
|
* @return void |
105
|
|
|
*/ |
106
|
16 |
|
public function traverse(Closure $callback = null): void |
107
|
|
|
{ |
108
|
16 |
|
$callback ??= fn () => true; |
109
|
|
|
|
110
|
16 |
|
foreach ($this as $key => $value) { |
111
|
9 |
|
$callback($value, $key, $this); |
112
|
|
|
} |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* Set the JSON decoder |
117
|
|
|
* |
118
|
|
|
* @param Decoder $decoder |
119
|
|
|
* @return static |
120
|
|
|
*/ |
121
|
|
|
public function decoder(Decoder $decoder): static |
122
|
|
|
{ |
123
|
|
|
$this->config->decoder = $decoder; |
124
|
|
|
|
125
|
|
|
return $this; |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
/** |
129
|
|
|
* Retrieve the parsing progress |
130
|
|
|
* |
131
|
|
|
* @return Progress |
132
|
|
|
*/ |
133
|
|
|
public function progress(): Progress |
134
|
|
|
{ |
135
|
|
|
return $this->parser->progress(); |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* The number of bytes to read in each chunk |
140
|
|
|
* |
141
|
|
|
* @param int<1, max> $bytes |
142
|
|
|
* @return static |
143
|
|
|
*/ |
144
|
|
|
public function bytes(int $bytes): static |
145
|
|
|
{ |
146
|
|
|
$this->config->bytes = $bytes; |
147
|
|
|
|
148
|
|
|
return $this; |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
/** |
152
|
|
|
* Set the patch to apply during a decoding error |
153
|
|
|
* |
154
|
|
|
* @param mixed $patch |
155
|
|
|
* @return static |
156
|
|
|
*/ |
157
|
4 |
|
public function patchDecodingError(mixed $patch = null): static |
158
|
|
|
{ |
159
|
4 |
|
return $this->onDecodingError(function (DecodedValue $decoded) use ($patch) { |
160
|
4 |
|
$decoded->value = is_callable($patch) ? $patch($decoded) : $patch; |
161
|
4 |
|
}); |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* Set the logic to run during a decoding error |
166
|
|
|
* |
167
|
|
|
* @param Closure $callback |
168
|
|
|
* @return static |
169
|
|
|
*/ |
170
|
5 |
|
public function onDecodingError(Closure $callback): static |
171
|
|
|
{ |
172
|
5 |
|
$this->config->onDecodingError = $callback; |
173
|
|
|
|
174
|
5 |
|
return $this; |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
/** |
178
|
|
|
* Set the logic to run during a syntax error |
179
|
|
|
* |
180
|
|
|
* @param Closure $callback |
181
|
|
|
* @return static |
182
|
|
|
*/ |
183
|
1 |
|
public function onSyntaxError(Closure $callback): static |
184
|
|
|
{ |
185
|
1 |
|
$this->config->onSyntaxError = $callback; |
186
|
|
|
|
187
|
1 |
|
return $this; |
188
|
|
|
} |
189
|
|
|
} |
190
|
|
|
|
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.