Completed
Pull Request — master (#11)
by Daniel
06:20
created

Transform::collapse()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 19
ccs 12
cts 12
cp 1
rs 9.2
cc 4
eloc 10
nc 4
nop 1
crap 4
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 40
    public function __construct()
28
    {
29 40
        $this->access     = new Access();
30 40
        $this->enumerator = new Enumerator();
31 40
    }
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
            // fix for hhvm #1871 bug
118 1
            if (defined('HHVM_VERSION')) {
119
                $combinator->next();
120
            }
121
122 1
            $index      = $combinator->key();
123
124 1
            if ($overwrite || !isset($combined[$index])) {
125 1
                $combined[$index] = $combinator->current();
126 1
            }
127 1
        }
128
129 1
        return $combined;
130
    }
131
132
    /**
133
     * Collapse a nested array down to an array of flat key=>value pairs
134
     */
135 3
    public function collapse(array $array)
136
    {
137 3
        $newArray = [];
138
139 3
        foreach ($array as $key => $value) {
140 3
            if (is_array($value)) {
141
                // strip any manually added '.'
142 3
                if (preg_match('/\./', $key)) {
143 2
                    $key = substr($key, 0, -2);
144 2
                }
145
146 3
                $this->recurseCollapse($value, $newArray, (array) $key);
147 3
            } else {
148 1
                $newArray[$key] = $value;
149
            }
150 3
        }
151
152 3
        return $newArray;
153
    }
154
155
    /**
156
     * Divide an array into two arrays. One with keys and the other with values.
157
     *
158
     * @param array $array
159
     *
160
     * @return array[]
161
     */
162 1
    public function divide($array)
163
    {
164 1
        return [array_keys($array), array_values($array)];
165
    }
166
167
    /**
168
     * Stripe all empty items.
169
     *
170
     * @param array $array
171
     *
172
     * @return array
173
     */
174 1
    public function stripEmpty(array $array)
175
    {
176
        return array_filter($array, function ($item) {
177 1
            if (is_null($item)) {
178 1
                return false;
179
            }
180
181 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...
182 1
                return false;
183
            }
184
185 1
            return true;
186 1
        });
187
    }
188
189
    /**
190
     * Remove all instances of $ignore found in $elements (=== is used).
191
     *
192
     * @param array $array
193
     * @param array $ignore
194
     *
195
     * @return array
196
     */
197 1
    public function without(array $array, array $ignore)
198
    {
199 1
        foreach ($array as $key => $node) {
200 1
            if (in_array($node, $ignore, true)) {
201 1
                unset($array[$key]);
202 1
            }
203 1
        }
204
205 1
        return array_values($array);
206
    }
207
208
    /**
209
     * Reindexes a list of values.
210
     *
211
     * @param array $array
212
     * @param array $map      An map of correspondances of the form
213
     *                        ['currentIndex' => 'newIndex'].
214
     * @param bool  $unmapped Whether or not to keep keys that are not
215
     *                        remapped.
216
     *
217
     * @return array
218
     */
219 1
    public function reindex(array $array, array $map, $unmapped = true)
220
    {
221
        $reindexed = $unmapped
222 1
            ? $array
223 1
            : [];
224
225 1
        foreach ($map as $from => $to) {
226 1
            if (isset($array[$from])) {
227 1
                $reindexed[$to] = $array[$from];
228 1
            }
229 1
        }
230
231 1
        return $reindexed;
232
    }
233
234
    /**
235
     * Merges two or more arrays into one recursively.
236
     *
237
     * @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...
238
     *
239
     * @return array
240
     */
241 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...
242
    {
243 4
        $args  = func_get_args();
244 4
        $array = array_shift($args);
245
246 4
        while (!empty($args)) {
247 4
            $next = array_shift($args);
248
249 4
            foreach ($next as $key => $value) {
250 4
                if (is_int($key)) {
251 3
                    if (isset($array[$key])) {
252 2
                        $array[] = $value;
253 2
                    } else {
254 1
                        $array[$key] = $value;
255
                    }
256 4
                } elseif (is_array($value) && isset($array[$key]) && is_array($array[$key])) {
257 1
                    $array[$key] = $this->merge($array[$key], $value);
258 1
                } else {
259 2
                    $array[$key] = $value;
260
                }
261 4
            }
262 4
        }
263
264 4
        return $array;
265
    }
266
267
    /**
268
     *  Makes every value that is numerically indexed a key, given $default
269
     *  as value.
270
     *
271
     *  @param array $array
272
     *  @param mixed $default
273
     *
274
     *  @return array
275
     */
276 1
    public function normalize(array $array, $default)
277
    {
278 1
        $normalized = [];
279
280 1
        foreach ($array as $key => $value) {
281 1
            if (is_numeric($key)) {
282 1
                $key   = $value;
283 1
                $value = $default;
284 1
            }
285
286 1
            $normalized[$key] = $value;
287 1
        }
288
289 1
        return $normalized;
290
    }
291
292
    /**
293
     * Extend one array with another.
294
     *
295
     * @param array $arrays
296
     *
297
     * @return array
298
     */
299 3
    public function extend(array $arrays)
300
    {
301 3
        $merged = [];
302
303 3
        foreach (func_get_args() as $array) {
304 3
            foreach ($array as $key => $value) {
305 3
                if (is_array($value) && $this->access->has($merged, $key) && is_array($merged[$key])) {
306 2
                    $merged[$key] = $this->extend($merged[$key], $value);
307 2
                } else {
308 3
                    $merged[$key] = $value;
309
                }
310 3
            }
311 3
        }
312
313 3
        return $merged;
314
    }
315
316
    /**
317
     * Transforms a 1-dimensional array into a multi-dimensional one,
318
     * exploding keys according to a separator.
319
     *
320
     * @param array $array
321
     *
322
     * @return array
323
     */
324 1
    public function asHierarchy(array $array)
325
    {
326 1
        $hierarchy = [];
327
328 1
        foreach ($array as $key => $value) {
329 1
            $segments     = explode('.', $key);
330 1
            $valueSegment = array_pop($segments);
331 1
            $branch       = &$hierarchy;
332
333 1
            foreach ($segments as $segment) {
334 1
                if (!isset($branch[$segment])) {
335 1
                    $branch[$segment] = [];
336 1
                }
337
338 1
                $branch = &$branch[$segment];
339 1
            }
340
341 1
            $branch[$valueSegment] = $value;
342 1
        }
343
344 1
        return $hierarchy;
345
    }
346
347
    /**
348
     * Separates elements from an array into groups.
349
     * The function maps an element to the key that will be used for grouping.
350
     * If no function is passed, the element itself will be used as key.
351
     *
352
     * @param array         $array
353
     * @param callable|null $callback
354
     *
355
     * @return array
356
     */
357 1
    public function groupBy(array $array, callable $callback = null)
358
    {
359
        $callback = $callback ?: function ($value) {
360 1
            return $value;
361 1
        };
362
363 1
        return array_reduce(
364 1
            $array,
365 1
            function ($buckets, $value) use ($callback) {
366 1
                $key = call_user_func($callback, $value);
367
368 1
                if (!array_key_exists($key, $buckets)) {
369 1
                    $buckets[$key] = [];
370 1
                }
371
372 1
                $buckets[$key][] = $value;
373
374 1
                return $buckets;
375 1
            },
376 1
            []
377 1
        );
378
    }
379
380
    /**
381
     * Flatten a multi-dimensional associative array with dots.
382
     *
383
     * @param array  $array
384
     * @param string $prepend
385
     *
386
     * @return array
387
     */
388 4
    public function dot($array, $prepend = '')
389
    {
390 4
        $cache = serialize(['array' => $array, 'prepend' => $prepend]);
391
392 4
        if (array_key_exists($cache, $this->dotted)) {
393 1
            return $this->dotted[$cache];
394
        }
395
396 4
        $results = [];
397
398 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...
399 4
            if (is_array($value)) {
400 2
                $results = array_merge($results, $this->dot($value, $prepend . $key . '.'));
401 2
            } else {
402 4
                $results[$prepend . $key] = $value;
403
            }
404 4
        }
405
406 4
        return $this->dotted[$cache] = $results;
407
    }
408
409
    /**
410
     * Flatten a nested array to a separated key.
411
     *
412
     * @param array       $array
413
     * @param string|null $separator
414
     * @param string      $prepend
415
     *
416
     * @return array
417
     */
418 1
    public function flatten(array $array, $separator = null, $prepend = '')
419
    {
420 1
        $flattened = [];
421
422 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...
423 1
            if (is_array($value)) {
424 1
                $flattened = array_merge($flattened, $this->flatten($value, $separator, $prepend . $key . $separator));
425 1
            } else {
426 1
                $flattened[$prepend . $key] = $value;
427
            }
428 1
        }
429
430 1
        return $flattened;
431
    }
432
433
    /**
434
     * Expand a flattened array with dots to a multi-dimensional associative array.
435
     *
436
     * @param array  $array
437
     * @param string $prepend
438
     *
439
     * @return array
440
     */
441 4
    public function expand(array $array, $prepend = '')
442
    {
443 4
        $results = [];
444
445 4
        if ($prepend) {
446 2
            $prepend .= '.';
447 2
        }
448
449 4
        foreach ($array as $key => $value) {
450 4
            if ($prepend) {
451 2
                $pos = strpos($key, $prepend);
452
453 2
                if ($pos === 0) {
454 1
                    $key = substr($key, strlen($prepend));
455 1
                }
456 2
            }
457
458 4
            $results = $this->access->set($results, $key, $value);
459 4
        }
460
461 4
        return $results;
462
    }
463
464
    /**
465
     * Reset all numerical indexes of an array (start from zero).
466
     * Non-numerical indexes will stay untouched. Returns a new array.
467
     *
468
     * @param array      $array
469
     * @param bool|false $deep
470
     *
471
     * @return array
472
     */
473 3
    public function reset(array $array, $deep = false)
474
    {
475 3
        $target = [];
476
477 3
        foreach ($array as $key => $value) {
478 3
            if ($deep && is_array($value)) {
479 1
                $value = $this->reset($value);
480 1
            }
481
482 3
            if (is_numeric($key)) {
483 3
                $target[] = $value;
484 3
            } else {
485 3
                $target[$key] = $value;
486
            }
487 3
        }
488
489 3
        return $target;
490
    }
491
492
    /**
493
     * Extend one array with another. Non associative arrays will not be merged
494
     * but rather replaced.
495
     *
496
     * @param array $arrays
497
     *
498
     * @return array
499
     */
500 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...
501
    {
502 4
        $merged     = [];
503
504 4
        foreach (func_get_args() as $array) {
505 4
            foreach ($array as $key => $value) {
506 4
                if (is_array($value) && $this->access->has($merged, $key) && is_array($merged[$key])) {
507 3
                    if ($this->enumerator->isAssoc($value) && $this->enumerator->isAssoc($merged[$key])) {
508 2
                        $merged[$key] = $this->extendDistinct($merged[$key], $value);
509
510 2
                        continue;
511
                    }
512 2
                }
513
514 4
                $merged[$key] = $value;
515 4
            }
516 4
        }
517
518 4
        return $merged;
519
    }
520
521
    /**
522
     * Sort the array using the given callback.
523
     *
524
     * @param array    $array
525
     * @param callable $callback
526
     * @param int      $options
527
     * @param bool     $descending
528
     *
529
     * @return array
530
     */
531 1
    public function sort(array $array, callable $callback, $options = SORT_REGULAR, $descending = false)
532
    {
533 1
        $results = [];
534
535
        // First we will loop through the items and get the comparator from a callback
536
        // function which we were given. Then, we will sort the returned values and
537
        // and grab the corresponding values for the sorted keys from this array.
538 1
        foreach ($array as $key => $value) {
539 1
            $results[$key] = $callback($value, $key);
540 1
        }
541
542 1
        $descending ? arsort($results, $options)
543 1
                    : asort($results, $options);
544
545
        // Once we have sorted all of the keys in the array, we will loop through them
546
        // and grab the corresponding model so we can set the underlying items list
547
        // to the sorted version. Then we'll just return the collection instance.
548 1
        foreach (array_keys($results) as $key) {
549 1
            $results[$key] = $array[$key];
550 1
        }
551
552 1
        return $results;
553
    }
554
555
    /**
556
     * Recursively sort an array by keys and values.
557
     *
558
     * @param array $array
559
     *
560
     * @return array
561
     */
562 1
    public function sortRecursive(array $array)
563
    {
564 1
        foreach ($array as &$value) {
565 1
            if (is_array($value)) {
566 1
                $value = $this->sortRecursive($value);
567 1
            }
568 1
        }
569
570
        // sort associative array
571 1
        if ($this->enumerator->isAssoc($array)) {
572 1
            ksort($array);
573
            // sort regular array
574 1
        } else {
575 1
            sort($array);
576
        }
577
578 1
        return $array;
579
    }
580
581
    /**
582
     * Will turn each element in $arr into an array then appending
583
     * the associated indexs from the other arrays into this array as well.
584
     *
585
     * @param array $array
586
     * @param array $arrays
587
     *
588
     * @return array
589
     */
590 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...
591
    {
592 2
        $args = func_get_args();
593 2
        array_shift($args);
594
595 2
        foreach ($array as $key => $value) {
596 2
            $array[$key] = [$value];
597
598 2
            foreach ($args as $k => $v) {
599 2
                $array[$key][] = current($args[$k]);
600
601 2
                if (next($args[$k]) === false && $args[$k] !== [null]) {
602 1
                    $args[$k] = [null];
603 1
                }
604 2
            }
605 2
        }
606
607 2
        return $array;
608
    }
609
610
    /**
611
     * Recurse through an array, add the leaf items to the $newArray var
612
     *
613
     * @param array $subject
614
     * @param array &$newArray
615
     * @param array $stack
616
     *
617
     * @return string[]|null
618
     */
619 3
    private function recurseCollapse(array $subject, array &$newArray, $stack = [])
620
    {
621 3
        foreach ($subject as $key => $value) {
622 3
            $fstack = array_merge($stack, [$key]);
623
624 3
            if (is_array($value)) {
625 3
                $this->recurseCollapse($value, $newArray, $fstack);
626 3
            } else {
627 3
                $top       = array_shift($fstack);
628 3
                $arrayPart = count($fstack) ? '.' . implode('.', $fstack) : '';
629 3
                $newArray[$top . $arrayPart] = $value;
630
            }
631 3
        }
632 3
    }
633
}
634