Issues (94)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

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 ($callback($value, $key)) {
313
                return $value;
314
            }
315
        }
316
317
        return $default;
318
    }
319
320
    /**
321
     * Returns the first element of the array,
322
     *   OR the first $elements of the array when $elements is a positive integer,
323
     *   OR the first element which passes the given truth test when $elements is a callable.
324
     *
325
     * Aliases:  @see \nyx\utils\Arr::head(), \nyx\utils\Arr::take()
326
     * Opposite: @see \nyx\utils\Arr::last()
327
     *
328
     * @param   array           $array      The array to traverse.
329
     * @param   callable|int    $elements   The truth test the value should pass or an integer denoting how many
330
     *                                      of the initial elements of the array should be returned.
331
     *                                      When not given, the method will return the first element of the array.
332
     * @param   mixed           $default    The default value to be returned if none of the elements passes
333
     *                                      the test or the array is empty.
334
     * @throws  \InvalidArgumentException   When $elements is an integer smaller than 1.
335
     * @throws  \InvalidArgumentException   When $elements is neither a valid integer nor a callable.
336
     * @return  mixed
337
     */
338 View Code Duplication
    public static function first(array $array, $elements = null, $default = null)
0 ignored issues
show
This method seems to be duplicated in 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...
339
    {
340
        if (empty($array)) {
341
            return $default;
342
        }
343
344
        // Most common use case - simply return the first value of the array.
345
        if (!isset($elements) || $elements === 1) {
346
            return reset($array);
347
        }
348
349
        // With a integer given, return a slice containing the first $callback elements.
350
        if (is_int($elements)) {
351
352
            if ($elements < 1) {
353
                throw new \InvalidArgumentException("At least 1 element must be requested, while [$elements] were requested.");
354
            }
355
356
            return array_slice($array, 0, $elements);
357
        }
358
359
        // With a callable given, return the first value which passes the given truth test.
360
        if (is_callable($elements)) {
361
            return static::find($array, $elements, $default);
362
        }
363
364
        throw new \InvalidArgumentException('Expected $callback to be a positive integer or a callable, got ['.diagnostics\Debug::getTypeName($elements).'] instead.');
365
    }
366
367
    /**
368
     * Flattens a multi-dimensional array.
369
     *
370
     * @param   array   $array  The initial array.
371
     * @return  array           The flattened array.
372
     */
373
    public static function flatten(array $array)
374
    {
375
        $results = [];
376
377
        array_walk_recursive($array, function($x) use (&$results) {
378
            $results[] = $x;
379
        });
380
381
        return $results;
382
    }
383
384
    /**
385
     * Returns a string delimited key from an array, with a default value if the given key does not exist. If null
386
     * is given instead of a key, the whole initial array will be returned.
387
     *
388
     * Note: Nested objects will be accessed as if they were arrays, eg. if "some.nested.object" is an object,
389
     *       then looking for "some.nested.object.property" will be handled just as a normal array would.
390
     *
391
     * @param   array           $array      The array to search in.
392
     * @param   string|array    $key        The string delimited key or a chain (array) of nested keys pointing
393
     *                                      to the desired key.
394
     * @param   mixed           $default    The default value.
395
     * @param   string          $delimiter  The delimiter to use when exploding the key into parts.
396
     * @return  mixed
397
     */
398
    public static function get(array $array, $key = null, $default = null, string $delimiter = null)
399
    {
400
        // Make loops easier for the end-user - return the initial array if the key is null instead of forcing
401
        // a valid value.
402
        if (!isset($key)) {
403
            return $array;
404
        }
405
406
        // Which string delimiter should we use?
407
        if (!isset($delimiter)) {
408
            $delimiter = static::$delimiter;
409
        }
410
411
        // If the key is string delimited, we need to explode it into an array of segments.
412
        $segments = is_array($key) ? $key : explode($delimiter, $key);
413
414
        // One dimension at a time.
415
        while ($segment = array_shift($segments)) {
416
417
            // If the current segment is a wildcard, make sure the it points to an array
418
            // and pluck the remaining segments from it.
419
            if ($segment === '*') {
420
                return is_array($array) ? static::pluck($array, $segments, $delimiter) : $default;
421
            }
422
423
            // Note: isset() is the cheapest condition to check for while being rather probable at the same time,
424
            // thus the seemingly unintuitive condition ordering.
425
            if (isset($array->{$segment})) {
426
                $array = $array->{$segment};
427
            } elseif (is_array($array) && array_key_exists($segment, $array)) {
428
                $array = $array[$segment];
429
            } else {
430
                return $default;
431
            }
432
        }
433
434
        return $array;
435
    }
436
437
    /**
438
     * Checks whether the given string delimited key exists in the array.
439
     *
440
     * Note: Nested objects will not be accessed as if they were arrays, ie. if "some.nested.object" is an object,
441
     *       not an array, then looking for "some.nested.object.key" will always return false.
442
     *
443
     * @param   array   $array      The array to search in.
444
     * @param   string  $key        The string delimited key.
445
     * @param   string  $delimiter  The delimiter to use when exploding the key into parts.
446
     * @return  bool                True when the given key exists, false otherwise.
447
     */
448
    public static function has(array $array, string $key, string $delimiter = null)
449
    {
450
        // Which string delimiter should we use?
451
        if (null === $delimiter) {
452
            $delimiter = static::$delimiter;
453
        }
454
455
        foreach (explode($delimiter, $key) as $segment) {
456
            if (!is_array($array) || !array_key_exists($segment, $array)) {
457
                return false;
458
            }
459
460
            $array = $array[$segment];
461
        }
462
463
        return true;
464
    }
465
466
    /**
467
     * @see \nyx\utils\Arr::first()
468
     */
469
    public static function head(array $array, $callback = null, $default = null)
470
    {
471
        return static::first($array, $callback, $default);
472
    }
473
474
    /**
475
     * Returns all but the last value(s) of the given array.
476
     *
477
     * If a callable is passed, elements at the end of the array are excluded from the result as long as the
478
     * callback returns a truthy value. If a number is passed, the last n values are excluded from the result.
479
     *
480
     * @param   array           $array      The array to traverse.
481
     * @param   callable|int    $callback   The truth test the value should pass or an integer denoting how many
482
     *                                      of the final elements of the array should be excluded. The count is
483
     *                                      1-indexed, ie. if you want to exclude the last 2 elements, pass 2.
484
     * @param   mixed           $default    The default value to be returned if none of the elements passes the test.
485
     *                                      Only useful when $callback is a callable.
486
     * @return  mixed
487
     */
488
    public static function initial(array $array, $callback, $default = null)
489
    {
490
        // When given a callable, keep counting as long as the callable returns a truthy value.
491 View Code Duplication
        if (is_callable($callback)) {
0 ignored issues
show
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...
492
            $i = 0;
493
494
            foreach (array_reverse($array) as $key => $value) {
495
                if (!$callback($value, $key)) {
496
                    break;
497
                }
498
499
                $i++;
500
            }
501
502
            // If we didn't get at least a single truthy value, return the default.
503
            if ($i === 0) {
504
                return $default;
505
            }
506
507
            // Otherwise we're just gonna overwrite the $callback and proceed as if it were an integer in the
508
            // first place.
509
            $callback = $i;
510
        }
511
512
        // At this point we need a positive integer, 1 at minimum.
513
        return array_slice($array, 0, -(int) (!$callback ? 1 : abs($callback)));
514
    }
515
516
    /**
517
     * Checks whether the given array is an associative array.
518
     *
519
     * @param   array   $array  The array to check.
520
     * @return  bool            True when the array is associative, false otherwise.
521
     */
522
    public static function isAssociative(array $array) : bool
523
    {
524
        return array_keys($array) !== range(0, count($array) - 1);
525
    }
526
527
    /**
528
     * Checks whether the given array is a multidimensional array.
529
     *
530
     * @param   array   $array  The array to check.
531
     * @return  bool            True when the array has multiple dimensions, false otherwise.
532
     */
533
    public static function isMultidimensional(array $array) : bool
534
    {
535
        return count($array) !== count($array, COUNT_RECURSIVE);
536
    }
537
538
    /**
539
     * Returns the last element of the array,
540
     *   OR the last $elements of the array when $elements is a positive integer,
541
     *   OR the last element which passes the given truth test when $elements is a callable.
542
     *
543
     * Opposite: @see \nyx\utils\Arr::first()
544
     *
545
     * @param   array           $array      The array to traverse.
546
     * @param   callable|int    $elements   The truth test the value should pass or a positive integer
547
     *                                      denoting how many of the final elements of the array should be returned.
548
     *                                      When not given, the method will return the first element of the array.
549
     * @param   mixed           $default    The default value to be returned if none of the elements passes
550
     *                                      the test or the array is empty.
551
     * @throws  \InvalidArgumentException   When $elements is an integer smaller than 1.
552
     * @throws  \InvalidArgumentException   When $elements is neither a valid integer nor a callable.
553
     * @return  mixed
554
     */
555 View Code Duplication
    public static function last(array $array, $elements = null, $default = null)
0 ignored issues
show
This method seems to be duplicated in 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...
556
    {
557
        if (empty($array)) {
558
            return $default;
559
        }
560
561
        // Most common use case - simply return the last value of the array.
562
        if (!isset($elements) || $elements === 1) {
563
            return end($array);
564
        }
565
566
        // With a integer given, return a slice containing the last $elements elements.
567
        if (is_int($elements)) {
568
569
            if ($elements < 1) {
570
                throw new \InvalidArgumentException("At least 1 element must be requested, while [$elements] were requested.");
571
            }
572
573
            return array_slice($array, -$elements);
574
        }
575
576
        // With a callable given, return the last value which passes the given truth test.
577
        if (is_callable($elements)) {
578
            return static::find(array_reverse($array), $elements, $default);
579
        }
580
581
        throw new \InvalidArgumentException('Expected $elements to be a positive integer or a callable, got ['.diagnostics\Debug::getTypeName($elements).'] instead.');
582
    }
583
584
    /**
585
     * Returns the biggest value from the given array.
586
     *
587
     * @param   array   $array  The array to traverse.
588
     * @return  mixed           The resulting value.
589
     */
590
    public static function max(array $array)
591
    {
592
        // Avoid some overhead at this point already if possible.
593
        if (empty($array)) {
594
            return null;
595
        }
596
597
        // Sort in a descending order.
598
        arsort($array);
599
600
        // Return the first element of the sorted array.
601
        return reset($array);
602
    }
603
604
    /**
605
     * Merges 2 or more arrays recursively. Differs in two important aspects from array_merge_recursive(), to
606
     * more closely resemble the standard behaviour of the non-recursive array_merge():
607
     *   - In case of 2 different values, when they are not arrays, the latter one overwrites the earlier instead
608
     *     of merging them into an array;
609
     *   - Non-conflicting numeric keys are left unchanged. In case of a conflict, the new value will be appended
610
     *     to the resulting array (not preserving its key).
611
     *
612
     * @param   array   $array      The initial array.
613
     * @param   array   ...$with    One or more (ie. separate arguments) arrays to merge in.
614
     * @return  array               The resulting merged array.
615
     */
616
    public static function merge(array $array, array ...$with) : array
617
    {
618
        foreach ($with as $arr) {
619
            foreach ($arr as $key => $value) {
620
                // Append numeric keys.
621
                if (is_int($key)) {
622
                    array_key_exists($key, $array) ? $array[] = $value : $array[$key] = $value;
623
                }
624
                // Merge multi-dimensional arrays recursively.
625
                elseif (is_array($value) && array_key_exists($key, $array) && is_array($array[$key])) {
626
                    $array[$key] = static::merge($array[$key], $value);
627
                } else {
628
                    $array[$key] = $value;
629
                }
630
            }
631
        }
632
633
        return $array;
634
    }
635
636
    /**
637
     * Returns the smallest value from the given array.
638
     *
639
     * @param   array   $array  The array to traverse.
640
     * @return  mixed           The resulting value.
641
     */
642
    public static function min(array $array)
643
    {
644
        // Avoid some overhead at this point already if possible.
645
        if (empty($array)) {
646
            return null;
647
        }
648
649
        // Sort in an ascending order.
650
        asort($array);
651
652
        // Return the first element of the sorted array.
653
        return reset($array);
654
    }
655
656
    /**
657
     * Returns a subset of the given array, containing only the specified keys.
658
     *
659
     * @param   array   $array  The initial array.
660
     * @param   array   $keys   An array of keys (the keys are expected to be values of this array).
661
     * @return  array
662
     */
663
    public static function only(array $array, array $keys) : array
664
    {
665
        return array_intersect_key($array, array_values($keys));
666
    }
667
668
    /**
669
     * Returns a random value from the given array, or $elements random values when $elements is a positive integer, or
670
     * the first random element that passes the given truth test when $elements is a callable.
671
     *
672
     * @param   array           $array      The array to search in.
673
     * @param   callable|int    $elements   The number of random values to return or a callable to return the first
674
     *                                      randomly shuffled element that passes the given truth test.
675
     * @param   mixed           $default    The default value to be returned if none of the elements passes
676
     *                                      the test or the array is empty.
677
     * @return  mixed
678
     */
679
    public static function pick(array $array, $elements = null, $default = null)
680
    {
681
        // There are *slightly* better performing alternatives, but simply shuffling and delegating
682
        // the actual picking to static::first() allows us to avoid a fair amount of duplicated code.
683
        shuffle($array);
684
685
        return static::first($array, $elements, $default);
686
    }
687
688
    /**
689
     * Given an array containing other arrays or objects, looks for the value with the given key/property
690
     * of $key within them and returns a new array containing all values of said key from the initial array.
691
     * Essentially like fetching a single column from a classic database table.
692
     *
693
     * When the optional $index parameter is given, the resulting array will be indexed by the values corresponding
694
     * to the given $index.
695
     *
696
     * @see     array_column()  A faster and simpler alternative, if you do not need to pluck data with support
697
     *                          for delimited keys or wildcards.
698
     *
699
     * @param   array           $array      The array to search in.
700
     * @param   string|array    $key        The key of the value to look for.
701
     * @param   string|array    $index      The key of the value to index the resulting array by.
702
     * @param   string          $delimiter  The delimiter to use when exploding the key into parts.
703
     * @return  array
704
     */
705
    public static function pluck(array $array, $key, $index = null, string $delimiter = null) : array
706
    {
707
        $results = [];
708
709
        // Which string delimiter should we use?
710
        if (!isset($delimiter)) {
711
            $delimiter = static::$delimiter;
712
        }
713
714
        foreach ($array as $item) {
715
716
            $itemValue = static::get($item, $key, $delimiter);
717
718
            // If the key given is null, the resulting array will contain numerically indexed keys.
719
            if (!isset($index)) {
720
                $results[] = $itemValue;
721
            }
722
            // Otherwise we are going use the value of the given key and use it in the resulting array as key
723
            // for the value determined earlier.
724
            else {
725
                $results[static::get($item, $index, $delimiter)] = $itemValue;
726
            }
727
        }
728
729
        return $results;
730
    }
731
732
    /**
733
     * Returns the value for a string delimited key from an array and then removes it.
734
     *
735
     * @param   array   $array      The array to search in.
736
     * @param   string  $key        The string delimited key.
737
     * @param   string  $delimiter  The delimiter to use when exploding the key into parts.
738
     * @return  mixed
739
     */
740
    public static function pull(&$array, string $key, string $delimiter = null)
741
    {
742
        $value = static::get($array, $key, null, $delimiter);
743
744
        static::remove($array, $key, $delimiter);
745
746
        return $value;
747
    }
748
749
    /**
750
     * Removes a string delimited key from the given array.
751
     *
752
     * @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...
753
     * @param   string  $key        The string delimited key.
754
     * @param   string  $delimiter  The delimiter to use when exploding the key into parts.
755
     */
756
    public static function remove(array& $array, string $key, string $delimiter = null)
757
    {
758
        // Which string delimiter should we use?
759
        if (!isset($delimiter)) {
760
            $delimiter = static::$delimiter;
761
        }
762
763
        // Explode the key according to that delimiter.
764
        $keys = explode($delimiter, $key);
765
766
        while ($key = array_shift($keys)) {
767
            if (!isset($array[$key]) || !is_array($array[$key])) {
768
                return;
769
            }
770
771
            $array =& $array[$key];
772
        }
773
774
        unset($array[array_shift($keys)]);
775
    }
776
777
    /**
778
     * Returns all but the first value of the given array, all but the first elements for which the $callback
779
     * returns true if $callback is a callable, or all but the first $callback elements if $callback is a number.
780
     *
781
     * Aliases:
782
     *  - @see Arr::tail()
783
     *
784
     * @param   array               $array      The array to traverse.
785
     * @param   callable|int|bool   $callback   The truth test the value should pass or an integer denoting how many
786
     *                                          of the initial elements of the array should be excluded. The count
787
     *                                          is 1-indexed, ie. if you want to exclude the first 2 elements, pass 2.
788
     *                                          When a falsy value is given, the method will return all but the first
789
     *                                          element of the array.
790
     * @param   mixed               $default    The default value to be returned if none of the elements passes the
791
     *                                          test or the array contains no more than one item.
792
     * @return  mixed
793
     */
794
    public static function rest(array $array, $callback = false, $default = null)
795
    {
796
        // Avoid some overhead at this point already if possible. We need at least 2 elements in the array for
797
        // this method to make any usage sense.
798
        if (2 > count($array)) {
799
            return $default;
800
        }
801
802
        // For a falsy callback, return all but the first element of the array.
803
        if (!$callback) {
804
            return array_slice($array, 1);
805
        }
806
807
        // With a callable given, keep counting as long as the callable returns a truthy value.
808 View Code Duplication
        if (is_callable($callback)) {
0 ignored issues
show
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...
809
            $i = 0;
810
811
            foreach ($array as $key => $value) {
812
                if (!$callback($value, $key)) {
813
                    break;
814
                }
815
816
                $i++;
817
            }
818
819
            // If we didn't get at least a single truthy value, return the default.
820
            if ($i === 0) {
821
                return $default;
822
            }
823
824
            // Otherwise we're just gonna overwrite the $callback and proceed as if it were an integer in the
825
            // first place.
826
            $callback = $i;
827
        }
828
829
        // Return the final $callback elements.
830
        return array_slice($array, abs((int) $callback));
831
    }
832
833
    /**
834
     * Sets the given value for a string delimited key within the given array. If null is given instead of a key,
835
     * the whole initial array will be overwritten with the given value.
836
     *
837
     * @param   array   $array      The array to set the value in.
838
     * @param   string  $key        The string delimited key.
839
     * @param   mixed   $value      The value to set.
840
     * @param   string  $delimiter  The delimiter to use when exploding the key into parts.
841
     * @return  mixed
842
     */
843
    public static function set(array& $array, $key, $value, string $delimiter = null)
844
    {
845
        // Make loops easier for the end-user - overwrite the whole array if the key is null.
846
        if (null === $key) {
847
            return $array = $value;
848
        }
849
850
        // Which string delimiter should we use?
851
        if (null === $delimiter) {
852
            $delimiter = static::$delimiter;
853
        }
854
855
        // Explode the key according to that delimiter.
856
        $keys = explode($delimiter, $key);
857
858
        while (count($keys) > 1) {
859
            $key = array_shift($keys);
860
861
            if (!isset($array[$key]) || !is_array($array[$key])) {
862
                $array[$key] = [];
863
            }
864
865
            $array =& $array[$key];
866
        }
867
868
        return $array[array_shift($keys)] = $value;
869
    }
870
871
    /**
872
     * Alias for {@see static::any()}
873
     */
874
    public static function some(array $array, callable $callback) : bool
875
    {
876
        return static::any($array, $callback);
877
    }
878
879
    /**
880
     * Alias for {@see static::rest()}
881
     */
882
    public static function tail(array $array, $callback = false, $default = null)
883
    {
884
        return static::rest($array, $callback, $default);
885
    }
886
887
    /**
888
     * @see \nyx\utils\Arr::first()
889
     */
890
    public static function take(array $array, $callback = null, $default = null)
891
    {
892
        return static::first($array, $callback, $default);
893
    }
894
895
    /**
896
     * Returns an array based on the initial array with all occurrences of the passed values removed. Uses strict
897
     * equality comparisons.
898
     *
899
     * @param   array   $array      The array to traverse.
900
     * @param   mixed   ...$values  The values which should get removed.
901
     * @return  array
902
     */
903
    public static function without(array $array, ...$values) : array
904
    {
905
        return array_filter($array, function($value) use ($values) {
906
            return !in_array($value, $values, true);
907
        });
908
    }
909
}
910