Completed
Push — master ( 72a4f1...dadb56 )
by Michał
02:23
created

Arr.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php namespace nyx\utils;
2
3
// External dependencies
4
use nyx\diagnostics;
5
6
/**
7
 * Arr
8
 *
9
 * The Arr class provides a few helper methods to make dealing with arrays easier.
10
 *
11
 * All methods which work with string delimited keys accept a string delimiter. If none is given (ie. null is passed),
12
 * the default delimiter (dot) set statically in this class will be used.
13
 *
14
 * Some code in this class can be simplified and some duplication could be avoided but it is laid out so that the
15
 * most common use cases are checked for first with performance being the priority.
16
 *
17
 * Some methods have aliases. To avoid the overhead please use the base methods, not the aliases. The methods which
18
 * have aliases are documented as such and each alias directly points to the base method.
19
 *
20
 * Note: This class is based on Laravel, FuelPHP, Lo-dash and a few others, but certain methods which appear in
21
 * those are not included since they would add overhead we consider 'not worth it' and don't want to encourage
22
 * the use thereof:
23
 *
24
 *   - Arr::each()                      -> use array_map() instead.
25
 *   - Arr::filter(), Arr::reject()     -> use array_filter() instead.
26
 *   - Arr::range()                     -> use range() instead.
27
 *   - Arr::repeat()                    -> use array_fill() instead.
28
 *   - Arr::search()                    -> use array_search() instead.
29
 *   - Arr::shuffle()                   -> use shuffle() instead.
30
 *   - Arr::size()                      -> use count() instead.
31
 *
32
 * @package     Nyx\Utils
33
 * @version     0.1.0
34
 * @author      Michal Chojnacki <[email protected]>
35
 * @copyright   2012-2016 Nyx Dev Team
36
 * @link        http://docs.muyo.io/nyx/utils/index.html
37
 * @todo        Arr::sort() and Arr:sortBy() (add sortBy() to core\traits\Collection as well).
38
 * @todo        Add ArrayObject support? How? Just strip the array type hints so as to not add overhead with checks?
39
 */
40
class Arr
41
{
42
    /**
43
     * The traits of the Arr class.
44
     */
45
    use traits\StaticallyExtendable;
46
47
    /**
48
     * @var string  The default delimiter to use to separate array dimensions.
49
     */
50
    public static $delimiter = '.';
51
52
    /**
53
     * Adds an element to the given array but only if it does not yet exist.
54
     *
55
     * Note: Null as value of an item is considered a non-existing item for the purposes
56
     *       of this method.
57
     *
58
     * @param   array   $array      The array to which the element should be added.
59
     * @param   string  $key        The key at which the value should be added.
60
     * @param   mixed   $value      The value of the element.
61
     * @param   string  $delimiter  The delimiter to use when exploding the key into parts.
62
     */
63
    public static function add(array& $array, string $key, $value, string $delimiter = null)
64
    {
65
        if (null === static::get($array, $key, null, $delimiter)) {
66
            static::set($array, $key, $value, $delimiter);
67
        }
68
    }
69
70
    /**
71
     * Checks whether all elements in the given array pass the given truth test.
72
     *
73
     * Aliases:
74
     *  - @see Arr::every()
75
     *
76
     * @param   array       $array      The array to traverse.
77
     * @param   callable    $callback   The truth test the elements should pass.
78
     * @return  bool                    True when the elements passed the truth test, false otherwise.
79
     */
80
    public static function all(array $array, callable $callback) : bool
81
    {
82
        // Map the array and then search for a 'false' boolean. If none is found,
83
        // we assume all elements passed the test.
84
        return !in_array(false, array_map($callback, $array), true);
85
    }
86
87
    /**
88
     * Checks whether any of the elements in the given array passes the given truth test.
89
     *
90
     * Aliases:
91
     *  - @see Arr::some()
92
     *
93
     * @param   array       $array      The array to traverse.
94
     * @param   callable    $callback   The truth test the elements should pass.
95
     * @return  bool                    True when at least on the the elements passed the truth test, false
96
     *                                  otherwise.
97
     */
98
    public static function any(array $array, callable $callback) : bool
99
    {
100
        // Map the array and then search for a 'true' boolean. If at least one is found,
101
        // we assume at least one element passed the test.
102
        return in_array(true, array_map($callback, $array), true);
103
    }
104
105
    /**
106
     * Returns the average value of the given array.
107
     *
108
     * @param   array   $array      The array to traverse.
109
     * @param   int     $decimals   The number of decimals to return.
110
     * @return  float               The average value.
111
     */
112
    public static function average(array $array, int $decimals = 0) : float
113
    {
114
        return round((array_sum($array) / count($array)), $decimals);
115
    }
116
117
    /**
118
     * Removes all elements containing falsy values from the given array. The keys are preserved.
119
     *
120
     * See {@link http://php.net/manual/en/language.types.boolean.php} for information on which values evaluate
121
     * to false.
122
     *
123
     * @param   array   $array  The array to traverse.
124
     * @return  array           The resulting array.
125
     */
126
    public static function clean(array $array) : array
127
    {
128
        return array_filter($array, function($value) {
129
            return (bool) $value;
130
        });
131
    }
132
133
    /**
134
     * Collapses an array of arrays into a single array. Not recursive, ie. only the first dimension of arrays
135
     * will be collapsed down.
136
     *
137
     * Standard array_merge() rules apply - @link http://php.net/manual/en/function.array-merge.php -
138
     * non-array values with numeric keys will be appended to the resulting array in the order they are given, while
139
     * non-array values with non-numeric keys will have their keys preserved but the values may be overwritten by
140
     * the nested arrays being collapsed down if those contain values with the same non-numeric keys. Latter arrays
141
     * overwrite previous arrays' keys on collisions.
142
     *
143
     * @param   array   $array  The array to collapse.
144
     * @return  array           The resulting array.
145
     */
146
    public static function collapse(array $array) : array
147
    {
148
        $results = [];
149
150
        foreach ($array as $key => $item) {
151
152
            // Nested arrays will be merged in (non-recursively).
153
            if (is_array($item)) {
154
                $results = array_merge($results, $item); continue;
155
            }
156
157
            // Values with numeric keys will be appended in any case.
158
            if (is_int($key)) {
159
                $results[] = $item; continue;
160
            }
161
162
            // Non-numeric keys. If we've got the given key in $results already, it means it was merged
163
            // in from one of the nested arrays and those are meant to overwrite the initial values on collisions -
164
            // thus we're not going to do anything in that case.
165
            if (!array_key_exists($key, $results)) {
166
                $results[$key] = $item;
167
            }
168
        }
169
170
        return $results;
171
    }
172
173
    /**
174
     * Checks whether the given value is contained within the given array. Equivalent of a recursive in_array.
175
     * When you are sure you are dealing with a 1-dimensional array, use in_array instead to avoid the overhead.
176
     *
177
     * @param   array   $haystack   The array to search in.
178
     * @param   mixed   $needle     The value to search for.
179
     * @param   bool    $strict     Whether strict equality matches should be performed on the values.
180
     * @return  bool                True when the value was found in the array, false otherwise.
181
     */
182
    public static function contains(array $haystack, $needle, bool $strict = true)
183
    {
184
        foreach ($haystack as $value) {
185
            if ((!$strict && $needle == $value) || $needle === $value) {
186
                return true;
187
            }
188
189
            if (is_array($value) && static::contains($needle, $value, $strict)) {
190
                return true;
191
            }
192
        }
193
194
        return false;
195
    }
196
197
    /**
198
     * Flattens a multi-dimensional array using the given delimiter.
199
     *
200
     * @param   array   $array      The initial array.
201
     * @param   string  $prepend    A string that should be prepended to the keys.
202
     * @param   string  $delimiter  The delimiter to use when exploding the key into parts.
203
     * @return  array               The resulting array.
204
     */
205
    public static function delimit(array $array, string $prepend = '', string $delimiter = null)
206
    {
207
        // Results holder.
208
        $results = [];
209
210
        // Which string delimiter should we use?
211
        if (null === $delimiter) {
212
            $delimiter = static::$delimiter;
213
        }
214
215
        foreach ($array as $key => $value) {
216
            if (is_array($value) && !empty($value)) {
217
                $results = array_merge($results, static::delimit($value, $prepend.$key.$delimiter));
218
            } else {
219
                $results[$prepend.$key] = $value;
220
            }
221
        }
222
223
        return $results;
224
    }
225
226
    /**
227
     * Alias for @see Arr::find()
228
     */
229
    public static function detect(array $array, callable $callback, $default = null)
230
    {
231
        return static::find($array, $callback, $default);
232
    }
233
234
    /**
235
     * Divides an array into two arrays - the first containing the keys, the second containing the values.
236
     *
237
     * @param   array   $array  The initial array.
238
     * @return  array           The resulting array.
239
     */
240
    public static function divide(array $array)
241
    {
242
        return [array_keys($array), array_values($array)];
243
    }
244
245
    /**
246
     * Alias for @see Arr::all()
247
     */
248
    public static function every(array $array, callable $callback)
249
    {
250
        return static::all($array, $callback);
251
    }
252
253
    /**
254
     * Returns a subset of the given array, containing all keys except for the ones specified.
255
     *
256
     * @param   array   $array  The initial array.
257
     * @param   array   $keys   An array of keys to exclude from the initial array.
258
     * @return  array
259
     */
260
    public static function except(array $array, array $keys)
261
    {
262
        return array_diff_key($array, array_flip($keys));
263
    }
264
265
    /**
266
     * Fetches a flattened array of an element nested in the initial array.
267
     *
268
     * @param   array   $array      The initial array.
269
     * @param   string  $key        The string delimited key.
270
     * @param   string  $delimiter  The delimiter to use when exploding the key into parts.
271
     * @return  array               The resulting array.
272
     */
273
    public static function fetch(array $array, string $key, string $delimiter = null) : array
274
    {
275
        // Results holder.
276
        $results = [];
277
278
        // Which string delimiter should we use?
279
        if (null === $delimiter) {
280
            $delimiter = static::$delimiter;
281
        }
282
283
        foreach (explode($delimiter, $key) as $segment) {
284
            $results = [];
285
286
            foreach ($array as $value) {
287
                $value = (array) $value;
288
289
                $results[] = $value[$segment];
290
            }
291
292
            $array = array_values($results);
293
        }
294
295
        return array_values($results);
296
    }
297
298
    /**
299
     * Returns the first value which passes the given truth test.
300
     *
301
     * Aliases:
302
     *  - @see Arr::detect()
303
     *
304
     * @param   array       $array      The array to traverse.
305
     * @param   callable    $callback   The truth test the value should pass.
306
     * @param   mixed       $default    The default value to be returned if none of the elements passes the test.
307
     * @return  mixed
308
     */
309
    public static function find(array $array, callable $callback, $default = null)
310
    {
311
        foreach ($array as $key => $value) {
312
            if (call_user_func($callback, $key, $value)) {
313
                return $value;
314
            }
315
        }
316
317
        return $default;
318
    }
319
320
    /**
321
     * Returns the first element of the array, the first $elements of the array when $elements is a positive integer,
322
     * or the first element which passes the given truth test when the $elements is a callable.
323
     *
324
     * Aliases:  @see \nyx\utils\Arr::head(), \nyx\utils\Arr::take()
325
     * Opposite: @see \nyx\utils\Arr::last()
326
     *
327
     * @param   array           $array      The array to traverse.
328
     * @param   callable|int    $elements   The truth test the value should pass or an integer denoting how many
329
     *                                      of the initial elements of the array should be returned.
330
     *                                      When not given, the method will return the first element of the array.
331
     * @param   mixed           $default    The default value to be returned if none of the elements passes
332
     *                                      the test or the array is empty.
333
     * @throws  \UnderflowException         When more values are requested than there are items in the array.
334
     * @throws  \InvalidArgumentException   When $elements is neither a valid integer nor a callable.
335
     * @return  mixed
336
     */
337
    public static function first(array $array, $elements = null, $default = null)
338
    {
339
        if (empty($array)) {
340
            return $default;
341
        }
342
343
        // Most common use case - simply return the first value of the array.
344
        if (!isset($elements) || $elements === 1) {
345
            return reset($array);
346
        }
347
348
        // With a integer given, return a slice containing the first $callback elements.
349
        if (is_int($elements)) {
350
351
            if ($elements < 1) {
352
                throw new \InvalidArgumentException("At least 1 element must be requested, while [$elements] were requested.");
353
            }
354
355
            if ($elements > ($count = count($array))) {
356
                throw new \UnderflowException("Requested [$elements] items, but the structure only contains [$count] items.");
357
            }
358
359
            return array_slice($array, 0, $elements);
360
        }
361
362
        // With a callable given, return the first value which passes the given truth test.
363
        if (is_callable($elements)) {
364
            return static::find($array, $elements, $default);
365
        }
366
367
        throw new \InvalidArgumentException('Expected $callback to be a positive integer or a callable, got ['.diagnostics\Debug::getTypeName($elements).'] instead.');
368
    }
369
370
    /**
371
     * Flattens a multi-dimensional array.
372
     *
373
     * @param   array   $array  The initial array.
374
     * @return  array           The flattened array.
375
     */
376
    public static function flatten(array $array)
377
    {
378
        $results = [];
379
380
        array_walk_recursive($array, function($x) use (&$results) {
381
            $results[] = $x;
382
        });
383
384
        return $results;
385
    }
386
387
    /**
388
     * Returns a string delimited key from an array, with a default value if the given key does not exist. If null
389
     * is given instead of a key, the whole initial array will be returned.
390
     *
391
     * Note: Nested objects will be accessed as if they were arrays, eg. if "some.nested.object" is an object,
392
     *       then looking for "some.nested.object.property" will be handled just as a normal array would.
393
     *
394
     * @param   array           $array      The array to search in.
395
     * @param   string|array    $key        The string delimited key or a chain (array) of nested keys pointing
396
     *                                      to the desired key.
397
     * @param   mixed           $default    The default value.
398
     * @param   string          $delimiter  The delimiter to use when exploding the key into parts.
399
     * @return  mixed
400
     */
401
    public static function get(array $array, $key = null, $default = null, string $delimiter = null)
402
    {
403
        // Make loops easier for the end-user - return the initial array if the key is null instead of forcing
404
        // a valid value.
405
        if (!isset($key)) {
406
            return $array;
407
        }
408
409
        // Which string delimiter should we use?
410
        if (!isset($delimiter)) {
411
            $delimiter = static::$delimiter;
412
        }
413
414
        // If the key is string delimited, we need to explode it into an array of segments.
415
        $segments = is_array($key) ? $key : explode($delimiter, $key);
416
417
        // One dimension at a time.
418
        while ($segment = array_shift($segments)) {
419
420
            // If the current segment is a wildcard, make sure the it points to an array
421
            // and pluck the remaining segments from it.
422
            if ($segment === '*') {
423
                return is_array($array) ? static::pluck($array, $segments, $delimiter) : $default;
424
            }
425
426
            // Note: isset() is the cheapest condition to check for while being rather probable at the same time,
427
            // thus the seemingly unintuitive condition ordering.
428
            if (isset($array->{$segment})) {
429
                $array = $array->{$segment};
430
            } elseif (is_array($array) && array_key_exists($segment, $array)) {
431
                $array = $array[$segment];
432
            } else {
433
                return $default;
434
            }
435
        }
436
437
        return $array;
438
    }
439
440
    /**
441
     * Checks whether the given string delimited key exists in the array.
442
     *
443
     * Note: Nested objects will not be accessed as if they were arrays, ie. if "some.nested.object" is an object,
444
     *       not an array, then looking for "some.nested.object.key" will always return false.
445
     *
446
     * @param   array   $array      The array to search in.
447
     * @param   string  $key        The string delimited key.
448
     * @param   string  $delimiter  The delimiter to use when exploding the key into parts.
449
     * @return  bool                True when the given key exists, false otherwise.
450
     */
451
    public static function has(array $array, string $key, string $delimiter = null)
452
    {
453
        // Which string delimiter should we use?
454
        if (null === $delimiter) {
455
            $delimiter = static::$delimiter;
456
        }
457
458
        foreach (explode($delimiter, $key) as $segment) {
459
            if (!is_array($array) || !array_key_exists($segment, $array)) {
460
                return false;
461
            }
462
463
            $array = $array[$segment];
464
        }
465
466
        return true;
467
    }
468
469
    /**
470
     * @see \nyx\utils\Arr::first()
471
     */
472
    public static function head(array $array, $callback = null, $default = null)
473
    {
474
        return static::first($array, $callback, $default);
475
    }
476
477
    /**
478
     * Returns all but the last value(s) of the given array.
479
     *
480
     * If a callable is passed, elements at the end of the array are excluded from the result as long as the
481
     * callback returns a truthy value. If a number is passed, the last n values are excluded from the result.
482
     *
483
     * @param   array           $array      The array to traverse.
484
     * @param   callable|int    $callback   The truth test the value should pass or an integer denoting how many
485
     *                                      of the final elements of the array should be excluded. The count is
486
     *                                      1-indexed, ie. if you want to exclude the last 2 elements, pass 2.
487
     * @param   mixed           $default    The default value to be returned if none of the elements passes the test.
488
     *                                      Only useful when $callback is a callable.
489
     * @return  mixed
490
     */
491
    public static function initial(array $array, $callback, $default = null)
492
    {
493
        // When given a callable, keep counting as long as the callable returns a truthy value.
494 View Code Duplication
        if (is_callable($callback)) {
495
            $i = 0;
496
497
            foreach (array_reverse($array) as $key => $value) {
498
                if (!call_user_func($callback, $key, $value)) {
499
                    break;
500
                }
501
502
                $i++;
503
            }
504
505
            // If we didn't get at least a single truthy value, return the default.
506
            if ($i === 0) {
507
                return $default;
508
            }
509
510
            // Otherwise we're just gonna overwrite the $callback and proceed as if it were an integer in the
511
            // first place.
512
            $callback = $i;
513
        }
514
515
        // At this point we need a positive integer, 1 at minimum.
516
        return array_slice($array, 0, -(int) (!$callback ? 1 : abs($callback)));
517
    }
518
519
    /**
520
     * Checks whether the given array is an associative array.
521
     *
522
     * @param   array   $array  The array to check.
523
     * @return  bool            True when the array is associative, false otherwise.
524
     */
525
    public static function isAssociative(array $array) : bool
526
    {
527
        return array_keys($array) !== range(0, count($array) - 1);
528
    }
529
530
    /**
531
     * Checks whether the given array is a multidimensional array.
532
     *
533
     * @param   array   $array  The array to check.
534
     * @return  bool            True when the array has multiple dimensions, false otherwise.
535
     */
536
    public static function isMultidimensional(array $array) : bool
537
    {
538
        return count($array) !== count($array, COUNT_RECURSIVE);
539
    }
540
541
    /**
542
     * Returns the last element of the array, the final $callback elements of the array when $callback is a number,
543
     * or the last element which passes the given truth test when the $callback is a callable.
544
     *
545
     * @param   array               $array      The array to traverse.
546
     * @param   callable|int|bool   $callback   The truth test the value should pass or an integer / numeric string
547
     *                                          denoting how many of the final elements of the array should be returned.
548
     *                                          When *any* other value is given, the method will return the last
549
     *                                          element of the array.
550
     * @param   mixed               $default    The default value to be returned if none of the elements passes
551
     *                                          the test or the array is empty.
552
     * @return  mixed
553
     */
554
    public static function last(array $array, $callback = false, $default = null)
555
    {
556
        // Avoid some overhead at this point already if possible.
557
        if (empty($array)) {
558
            return $default;
559
        }
560
561
        // Most common use case - simply return the last value of the array.
562
        if (!$callback) {
563
            return end($array);
564
        }
565
566
        // With a callable given, return the last value which passes the given truth test.
567
        if (is_callable($callback)) {
568
            foreach (array_reverse($array) as $key => $value) {
569
                if (call_user_func($callback, $key, $value)) {
570
                    return $value;
571
                }
572
            }
573
574
            return $default;
575
        }
576
577
        // Return only the last element when abs(callback) equals 1, otherwise return the final $callback elements.
578
        return (-1 === $callback = -1 * abs((int) $callback)) ? end($array) : array_slice($array, $callback);
579
    }
580
581
    /**
582
     * Returns the biggest value from the given array.
583
     *
584
     * @param   array   $array  The array to traverse.
585
     * @return  mixed           The resulting value.
586
     */
587
    public static function max(array $array)
588
    {
589
        // Avoid some overhead at this point already if possible.
590
        if (empty($array)) {
591
            return null;
592
        }
593
594
        // Sort in a descending order.
595
        arsort($array);
596
597
        // Return the first element of the sorted array.
598
        return reset($array);
599
    }
600
601
    /**
602
     * Merges 2 or more arrays recursively. Differs in two important aspects from array_merge_recursive(), to
603
     * more closely resemble the standard behaviour of the non-recursive array_merge():
604
     *   - In case of 2 different values, when they are not arrays, the latter one overwrites the earlier instead
605
     *     of merging them into an array;
606
     *   - Non-conflicting numeric keys are left unchanged. In case of a conflict, the new value will be appended
607
     *     to the resulting array (not preserving its key).
608
     *
609
     * @param   array   $array      The initial array.
610
     * @param   array   ...$with    One or more (ie. separate arguments) arrays to merge in.
611
     * @return  array               The resulting merged array.
612
     */
613
    public static function merge(array $array, array ...$with) : array
614
    {
615
        foreach ($with as $arr) {
616
            foreach ($arr as $key => $value) {
617
                // Append numeric keys.
618
                if (is_int($key)) {
619
                    array_key_exists($key, $array) ? $array[] = $value : $array[$key] = $value;
620
                }
621
                // Merge multi-dimensional arrays recursively.
622
                elseif (is_array($value) && array_key_exists($key, $array) && is_array($array[$key])) {
623
                    $array[$key] = static::merge($array[$key], $value);
624
                } else {
625
                    $array[$key] = $value;
626
                }
627
            }
628
        }
629
630
        return $array;
631
    }
632
633
    /**
634
     * Returns the smallest value from the given array.
635
     *
636
     * @param   array   $array  The array to traverse.
637
     * @return  mixed           The resulting value.
638
     */
639
    public static function min(array $array)
640
    {
641
        // Avoid some overhead at this point already if possible.
642
        if (empty($array)) {
643
            return null;
644
        }
645
646
        // Sort in an ascending order.
647
        asort($array);
648
649
        // Return the first element of the sorted array.
650
        return reset($array);
651
    }
652
653
    /**
654
     * Returns a subset of the given array, containing only the specified keys.
655
     *
656
     * @param   array   $array  The initial array.
657
     * @param   array   $keys   An array of keys (the keys are expected to be values of this array).
658
     * @return  array
659
     */
660
    public static function only(array $array, array $keys) : array
661
    {
662
        return array_intersect_key($array, array_values($keys));
663
    }
664
665
    /**
666
     * Returns a random value from the given array, or $elements random values when $elements is a positive integer, or
667
     * the first random element that passes the given truth test when $elements is a callable.
668
     *
669
     * @param   array           $array      The array to search in.
670
     * @param   callable|int    $elements   The number of random values to return or a callable to return the first
671
     *                                      randomly shuffled element that passes the given truth test.
672
     * @param   mixed           $default    The default value to be returned if none of the elements passes
673
     *                                      the test or the array is empty.
674
     * @return  mixed
675
     */
676
    public static function pick(array $array, $elements = null, $default = null)
677
    {
678
        // There are *slightly* better performing alternatives, but simply shuffling and delegating
679
        // the actual picking to static::first() allows us to avoid a fair amount of duplicated code.
680
        shuffle($array);
681
682
        return static::first($array, $elements, $default);
683
    }
684
685
    /**
686
     * Given an array containing other arrays or objects, this method will look for the value with the given
687
     * key/property of $value within them and return a new array containing all values of said key from the
688
     * initial array. Essentially like fetching a column from a classic database table.
689
     *
690
     * When the optional $key parameter is given, the resulting array will be indexed by the values corresponding
691
     * to the given $key.
692
     *
693
     * @see     array_column()  A faster and simpler alternative, if you do not need to pluck data with support
694
     *                          for delimited keys or wildcards.
695
     *
696
     * @param   array           $array      The array to search in.
697
     * @param   string|array    $value      The key of the value to look for.
698
     * @param   string|array    $key        The key of the value to index the resulting array by.
699
     * @param   string          $delimiter  The delimiter to use when exploding the key into parts.
700
     * @return  array
701
     */
702
    public static function pluck(array $array, $value, $key = null, string $delimiter = null) : array
703
    {
704
        $results = [];
705
706
        // Which string delimiter should we use?
707
        if (!isset($delimiter)) {
708
            $delimiter = static::$delimiter;
709
        }
710
711
        foreach ($array as $item) {
712
713
            $itemValue = static::get($item, $value, $delimiter);
714
715
            // If the key given is null, the resulting array will contain numerically indexed keys.
716
            if (!isset($key)) {
717
                $results[] = $itemValue;
718
            }
719
            // Otherwise we are going use the value of the given key and use it in the resulting array as key
720
            // for the value determined earlier.
721
            else {
722
                $results[static::get($item, $key, $delimiter)] = $itemValue;
723
            }
724
        }
725
726
        return $results;
727
    }
728
729
    /**
730
     * Returns the value for a string delimited key from an array and then removes it.
731
     *
732
     * @param   array   $array      The array to search in.
733
     * @param   string  $key        The string delimited key.
734
     * @param   string  $delimiter  The delimiter to use when exploding the key into parts.
735
     * @return  mixed
736
     */
737
    public static function pull(&$array, string $key, string $delimiter = null)
738
    {
739
        $value = static::get($array, $key, null, $delimiter);
740
741
        static::remove($array, $key, $delimiter);
742
743
        return $value;
744
    }
745
746
    /**
747
     * Removes a string delimited key from the given array.
748
     *
749
     * @param   array&  $array      The array to search in.
0 ignored issues
show
The doc-type array& could not be parsed: Unknown type name "array&" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
750
     * @param   string  $key        The string delimited key.
751
     * @param   string  $delimiter  The delimiter to use when exploding the key into parts.
752
     */
753
    public static function remove(array& $array, string $key, string $delimiter = null)
754
    {
755
        // Which string delimiter should we use?
756
        if (!isset($delimiter)) {
757
            $delimiter = static::$delimiter;
758
        }
759
760
        // Explode the key according to that delimiter.
761
        $keys = explode($delimiter, $key);
762
763
        while ($key = array_shift($keys)) {
764
            if (!isset($array[$key]) || !is_array($array[$key])) {
765
                return;
766
            }
767
768
            $array =& $array[$key];
769
        }
770
771
        unset($array[array_shift($keys)]);
772
    }
773
774
    /**
775
     * Returns all but the first value of the given array, all but the first elements for which the $callback
776
     * returns true if $callback is a callable, or all but the first $callback elements if $callback is a number.
777
     *
778
     * Aliases:
779
     *  - @see Arr::tail()
780
     *
781
     * @param   array               $array      The array to traverse.
782
     * @param   callable|int|bool   $callback   The truth test the value should pass or an integer denoting how many
783
     *                                          of the initial elements of the array should be excluded. The count
784
     *                                          is 1-indexed, ie. if you want to exclude the first 2 elements, pass 2.
785
     *                                          When a falsy value is given, the method will return all but the first
786
     *                                          element of the array.
787
     * @param   mixed               $default    The default value to be returned if none of the elements passes the
788
     *                                          test or the array contains no more than one item.
789
     * @return  mixed
790
     */
791
    public static function rest(array $array, $callback = false, $default = null)
792
    {
793
        // Avoid some overhead at this point already if possible. We need at least 2 elements in the array for
794
        // this method to make any usage sense.
795
        if (2 > count($array)) {
796
            return $default;
797
        }
798
799
        // For a falsy callback, return all but the first element of the array.
800
        if (!$callback) {
801
            return array_slice($array, 1);
802
        }
803
804
        // With a callable given, keep counting as long as the callable returns a truthy value.
805 View Code Duplication
        if (is_callable($callback)) {
806
            $i = 0;
807
808
            foreach ($array as $key => $value) {
809
                if (!call_user_func($callback, $key, $value)) {
810
                    break;
811
                }
812
813
                $i++;
814
            }
815
816
            // If we didn't get at least a single truthy value, return the default.
817
            if ($i === 0) {
818
                return $default;
819
            }
820
821
            // Otherwise we're just gonna overwrite the $callback and proceed as if it were an integer in the
822
            // first place.
823
            $callback = $i;
824
        }
825
826
        // Return the final $callback elements.
827
        return array_slice($array, abs((int) $callback));
828
    }
829
830
    /**
831
     * Sets the given value for a string delimited key within the given array. If null is given instead of a key,
832
     * the whole initial array will be overwritten with the given value.
833
     *
834
     * @param   array   $array      The array to set the value in.
835
     * @param   string  $key        The string delimited key.
836
     * @param   mixed   $value      The value to set.
837
     * @param   string  $delimiter  The delimiter to use when exploding the key into parts.
838
     * @return  mixed
839
     */
840
    public static function set(array& $array, $key, $value, string $delimiter = null)
841
    {
842
        // Make loops easier for the end-user - overwrite the whole array if the key is null.
843
        if (null === $key) {
844
            return $array = $value;
845
        }
846
847
        // Which string delimiter should we use?
848
        if (null === $delimiter) {
849
            $delimiter = static::$delimiter;
850
        }
851
852
        // Explode the key according to that delimiter.
853
        $keys = explode($delimiter, $key);
854
855
        while (count($keys) > 1) {
856
            $key = array_shift($keys);
857
858
            if (!isset($array[$key]) || !is_array($array[$key])) {
859
                $array[$key] = [];
860
            }
861
862
            $array =& $array[$key];
863
        }
864
865
        return $array[array_shift($keys)] = $value;
866
    }
867
868
    /**
869
     * Alias for {@see static::any()}
870
     */
871
    public static function some(array $array, callable $callback) : bool
872
    {
873
        return static::any($array, $callback);
874
    }
875
876
    /**
877
     * Alias for {@see static::rest()}
878
     */
879
    public static function tail(array $array, $callback = false, $default = null)
880
    {
881
        return static::rest($array, $callback, $default);
882
    }
883
884
    /**
885
     * @see \nyx\utils\Arr::first()
886
     */
887
    public static function take(array $array, $callback = null, $default = null)
888
    {
889
        return static::first($array, $callback, $default);
890
    }
891
892
    /**
893
     * Returns an array based on the initial array with all occurrences of the passed values removed. Uses strict
894
     * equality comparisons.
895
     *
896
     * @param   array   $array      The array to traverse.
897
     * @param   mixed   ...$values  The values which should get removed.
898
     * @return  array
899
     */
900
    public static function without(array $array, ...$values) : array
901
    {
902
        return array_filter($array, function($value) use ($values) {
903
            return !in_array($value, $values, true);
904
        });
905
    }
906
}
907