Passed
Branch master (c5c3fa)
by Nate
03:35
created

JsonReader::nextNull()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 5
cts 5
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 5
nc 1
nop 0
crap 1
1
<?php
2
/*
3
 * Copyright (c) Nate Brunette.
4
 * Distributed under the MIT License (http://opensource.org/licenses/MIT)
5
 */
6
7
namespace Tebru\Gson\Internal;
8
9
use Iterator;
10
use Tebru\Gson\Element\JsonElement;
11
use Tebru\Gson\Exception\UnexpectedJsonTokenException;
12
use Tebru\Gson\JsonReadable;
13
use Tebru\Gson\JsonToken;
14
15
/**
16
 * Class JsonReader
17
 *
18
 * @author Nate Brunette <[email protected]>
19
 */
20
abstract class JsonReader implements JsonReadable
21
{
22
    /**
23
     * A stack representing the next element to be consumed
24
     *
25
     * @var array
26
     */
27
    protected $stack = [];
28
29
    /**
30
     * An array of types that map to the position in the stack
31
     *
32
     * @var array
33
     */
34
    protected $stackTypes = [];
35
36
    /**
37
     * The current size of the stack
38
     *
39
     * @var int
40
     */
41
    protected $stackSize = 0;
42
43
    /**
44
     * An array of path names that correspond to the current stack
45
     *
46
     * @var array
47
     */
48
    protected $pathNames = [];
49
50
    /**
51
     * An array of path indicies that correspond to the current stack. This array could contain invalid
52
     * values at indexes outside the current stack. It could also contain incorrect values at indexes
53
     * where a path name is used. Data should only be fetched by referencing the current position in the stack.
54
     *
55
     * @var array
56
     */
57
    protected $pathIndices = [];
58
59
    /**
60
     * A cache of the current [@see JsonToken].  This should get nulled out
61
     * whenever a new token should be returned with the subsequent call
62
     * to [@see JsonDecodeReader::peek]
63
     *
64
     * @var
65
     */
66
    protected $currentToken;
67
68
    /**
69
     * Returns an enum representing the type of the next token without consuming it
70
     *
71
     * @return string
72
     */
73
    abstract public function peek(): string;
74
75
    /**
76
     * Get the current read path in json xpath format
77
     *
78
     * @return string
79
     */
80
    abstract public function getPath(): string;
81
82
    /**
83
     * Push an element onto the stack
84
     *
85
     * @param JsonElement|Iterator $element
86
     * @param string $type
0 ignored issues
show
Documentation introduced by
Should the type for parameter $type not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
87
     */
88
    abstract protected function push($element, $type = null): void;
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
89
90
    /**
91
     * Consumes the next token and asserts it's the end of an array
92
     *
93
     * @return void
94
     * @throws \Tebru\Gson\Exception\UnexpectedJsonTokenException If the next token is not END_ARRAY
95
     */
96 13
    public function endArray(): void
97
    {
98 13
        $this->expect(JsonToken::END_ARRAY);
99
100 11
        $this->pop();
101 11
        $this->incrementPathIndex();
102 11
    }
103
104
    /**
105
     * Consumes the next token and asserts it's the end of an object
106
     *
107
     * @return void
108
     * @throws \Tebru\Gson\Exception\UnexpectedJsonTokenException If the next token is not END_OBJECT
109
     */
110 11
    public function endObject(): void
111
    {
112 11
        $this->expect(JsonToken::END_OBJECT);
113
114 9
        $this->pop();
115 9
        $this->incrementPathIndex();
116 9
    }
117
118
    /**
119
     * Returns true if the array or object has another element
120
     *
121
     * If the current scope is not an array or object, this returns false
122
     *
123
     * @return bool
124
     */
125 10
    public function hasNext(): bool
126
    {
127 10
        $peek = $this->peek();
128
129 10
        return $peek !== JsonToken::END_OBJECT && $peek !== JsonToken::END_ARRAY;
130
    }
131
132
    /**
133
     * Consumes the next name and returns it
134
     *
135
     * @return string
136
     * @throws \Tebru\Gson\Exception\UnexpectedJsonTokenException If the next token is not NAME
137
     */
138 25
    public function nextName(): string
139
    {
140 25
        $this->expect(JsonToken::NAME);
141
142
        /** @var Iterator $iterator */
143 25
        $iterator = $this->stack[$this->stackSize - 1];
144 25
        $key = $iterator->key();
145 25
        $value = $iterator->current();
146 25
        $iterator->next();
147
148 25
        $this->pathNames[$this->stackSize - 1] = $key;
149
150 25
        $this->push($value);
151
152 25
        return (string)$key;
153
    }
154
155
    /**
156
     * Consumes the value of the next token and asserts it's null
157
     *
158
     * @return null
159
     * @throws \Tebru\Gson\Exception\UnexpectedJsonTokenException If the next token is not NAME or NULL
160
     */
161 4
    public function nextNull()
162
    {
163 4
        $this->expect(JsonToken::NULL);
164
165 2
        $this->pop();
166
167 2
        $this->incrementPathIndex();
168
169 2
        return null;
170
    }
171
172
    /**
173
     * Skip the next value.  If the next value is an object or array, all children will
174
     * also be skipped.
175
     *
176
     * @return void
177
     */
178 2
    public function skipValue(): void
179
    {
180 2
        $this->pop();
181 2
    }
182
183
    /**
184
     * Pop the last element off the stack and return it
185
     *
186
     * @return JsonElement|Iterator|mixed
187
     */
188 105
    protected function pop()
189
    {
190 105
        $this->stackSize--;
191 105
        array_pop($this->stackTypes);
192 105
        $this->currentToken = null;
193
194 105
        return array_pop($this->stack);
195
    }
196
197
    /**
198
     * Check that the next token equals the expectation
199
     *
200
     * @param string $expectedToken
201
     * @throws \Tebru\Gson\Exception\UnexpectedJsonTokenException If the next token is not the expectation
202
     */
203 116
    protected function expect(string $expectedToken)
204
    {
205 116
        if ($this->peek() === $expectedToken) {
206 105
            return;
207
        }
208
209
        // increment the path index because exceptions are thrown before this value is increased. We
210
        // want to display the current index that has a problem.
211 20
        $this->incrementPathIndex();
212
213 20
        throw new UnexpectedJsonTokenException(
214 20
            sprintf('Expected "%s", but found "%s" at "%s"', $expectedToken, $this->peek(), $this->getPath())
215
        );
216
    }
217
218
    /**
219
     * Increment the path index. This should be called any time a new value is parsed.
220
     */
221 81
    protected function incrementPathIndex(): void
222
    {
223 81
        $index = $this->stackSize - 1;
224 81
        if ($index >= 0) {
225 43
            if (!isset($this->pathIndices[$index])) {
226 32
                $this->pathIndices[$index] = 0;
227
            }
228 43
            $this->pathIndices[$index]++;
229
        }
230 81
    }
231
}
232