Parser::toArray()   B
last analyzed

Complexity

Conditions 7
Paths 36

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 7

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 10
c 1
b 0
f 0
nc 36
nop 0
dl 0
loc 17
ccs 11
cts 11
cp 1
crap 7
rs 8.8333
1
<?php
2
3
namespace Cerbero\JsonParser\Tokens;
4
5
use Cerbero\JsonParser\Decoders\ConfigurableDecoder;
6
use Cerbero\JsonParser\Exceptions\SyntaxException;
7
use Cerbero\JsonParser\Tokens\CompoundBegin;
8
use Cerbero\JsonParser\Tokens\CompoundEnd;
9
use Cerbero\JsonParser\Tokens\Token;
10
use Cerbero\JsonParser\ValueObjects\Config;
11
use Cerbero\JsonParser\ValueObjects\State;
12
use Generator;
13
use IteratorAggregate;
14
use Traversable;
15
16
/**
17
 * The JSON parser.
18
 *
19
 * @implements IteratorAggregate<string|int, mixed>
20
 */
21
final class Parser implements IteratorAggregate
22
{
23
    /**
24
     * The decoder handling potential errors.
25
     *
26
     * @var ConfigurableDecoder
27
     */
28
    private readonly ConfigurableDecoder $decoder;
29
30
    /**
31
     * Whether the parser is fast-forwarding.
32
     *
33
     * @var bool
34
     */
35
    private bool $isFastForwarding = false;
36
37
    /**
38
     * Instantiate the class.
39
     *
40
     * @param Generator<int, Token> $tokens
41
     * @param Config $config
42
     */
43 373
    public function __construct(private readonly Generator $tokens, private readonly Config $config)
44
    {
45 373
        $this->decoder = new ConfigurableDecoder($config);
0 ignored issues
show
Bug introduced by
The property decoder is declared read-only in Cerbero\JsonParser\Tokens\Parser.
Loading history...
46
    }
47
48
    /**
49
     * Retrieve the JSON fragments
50
     *
51
     * @return Traversable<string|int, mixed>
52
     */
53 361
    public function getIterator(): Traversable
54
    {
55 361
        $state = new State($this->config->pointers, fn () => new self($this->lazyLoad(), clone $this->config));
56
57 361
        foreach ($this->tokens as $token) {
58 357
            if ($this->isFastForwarding) {
59 9
                continue;
60 357
            } elseif (!$token->matches($state->expectedToken)) {
61 1
                throw new SyntaxException($token);
62
            }
63
64 357
            $state->mutateByToken($token);
65
66 357
            if (!$token->endsChunk() || $state->tree->isDeep()) {
67 357
                continue;
68
            }
69
70 355
            if ($state->hasBuffer()) {
71
                /** @var string|int $key */
72 265
                $key = $this->decoder->decode($state->tree->currentKey());
73 265
                $value = $this->decoder->decode($state->value());
74 264
                $wrapper = $value instanceof self ? ($this->config->wrapper)($value) : $value;
75
76 264
                yield $key => $state->callPointer($wrapper, $key);
77
78 264
                $value instanceof self && $value->fastForward();
79
            }
80
81 354
            if ($state->canStopParsing()) {
82 162
                break;
83
            }
84
        }
85
    }
86
87
    /**
88
     * Retrieve the generator to lazy load the current compound
89
     *
90
     * @return Generator<int, Token>
91
     */
92 34
    public function lazyLoad(): Generator
93
    {
94 34
        $depth = 0;
95
96
        do {
97 34
            yield $token = $this->tokens->current();
98
99 34
            if ($token instanceof CompoundBegin) {
100 34
                $depth++;
101 34
            } elseif ($token instanceof CompoundEnd) {
102 34
                $depth--;
103
            }
104
105 34
            $depth > 0 && $this->tokens->next();
106 34
        } while ($depth > 0);
107
    }
108
109
    /**
110
     * Eager load the current compound into an array
111
     *
112
     * @return array<string|int, mixed>
113
     */
114 191
    public function toArray(): array
115
    {
116 191
        $index = 0;
117 191
        $array = [];
118 191
        $hasWildcards = false;
119
120 191
        foreach ($this as $key => $value) {
121 137
            if (isset($array[$index][$key])) {
122 22
                $index++;
123 22
                $hasWildcards = true;
124
            }
125
126 137
            $turnsIntoArray = is_object($value) && method_exists($value, 'toArray');
127 137
            $array[$index][$key] = $turnsIntoArray ? $value->toArray() : $value;
128
        }
129
130 191
        return $hasWildcards || empty($array) ? $array : $array[0];
131
    }
132
133
    /**
134
     * Fast-forward the parser
135
     *
136
     * @return void
137
     */
138 34
    public function fastForward(): void
139
    {
140 34
        if (!$this->tokens->valid()) {
141 25
            return;
142
        }
143
144 9
        $this->isFastForwarding = true;
145
146 9
        foreach ($this as $value) {
147
            $value instanceof self && $value->fastForward(); // @codeCoverageIgnore
148
        }
149
    }
150
}
151