Passed
Push — master ( be5d9b...e3fed7 )
by Banciu N. Cristian Mihai
02:53
created

DotArray::normalize()   A

Complexity

Conditions 6
Paths 4

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 6

Importance

Changes 0
Metric Value
cc 6
eloc 8
nc 4
nop 1
dl 0
loc 16
ccs 9
cts 9
cp 1
crap 6
rs 9.2222
c 0
b 0
f 0
1
<?php
2
3
namespace BinaryCube\DotArray;
4
5
/**
6
 * DotArray
7
 *
8
 * @package BinaryCube\DotArray
9
 * @author  Banciu N. Cristian Mihai <[email protected]>
10
 * @license https://github.com/binary-cube/dot-array/blob/master/LICENSE <MIT License>
11
 * @link    https://github.com/binary-cube/dot-array
12
 */
13
class DotArray implements
14
    \ArrayAccess,
15
    \IteratorAggregate,
16
    \Serializable,
17
    \JsonSerializable,
18
    \Countable
19
{
20
21
    /* Traits. */
22
    use DotPathTrait;
23
    use DotFilteringTrait;
24
25
    /**
26
     * Unique object identifier.
27
     *
28
     * @var string
29
     */
30
    protected $uniqueIdentifier;
31
32
    /**
33
     * Stores the original data.
34
     *
35
     * @var array
36
     */
37
    protected $items;
38
39
40
    /**
41
     * Creates an DotArray object.
42
     *
43
     * @param mixed $items
44
     *
45
     * @return static
46
     */
47 15
    public static function create($items)
48
    {
49 15
        return (new static($items));
50
    }
51
52
53
    /**
54
     * @param string $json
55
     *
56
     * @return static
57
     */
58 1
    public static function createFromJson($json)
59
    {
60 1
        return static::create(\json_decode($json, true));
61
    }
62
63
64
    /**
65
     * Return the given items as an array
66
     *
67
     * @param mixed $items
68
     *
69
     * @return array
70
     */
71 17
    protected static function normalize($items)
72
    {
73 17
        if ($items instanceof self) {
74 4
            $items = $items->toArray();
75
        }
76
77 17
        if (\is_array($items)) {
78 17
            foreach ($items as $k => $v) {
79 17
                if (\is_array($v) || $v instanceof self) {
80 17
                    $v = static::normalize($v);
81
                }
82 17
                $items[$k] = $v;
83
            }
84
        }
85
86 17
        return (array) $items;
87
    }
88
89
90
    /**
91
     * @param array|DotArray|mixed      $array1
92
     * @param null|array|DotArray|mixed $array2
93
     *
94
     * @return array
95
     */
96 1
    protected static function mergeRecursive($array1, $array2 = null)
97
    {
98 1
        $args = static::normalize(\func_get_args());
99 1
        $res  = \array_shift($args);
100
101 1
        while (!empty($args)) {
102 1
            foreach (\array_shift($args) as $k => $v) {
103 1
                if (\is_int($k) && \array_key_exists($k, $res)) {
104 1
                    $res[] = $v;
105 1
                    continue;
106
                }
107
108 1
                if (\is_array($v) && isset($res[$k]) && \is_array($res[$k])) {
109 1
                    $v = static::mergeRecursive($res[$k], $v);
110
                }
111
112 1
                $res[$k] = $v;
113
            }
114
        }
115
116 1
        return $res;
117
    }
118
119
120
    /**
121
     * DotArray Constructor.
122
     *
123
     * @param mixed $items
124
     */
125 17
    public function __construct($items = [])
126
    {
127 17
        $this->items = static::normalize($items);
128
129 17
        $this->uniqueIdentifier();
130 17
    }
131
132
133
    /**
134
     * DotArray Destructor.
135
     */
136 15
    public function __destruct()
137
    {
138 15
        unset($this->uniqueIdentifier);
139 15
        unset($this->items);
140 15
    }
141
142
143
    /**
144
     * Call object as function.
145
     *
146
     * @param null|string $key
147
     *
148
     * @return mixed|static
149
     */
150 1
    public function __invoke($key = null)
151
    {
152 1
        return $this->get($key);
153
    }
154
155
156
    /**
157
     * @return string
158
     */
159 17
    public function uniqueIdentifier()
160
    {
161 17
        if (empty($this->uniqueIdentifier)) {
162 17
            $this->uniqueIdentifier = static::segmentsToKey(
163
                [
164 17
                    static::class,
165 17
                    \uniqid('', true),
166 17
                    \microtime(true),
167
                ]
168
            );
169
        }
170
171 17
        return $this->uniqueIdentifier;
172
    }
173
174
175
    /**
176
     * Merges one or more arrays into master recursively.
177
     * If each array has an element with the same string key value, the latter
178
     * will overwrite the former (different from array_merge_recursive).
179
     * Recursive merging will be conducted if both arrays have an element of array
180
     * type and are having the same key.
181
     * For integer-keyed elements, the elements from the latter array will
182
     * be appended to the former array.
183
     *
184
     * @param array|DotArray|mixed $array Array to be merged from. You can specify additional
185
     *                                    arrays via second argument, third argument, fourth argument etc.
186
     *
187
     * @return static
188
     */
189 1
    public function merge($array)
190
    {
191 1
        $this->items = \call_user_func_array(
192
            [
193 1
                $this, 'mergeRecursive',
194
            ],
195 1
            \array_merge(
196 1
                [$this->items],
197 1
                \func_get_args()
198
            )
199
        );
200
201 1
        return $this;
202
    }
203
204
205
    /**
206
     * @param string|null|mixed $key
207
     * @param mixed             $default
208
     *
209
     * @return array|mixed
210
     */
211 12
    protected function &read($key = null, $default = null)
212
    {
213 12
        $segments = static::pathToSegments($key);
214 12
        $items    = &$this->items;
215
216 12
        foreach ($segments as $segment) {
217
            if (
218 12
                !\is_array($items)
219 12
                || !\array_key_exists($segment, $items)
220
            ) {
221 2
                return $default;
222
            }
223
224 12
            $items = &$items[$segment];
225
        }
226
227 12
        unset($segments);
228
229 12
        return $items;
230
    }
231
232
233
    /**
234
     * @param string $key
235
     * @param mixed  $value
236
     *
237
     * @return void
238
     */
239 4
    protected function write($key, $value)
240
    {
241 4
        $segments = static::pathToSegments($key);
242 4
        $count    = \count($segments);
243 4
        $items    = &$this->items;
244
245 4
        for ($i = 0; $i < $count; $i++) {
246 4
            $segment = $segments[$i];
247
248
            if (
249 4
                (!isset($items[$segment]) || !\is_array($items[$segment]))
250 4
                && ($i < ($count - 1))
251
            ) {
252 1
                $items[$segment] = [];
253
            }
254
255 4
            $items = &$items[$segment];
256
        }
257
258 4
        if (\is_array($value) || $value instanceof self) {
259 4
            $value = static::normalize($value);
260
        }
261
262 4
        $items = $value;
263
264 4
        if (!\is_array($this->items)) {
0 ignored issues
show
introduced by
The condition is_array($this->items) is always true.
Loading history...
265 1
            $this->items = static::normalize($this->items);
266
        }
267 4
    }
268
269
270
    /**
271
     * Delete the given key or keys.
272
     *
273
     * @param string $key
274
     *
275
     * @return void
276
     */
277 1
    protected function remove($key)
278
    {
279 1
        $segments = static::pathToSegments($key);
280 1
        $count    = \count($segments);
281 1
        $items    = &$this->items;
282
283 1
        for ($i = 0; $i < $count; $i++) {
284 1
            $segment = $segments[$i];
285
286
            // Nothing to unset.
287 1
            if (!\array_key_exists($segment, $items)) {
288 1
                break;
289
            }
290
291
            // Last item, time to unset.
292 1
            if ($i === ($count - 1)) {
293 1
                unset($items[$segment]);
294 1
                break;
295
            }
296
297 1
            $items = &$items[$segment];
298
        }
299 1
    }
300
301
302
    /**
303
     * @param string $key
304
     *
305
     * @return bool
306
     */
307 2
    public function has($key)
308
    {
309 2
        $identifier = $this->uniqueIdentifier();
310
311 2
        return ($identifier !== $this->read($key, $identifier));
312
    }
313
314
315
    /**
316
     * Check if a given key contains empty values (null, [], 0, false)
317
     *
318
     * @param null|string $key
319
     *
320
     * @return bool
321
     */
322 1
    public function isEmpty($key = null)
323
    {
324 1
        $items = $this->read($key, null);
325
326 1
        return empty($items);
327
    }
328
329
330
    /**
331
     * @param null|string $key
332
     * @param null|mixed  $default
333
     *
334
     * @return mixed|static
335
     */
336 11
    public function get($key = null, $default = null)
337
    {
338 11
        $items = $this->read($key, $default);
339
340 11
        if (\is_array($items)) {
341 11
            $items = static::create($items);
342
        }
343
344 11
        return $items;
345
    }
346
347
348
    /**
349
     * Set the given value to the provided key or keys.
350
     *
351
     * @param null|string|array $keys
352
     * @param mixed|mixed       $value
353
     *
354
     * @return static
355
     */
356 2
    public function set($keys = null, $value = [])
357
    {
358 2
        $keys = (array) (!isset($keys) ? [$keys] : $keys);
359
360 2
        foreach ($keys as $key) {
361 2
            $this->write($key, $value);
362
        }
363
364 2
        return $this;
365
    }
366
367
368
    /**
369
     * Delete the given key or keys.
370
     *
371
     * @param string|array $keys
372
     *
373
     * @return static
374
     */
375 1
    public function delete($keys)
376
    {
377 1
        $keys = (array) $keys;
378
379 1
        foreach ($keys as $key) {
380 1
            $this->remove($key);
381
        }
382
383 1
        return $this;
384
    }
385
386
387
    /**
388
     * Set the contents of a given key or keys to the given value (default is empty array).
389
     *
390
     * @param null|string|array $keys
391
     * @param array|mixed       $value
392
     *
393
     * @return static
394
     */
395 1
    public function clear($keys = null, $value = [])
396
    {
397 1
        $keys = (array) (!isset($keys) ? [$keys] : $keys);
398
399 1
        foreach ($keys as $key) {
400 1
            $this->write($key, $value);
401
        }
402
403 1
        return $this;
404
    }
405
406
407
    /**
408
     * Returning the first value from the current array.
409
     *
410
     * @return mixed
411
     */
412 1
    public function first()
413
    {
414 1
        $items = $this->items;
415
416 1
        return \array_shift($items);
417
    }
418
419
420
    /**
421
     * Whether a offset exists
422
     *
423
     * @link https://php.net/manual/en/arrayaccess.offsetexists.php
424
     *
425
     * @param mixed $offset An offset to check for.
426
     *
427
     * @return boolean true on success or false on failure.
428
     *
429
     * The return value will be casted to boolean if non-boolean was returned.
430
     * @since  5.0.0
431
     */
432 1
    public function offsetExists($offset)
433
    {
434 1
        return $this->has($offset);
435
    }
436
437
438
    /**
439
     * Offset to retrieve
440
     *
441
     * @link https://php.net/manual/en/arrayaccess.offsetget.php
442
     *
443
     * @param mixed $offset The offset to retrieve.
444
     *
445
     * @return mixed Can return all value types.
446
     *
447
     * @since 5.0.0
448
     */
449 5
    public function &offsetGet($offset)
450
    {
451 5
        return $this->read($offset, null);
452
    }
453
454
455
    /**
456
     * Offset to set
457
     *
458
     * @link https://php.net/manual/en/arrayaccess.offsetset.php
459
     *
460
     * @param mixed $offset
461
     * @param mixed $value
462
     *
463
     * @return void
464
     *
465
     * @since 5.0.0
466
     */
467 2
    public function offsetSet($offset, $value)
468
    {
469 2
        $this->write($offset, $value);
470 2
    }
471
472
473
    /**
474
     * Offset to unset
475
     *
476
     * @link https://php.net/manual/en/arrayaccess.offsetunset.php
477
     *
478
     * @param mixed $offset The offset to unset.
479
     *
480
     * @return void
481
     *
482
     * @since 5.0.0
483
     */
484 1
    public function offsetUnset($offset)
485
    {
486 1
        if (\array_key_exists($offset, $this->items)) {
487 1
            unset($this->items[$offset]);
488 1
            return;
489
        }
490
491 1
        $this->remove($offset);
492 1
    }
493
494
495
    /**
496
     * Count elements of an object
497
     *
498
     * @link https://php.net/manual/en/countable.count.php
499
     *
500
     * @param int $mode
501
     *
502
     * @return int The custom count as an integer.
503
     *
504
     * @since 5.1.0
505
     */
506 2
    public function count($mode = COUNT_NORMAL)
507
    {
508 2
        return \count($this->items, $mode);
509
    }
510
511
512
    /**
513
     * Specify data which should be serialized to JSON
514
     *
515
     * @link https://php.net/manual/en/jsonserializable.jsonserialize.php
516
     *
517
     * @return mixed data which can be serialized by <b>json_encode</b>,
518
     * which is a value of any type other than a resource.
519
     *
520
     * @since 5.4.0
521
     */
522 1
    public function jsonSerialize()
523
    {
524 1
        return $this->items;
525
    }
526
527
528
    /**
529
     * String representation of object
530
     *
531
     * @link https://php.net/manual/en/serializable.serialize.php
532
     *
533
     * @return string the string representation of the object or null
534
     *
535
     * @since 5.1.0
536
     */
537 1
    public function serialize()
538
    {
539 1
        return \serialize($this->items);
540
    }
541
542
543
    /**
544
     * Constructs the object
545
     *
546
     * @link https://php.net/manual/en/serializable.unserialize.php
547
     *
548
     * @param string $serialized The string representation of the object.
549
550
     * @return void
551
     *
552
     * @since 5.1.0
553
     */
554 1
    public function unserialize($serialized)
555
    {
556 1
        $this->items = \unserialize($serialized);
557 1
    }
558
559
560
    /**
561
     * Retrieve an external iterator.
562
     *
563
     * @link https://php.net/manual/en/iteratoraggregate.getiterator.php
564
     *
565
     * @return \ArrayIterator An instance of an object implementing Iterator or Traversable
566
     *
567
     * @since 5.0.0
568
     */
569 1
    public function getIterator()
570
    {
571 1
        return new \ArrayIterator($this->items);
572
    }
573
574
575
    /**
576
     * @return array
577
     */
578 12
    public function toArray()
579
    {
580 12
        return $this->items;
581
    }
582
583
584
    /**
585
     * @param int $options
586
     *
587
     * @return string
588
     */
589 1
    public function toJson($options = 0)
590
    {
591 1
        return (string) \json_encode($this->items, $options);
592
    }
593
594
595
    /**
596
     * Flatten the internal array using the dot delimiter,
597
     * also the keys are wrapped inside {key} (1 x curly braces).
598
     *
599
     * @return array
600
     */
601 1
    public function toFlat()
602
    {
603 1
        return static::flatten($this->items);
604
    }
605
606
607
}
608