AbstractListener::endArray()   A
last analyzed

Complexity

Conditions 5
Paths 5

Size

Total Lines 21
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 5.0342

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 8
c 1
b 0
f 0
nc 5
nop 0
dl 0
loc 21
ccs 8
cts 9
cp 0.8889
crap 5.0342
rs 9.6111
1
<?php
2
3
namespace Cerbero\JsonObjects\Listeners;
4
5
use JsonStreamingParser\Listener\ListenerInterface;
6
7
/**
8
 * The abstract listener.
9
 *
10
 */
11
abstract class AbstractListener implements ListenerInterface
12
{
13
    /**
14
     * The current depth within the JSON stream.
15
     *
16
     * @var int
17
     */
18
    protected $depth = 0;
19
20
    /**
21
     * The current JSON object stack.
22
     *
23
     * @var array
24
     */
25
    protected $stack = [];
26
27
    /**
28
     * The current key for each depth.
29
     *
30
     * @var array
31
     */
32
    protected $keyByDepth = [];
33
34
    /**
35
     * The current position within the JSON stream.
36
     *
37
     * @var array
38
     */
39
    protected $breadcrumbs = [];
40
41
    /**
42
     * The target containing the JSON objects.
43
     *
44
     * @var array
45
     */
46
    protected $target = [];
47
48
    /**
49
     * Set the target from the given key
50
     *
51
     * @param string $key
52
     * @return void
53
     */
54 5
    public function setTargetFromKey(string $key): self
55
    {
56
        // Turn the key using dot notation into an array
57 5
        $this->target = array_filter(explode('.', $key));
58
59 5
        return $this;
60
    }
61
62
    /**
63
     * Listen to the start of the document.
64
     *
65
     * @return void
66
     */
67 8
    public function startDocument(): void
68
    {
69
        // Ignore the start of the document
70 8
    }
71
72
    /**
73
     * Listen to the end of the document.
74
     *
75
     * @return void
76
     */
77 1
    public function endDocument(): void
78
    {
79
        // Free the memory at the end of the document
80 1
        $this->stack = [];
81 1
        $this->keyByDepth = [];
82 1
        $this->breadcrumbs = [];
83 1
    }
84
85
    /**
86
     * Listen to the start of the object.
87
     *
88
     * @return void
89
     */
90 7
    public function startObject(): void
91
    {
92
        // Every object increases the depth when it starts
93 7
        $this->depth++;
94
95
        // Only add objects within the target or all objects if no target is set
96 7
        if ($this->shouldBeExtracted()) {
97 7
            $this->stack[] = [];
98
        }
99 7
    }
100
101
    /**
102
     * Determine whether the current object should be extracted
103
     *
104
     * @return bool
105
     */
106 7
    protected function shouldBeExtracted(): bool
107
    {
108
        // All JSON objects should be extracted if no target is set
109 7
        if (empty($this->target)) {
110 2
            return true;
111
        }
112
113
        // Determine whether the current JSON object is within the target
114 5
        $length = count($this->target);
115 5
        return array_slice($this->breadcrumbs, 0, $length) === $this->target;
116
    }
117
118
    /**
119
     * Listen to the end of the object.
120
     *
121
     * @return void
122
     */
123 7
    public function endObject(): void
124
    {
125
        // Every object decreases the depth when it ends
126 7
        $this->depth--;
127
128 7
        if ($this->shouldBeSkipped()) {
129 2
            return;
130
        }
131
132
        // When an object ends, update the current position to the object's parent key
133 7
        array_pop($this->breadcrumbs);
134
135 7
        $object = array_pop($this->stack);
136
137
        // If the stack is empty, the object has been fully extracted and can be processed
138
        // Otherwise it is a nested object to be paired to a key or added to an array
139 7
        if (empty($this->stack) && !empty($object)) {
140 7
            $this->processExtractedObject($object);
141
        } else {
142 1
            $this->value($object);
143
        }
144 4
    }
145
146
    /**
147
     * Determine whether the current object should be skipped
148
     *
149
     * @return bool
150
     */
151 7
    protected function shouldBeSkipped(): bool
152
    {
153 7
        return !$this->shouldBeExtracted();
154
    }
155
156
    /**
157
     * Process the given extracted object.
158
     *
159
     * @param array $object
160
     * @return void
161
     */
162
    abstract protected function processExtractedObject(array $object): void;
163
164
    /**
165
     * Listen to the start of the array.
166
     *
167
     * @return void
168
     */
169 7
    public function startArray(): void
170
    {
171
        // If the document starts with an array, ignore it
172 7
        if ($this->depth === 0) {
173 5
            return;
174
        }
175
176 5
        $this->depth++;
177
178
        // Asterisks indicate that the current position is within an array
179 5
        if (!empty($this->target)) {
180 5
            $this->breadcrumbs[] = '*';
181
        }
182
183
        // If the target is an array, extract its JSON objects but ignore the wrapping array
184 5
        if ($this->shouldBeExtracted() && !$this->isTarget()) {
185
            $this->stack[] = [];
186
        }
187 5
    }
188
189
    /**
190
     * Determine whether the current element is the target
191
     *
192
     * @return bool
193
     */
194 3
    protected function isTarget(): bool
195
    {
196 3
        if (empty($this->target)) {
197
            return false;
198
        }
199
200
        // An element is the target if their positions and depths coincide
201 3
        return $this->shouldBeExtracted() && count($this->target) === $this->depth;
202
    }
203
204
    /**
205
     * Listen to the end of the array.
206
     *
207
     * @return void
208
     */
209 2
    public function endArray(): void
210
    {
211
        // If the document ends with an array, ignore it
212 2
        if ($this->depth === 0) {
213 1
            return;
214
        }
215
216 2
        $this->depth--;
217
218
        // Update the current position if a target is set
219 2
        if (!empty($this->target)) {
220 2
            array_pop($this->breadcrumbs);
221
        }
222
223
        // If the target is an array, extract its JSON objects but ignore the wrapping array
224 2
        if ($this->shouldBeSkipped() || $this->isTarget()) {
225 2
            return;
226
        }
227
228
        // The nested array is ready to be paired to a key or added to an array
229
        $this->value(array_pop($this->stack));
230
    }
231
232
    /**
233
     * Listen to the key.
234
     *
235
     * @param string $key
236
     * @return void
237
     */
238 7
    public function key(string $key): void
239
    {
240 7
        $this->keyByDepth[$this->depth] = $key;
241
242
        // Update the current position if a target is set
243 7
        if (!empty($this->target)) {
244 5
            $this->breadcrumbs[$this->depth - 1] = $key;
245 5
            $this->breadcrumbs = array_slice($this->breadcrumbs, 0, $this->depth);
246
        }
247 7
    }
248
249
    /**
250
     * Listen to the value.
251
     *
252
     * @param mixed $value
253
     * @return void
254
     */
255 7
    public function value($value): void
256
    {
257 7
        if ($this->shouldBeSkipped()) {
258 4
            return;
259
        }
260
261 7
        $object = array_pop($this->stack);
262
263
        // Pair the value to the current key if set or add the value to an array
264 7
        if (empty($this->keyByDepth[$this->depth])) {
265
            $object[] = $value;
266
        } else {
267 7
            $object[$this->keyByDepth[$this->depth]] = $value;
268 7
            $this->keyByDepth[$this->depth] = null;
269
        }
270
271 7
        $this->stack[] = $object;
272 7
    }
273
274
    /**
275
     * Listen to the whitespace.
276
     *
277
     * @param string $whitespace
278
     * @return void
279
     */
280
    public function whitespace(string $whitespace): void
281
    {
282
        // Ignore the whitespaces
283
    }
284
}
285