Passed
Branch master (f07ed3)
by Nate
02:39
created

JsonReader::expect()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 14
ccs 6
cts 6
cp 1
rs 9.7998
cc 2
nc 2
nop 1
crap 2
1
<?php
2
/*
3
 * Copyright (c) Nate Brunette.
4
 * Distributed under the MIT License (http://opensource.org/licenses/MIT)
5
 */
6
7
declare(strict_types=1);
8
9
namespace Tebru\Gson\Internal;
10
11
use Tebru\Gson\Exception\JsonSyntaxException;
12
use Tebru\Gson\JsonReadable;
13
use Tebru\Gson\ReaderContext;
14
use Tebru\Gson\JsonToken;
15
16
/**
17
 * Class JsonReader
18
 *
19
 * @author Nate Brunette <[email protected]>
20
 */
21
abstract class JsonReader implements JsonReadable
22
{
23
    /**
24
     * A stack representing the next element to be consumed
25
     *
26
     * @var array
27
     */
28
    protected $stack = [null];
29
30
    /**
31
     * An array of types that map to the position in the stack
32
     *
33
     * @var array
34
     */
35
    protected $stackTypes = [JsonToken::END_DOCUMENT];
36
37
    /**
38
     * The current size of the stack
39
     *
40
     * @var int
41
     */
42
    protected $stackSize = 1;
43
44
    /**
45
     * An array of path names that correspond to the current stack
46
     *
47
     * @var array
48
     */
49
    protected $pathNames = [];
50
51
    /**
52
     * An array of path indices that correspond to the current stack. This array could contain invalid
53
     * values at indexes outside the current stack. It could also contain incorrect values at indexes
54
     * where a path name is used. Data should only be fetched by referencing the $pathIndex
55
     *
56
     * @var int[]
57
     */
58
    protected $pathIndices = [-1];
59
60
    /**
61
     * The current path index corresponding to the pathIndices array
62
     *
63
     * @var int
64
     */
65
    protected  $pathIndex = 0;
0 ignored issues
show
Coding Style introduced by
Scope keyword "protected" must be followed by a single space; found 2
Loading history...
66
67
    /**
68
     * A cache of the current [@see JsonToken].  This should get nulled out
69
     * whenever a new token should be returned with the subsequent call
70
     * to [@see JsonDecodeReader::peek]
71
     *
72
     * @var int|null
73
     */
74
    protected $currentToken;
75
76
    /**
77
     * The original payload
78
     *
79
     * @var mixed
80
     */
81
    protected $payload;
82
83
    /**
84
     * Runtime context to be used while reading
85
     *
86
     * @var ReaderContext
87
     */
88
    protected $context;
89
90
    /**
91
     * Consumes the next token and asserts it's the end of an array
92
     *
93
     * @return void
94
     */
95 13
    public function endArray(): void
96
    {
97 13
        if ($this->stackTypes[$this->stackSize - 1] !== JsonToken::END_ARRAY) {
98 2
            $this->pathIndices[$this->pathIndex]++;
99 2
            $this->assertionFailed(JsonToken::END_ARRAY);
100
        }
101
102 11
        $this->stackSize--;
103 11
        $this->pathIndex--;
104 11
    }
105
106
    /**
107
     * Consumes the next token and asserts it's the end of an object
108
     *
109
     * @return void
110
     */
111 15
    public function endObject(): void
112
    {
113 15
        if ($this->stackTypes[$this->stackSize - 1] !== JsonToken::END_OBJECT) {
114 2
            $this->assertionFailed(JsonToken::END_OBJECT);
115
        }
116
117 13
        $this->stackSize--;
118 13
        $this->pathNames[$this->pathIndex--] = null;
119 13
    }
120
121
    /**
122
     * Returns true if the array or object has another element
123
     *
124
     * If the current scope is not an array or object, this returns false
125
     *
126
     * @return bool
127
     */
128 14
    public function hasNext(): bool
129
    {
130 14
        $peek = $this->stackTypes[$this->stackSize - 1];
131
132 14
        return $peek !== JsonToken::END_OBJECT && $peek !== JsonToken::END_ARRAY;
133
    }
134
135
    /**
136
     * Consumes the next name and returns it
137
     *
138
     * @return string
139
     */
140 31
    public function nextName(): string
141
    {
142 31
        if ($this->stackTypes[$this->stackSize - 1] !== JsonToken::NAME) {
143 2
            $this->assertionFailed(JsonToken::NAME);
144
        }
145
146 31
        $name = (string)$this->stack[--$this->stackSize];
147
148 31
        $this->pathNames[$this->pathIndex] = $name;
149
150 31
        return $name;
151
    }
152
153
    /**
154
     * Consumes the value of the next token and asserts it's null
155
     *
156
     * @return void
157
     */
158 4
    public function nextNull(): void
159
    {
160 4
        $this->pathIndices[$this->pathIndex]++;
161
162 4
        if ($this->stackTypes[$this->stackSize - 1] !== JsonToken::NULL) {
163 2
            $this->assertionFailed(JsonToken::NULL);
164
        }
165
166 2
        $this->stackSize--;
167 2
    }
168
169
    /**
170
     * Skip the next value.  If the next value is an object or array, all children will
171
     * also be skipped.
172
     *
173
     * @return void
174
     */
175 7
    public function skipValue(): void
176
    {
177 7
        $this->stackSize--;
178
179 7
        switch ($this->stackTypes[$this->stackSize]) {
180 7
            case JsonToken::BEGIN_OBJECT:
181 5
            case JsonToken::BEGIN_ARRAY:
182 3
                $this->stackSize--;
183 3
                break;
184
        }
185
186 7
        $this->pathIndices[$this->pathIndex]--;
187 7
    }
188
189
    /**
190
     * Returns the type of the next token without consuming it
191
     *
192
     * @return string
193
     */
194 64
    public function peek(): string
195
    {
196 64
        return $this->stackTypes[$this->stackSize - 1];
197
    }
198
199
    /**
200
     * Get the current read path in json xpath format
201
     *
202
     * @return string
203
     */
204 33
    public function getPath(): string
205
    {
206 33
        $result[] = '$';
0 ignored issues
show
Comprehensibility Best Practice introduced by
$result was never initialized. Although not strictly required by PHP, it is generally a good practice to add $result = array(); before regardless.
Loading history...
207
208 33
        for ($index = 1; $index <= $this->pathIndex; $index++) {
209 23
            if (!empty($this->pathNames[$index])) {
210 8
                $result[] .= '.'.$this->pathNames[$index];
211 8
                continue;
212
            }
213
214
            // skip initial value
215 16
            if ($this->pathIndices[$this->pathIndex] === -1) {
216 7
                continue;
217
            }
218
219 9
            $result[] .= '['.$this->pathIndices[$index].']';
220
        }
221
222 33
        return implode($result);
223
    }
224
225
    /**
226
     * Returns the original json after json_decode
227
     *
228
     * @return mixed
229
     */
230 2
    public function getPayload()
231
    {
232 2
        return $this->payload;
233
    }
234
235
    /**
236
     * Get context to be used during deserialization
237
     *
238
     * @return ReaderContext
239
     */
240 5
    public function getContext(): ReaderContext
241
    {
242 5
        return $this->context;
243
    }
244
245
    /**
246
     * Check that the next token equals the expectation
247
     *
248
     * @param string $expectedToken
249
     * @return void
250
     * @throws \Tebru\Gson\Exception\JsonSyntaxException If the next token is not the expectation
251
     */
252 20
    protected function assertionFailed(string $expectedToken): void
253
    {
254 20
        throw new JsonSyntaxException(
255 20
            \sprintf(
256 20
                'Expected "%s", but found "%s" at "%s"',
257 20
                $expectedToken,
258 20
                $this->stackTypes[$this->stackSize - 1],
259 20
                $this->getPath()
260
            )
261
        );
262
    }
263
}
264