Passed
Push — master ( d3f706...95ff55 )
by Sebastian
08:48
created

ArrayDataCollection   A

Complexity

Total Complexity 42

Size/Duplication

Total Lines 360
Duplicated Lines 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
wmc 42
eloc 76
c 5
b 0
f 0
dl 0
loc 360
rs 9.0399

23 Methods

Rating   Name   Duplication   Size   Complexity  
A getTimestamp() 0 10 2
A setMicrotime() 0 3 1
A getMicrotime() 0 9 2
A keyHasValue() 0 3 1
A getString() 0 13 3
A getJSONArray() 0 14 4
A setKeys() 0 8 2
A getArray() 0 9 2
A removeKey() 0 4 1
A __construct() 0 3 1
A setKey() 0 4 1
A getKey() 0 3 1
A keyExists() 0 3 1
A createFromJSON() 0 3 1
A mergeWith() 0 3 1
A getInt() 0 9 2
A create() 0 7 2
A getData() 0 3 1
A combine() 0 3 1
A getBool() 0 16 5
A getFloat() 0 9 2
A getDateTime() 0 21 4
A setDateTime() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like ArrayDataCollection 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.

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 ArrayDataCollection, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @package Application Utils
4
 * @subpackage Collections
5
 * @see \AppUtils\ArrayDataCollection
6
 */
7
8
declare(strict_types=1);
9
10
namespace AppUtils;
11
12
use AppUtils\ConvertHelper\JSONConverter;
13
use AppUtils\ConvertHelper\JSONConverter\JSONConverterException;
14
use DateTime;
15
use Exception;
16
17
/**
18
 * Collection class used to work with associative arrays used to
19
 * store key => value pairs.
20
 *
21
 * Offers strict typed methods to access the available keys, to
22
 * remove the hassle of checking whether keys exist, and whether
23
 * they are of the expected type.
24
 *
25
 * ## Exception-free handling
26
 *
27
 * The collection is not intended to validate any of the stored
28
 * data, this is the purview of the host class. The utility
29
 * methods will only return values that match the expected type.
30
 * Invalid data is ignored, and a matching default value returned.
31
 *
32
 * @package Application Utils
33
 * @subpackage Collections
34
 * @author Sebastian Mordziol <[email protected]>
35
 */
36
class ArrayDataCollection
37
{
38
    /**
39
     * @var array<string,mixed>
40
     */
41
    protected array $data;
42
43
    /**
44
     * @param array<string,mixed> $data
45
     */
46
    public function __construct(array $data=array())
47
    {
48
        $this->data = $data;
49
    }
50
51
    /**
52
     * @param ArrayDataCollection|array<string,mixed>|NULL $data
53
     * @return ArrayDataCollection
54
     */
55
    public static function create($data=array()) : ArrayDataCollection
56
    {
57
        if($data instanceof self) {
58
            return $data;
59
        }
60
61
        return new ArrayDataCollection($data);
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type null; however, parameter $data of AppUtils\ArrayDataCollection::__construct() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

61
        return new ArrayDataCollection(/** @scrutinizer ignore-type */ $data);
Loading history...
62
    }
63
64
    /**
65
     * Creates an array converter from a JSON encoded array.
66
     *
67
     * @param string $json
68
     * @return ArrayDataCollection
69
     * @throws JSONConverterException
70
     */
71
    public static function createFromJSON(string $json) : ArrayDataCollection
72
    {
73
        return self::create(JSONConverter::json2array($json));
74
    }
75
76
    /**
77
     * @return array<string,mixed>
78
     */
79
    public function getData() : array
80
    {
81
        return $this->data;
82
    }
83
84
    /**
85
     * @param array<string,mixed> $data
86
     * @return $this
87
     */
88
    public function setKeys(array $data) : self
89
    {
90
        foreach($data as $key => $value)
91
        {
92
            $this->setKey($key, $value);
93
        }
94
95
        return $this;
96
    }
97
98
    /**
99
     * @param string $name
100
     * @param mixed|NULL $value
101
     * @return $this
102
     */
103
    public function setKey(string $name, $value) : self
104
    {
105
        $this->data[$name] = $value;
106
        return $this;
107
    }
108
109
    /**
110
     * Merges the current collection's data with that of
111
     * the target collection, replacing existing values.
112
     *
113
     * @param ArrayDataCollection $collection
114
     * @return $this
115
     */
116
    public function mergeWith(ArrayDataCollection $collection) : self
117
    {
118
        return $this->setKeys($collection->getData());
119
    }
120
121
    /**
122
     * Combines the current collection's data with the
123
     * target collection, and returns a new collection
124
     * that contains the data of both collections.
125
     *
126
     * NOTE: The source collection's values are overwritten
127
     * by the target collection in the process.
128
     *
129
     * @param ArrayDataCollection $collection
130
     * @return ArrayDataCollection
131
     */
132
    public function combine(ArrayDataCollection $collection) : ArrayDataCollection
133
    {
134
        return self::create($this->data)->setKeys($collection->getData());
135
    }
136
137
    /**
138
     * @param string $name
139
     * @return mixed|null
140
     */
141
    public function getKey(string $name)
142
    {
143
        return $this->data[$name] ?? null;
144
    }
145
146
    /**
147
     * The stored value can be a string or a number.
148
     * All other types and values will return an empty
149
     * string.
150
     *
151
     * @param string $name
152
     * @return string
153
     */
154
    public function getString(string $name) : string
155
    {
156
        $value = $this->getKey($name);
157
158
        if(is_string($value)) {
159
            return $value;
160
        }
161
162
        if(is_numeric($value)) {
163
            return (string)$value;
164
        }
165
166
        return '';
167
    }
168
169
    /**
170
     * The stored value can be an integer or float,
171
     * or a string containing an integer or float.
172
     * All other types and values return <code>0</code>.
173
     *
174
     * @param string $name
175
     * @return int
176
     */
177
    public function getInt(string $name) : int
178
    {
179
        $value = $this->getKey($name);
180
181
        if(is_numeric($value)) {
182
            return (int)$value;
183
        }
184
185
        return 0;
186
    }
187
188
    /**
189
     * Attempts to decode the stored string as JSON.
190
     *
191
     * NOTE: Only _valid JSON_ that decodes into an array is
192
     * accepted. Invalid JSON, booleans or numbers will
193
     * return an empty array.
194
     *
195
     * @param string $name
196
     * @return array<mixed> The decoded array, or an empty array otherwise.
197
     */
198
    public function getJSONArray(string $name) : array
199
    {
200
        $value = $this->getKey($name);
201
202
        // Does not need to be decoded after all
203
        if(is_array($value)) {
204
            return $value;
205
        }
206
207
        if(empty($value) || !is_string($value)) {
208
            return array();
209
        }
210
211
        return JSONConverter::json2arraySilent($value);
212
    }
213
214
    public function getArray(string $name) : array
215
    {
216
        $value = $this->getKey($name);
217
218
        if(is_array($value)) {
219
            return $value;
220
        }
221
222
        return array();
223
    }
224
225
    public function getBool(string $name) : bool
226
    {
227
        $value = $this->getKey($name);
228
229
        if(is_string($value)) {
230
            $value = strtolower($value);
231
        }
232
233
        return
234
            $value === true
235
            ||
236
            $value === 'true'
237
            ||
238
            $value === 'yes'
239
            ||
240
            $value === 1;
241
    }
242
243
    public function getFloat(string $name) : float
244
    {
245
        $value = $this->getKey($name);
246
247
        if(is_numeric($value)) {
248
            return (float)$value;
249
        }
250
251
        return 0.0;
252
    }
253
254
    /**
255
     * Whether the specified key exists in the data set,
256
     * even if its value is <code>NULL</code>.
257
     *
258
     * @param string $name
259
     * @return bool
260
     */
261
    public function keyExists(string $name) : bool
262
    {
263
        return array_key_exists($name, $this->data);
264
    }
265
266
    /**
267
     * Whether the specified key exists in the data set,
268
     * and has a non-<code>NULL</code> value.
269
     *
270
     * @param string $name
271
     * @return bool
272
     */
273
    public function keyHasValue(string $name) : bool
274
    {
275
        return isset($this->data[$name]);
276
    }
277
278
    /**
279
     * Removes the specified key from the data set, if it exists.
280
     *
281
     * @param string $name
282
     * @return $this
283
     */
284
    public function removeKey(string $name) : self
285
    {
286
        unset($this->data[$name]);
287
        return $this;
288
    }
289
290
    /**
291
     * Fetches a {@see DateTime} instance from the stored
292
     * key value, which can be either of the following:
293
     *
294
     * - A timestamp (int|string)
295
     * - A DateTime string
296
     *
297
     * @param string $name
298
     * @return DateTime|null The {@see DateTime} instance, or <code>NULL</code> if empty or invalid.
299
     */
300
    public function getDateTime(string $name) : ?DateTime
301
    {
302
        $value = $this->getString($name);
303
304
        if(empty($value)) {
305
            return null;
306
        }
307
308
        if(is_numeric($value)) {
309
            $date = new DateTime();
310
            $date->setTimestamp((int)$value);
311
            return $date;
312
        }
313
314
        try
315
        {
316
            return new DateTime($value);
317
        }
318
        catch (Exception $e)
319
        {
320
            return null;
321
        }
322
    }
323
324
    /**
325
     * Restores a {@see Microtime} instance from a key previously
326
     * set using {@see ArrayDataCollection::setMicrotime()}.
327
     *
328
     * @param string $name
329
     * @return Microtime|null The {@see Microtime} instance, or <code>NULL</code> if empty or invalid.
330
     */
331
    public function getMicrotime(string $name) : ?Microtime
332
    {
333
        try
334
        {
335
            return Microtime::createFromString($this->getString($name));
336
        }
337
        catch (Exception $e)
338
        {
339
            return null;
340
        }
341
    }
342
343
    /**
344
     * Sets a date and time key, which can be restored later
345
     * using {@see ArrayDataCollection::getDateTime()} or
346
     * {@see ArrayDataCollection::getTimestamp()}.
347
     *
348
     * @param string $name
349
     * @param DateTime $time
350
     * @return $this
351
     */
352
    public function setDateTime(string $name, DateTime $time) : self
353
    {
354
        return $this->setKey($name, $time->format(DATE_W3C));
355
    }
356
357
    /**
358
     * Sets a microtime key: Guarantees that the microseconds
359
     * information will be persisted correctly if restored later
360
     * using {@see ArrayDataCollection::getMicrotime()}.
361
     *
362
     * **NOTE:** Fetching a timestamp from a microtime key with
363
     * {@see ArrayDataCollection::getTimestamp()} will work,
364
     * but the microseconds information will be lost.
365
     *
366
     * @param $name
367
     * @param Microtime $time
368
     * @return $this
369
     */
370
    public function setMicrotime($name, Microtime $time) : self
371
    {
372
        return $this->setKey($name, $time->getISODate());
373
    }
374
375
    /**
376
     * Fetches a stored timestamp. The source value can be
377
     * any of the following:
378
     *
379
     * - An timestamp (int|string)
380
     * - A DateTime key
381
     * - A Microtime key
382
     *
383
     * @param string $name
384
     * @return int The timestamp, or <code>0</code> if none/invalid.
385
     */
386
    public function getTimestamp(string $name) : int
387
    {
388
        $date = $this->getDateTime($name);
389
390
        if($date !== null)
391
        {
392
            return $date->getTimestamp();
393
        }
394
395
        return 0;
396
    }
397
}
398