JsonArray   B
last analyzed

Complexity

Total Complexity 40

Size/Duplication

Total Lines 323
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
wmc 40
lcom 1
cbo 0
dl 0
loc 323
rs 8.2608
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 3
A setData() 0 6 1
A merge() 0 4 1
A getData() 0 4 1
A load() 0 12 2
A splitPath() 0 13 3
A unescape() 0 4 1
A escape() 0 4 1
B get() 0 27 6
B set() 0 35 6
A has() 0 15 3
A remove() 0 4 1
A isEmpty() 0 4 2
A getEntries() 0 16 4
A uasort() 0 11 3
A __toString() 0 5 1
A jsonSerialize() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like JsonArray often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use JsonArray, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * This file is part of tenside/core.
5
 *
6
 * (c) Christian Schiffler <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 *
11
 * This project is provided in good faith and hope to be usable by anyone.
12
 *
13
 * @package    tenside/core
14
 * @author     Christian Schiffler <[email protected]>
15
 * @author     Andreas Schempp <[email protected]>
16
 * @copyright  2015 Christian Schiffler <[email protected]>
17
 * @license    https://github.com/tenside/core/blob/master/LICENSE MIT
18
 * @link       https://github.com/tenside/core
19
 * @filesource
20
 */
21
22
namespace Tenside\Core\Util;
23
24
/**
25
 * Generic path following json file handler.
26
 *
27
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
28
 */
29
class JsonArray implements \JsonSerializable
30
{
31
    /**
32
     * The json data.
33
     *
34
     * @var array
35
     */
36
    private $data;
37
38
    /**
39
     * Create a new instance.
40
     *
41
     * @param string|array $data The json data.
42
     */
43
    public function __construct($data = '{}')
44
    {
45
        if (is_string($data)) {
46
            $this->load($data);
47
        } elseif (is_array($data)) {
48
            $this->setData($data);
49
        }
50
    }
51
52
    /**
53
     * Set the data.
54
     *
55
     * @param array $data The data array.
56
     *
57
     * @return JsonArray
58
     */
59
    public function setData($data)
60
    {
61
        $this->data = (array) $data;
62
63
        return $this;
64
    }
65
66
    /**
67
     * Merge the passed data into this instance.
68
     *
69
     * @param array $data The data to absorb.
70
     *
71
     * @return JsonArray
72
     */
73
    public function merge($data)
74
    {
75
        return $this->setData(array_replace_recursive($this->getData(), (array) $data));
76
    }
77
78
    /**
79
     * Retrieve the data as array.
80
     *
81
     * @return array
82
     */
83
    public function getData()
84
    {
85
        return $this->data;
86
    }
87
88
    /**
89
     * Load the data.
90
     *
91
     * @param string $data The json data.
92
     *
93
     * @return JsonArray
94
     *
95
     * @throws \RuntimeException When the data is invalid.
96
     */
97
    public function load($data)
98
    {
99
        $data = json_decode($data, true);
100
101
        if (JSON_ERROR_NONE !== json_last_error()) {
102
            throw new \RuntimeException('Error: json decode failed. ' . json_last_error_msg(), 1);
103
        }
104
105
        $this->setData($data);
106
107
        return $this;
108
    }
109
110
    /**
111
     * Split the path into chunks.
112
     *
113
     * @param string $path The path to split.
114
     *
115
     * @return array
116
     *
117
     * @throws \InvalidArgumentException When the path is invalid.
118
     */
119
    protected function splitPath($path)
120
    {
121
        $chunks = array_map(
122
            [$this, 'unescape'],
123
            preg_split('#(?<!\\\)\/#', ltrim($path, '/'))
124
        );
125
126
        if (empty($chunks) || (array_filter($chunks) !== $chunks)) {
127
            throw new \InvalidArgumentException('Invalid path provided:' . $path);
128
        }
129
130
        return $chunks;
131
    }
132
133
    /**
134
     * Escape a string to be used as literal path.
135
     *
136
     * @param string $path The string to escape.
137
     *
138
     * @return string
139
     */
140
    public function unescape($path)
141
    {
142
        return str_replace('\/', '/', $path);
143
    }
144
145
    /**
146
     * Escape a string to be used as literal path.
147
     *
148
     * @param string $path The string to escape.
149
     *
150
     * @return string
151
     */
152
    public function escape($path)
153
    {
154
        return str_replace('/', '\/', $path);
155
    }
156
157
    /**
158
     * Retrieve a value.
159
     *
160
     * @param string $path       The path of the value.
161
     *
162
     * @param bool   $forceArray Flag if the result shall be casted to array.
163
     *
164
     * @return array|string|int|null
165
     */
166
    public function get($path, $forceArray = false)
167
    {
168
        // special case, root element.
169
        if ($path === '/') {
170
            return $this->data;
171
        }
172
173
        $chunks = $this->splitPath($path);
174
        $scope  = $this->data;
175
176
        while (null !== ($sub = array_shift($chunks))) {
177
            if (isset($scope[$sub])) {
178
                if ($forceArray) {
179
                    $scope = (array) $scope[$sub];
180
                } else {
181
                    $scope = $scope[$sub];
182
                }
183
            } else {
184
                if ($forceArray) {
185
                    return [];
186
                } else {
187
                    return null;
188
                }
189
            }
190
        }
191
        return $scope;
192
    }
193
194
    /**
195
     * Set a value.
196
     *
197
     * @param string $path  The path of the value.
198
     *
199
     * @param mixed  $value The value to set.
200
     *
201
     * @return JsonArray
202
     */
203
    public function set($path, $value)
204
    {
205
        // special case, root element.
206
        if ($path === '/') {
207
            $this->data = (array) $value;
208
            return $this;
209
        }
210
211
        $chunks = $this->splitPath($path);
212
        $scope  = &$this->data;
213
        $count  = count($chunks);
214
215
        while ($count > 1) {
216
            $sub   = array_shift($chunks);
217
            $count = count($chunks);
218
219
            if ((!(isset($scope[$sub]) && is_array($scope[$sub])))) {
220
                $scope[$sub] = [];
221
            }
222
223
            $scope = &$scope[$sub];
224
        }
225
226
        $sub = $chunks[0];
227
228
        if ($value === null) {
229
            unset($scope[$sub]);
230
231
            return $this;
232
        }
233
234
        $scope[$sub] = $value;
235
236
        return $this;
237
    }
238
239
    /**
240
     * Check if a value exists.
241
     *
242
     * @param string $path The path of the value.
243
     *
244
     * @return bool
245
     */
246
    public function has($path)
247
    {
248
        $chunks = $this->splitPath($path);
249
        $scope  = $this->data;
250
251
        while (null !== ($sub = array_shift($chunks))) {
252
            if (isset($scope[$sub])) {
253
                $scope = $scope[$sub];
254
            } else {
255
                return false;
256
            }
257
        }
258
259
        return true;
260
    }
261
262
    /**
263
     * Unset a value.
264
     *
265
     * @param string $path The path of the value.
266
     *
267
     * @return JsonArray
268
     */
269
    public function remove($path)
270
    {
271
        return $this->set($path, null);
272
    }
273
274
    /**
275
     * Check if a given path has an empty value (or does not exist).
276
     *
277
     * @param string $path The sub path to be sorted.
278
     *
279
     * @return bool
280
     */
281
    public function isEmpty($path)
282
    {
283
        return (null === ($value = $this->get($path))) || empty($value);
284
    }
285
286
    /**
287
     * Retrieve the contained keys at the given path.
288
     *
289
     * @param string $path The sub path to be examined.
290
     *
291
     * @return string[]
292
     */
293
    public function getEntries($path)
294
    {
295
        $entries = $this->get($path);
296
        $result  = [];
297
        $prefix  = trim($path, '/');
298
        if (strlen($prefix)) {
299
            $prefix .= '/';
300
        }
301
        if (is_array($entries)) {
302
            foreach (array_keys($entries) as $key) {
303
                $result[] = $prefix . $this->escape($key);
304
            }
305
        }
306
307
        return $result;
308
    }
309
310
    /**
311
     * Sort the array by the provided user function.
312
     *
313
     * @param callable $callback The callback function to use.
314
     *
315
     * @param string   $path     The sub path to be sorted.
316
     *
317
     * @return void
318
     */
319
    public function uasort($callback, $path = '/')
320
    {
321
        $value = $this->get($path);
322
        if (null === $value || !is_array($value)) {
323
            return;
324
        }
325
326
        uasort($value, $callback);
327
328
        $this->set($path, $value);
329
    }
330
331
    /**
332
     * Encode the array as string and return it.
333
     *
334
     * @return string
335
     */
336
    public function __toString()
337
    {
338
        // Do not use PHP_EOL here, PHP only uses newline and not crlf on Windows.
339
        return json_encode($this, (JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)) . "\n";
340
    }
341
342
    /**
343
     * Return the data which should be serialized to JSON.
344
     *
345
     * @return object
346
     */
347
    public function jsonSerialize()
348
    {
349
        return (object) $this->data;
350
    }
351
}
352