Passed
Push — master ( 9eb54d...c971cd )
by Andrea Marco
03:39
created

AbstractListener   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 271
Duplicated Lines 0 %

Test Coverage

Coverage 83.33%

Importance

Changes 0
Metric Value
eloc 59
dl 0
loc 271
ccs 60
cts 72
cp 0.8333
rs 9.92
c 0
b 0
f 0
wmc 31

13 Methods

Rating   Name   Duplication   Size   Complexity  
A setTargetFromKey() 0 6 1
A endArray() 0 21 5
A key() 0 8 2
A whitespace() 0 2 1
A startObject() 0 8 2
A shouldBeSkipped() 0 3 1
A endObject() 0 20 4
A endDocument() 0 6 1
A isTarget() 0 8 3
A startArray() 0 17 5
A startDocument() 0 2 1
A shouldBeExtracted() 0 10 2
A value() 0 17 3
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 12
    public function setTargetFromKey(string $key) : self
55
    {
56
        // Turn the key using dot notation into an array
57 12
        $this->target = array_filter(explode('.', $key));
58
59 12
        return $this;
60
    }
61
62
    /**
63
     * Listen to the start of the document.
64
     *
65
     * @return void
66
     */
67 21
    public function startDocument() : void
68
    {
69
        // Ignore the start of the document
70 21
    }
71
72
    /**
73
     * Listen to the end of the document.
74
     *
75
     * @return void
76
     */
77
    public function endDocument() : void
78
    {
79
        // Free the memory at the end of the document
80
        $this->stack = [];
81
        $this->keyByDepth = [];
82
        $this->breadcrumbs = [];
83
    }
84
85
    /**
86
     * Listen to the start of the object.
87
     *
88
     * @return void
89
     */
90 18
    public function startObject() : void
91
    {
92
        // Every object increases the depth when it starts
93 18
        $this->depth++;
94
95
        // Only add objects within the target or all objects if no target is set
96 18
        if ($this->shouldBeExtracted()) {
97 18
            $this->stack[] = [];
98
        }
99 18
    }
100
101
    /**
102
     * Determine whether the current object should be extracted
103
     *
104
     * @return bool
105
     */
106 18
    protected function shouldBeExtracted() : bool
107
    {
108
        // All JSON objects should be extracted if no target is set
109 18
        if (empty($this->target)) {
110 6
            return true;
111
        }
112
113
        // Determine whether the current JSON object is within the target
114 12
        $length = count($this->target);
115 12
        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 18
    public function endObject() : void
124
    {
125
        // Every object decreases the depth when it ends
126 18
        $this->depth--;
127
128 18
        if ($this->shouldBeSkipped()) {
129 3
            return;
130
        }
131
132
        // When an object ends, update the current position to the object's parent key
133 18
        array_pop($this->breadcrumbs);
134
135 18
        $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 18
        if (empty($this->stack) && !empty($object)) {
140 18
            $this->processExtractedObject($object);
141
        } else {
142 3
            $this->value($object);
143
        }
144 9
    }
145
146
    /**
147
     * Determine whether the current object should be skipped
148
     *
149
     * @return bool
150
     */
151 18
    protected function shouldBeSkipped() : bool
152
    {
153 18
        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 18
    public function startArray() : void
170
    {
171
        // If the document starts with an array, ignore it
172 18
        if ($this->depth === 0) {
173 12
            return;
174
        }
175
176 12
        $this->depth++;
177
178
        // Asterisks indicate that the current position is within an array
179 12
        if (!empty($this->target)) {
180 12
            $this->breadcrumbs[] = '*';
181
        }
182
183
        // If the target is an array, extract its JSON objects but ignore the wrapping array
184 12
        if ($this->shouldBeExtracted() && !$this->isTarget()) {
185
            $this->stack[] = [];
186
        }
187 12
    }
188
189
    /**
190
     * Determine whether the current element is the target
191
     *
192
     * @return bool
193
     */
194 6
    protected function isTarget() : bool
195
    {
196 6
        if (empty($this->target)) {
197
            return false;
198
        }
199
200
        // An element is the target if their positions and depths coincide
201 6
        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 3
    public function endArray() : void
210
    {
211
        // If the document ends with an array, ignore it
212 3
        if ($this->depth === 0) {
213
            return;
214
        }
215
216 3
        $this->depth--;
217
218
        // Update the current position if a target is set
219 3
        if (!empty($this->target)) {
220 3
            array_pop($this->breadcrumbs);
221
        }
222
223
        // If the target is an array, extract its JSON objects but ignore the wrapping array
224 3
        if ($this->shouldBeSkipped() || $this->isTarget()) {
225 3
            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 18
    public function key(string $key) : void
239
    {
240 18
        $this->keyByDepth[$this->depth] = $key;
241
242
        // Update the current position if a target is set
243 18
        if (!empty($this->target)) {
244 12
            $this->breadcrumbs[$this->depth - 1] = $key;
245 12
            $this->breadcrumbs = array_slice($this->breadcrumbs, 0, $this->depth);
246
        }
247 18
    }
248
249
    /**
250
     * Listen to the value.
251
     *
252
     * @param mixed $value
253
     * @return void
254
     */
255 18
    public function value($value) : void
256
    {
257 18
        if ($this->shouldBeSkipped()) {
258 9
            return;
259
        }
260
261 18
        $object = array_pop($this->stack);
262
263
        // Pair the value to the current key if set or add the value to an array
264 18
        if (empty($this->keyByDepth[$this->depth])) {
265
            $object[] = $value;
266
        } else {
267 18
            $object[$this->keyByDepth[$this->depth]] = $value;
268 18
            $this->keyByDepth[$this->depth] = null;
269
        }
270
271 18
        $this->stack[] = $object;
272 18
    }
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