Passed
Push — master ( e7798d...4b26b6 )
by Mikhail
02:39
created

Parser::events()   F

Complexity

Conditions 41
Paths 161

Size

Total Lines 178
Code Lines 130

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 94
CRAP Score 43.4384

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 41
eloc 130
c 2
b 1
f 0
nc 161
nop 0
dl 0
loc 178
ccs 94
cts 106
cp 0.8868
crap 43.4384
rs 2.9266

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
declare(strict_types=1);
3
4
namespace JsonDecodeStream;
5
6
use Generator;
7
use JsonDecodeStream\Collector\Collector;
8
use JsonDecodeStream\Collector\CollectorInterface;
9
use JsonDecodeStream\Exception\ParserException;
10
use JsonDecodeStream\Internal\SourceBuffer;
11
use JsonDecodeStream\Internal\Stack;
12
use JsonDecodeStream\Internal\StackFrame;
13
use JsonDecodeStream\Source\FileSource;
14
use JsonDecodeStream\Source\Psr7Source;
15
use JsonDecodeStream\Source\SourceInterface;
16
use JsonDecodeStream\Source\StreamSource;
17
use JsonDecodeStream\Source\StringSource;
18
use Psr\Http\Message\StreamInterface;
19
20
class Parser
21
{
22
    /** @var SourceBuffer */
23
    protected $buffer;
24
25
    /** @var Stack */
26
    protected $stack;
27
28 44
    public function __construct(SourceInterface $source)
29
    {
30 44
        $this->buffer = new SourceBuffer($source);
31 44
    }
32
33 17
    public static function fromString(string $string)
34
    {
35 17
        return new static(new StringSource($string));
36
    }
37
38 27
    public static function fromFile(string $path)
39
    {
40 27
        return new static(new FileSource($path));
41
    }
42
43
    public static function fromStream($stream)
44
    {
45
        return new static(new StreamSource($stream));
46
    }
47
48
    public static function fromPsr7(StreamInterface $stream)
49
    {
50
        return new static(new Psr7Source($stream));
51
    }
52
53
    /**
54
     * @param string|string[]|Collector|Collector[] $selectors
55
     * single or coma-separated selector string
56
     * or custom CollectorInterface implementation
57
     * or array of any of both
58
     * @param bool                                  $objectsAsAssoc
59
     * @return iterable|Generator
60
     * @throws Exception\CollectorException
61
     * @throws Exception\SelectorException
62
     * @throws Exception\TokenizerException
63
     * @throws ParserException
64
     */
65 7
    public function items($selectors, bool $objectsAsAssoc = false)
66
    {
67 7
        if (is_string($selectors)) {
68 5
            $selectorsArray = explode(',', $selectors);
69 2
        } else if (is_array($selectors)) {
70
            $selectorsArray = $selectors;
71 2
        } else if ($selectors instanceof CollectorInterface) {
0 ignored issues
show
introduced by
$selectors is always a sub-type of JsonDecodeStream\Collector\CollectorInterface.
Loading history...
72 2
            $selectorsArray = [ $selectors ];
73
        } else {
74
            throw new ParserException('Unexpected selectors are provided', ParserException::CODE_INVALID_ARGUMENT);
75
        }
76 7
        if (empty($selectorsArray)) {
77
            throw new ParserException('No selectors are provided', ParserException::CODE_INVALID_ARGUMENT);
78
        }
79 7
        $collectors = [];
80 7
        foreach ($selectorsArray as $selector) {
81 7
            if (is_string($selector)) {
82 5
                $collectors[] = new Collector($selector, $objectsAsAssoc);
83 2
            } elseif ($selector instanceof CollectorInterface) {
84 2
                $collectors[] = $selector;
85
            } else {
86
                throw new ParserException(
87
                    'Invalid collector: '
88
                    . is_object($selector) ? get_class($selector) : gettype($selector),
89
                    ParserException::CODE_INVALID_ARGUMENT
90
                );
91
            }
92
        }
93
94 7
        foreach ($this->events() as $event) {
95 7
            foreach ($collectors as $collector) {
96 7
                $yielded = $collector->processEvent($event);
97 7
                if (is_array($yielded)) {
98 6
                    if (count($yielded) != 2) {
99
                        throw ParserException::unexpectedCollectorReturn($yielded, $event);
0 ignored issues
show
Bug introduced by
Are you sure the usage of JsonDecodeStream\Excepti...eturn($yielded, $event) targeting JsonDecodeStream\Excepti...pectedCollectorReturn() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
100
                    }
101 6
                    [ $key, $value ] = $yielded;
102 6
                    yield $key => $value;
103 7
                } else if ($yielded instanceof Generator) {
104 1
                    foreach ($yielded as $yieldedSingle) {
105 1
                        if (!is_array($yieldedSingle) || count($yieldedSingle) != 2) {
106
                            throw ParserException::unexpectedCollectorReturn($yielded, $event);
0 ignored issues
show
Bug introduced by
Are you sure the usage of JsonDecodeStream\Excepti...eturn($yielded, $event) targeting JsonDecodeStream\Excepti...pectedCollectorReturn() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
107
                        }
108 1
                        [ $key, $value ] = $yieldedSingle;
109 1
                        yield $key => $value;
110
                    }
111 6
                } else if ($yielded === null) {
112 6
                    continue;
113
                } else {
114
                    throw ParserException::unexpectedCollectorReturn($yielded, $event);
0 ignored issues
show
Bug introduced by
Are you sure the usage of JsonDecodeStream\Excepti...eturn($yielded, $event) targeting JsonDecodeStream\Excepti...pectedCollectorReturn() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
115
                }
116
            }
117
        }
118 7
    }
119
120
    /**
121
     * @return Generator|Event[]
122
     * @psalm-return \Generator<Event>
123
     * @throws ParserException
124
     * @noinspection PhpStatementHasEmptyBodyInspection
125
     */
126 44
    public function events(): Generator
127
    {
128 44
        $stack = new Stack();
129 44
        $tokens = $this->tokens();
130
131
        // shortcut to event factory
132 44
        $createEvent = function (string $eventId, $value = null) use ($stack, &$token): Event {
133 44
            return $this->createEvent($eventId, $value, $stack, $token->getLineNumber(), $token->getCharNumber());
134 44
        };
135
136 44
        foreach ($tokens as $token) {
137 44
            if ($token->getId() == Token::WHITESPACE) {
138
                // ignore whitespaces
139 42
                continue;
140
            }
141
142 44
            if ($stack->isEmpty()) {
143 44
                switch ($token->getId()) {
144
                    case Token::OBJECT_START:
145 29
                        yield $createEvent(Event::DOCUMENT_START);
146 29
                        yield $createEvent(Event::OBJECT_START);
147
148 29
                        $stack->push(StackFrame::object());
149 29
                        break;
150
151
                    case Token::ARRAY_START:
152 15
                        yield $createEvent(Event::DOCUMENT_START);
153 15
                        yield $createEvent(Event::ARRAY_START);
154
155 15
                        $stack->push(StackFrame::array());
156 15
                        break;
157
158
                    default:
159
                        throw ParserException::unexpectedToken($token);
160
                }
161 44
                continue;
162
            }
163 44
            if ($stack->current()->isAwaitsComa()) {
164 42
                if ($token->getId() == Token::COMA) {
165 42
                    $stack->current()->setAwaitsComa(false);
166 42
                    if ($stack->current()->isObject()) {
167 29
                        $stack->current()->setLastKey(null);
168 29
                        $stack->current()->setAwaitsKey(true);
169
                    }
170 42
                    continue;
171 42
                } elseif ($stack->current()->isObject() && $token->getId() == Token::OBJECT_END) {
172
                    // pass
173 36
                } elseif ($stack->current()->isArray() && $token->getId() == Token::ARRAY_END) {
174
                    // pass
175
                } else {
176
                    throw ParserException::expectedButGot('","', $token);
177
                }
178
            }
179 44
            if ($stack->current()->isAwaitsKeyDelimiter()) {
180 34
                if ($token->getId() == Token::KEY_DELIMITER) {
181 34
                    $stack->current()->setAwaitsKeyDelimiter(false);
182 34
                    continue;
183
                } else {
184
                    throw ParserException::expectedButGot('":"', $token);
185
                }
186
            }
187 44
            if ($stack->current()->isAwaitsKey()) {
188 35
                if ($token->getId() != Token::STRING && $token->getId() != Token::OBJECT_END) {
189
                    throw ParserException::expectedButGot('object key', $token);
190
                }
191
            }
192
193 44
            if ($stack->current()->isArray()) {
194 37
                switch ($token->getId()) {
195
                    case Token::STRING:
196
                    case Token::NUMBER:
197
                    case Token::NULL:
198
                    case Token::TRUE:
199
                    case Token::FALSE:
200 35
                        $stack->current()->setAwaitsComa(true);
201 35
                        $stack->current()->setLastKey(
202 35
                            $stack->current()->getElementCount()
203
                        );
204 35
                        $stack->current()->incrementElementCount();
205 35
                        yield $createEvent(Event::VALUE, $token->getValue());
206 35
                        break;
207
208
                    case Token::ARRAY_START:
209 7
                        yield $createEvent(Event::ARRAY_START);
210 7
                        $stack->current()->setAwaitsComa(true);
211 7
                        $stack->current()->setLastKey(
212 7
                            $stack->current()->getElementCount()
213
                        );
214 7
                        $stack->current()->incrementElementCount();
215 7
                        $stack->push(StackFrame::array());
216 7
                        break;
217
218
                    case Token::ARRAY_END:
219 37
                        $stack->pop();
220 37
                        yield $createEvent(Event::ARRAY_END);
221 37
                        if ($stack->isEmpty()) {
222 15
                            yield $createEvent(Event::DOCUMENT_END);
223
                        }
224 37
                        break;
225
226
                    case Token::OBJECT_START:
227 27
                        $stack->current()->setLastKey(
228 27
                            $stack->current()->getElementCount()
229
                        );
230 27
                        yield $createEvent(Event::OBJECT_START);
231 27
                        $stack->current()->setAwaitsComa(true);
232 27
                        $stack->current()->incrementElementCount();
233 27
                        $stack->push(StackFrame::object());
234 27
                        break;
235
236
                    case Token::OBJECT_END:
237
                        $stack->pop();
238
                        yield $createEvent(Event::OBJECT_END);
239
                        break;
240
241
                    default:
242
                        throw ParserException::unexpectedToken($token);
243
                }
244
245 37
                continue;
246
            }
247
248 35
            if ($stack->current()->isObject()) {
249 35
                switch ($token->getId()) {
250
                    case Token::STRING:
251 34
                        if ($stack->current()->isAwaitsKey()) {
252 34
                            yield $createEvent(Event::KEY, $token->getValue());
253 34
                            $stack->current()->setLastKey($token->getValue());
254 34
                            $stack->current()->setAwaitsKeyDelimiter(true);
255 34
                            $stack->current()->setAwaitsKey(false);
256
                        } else {
257 25
                            yield $createEvent(Event::VALUE, $token->getValue());
258 25
                            $stack->current()->setAwaitsComa(true);
259 25
                            $stack->current()->incrementElementCount();
260
                        }
261 34
                        break;
262
263
                    case Token::NUMBER:
264
                    case Token::NULL:
265
                    case Token::TRUE:
266
                    case Token::FALSE:
267 28
                        yield $createEvent(Event::VALUE, $token->getValue());
268 28
                        $stack->current()->setAwaitsComa(true);
269 28
                        $stack->current()->incrementElementCount();
270 28
                        break;
271
272
                    case Token::ARRAY_START:
273 28
                        yield $createEvent(Event::ARRAY_START);
274 28
                        $stack->current()->setAwaitsComa(true);
275 28
                        $stack->current()->incrementElementCount();
276 28
                        $stack->push(StackFrame::array());
277 28
                        break;
278
279
                    case Token::ARRAY_END:
280
                        yield $createEvent(Event::ARRAY_END);
281
                        $stack->pop();
282
                        break;
283
284
                    case Token::OBJECT_START:
285 27
                        yield $createEvent(Event::OBJECT_START);
286 27
                        $stack->current()->setAwaitsComa(true);
287 27
                        $stack->current()->incrementElementCount();
288 27
                        $stack->push(StackFrame::object());
289 27
                        break;
290
291
                    case Token::OBJECT_END:
292 35
                        $stack->pop();
293 35
                        yield $createEvent(Event::OBJECT_END);
294 35
                        if ($stack->isEmpty()) {
295 29
                            yield $createEvent(Event::DOCUMENT_END);
296
                        }
297 35
                        break;
298
299
                    default:
300
                        throw ParserException::unexpectedToken($token);
301
                }
302
303 35
                continue;
304
            }
305
        }
306 44
    }
307
308
    /**
309
     * @return iterable|Tokenizer|Token[]
310
     */
311 44
    public function tokens(): iterable
312
    {
313 44
        return new Tokenizer($this->buffer);
314
    }
315
316
    /**
317
     * @param string $eventId
318
     * @param        $value
319
     * @param Stack  $stack
320
     * @param int    $lineNumber
321
     * @param int    $charNumber
322
     * @return Event
323
     */
324 44
    protected function createEvent(string $eventId, $value, Stack $stack, int $lineNumber, int $charNumber): Event
325
    {
326 44
        return new Event($eventId, $value, $stack, $lineNumber, $charNumber);
327
    }
328
}
329