Completed
Push — master ( b08f0a...11f392 )
by Daniel
03:23
created

Transform::combine()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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