Completed
Push — master ( eb5437...e70327 )
by Daniel
03:22
created

Transform::unDot()   B

Complexity

Conditions 7
Paths 9

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 7

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 20
ccs 14
cts 14
cp 1
rs 8.2222
cc 7
eloc 11
nc 9
nop 2
crap 7
1
<?php
2
namespace Narrowspark\Arr;
3
4
class Transform
5
{
6
    /**
7
     * Dotted array cache.
8
     *
9
     * @var array
10
     */
11
    protected $dotted = [];
12
13
    /**
14
     * A instance of Access
15
     *
16
     * @var \Narrowspark\Arr\Access
17
     */
18
    protected $access;
19
20
    /**
21
     * A instance of Access
22
     *
23
     * @var \Narrowspark\Arr\Enumerator
24
     */
25
    protected $enumerator;
26
27 50
    public function __construct()
28
    {
29 50
        $this->access = new Access();
30 50
        $this->enumerator = new Enumerator();
31 50
    }
32
33
    /**
34
     * Pop value from sub array.
35
     *
36
     * @param array  $array
37
     * @param string $key
38
     *
39
     * @return mixed
40
     */
41 1
    public static function pop(array $array, $key)
42
    {
43 1
        $keys = explode('.', $key);
44
45 1
        foreach ($keys as $key) {
46 1
            if (! isset($array[$key])) {
47 1
                return;
48
            }
49
50 1
            $array = $array[$key];
51 1
        }
52
53 1
        if (! is_array($array)) {
54 1
            return;
55
        }
56
57 1
        return array_pop($array);
58
    }
59
60
    /**
61
     * Swap two elements between positions.
62
     *
63
     * @param array  $array array to swap
64
     * @param string $swapA
65
     * @param string $swapB
66
     *
67
     * @return array|null
68
     */
69 1
    public function swap(array $array, $swapA, $swapB)
70
    {
71 1
        list($array[$swapA], $array[$swapB]) = [$array[$swapB], $array[$swapA]];
72
73 1
        return $array;
74
    }
75
76
    /**
77
     * Create a new array consisting of every n-th element.
78
     *
79
     * @param array $array
80
     * @param int   $step
81
     * @param int   $offset
82
     *
83
     * @return array
84
     */
85 1
    public static function every($array, $step, $offset = 0)
86
    {
87 1
        $new = [];
88
89 1
        $position = 0;
90
91 1
        foreach ($array as $key => $item) {
92 1
            if ($position % $step === $offset) {
93 1
                $new[] = $item;
94 1
            }
95
96 1
            ++$position;
97 1
        }
98
99 1
        return $new;
100
    }
101
102
    /**
103
     * Indexes an array depending on the values it contains.
104
     *
105
     * @param array    $array
106
     * @param callable $callback  Function to combine values.
107
     * @param bool     $overwrite Should duplicate keys be overwritten?
108
     *
109
     * @return array Indexed values.
110
     */
111 1
    public function combine(array $array, callable $callback, $overwrite = true)
112
    {
113 1
        $combined = [];
114
115 1
        foreach ($array as $key => $value) {
116 1
            $combinator = call_user_func($callback, $value, $key);
117
118
            // fix for hhvm #1871 bug
119 1
            if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.10.0', '<=')) {
120
                $combinator->next();
121
            }
122
123 1
            $index = $combinator->key();
124
125 1
            if ($overwrite || ! isset($combined[$index])) {
126 1
                $combined[$index] = $combinator->current();
127 1
            }
128 1
        }
129
130 1
        return $combined;
131
    }
132
133
    /**
134
     * Collapse a nested array down to an array of flat key=>value pairs
135
     */
136 3
    public function collapse(array $array)
137
    {
138 3
        $newArray = [];
139
140 3
        foreach ($array as $key => $value) {
141 3
            if (is_array($value)) {
142
                // strip any manually added '.'
143 3
                if (preg_match('/\./', $key)) {
144 2
                    $key = substr($key, 0, -2);
145 2
                }
146
147 3
                $this->recurseCollapse($value, $newArray, (array) $key);
148 3
            } else {
149 1
                $newArray[$key] = $value;
150
            }
151 3
        }
152
153 3
        return $newArray;
154
    }
155
156
    /**
157
     * Divide an array into two arrays. One with keys and the other with values.
158
     *
159
     * @param array $array
160
     *
161
     * @return array[]
162
     */
163 1
    public function divide($array)
164
    {
165 1
        return [array_keys($array), array_values($array)];
166
    }
167
168
    /**
169
     * Stripe all empty items.
170
     *
171
     * @param array $array
172
     *
173
     * @return array
174
     */
175 1
    public function stripEmpty(array $array)
176
    {
177
        return array_filter($array, function ($item) {
178 1
            if (is_null($item)) {
179 1
                return false;
180
            }
181
182 1
            if (! trim($item)) {
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return (bool) trim($item);.
Loading history...
183 1
                return false;
184
            }
185
186 1
            return true;
187 1
        });
188
    }
189
190
    /**
191
     * Remove all instances of $ignore found in $elements (=== is used).
192
     *
193
     * @param array $array
194
     * @param array $ignore
195
     *
196
     * @return array
197
     */
198 1
    public function without(array $array, array $ignore)
199
    {
200 1
        foreach ($array as $key => $node) {
201 1
            if (in_array($node, $ignore, true)) {
202 1
                unset($array[$key]);
203 1
            }
204 1
        }
205
206 1
        return array_values($array);
207
    }
208
209
    /**
210
     * Reindexes a list of values.
211
     *
212
     * @param array $array
213
     * @param array $map      An map of correspondances of the form
214
     *                        ['currentIndex' => 'newIndex'].
215
     * @param bool  $unmapped Whether or not to keep keys that are not
216
     *                        remapped.
217
     *
218
     * @return array
219
     */
220 1
    public function reindex(array $array, array $map, $unmapped = true)
221
    {
222
        $reindexed = $unmapped
223 1
            ? $array
224 1
            : [];
225
226 1
        foreach ($map as $from => $to) {
227 1
            if (isset($array[$from])) {
228 1
                $reindexed[$to] = $array[$from];
229 1
            }
230 1
        }
231
232 1
        return $reindexed;
233
    }
234
235
    /**
236
     * Merges two or more arrays into one recursively.
237
     *
238
     * @param array $arrays.
0 ignored issues
show
Documentation introduced by
There is no parameter named $arrays.. Did you maybe mean $arrays?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
239
     *
240
     * @return array
241
     */
242 4
    public function merge(array $arrays)
0 ignored issues
show
Unused Code introduced by
The parameter $arrays is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
243
    {
244 4
        $args = func_get_args();
245 4
        $array = array_shift($args);
246
247 4
        while (! empty($args)) {
248 4
            $next = array_shift($args);
249
250 4
            foreach ($next as $key => $value) {
251 4
                if (is_int($key)) {
252 3
                    if (isset($array[$key])) {
253 2
                        $array[] = $value;
254 2
                    } else {
255 1
                        $array[$key] = $value;
256
                    }
257 4
                } elseif (is_array($value) && isset($array[$key]) && is_array($array[$key])) {
258 1
                    $array[$key] = $this->merge($array[$key], $value);
259 1
                } else {
260 2
                    $array[$key] = $value;
261
                }
262 4
            }
263 4
        }
264
265 4
        return $array;
266
    }
267
268
    /**
269
     *  Makes every value that is numerically indexed a key, given $default
270
     *  as value.
271
     *
272
     *  @param array $array
273
     *  @param mixed $default
274
     *
275
     *  @return array
276
     */
277 1
    public function normalize(array $array, $default)
278
    {
279 1
        $normalized = [];
280
281 1
        foreach ($array as $key => $value) {
282 1
            if (is_numeric($key)) {
283 1
                $key = $value;
284 1
                $value = $default;
285 1
            }
286
287 1
            $normalized[$key] = $value;
288 1
        }
289
290 1
        return $normalized;
291
    }
292
293
    /**
294
     * Extend one array with another.
295
     *
296
     * @param array $arrays
297
     *
298
     * @return array
299
     */
300 3
    public function extend(array $arrays)
301
    {
302 3
        $merged = [];
303
304 3
        foreach (func_get_args() as $array) {
305 3
            foreach ($array as $key => $value) {
306 3
                if (is_array($value) && $this->access->has($merged, $key) && is_array($merged[$key])) {
307 2
                    $merged[$key] = $this->extend($merged[$key], $value);
308 2
                } else {
309 3
                    $merged[$key] = $value;
310
                }
311 3
            }
312 3
        }
313
314 3
        return $merged;
315
    }
316
317
    /**
318
     * Transforms a 1-dimensional array into a multi-dimensional one,
319
     * exploding keys according to a separator.
320
     *
321
     * @param array $array
322
     *
323
     * @return array
324
     */
325 1
    public function asHierarchy(array $array)
326
    {
327 1
        $hierarchy = [];
328
329 1
        foreach ($array as $key => $value) {
330 1
            $segments = explode('.', $key);
331 1
            $valueSegment = array_pop($segments);
332 1
            $branch = &$hierarchy;
333
334 1
            foreach ($segments as $segment) {
335 1
                if (! isset($branch[$segment])) {
336 1
                    $branch[$segment] = [];
337 1
                }
338
339 1
                $branch = &$branch[$segment];
340 1
            }
341
342 1
            $branch[$valueSegment] = $value;
343 1
        }
344
345 1
        return $hierarchy;
346
    }
347
348
    /**
349
     * Separates elements from an array into groups.
350
     * The function maps an element to the key that will be used for grouping.
351
     * If no function is passed, the element itself will be used as key.
352
     *
353
     * @param array         $array
354
     * @param callable|null $callback
355
     *
356
     * @return array
357
     */
358 1
    public function groupBy(array $array, callable $callback = null)
359
    {
360
        $callback = $callback ?: function ($value) {
361 1
            return $value;
362 1
        };
363
364 1
        return array_reduce(
365 1
            $array,
366 1
            function ($buckets, $value) use ($callback) {
367 1
                $key = call_user_func($callback, $value);
368
369 1
                if (! array_key_exists($key, $buckets)) {
370 1
                    $buckets[$key] = [];
371 1
                }
372
373 1
                $buckets[$key][] = $value;
374
375 1
                return $buckets;
376 1
            },
377 1
            []
378 1
        );
379
    }
380
381
    /**
382
     * Flatten a multi-dimensional associative array with dots.
383
     *
384
     * @param array  $array
385
     * @param string $prepend
386
     *
387
     * @return array
388
     */
389 4
    public function dot($array, $prepend = '')
390
    {
391 4
        $cache = serialize(['array' => $array, 'prepend' => $prepend]);
392
393 4
        if (array_key_exists($cache, $this->dotted)) {
394 1
            return $this->dotted[$cache];
395
        }
396
397 4
        $results = [];
398
399 4 View Code Duplication
        foreach ($array as $key => $value) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
400 4
            if (is_array($value)) {
401 2
                $results = array_merge($results, $this->dot($value, $prepend . $key . '.'));
402 2
            } else {
403 4
                $results[$prepend . $key] = $value;
404
            }
405 4
        }
406
407 4
        return $this->dotted[$cache] = $results;
408
    }
409
410
    /**
411
     * Expand a dotted array. Acts the opposite way of Arr::dot().
412
     *
413
     * @param array $array
414
     * @param bool  $depth
415
     *
416
     * @return array
417
     */
418 10
    public function unDot($array, $depth = INF)
419
    {
420 10
        $results = [];
421
422 10
        foreach ($array as $key => $value) {
423 9
            if (count($dottedKeys = explode('.', $key, 2)) > 1) {
424 9
                $results[$dottedKeys[0]][$dottedKeys[1]] = $value;
425 9
            } else {
426 6
                $results[$key] = $value;
427
            }
428 10
        }
429
430 10
        foreach ($results as $key => $value) {
431 9
            if (is_array($value) && ! empty($value) && $depth > 1) {
432 7
                $results[$key] = $this->unDot($value, $depth - 1);
0 ignored issues
show
Documentation introduced by
$depth - 1 is of type integer|double, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
433 7
            }
434 10
        }
435
436 10
        return $results;
437
    }
438
439
    /**
440
     * Flatten a nested array to a separated key.
441
     *
442
     * @param array       $array
443
     * @param string|null $separator
444
     * @param string      $prepend
445
     *
446
     * @return array
447
     */
448 1
    public function flatten(array $array, $separator = null, $prepend = '')
449
    {
450 1
        $flattened = [];
451
452 1 View Code Duplication
        foreach ($array as $key => $value) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
453 1
            if (is_array($value)) {
454 1
                $flattened = array_merge($flattened, $this->flatten($value, $separator, $prepend . $key . $separator));
455 1
            } else {
456 1
                $flattened[$prepend . $key] = $value;
457
            }
458 1
        }
459
460 1
        return $flattened;
461
    }
462
463
    /**
464
     * Expand a flattened array with dots to a multi-dimensional associative array.
465
     *
466
     * @param array  $array
467
     * @param string $prepend
468
     *
469
     * @return array
470
     */
471 4
    public function expand(array $array, $prepend = '')
472
    {
473 4
        $results = [];
474
475 4
        if ($prepend) {
476 2
            $prepend .= '.';
477 2
        }
478
479 4
        foreach ($array as $key => $value) {
480 4
            if ($prepend) {
481 2
                $pos = strpos($key, $prepend);
482
483 2
                if ($pos === 0) {
484 1
                    $key = substr($key, strlen($prepend));
485 1
                }
486 2
            }
487
488 4
            $results = $this->access->set($results, $key, $value);
489 4
        }
490
491 4
        return $results;
492
    }
493
494
    /**
495
     * Reset all numerical indexes of an array (start from zero).
496
     * Non-numerical indexes will stay untouched. Returns a new array.
497
     *
498
     * @param array      $array
499
     * @param bool|false $deep
500
     *
501
     * @return array
502
     */
503 3
    public function reset(array $array, $deep = false)
504
    {
505 3
        $target = [];
506
507 3
        foreach ($array as $key => $value) {
508 3
            if ($deep && is_array($value)) {
509 1
                $value = $this->reset($value);
510 1
            }
511
512 3
            if (is_numeric($key)) {
513 3
                $target[] = $value;
514 3
            } else {
515 3
                $target[$key] = $value;
516
            }
517 3
        }
518
519 3
        return $target;
520
    }
521
522
    /**
523
     * Extend one array with another. Non associative arrays will not be merged
524
     * but rather replaced.
525
     *
526
     * @param array $arrays
527
     *
528
     * @return array
529
     */
530 4
    public function extendDistinct(array $arrays)
0 ignored issues
show
Unused Code introduced by
The parameter $arrays is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
531
    {
532 4
        $merged = [];
533
534 4
        foreach (func_get_args() as $array) {
535 4
            foreach ($array as $key => $value) {
536 4
                if (is_array($value) && $this->access->has($merged, $key) && is_array($merged[$key])) {
537 3
                    if ($this->enumerator->isAssoc($value) && $this->enumerator->isAssoc($merged[$key])) {
538 2
                        $merged[$key] = $this->extendDistinct($merged[$key], $value);
539
540 2
                        continue;
541
                    }
542 2
                }
543
544 4
                $merged[$key] = $value;
545 4
            }
546 4
        }
547
548 4
        return $merged;
549
    }
550
551
    /**
552
     * Sort the array using the given callback.
553
     *
554
     * @param array    $array
555
     * @param callable $callback
556
     * @param int      $options
557
     * @param bool     $descending
558
     *
559
     * @return array
560
     */
561 1
    public function sort(array $array, callable $callback, $options = SORT_REGULAR, $descending = false)
562
    {
563 1
        $results = [];
564
565
        // First we will loop through the items and get the comparator from a callback
566
        // function which we were given. Then, we will sort the returned values and
567
        // and grab the corresponding values for the sorted keys from this array.
568 1
        foreach ($array as $key => $value) {
569 1
            $results[$key] = $callback($value, $key);
570 1
        }
571
572 1
        $descending ? arsort($results, $options)
573 1
                    : asort($results, $options);
574
575
        // Once we have sorted all of the keys in the array, we will loop through them
576
        // and grab the corresponding model so we can set the underlying items list
577
        // to the sorted version. Then we'll just return the collection instance.
578 1
        foreach (array_keys($results) as $key) {
579 1
            $results[$key] = $array[$key];
580 1
        }
581
582 1
        return $results;
583
    }
584
585
    /**
586
     * Recursively sort an array by keys and values.
587
     *
588
     * @param array $array
589
     *
590
     * @return array
591
     */
592 1
    public function sortRecursive(array $array)
593
    {
594 1
        foreach ($array as &$value) {
595 1
            if (is_array($value)) {
596 1
                $value = $this->sortRecursive($value);
597 1
            }
598 1
        }
599
600
        // sort associative array
601 1
        if ($this->enumerator->isAssoc($array)) {
602 1
            ksort($array);
603
            // sort regular array
604 1
        } else {
605 1
            sort($array);
606
        }
607
608 1
        return $array;
609
    }
610
611
    /**
612
     * Will turn each element in $arr into an array then appending
613
     * the associated indexs from the other arrays into this array as well.
614
     *
615
     * @param array $array
616
     * @param array $arrays
617
     *
618
     * @return array
619
     */
620 2
    public function zip(array $array, array $arrays)
0 ignored issues
show
Unused Code introduced by
The parameter $arrays is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
621
    {
622 2
        $args = func_get_args();
623 2
        array_shift($args);
624
625 2
        foreach ($array as $key => $value) {
626 2
            $array[$key] = [$value];
627
628 2
            foreach ($args as $k => $v) {
629 2
                $array[$key][] = current($args[$k]);
630
631 2
                if (next($args[$k]) === false && $args[$k] !== [null]) {
632 1
                    $args[$k] = [null];
633 1
                }
634 2
            }
635 2
        }
636
637 2
        return $array;
638
    }
639
640
    /**
641
     * Recurse through an array, add the leaf items to the $newArray var
642
     *
643
     * @param array $subject
644
     * @param array &$newArray
645
     * @param array $stack
646
     *
647
     * @return string[]|null
648
     */
649 3
    private function recurseCollapse(array $subject, array &$newArray, $stack = [])
650
    {
651 3
        foreach ($subject as $key => $value) {
652 3
            $fstack = array_merge($stack, [$key]);
653
654 3
            if (is_array($value)) {
655 3
                $this->recurseCollapse($value, $newArray, $fstack);
656 3
            } else {
657 3
                $top = array_shift($fstack);
658 3
                $arrayPart = count($fstack) ? '.' . implode('.', $fstack) : '';
659 3
                $newArray[$top . $arrayPart] = $value;
660
            }
661 3
        }
662 3
    }
663
}
664