Passed
Push — develop ( 4b5082...21cba9 )
by nguereza
02:17
created

Arr   F

Complexity

Total Complexity 201

Size/Duplication

Total Lines 1173
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 394
dl 0
loc 1173
rs 2
c 1
b 0
f 0
wmc 201

42 Methods

Rating   Name   Duplication   Size   Complexity  
C getValue() 0 50 14
A remove() 0 11 3
A except() 0 5 1
C toArray() 0 51 15
B merge() 0 21 8
A isIndexed() 0 16 5
A pull() 0 7 1
A random() 0 29 6
A wrap() 0 6 3
A similar() 0 19 4
A query() 0 3 1
A isAccessible() 0 3 2
A only() 0 3 1
B index() 0 35 10
B get() 0 24 7
A set() 0 23 5
B filter() 0 46 9
A where() 0 3 1
A getKeyMaxWidth() 0 13 5
A last() 0 14 3
A isIn() 0 14 6
B multisort() 0 49 9
B forget() 0 33 9
A exists() 0 7 2
A first() 0 22 6
B isAssoc() 0 22 7
A flatten() 0 17 4
A keyExists() 0 16 4
A shuffle() 0 11 2
A prepend() 0 9 2
A crossJoin() 0 18 4
C has() 0 31 12
A isTraversable() 0 3 2
A getColumn() 0 14 4
A insert() 0 7 1
A isSubset() 0 9 3
A map() 0 14 3
A collapse() 0 12 3
A pluck() 0 25 6
A normalizeArguments() 0 27 5
A toString() 0 3 1
A isArrayable() 0 3 2

How to fix   Complexity   

Complex Class

Complex classes like Arr often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Arr, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * Platine Stdlib
5
 *
6
 * Platine Stdlib is a the collection of frequently used php features
7
 *
8
 * This content is released under the MIT License (MIT)
9
 *
10
 * Copyright (c) 2020 Platine Stdlib
11
 *
12
 * Permission is hereby granted, free of charge, to any person obtaining a copy
13
 * of this software and associated documentation files (the "Software"), to deal
14
 * in the Software without restriction, including without limitation the rights
15
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
 * copies of the Software, and to permit persons to whom the Software is
17
 * furnished to do so, subject to the following conditions:
18
 *
19
 * The above copyright notice and this permission notice shall be included in all
20
 * copies or substantial portions of the Software.
21
 *
22
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
 * SOFTWARE.
29
 */
30
31
/**
32
 *  @file Arr.php
33
 *
34
 *  The Array helper class
35
 *
36
 *  @package    Platine\Stdlib\Helper
37
 *  @author Platine Developers Team
38
 *  @copyright  Copyright (c) 2020
39
 *  @license    http://opensource.org/licenses/MIT  MIT License
40
 *  @link   http://www.iacademy.cf
41
 *  @version 1.0.0
42
 *  @filesource
43
 */
44
45
declare(strict_types=1);
46
47
namespace Platine\Stdlib\Helper;
48
49
use ArrayAccess;
50
use Closure;
51
use InvalidArgumentException;
52
use Platine\Stdlib\Contract\Arrayable;
53
use Traversable;
54
55
56
/**
57
 * Class Arr
58
 * @package Platine\Stdlib\Helper
59
 */
60
class Arr
61
{
62
63
    /**
64
     * Convert an array, object or string to array
65
     * @param array<mixed>|object|string $object
66
     * @param array<string, array<int|string, string>> $properties
67
     * @param bool $recursive
68
     * @return array<mixed>
69
     */
70
    public static function toArray(
71
        $object,
72
        array $properties = [],
73
        bool $recursive = true
74
    ): array {
75
        if (is_array($object)) {
76
            if ($recursive) {
77
                foreach ($object as $key => $value) {
78
                    if (is_array($value) || is_object($value)) {
79
                        $object[$key] = static::toArray($value, $properties, true);
80
                    }
81
                }
82
            }
83
84
            return $object;
85
        }
86
87
        if (is_object($object)) {
88
            if (!empty($properties)) {
89
                $className = get_class($object);
90
                if (!empty($properties[$className])) {
91
                    $result = [];
92
                    foreach ($properties[$className] as $key => $name) {
93
                        if (is_int($key)) {
94
                            $result[$name] = $object->{$name};
95
                        } else {
96
                            $result[$key] = static::getValue($object, $name);
97
                        }
98
                    }
99
100
                    return $recursive
101
                            ? static::toArray($result, $properties)
102
                            : $result;
103
                }
104
            }
105
106
            if ($object instanceof Arrayable) {
107
                $result = $object->toArray();
108
            } else {
109
                $result = [];
110
                foreach ((array)$object as $key => $value) {
111
                    $result[$key] = $value;
112
                }
113
            }
114
115
            return $recursive
116
                    ? static::toArray($result, $properties)
117
                    : $result;
118
        }
119
120
        return [$object];
121
    }
122
123
    /**
124
     * Merge the passed arrays
125
     * @param array<mixed> ...$args
126
     * @return array<mixed>
127
     */
128
    public static function merge(array ...$args): array
129
    {
130
        $res = (array)array_shift($args);
131
        while (!empty($args)) {
132
            $next = array_shift($args);
133
            foreach ($next as $key => $value) {
134
                if (is_int($key)) {
135
                    if (isset($res[$key])) {
136
                        $res[] = $value;
137
                    } else {
138
                        $res[$key] = $value;
139
                    }
140
                } elseif (is_array($value) && isset($res[$key]) && is_array($res[$key])) {
141
                    $res[$key] = self::merge($res[$key], $value);
142
                } else {
143
                    $res[$key] = $value;
144
                }
145
            }
146
        }
147
148
        return $res;
149
    }
150
151
    /**
152
     * Return the value of an array element or object property
153
     * for the given key or property name.
154
     *
155
     * @param array<mixed>|object $object
156
     * @param int|string|Closure|array<mixed> $key
157
     * @param mixed $default
158
     *
159
     * @return mixed If the key does not exist in the array or object,
160
     * the default value will be returned instead.
161
     */
162
    public static function getValue($object, $key, $default = null)
163
    {
164
        if ($key instanceof Closure) {
165
            return $key($object, $default);
166
        }
167
168
        if (is_array($key)) {
169
            $lastKey = array_pop($key);
170
            foreach ($key as $part) {
171
                $object = static::getValue($object, $part);
172
            }
173
            $key = $lastKey;
174
        }
175
176
        if (
177
            is_array($object) && (isset($object[$key])
178
                || array_key_exists($key, $object))
179
        ) {
180
            return $object[$key];
181
        }
182
183
        if (is_string($key)) {
184
            $pos = strpos($key, '.');
185
186
            if ($pos !== false) {
187
                $object = static::getValue(
188
                    $object,
189
                    substr($key, 0, $pos),
190
                    $default
191
                );
192
                $key = (string) substr($key, $pos + 1);
193
            }
194
        }
195
196
        if (is_object($object) && property_exists($object, $key)) {
197
            // this is will fail if the property does not exist,
198
            //  or __get() is not implemented
199
            // it is not reliably possible to check whether a property
200
            // is accessable beforehand
201
202
            return $object->{$key};
203
        }
204
205
        if (is_array($object)) {
206
            return (isset($object[$key]) || array_key_exists($key, $object))
207
                    ? $object[$key]
208
                    : $default;
209
        }
210
211
        return $default;
212
    }
213
214
    /**
215
     * Remove an item from an array and returns the value.
216
     * If the key does not exist in the array, the default value will be returned
217
     * @param array<mixed> $array
218
     * @param string|int $key
219
     * @param mixed $default
220
     *
221
     * @return mixed|null
222
     */
223
    public static function remove(array &$array, $key, $default = null)
224
    {
225
        if (isset($array[$key]) || array_key_exists($key, $array)) {
226
            $value = $array[$key];
227
228
            unset($array[$key]);
229
230
            return $value;
231
        }
232
233
        return $default;
234
    }
235
236
    /**
237
     * Return all of the given array except for a specified keys.
238
     * @param array<mixed> $array
239
     * @param array<int, int|string>|string|int $keys
240
     * @return array<mixed>
241
     */
242
    public static function except(array $array, $keys): array
243
    {
244
        static::forget($array, $keys);
245
246
        return $array;
247
    }
248
249
    /**
250
     * Remove one or many array items from a given array using "dot" notation.
251
     * @param array<mixed> $array
252
     * @param array<int, int|string>|string|int $keys
253
     * @return void
254
     */
255
    public static function forget(array &$array, $keys): void
256
    {
257
        $original = &$array;
258
259
        if (!is_array($keys)) {
260
            $keys = [$keys];
261
        }
262
263
        if (count($keys) === 0) {
264
            return;
265
        }
266
267
        foreach ($keys as $key) {
268
            if (static::exists($array, $key)) {
269
                unset($array[$key]);
270
271
                continue;
272
            }
273
274
            if (is_string($key)) {
275
                $parts = explode('.', $key);
276
277
                $array = &$original;
278
                while (count($parts) > 1) {
279
                    $part = array_shift($parts);
280
                    if (isset($array[$part]) && is_array($array[$part])) {
281
                        $array = &$array[$part];
282
                    } else {
283
                        continue 2;
284
                    }
285
                }
286
287
                unset($array[array_shift($parts)]);
288
            }
289
        }
290
    }
291
292
    /**
293
     * Get a value from the array, and remove it.
294
     * @param array<mixed> $array
295
     * @param string|int $key
296
     * @param mixed $default
297
     * @return mixed
298
     */
299
    public static function pull(array &$array, $key, $default = null)
300
    {
301
        $value = static::get($array, $key, $default);
302
303
        static::forget($array, $key);
304
305
        return $value;
306
    }
307
308
    /**
309
     * Indexes and/or groups the array according to a specified key.
310
     * The input should be either multidimensional array or an array of objects.
311
     *
312
     * The $key can be either a key name of the sub-array, a property
313
     * name of object, or an anonymous function that must return the value
314
     * that will be used as a key.
315
     *
316
     * $groups is an array of keys, that will be used to group the input
317
     * array into one or more sub-arrays based on keys specified.
318
     *
319
     * If the `$key` is specified as `null` or a value of an element
320
     * corresponding to the key is `null` in addition to `$groups` not
321
     * specified then the element is discarded.
322
     *
323
     * @param array<mixed> $array
324
     * @param string|Closure|null   $key
325
     * @param string|string[]|Closure[]|null $groups
326
     * @return array<mixed> the indexed and/or grouped array
327
     */
328
    public static function index(array $array, $key = null, $groups = []): array
329
    {
330
        $result = [];
331
        if (!is_array($groups)) {
332
            $groups = (array) $groups;
333
        }
334
335
        foreach ($array as $element) {
336
            $lastArray = &$result;
337
            foreach ($groups as $group) {
338
                /** @var int|string $value */
339
                $value = static::getValue($element, $group);
340
                if (!empty($lastArray) && !array_key_exists($value, $lastArray)) {
341
                    $lastArray[$value] = [];
342
                }
343
                $lastArray = &$lastArray[$value];
344
            }
345
346
            if ($key === null) {
347
                if (!empty($groups)) {
348
                    $lastArray[] = $element;
349
                }
350
            } else {
351
                $value = static::getValue($element, $key);
352
                if ($value !== null) {
353
                    if (is_float($value)) {
354
                        $value = (string) $value;
355
                    }
356
                    $lastArray[$value] = $element;
357
                }
358
            }
359
            unset($lastArray);
360
        }
361
362
        return $result;
363
    }
364
365
    /**
366
     * Returns the values of a specified column in an array.
367
     * The input array should be multidimensional or an array of objects.
368
     * @param array<mixed> $array
369
     * @param int|string|Closure $name
370
     * @param bool $keepKeys
371
     * @return array<mixed>
372
     */
373
    public static function getColumn(array $array, $name, bool $keepKeys = true): array
374
    {
375
        $result = [];
376
        if ($keepKeys) {
377
            foreach ($array as $key => $element) {
378
                $result[$key] = static::getValue($element, $name);
379
            }
380
        } else {
381
            foreach ($array as $element) {
382
                $result[] = static::getValue($element, $name);
383
            }
384
        }
385
386
        return $result;
387
    }
388
389
    /**
390
     * Builds a map (key-value pairs) from a multidimensional array or
391
     * an array of objects.
392
     * The `$from` and `$to` parameters specify the key names or property
393
     * names to set up the map.
394
     * Optionally, one can further group the map according to a
395
     * grouping field `$group`.
396
     *
397
     * @param array<mixed> $array
398
     * @param string|Closure $from
399
     * @param string|Closure $to
400
     * @param string|Closure|null $group
401
     * @return array<mixed>
402
     */
403
    public static function map(array $array, $from, $to, $group = null): array
404
    {
405
        $result = [];
406
        foreach ($array as $element) {
407
            $key = static::getValue($element, $from);
408
            $value = static::getValue($element, $to);
409
            if ($group !== null) {
410
                $result[static::getValue($element, $group)][$key] = $value;
411
            } else {
412
                $result[$key] = $value;
413
            }
414
        }
415
416
        return $result;
417
    }
418
419
    /**
420
     * Checks if the given array contains the specified key.
421
     * This method enhances the `array_key_exists()` function by supporting
422
     * case-insensitive key comparison.
423
     *
424
     * @param string $key
425
     * @param array<mixed> $array
426
     * @param bool $caseSensative
427
     * @return bool
428
     */
429
    public static function keyExists(
430
        string $key,
431
        array $array,
432
        bool $caseSensative = true
433
    ): bool {
434
        if ($caseSensative) {
435
            return array_key_exists($key, $array);
436
        }
437
438
        foreach (array_keys($array) as $k) {
439
            if (strcasecmp($key, $k) === 0) {
440
                return true;
441
            }
442
        }
443
444
        return false;
445
    }
446
447
    /**
448
     * Sorts an array of objects or arrays (with the same structure) by one
449
     * or several keys.
450
     * @param array<mixed> $array
451
     * @param string|Closure|array<string> $key the key(s) to be sorted by.
452
     *     This refers to a key name of the sub-array elements, a property name
453
     *      of the objects, or an anonymous function returning the values
454
     *   for comparison purpose. The anonymous function signature
455
     * should be: `function($item)`.
456
     * @param int|array<int> $direction
457
     * @param int|array<int> $sortFlag
458
     * @return void
459
     */
460
    public static function multisort(
461
        array &$array,
462
        $key,
463
        $direction = SORT_ASC,
464
        $sortFlag = SORT_REGULAR
465
    ): void {
466
        $keys = is_array($key) ? $key : [$key];
467
468
        if (empty($keys) || empty($array)) {
469
            return;
470
        }
471
472
        $count  = count($keys);
473
        if (is_scalar($direction)) {
474
            $direction = array_fill(0, $count, $direction);
475
        } elseif (count($direction) !== $count) {
476
            throw new InvalidArgumentException(
477
                'The length of the sort direction must be the same '
478
                    . 'as that of sort keys.'
479
            );
480
        }
481
        if (is_scalar($sortFlag)) {
482
            $sortFlag = array_fill(0, $count, $sortFlag);
483
        } elseif (count($sortFlag) !== $count) {
484
            throw new InvalidArgumentException(
485
                'The length of the sort flag must be the same '
486
                    . 'as that of sort keys.'
487
            );
488
        }
489
490
        /** @var array<int, mixed> $args */
491
        $args = [];
492
        foreach ($keys as $i => $k) {
493
            $flag = $sortFlag[$i];
494
            $args[] = static::getColumn($array, $k);
495
            $args[] = $direction[$i];
496
            $args[] = $flag;
497
        }
498
499
        // This fix is used for cases when main sorting specified by
500
        // columns has equal values
501
        // Without it it will lead to Fatal Error: Nesting level
502
        // too deep - recursive dependency?
503
        $args[] = range(1, count($array));
504
        $args[] = SORT_ASC;
505
        $args[] = SORT_NUMERIC;
506
        $args[] = &$array;
507
508
        array_multisort(...$args);
509
    }
510
511
    /**
512
     * Check whether the given array is an associative array.
513
     *
514
     * An array is associative if all its keys are strings.
515
     * If `$allStrings` is false, then an array will be treated as associative
516
     * if at least one of its keys is a string.
517
     *
518
     * Note that an empty array will NOT be considered associative.
519
     *
520
     * @param array<mixed> $array
521
     * @param bool $allStrings
522
     * @return bool
523
     */
524
    public static function isAssoc(array $array, bool $allStrings = true): bool
525
    {
526
        if (empty($array)) {
527
            return false;
528
        }
529
530
        if ($allStrings) {
531
            foreach ($array as $key => $value) {
532
                if (!is_string($key)) {
533
                    return false;
534
                }
535
            }
536
537
            return true;
538
        } else {
539
            foreach ($array as $key => $value) {
540
                if (is_string($key)) {
541
                    return true;
542
                }
543
            }
544
545
            return false;
546
        }
547
    }
548
549
    /**
550
     * Whether the given array is an indexed array.
551
     *
552
     * An array is indexed if all its keys are integers.
553
     * If `$consecutive` is true, then the array keys must be a consecutive
554
     * sequence starting from 0.
555
     *
556
     * Note that an empty array will be considered indexed.
557
     *
558
     * @param array<mixed> $array
559
     * @param bool $consecutive
560
     * @return bool
561
     */
562
    public static function isIndexed(array $array, bool $consecutive = false): bool
563
    {
564
        if (empty($array)) {
565
            return true;
566
        }
567
568
        if ($consecutive) {
569
            return array_keys($array) === range(0, count($array) - 1);
570
        } else {
571
            foreach ($array as $key => $value) {
572
                if (!is_int($key)) {
573
                    return false;
574
                }
575
            }
576
577
            return true;
578
        }
579
    }
580
581
    /**
582
     * Check whether an array or Traversable contains an element.
583
     *
584
     * This method does the same as the PHP function in_array()
585
     * but additionally works for objects that implement the
586
     * Traversable interface.
587
     *
588
     * @param mixed $needle
589
     * @param array<mixed>|Traversable<int|string, mixed> $array
590
     * @param bool $strict
591
     * @return bool
592
     */
593
    public static function isIn($needle, $array, bool $strict = false): bool
594
    {
595
        if ($array instanceof Traversable) {
596
            $array = iterator_to_array($array);
597
        }
598
599
        foreach ($array as $value) {
600
            if ($needle == $value && (!$strict || $needle === $value)) {
601
                return true;
602
            }
603
        }
604
605
606
        return in_array($needle, $array, $strict);
607
    }
608
609
    /**
610
     * Checks whether a variable is an array or Traversable.
611
     * @param mixed $var
612
     * @return bool
613
     */
614
    public static function isTraversable($var): bool
615
    {
616
        return is_array($var) || $var instanceof Traversable;
617
    }
618
619
    /**
620
     * Checks whether an array or Traversable is a subset of another array
621
     * or Traversable.
622
     *
623
     * This method will return `true`, if all elements of `$needles`
624
     * are contained in `$array`. If at least one element is missing,
625
     * `false` will be returned.
626
     *
627
     * @param array<mixed>|Traversable<int|string, mixed> $needles
628
     * @param array<mixed>|Traversable<int|string, mixed> $array
629
     * @param bool $strict
630
     * @return bool
631
     */
632
    public static function isSubset($needles, $array, bool $strict = false): bool
633
    {
634
        foreach ($needles as $needle) {
635
            if (!static::isIn($needle, $array, $strict)) {
636
                return false;
637
            }
638
        }
639
640
        return true;
641
    }
642
643
    /**
644
     * Filters array according to rules specified.
645
     * @param array<mixed> $array
646
     * @param array<string> $filters Rules that define array keys which should
647
     *  be left or removed from results.
648
     *         Each rule is:
649
     *            - `var` - `$array['var']` will be left in result.
650
     *            - `var.key` = only `$array['var']['key'] will be left in result.
651
     *            - `!var.key` = `$array['var']['key'] will be removed from result.
652
     * @return array<mixed>
653
     */
654
    public static function filter(array $array, array $filters): array
655
    {
656
        $result = [];
657
        $tobeRemoved = [];
658
659
        foreach ($filters as $filter) {
660
            $keys = explode('.', $filter);
661
            $globalKey = $keys[0];
662
            $localkey = $keys[1] ?? null;
663
            if ($globalKey[0] === '!') {
664
                $tobeRemoved[] = [
665
                    substr($globalKey, 1),
666
                    $localkey
667
                ];
668
669
                continue;
670
            }
671
672
            if (empty($array[$globalKey])) {
673
                continue;
674
            }
675
676
            if ($localkey === null) {
677
                $result[$globalKey] = $array[$globalKey];
678
                continue;
679
            }
680
681
            if (!isset($array[$globalKey][$localkey])) {
682
                continue;
683
            }
684
685
            if (array_key_exists($globalKey, $result)) {
686
                $result[$globalKey] = [];
687
            }
688
689
            $result[$globalKey][$localkey] = $array[$globalKey][$localkey];
690
        }
691
692
        foreach ($tobeRemoved as $value) {
693
            [$globalKey, $localkey] = $value;
694
            if (array_key_exists($globalKey, $result)) {
695
                unset($result[$globalKey][$localkey]);
696
            }
697
        }
698
699
        return $result;
700
    }
701
702
    /**
703
     * Checks whether a variable is an array accessible.
704
     * @param mixed $var
705
     * @return bool
706
     */
707
    public static function isAccessible($var): bool
708
    {
709
        return is_array($var) || $var instanceof ArrayAccess;
710
    }
711
712
    /**
713
     * Checks whether a variable is an array or instance of Arrayable.
714
     * @param mixed $var
715
     * @return bool
716
     */
717
    public static function isArrayable($var): bool
718
    {
719
        return is_array($var) || $var instanceof Arrayable;
720
    }
721
722
    /**
723
     * If the given value is not an array and not null, wrap it in one.
724
     * @param mixed $var
725
     * @return array<mixed>
726
     */
727
    public static function wrap($var): array
728
    {
729
        if ($var === null) {
730
            return [];
731
        }
732
        return is_array($var) ? $var : [$var];
733
    }
734
735
    /**
736
     * Check whether the given key exists in the provided array.
737
     *
738
     * @param array<mixed>|ArrayAccess<string|int, mixed> $array
739
     * @param string|int $key
740
     * @return bool
741
     */
742
    public static function exists($array, $key): bool
743
    {
744
        if (is_array($array)) {
745
            return array_key_exists($key, $array);
746
        }
747
748
        return $array->offsetExists($key);
749
    }
750
751
    /**
752
     * Get an item from an array using "dot" notation.
753
     *
754
     * @param array<mixed>|ArrayAccess<string|int, mixed> $array
755
     * @param string|int|null $key
756
     * @param mixed $default
757
     * @return mixed
758
     */
759
    public static function get($array, $key = null, $default = null)
760
    {
761
        if ($key === null) {
762
            return $array;
763
        }
764
765
        if (isset($array[$key])) {
766
            return $array[$key];
767
        }
768
769
        // Fix: If is int, stop continue find.
770
        if (!is_string($key)) {
771
            return $default;
772
        }
773
774
        foreach (explode('.', $key) as $segment) {
775
            if (static::isAccessible($array) && static::exists($array, $segment)) {
776
                $array = $array[$segment];
777
            } else {
778
                return $default;
779
            }
780
        }
781
782
        return $array;
783
    }
784
785
    /**
786
     * Check if an item exists in an array using "dot" notation.
787
     *
788
     * @param array<mixed>|ArrayAccess<string|int, mixed> $array
789
     * @param string|int $key
790
     * @return bool
791
     */
792
    public static function has($array, $key): bool
793
    {
794
        if (empty($array)) {
795
            return false;
796
        }
797
798
        if (
799
            (is_array($array) && array_key_exists($key, $array))
800
            || ($array instanceof ArrayAccess && $array->offsetExists($key))
801
        ) {
802
            return true;
803
        }
804
805
        // Fix: If is int, stop continue find.
806
        if (!is_string($key)) {
807
            return false;
808
        }
809
810
        foreach (explode('.', $key) as $segment) {
811
            if (
812
                ((is_array($array) && array_key_exists($segment, $array))
813
                || ($array instanceof ArrayAccess
814
                        && $array->offsetExists($segment)))
815
            ) {
816
                $array = $array[$segment];
817
            } else {
818
                return false;
819
            }
820
        }
821
822
        return true;
823
    }
824
825
    /**
826
     *
827
     * @param array<mixed> $array
828
     * @param string|null $key
829
     * @param mixed $value
830
     * @return void
831
     */
832
    public static function set(array &$array, ?string $key, $value): void
833
    {
834
        if ($key === null) {
835
            return;
836
        }
837
838
        $keys = explode('.', $key);
839
840
        while (count($keys) > 1) {
841
            $key = array_shift($keys);
842
843
            // If the key doesn't exist at this depth, we will just create
844
            // an empty array hold the next value, allowing us to create
845
            // the arrays to hold final values at the correct depth.
846
            // Then we'll keep digging into the array.
847
            if (!isset($array[$key]) || !is_array($array[$key])) {
848
                $array[$key] = [];
849
            }
850
851
            $array = &$array[$key];
852
        }
853
854
        $array[array_shift($keys)] = $value;
855
    }
856
857
    /**
858
     * Insert one array to another array
859
     *
860
     * @param array<mixed> $array
861
     * @param int $index
862
     * @param array<mixed> ...$inserts
863
     * @return void
864
     */
865
    public static function insert(
866
        array &$array,
867
        int $index,
868
        array ...$inserts
869
    ): void {
870
        $first = array_splice($array, 0, $index);
871
        $array = array_merge($first, $inserts, $array);
872
    }
873
874
    /**
875
     * Flatten a multi-dimensional array into a single level.
876
     * @param array<mixed> $array
877
     * @param int $depth
878
     * @return array<mixed>
879
     */
880
    public static function flatten(array $array, int $depth = PHP_INT_MAX): array
881
    {
882
        $result = [];
883
        foreach ($array as $item) {
884
            if (!is_array($item)) {
885
                $result[] = $item;
886
            } elseif ($depth === 1) {
887
                $result = array_merge($result, array_values($item));
888
            } else {
889
                $result = array_merge(
890
                    $result,
891
                    static::flatten($item, $depth - 1)
892
                );
893
            }
894
        }
895
896
        return $result;
897
    }
898
899
    /**
900
     * find similar text from an array
901
     *
902
     * @param string $need
903
     * @param array<int, string>|Traversable<int|string, mixed> $array
904
     * @param int $percentage
905
     * @return array<int, string>
906
     */
907
    public static function similar(
908
        string $need,
909
        $array,
910
        int $percentage = 45
911
    ): array {
912
        if (empty($need)) {
913
            return [];
914
        }
915
916
        $similar = [];
917
        $percent = 0;
918
        foreach ($array as $name) {
919
            similar_text($need, $name, $percent);
920
            if ($percentage <= (int) $percent) {
921
                $similar[] = $name;
922
            }
923
        }
924
925
        return $similar;
926
    }
927
928
    /**
929
     * Return the array key max width
930
     * @param array<int|string, mixed> $array
931
     * @param bool $expectInt
932
     * @return int
933
     */
934
    public static function getKeyMaxWidth(array $array, bool $expectInt = false): int
935
    {
936
        $max = 0;
937
        foreach ($array as $key => $value) {
938
            if (!$expectInt || !is_numeric($key)) {
939
                $width = mb_strlen((string)$key, 'UTF-8');
940
                if ($width > $max) {
941
                    $max = $width;
942
                }
943
            }
944
        }
945
946
        return $max;
947
    }
948
949
    /**
950
     * Return the first element in an array passing a given truth test.
951
     * @param array<mixed> $array
952
     * @param callable $callable
953
     * @param mixed $default
954
     *
955
     * @return mixed
956
     */
957
    public static function first(
958
        array $array,
959
        callable $callable = null,
960
        $default = null
961
    ) {
962
        if ($callable === null) {
963
            if (empty($array)) {
964
                return $default;
965
            }
966
967
            foreach ($array as $value) {
968
                return $value;
969
            }
970
        }
971
972
        foreach ($array as $key => $value) {
973
            if ($callable($value, $key)) {
974
                return $value;
975
            }
976
        }
977
978
        return $default;
979
    }
980
981
    /**
982
     * Return the last element in an array passing a given truth test.
983
     * @param array<mixed> $array
984
     * @param callable $callable
985
     * @param mixed $default
986
     *
987
     * @return mixed
988
     */
989
    public static function last(
990
        array $array,
991
        callable $callable = null,
992
        $default = null
993
    ) {
994
        if ($callable === null) {
995
            if (empty($array)) {
996
                return $default;
997
            }
998
999
            return end($array);
1000
        }
1001
1002
        return static::first(array_reverse($array, true), $callable, $default);
1003
    }
1004
1005
    /**
1006
     * Filter the array using the given callback.
1007
     * @param array<mixed> $array
1008
     * @param callable $callable
1009
     * @return array<mixed>
1010
     */
1011
    public static function where(array $array, callable $callable): array
1012
    {
1013
        return array_filter($array, $callable, ARRAY_FILTER_USE_BOTH);
1014
    }
1015
1016
    /**
1017
     * Convert the array into a query string.
1018
     * @param array<mixed> $array
1019
     * @return string
1020
     */
1021
    public static function query(array $array): string
1022
    {
1023
        return http_build_query($array, '', '&', PHP_QUERY_RFC3986);
1024
    }
1025
1026
    /**
1027
     * Get a subset of the items from the given array.
1028
     * @param array<mixed> $array
1029
     * @param array<int, int|string> $keys
1030
     * @return array<mixed>
1031
     */
1032
    public static function only(array $array, array $keys): array
1033
    {
1034
        return array_intersect_key($array, array_flip($keys));
1035
    }
1036
1037
    /**
1038
     * Pluck an array of values from an arrays.
1039
     * @param array<int, array<mixed>> $array
1040
     * @param string|int $value
1041
     * @param string|int|null $key
1042
     * @return array<mixed>
1043
     */
1044
    public static function pluck(array $array, $value, $key = null): array
1045
    {
1046
        $results = [];
1047
        foreach ($array as $item) {
1048
            if (is_array($item)) {
1049
                $itemValue = static::get($item, $value);
1050
1051
                // If the key is "null", we will just append the value to the array
1052
                // and keep looping. Otherwise we will key the array using
1053
                // the value of the key we received from the developer.
1054
                // Then we'll return the final array form.
1055
                if ($key === null) {
1056
                    $results[] = $itemValue;
1057
                } else {
1058
                    $itemKey = static::get($item, $key);
1059
                    if (is_object($itemKey) && method_exists($itemKey, '__toString')) {
1060
                        $itemKey = (string)$itemKey;
1061
                    }
1062
1063
                    $results[$itemKey] = $itemValue;
1064
                }
1065
            }
1066
        }
1067
1068
        return $results;
1069
    }
1070
1071
    /**
1072
     * Collapse an array of arrays into a single array.
1073
     * @param array<mixed> $array
1074
     * @return array<mixed>
1075
     */
1076
    public static function collapse(array $array): array
1077
    {
1078
        $results = [];
1079
        foreach ($array as $values) {
1080
            if (!is_array($values)) {
1081
                continue;
1082
            }
1083
1084
            $results = array_merge($results, $values);
1085
        }
1086
1087
        return $results;
1088
    }
1089
1090
    /**
1091
     * Cross join the given arrays, returning all possible permutations.
1092
     * @param array<mixed> ...$arrays
1093
     * @return array<mixed>
1094
     */
1095
    public static function crossJoin(array ...$arrays): array
1096
    {
1097
        $results = [[]];
1098
        foreach ($arrays as $index => $array) {
1099
            $append = [];
1100
1101
            foreach ($results as $product) {
1102
                foreach ($array as $item) {
1103
                    $product[$index] = $item;
1104
1105
                    $append[] = $product;
1106
                }
1107
            }
1108
1109
            $results = $append;
1110
        }
1111
1112
        return $results;
1113
    }
1114
1115
    /**
1116
     *
1117
     * @param array<int|string, mixed> $array
1118
     * @param mixed $value
1119
     * @param mixed|null $key
1120
     * @return array<mixed>
1121
     */
1122
    public static function prepend(array $array, $value, $key = null): array
1123
    {
1124
        if ($key === null) {
1125
            array_unshift($array, $value);
1126
        } else {
1127
            $array = [$key => $value] + $array;
1128
        }
1129
1130
        return $array;
1131
    }
1132
1133
    /**
1134
     * Get one or a specified number of random values from an array.
1135
     * @param array<mixed> $array
1136
     * @param int|null $number
1137
     *
1138
     * @return mixed
1139
     */
1140
    public static function random(array $array, ?int $number = null)
1141
    {
1142
        $requested = $number === null ? 1 : $number;
1143
        $count = count($array);
1144
1145
        if ($requested > $count) {
1146
            throw new InvalidArgumentException(sprintf(
1147
                'You requested %d items, but there are only %d items available',
1148
                $requested,
1149
                $count
1150
            ));
1151
        }
1152
1153
        if ($number === null) {
1154
            return $array[array_rand($array)];
1155
        }
1156
1157
        if ($number === 0) {
1158
            return [];
1159
        }
1160
1161
        $keys = array_rand($array, $number);
1162
1163
        $results = [];
1164
        foreach ((array)$keys as $key) {
1165
            $results[] = $array[$key];
1166
        }
1167
1168
        return $results;
1169
    }
1170
1171
    /**
1172
     * Convert an array to string
1173
     * @param array<mixed> $array
1174
     * @param string $glue
1175
     * @return string
1176
     */
1177
    public static function toString(array $array, string $glue = '_'): string
1178
    {
1179
        return implode($glue, $array);
1180
    }
1181
1182
    /**
1183
     * Shuffle the given array and return the result.
1184
     * @param array<mixed> $array
1185
     * @param int|null $seed
1186
     * @return array<mixed>
1187
     */
1188
    public static function shuffle(array $array, ?int $seed = null): array
1189
    {
1190
        if ($seed === null) {
1191
            shuffle($array);
1192
        } else {
1193
            mt_srand($seed);
1194
            shuffle($array);
1195
            mt_srand();
1196
        }
1197
1198
        return $array;
1199
    }
1200
1201
    /**
1202
     * Normalize command line arguments like splitting "-abc" and "--xyz=...".
1203
     * @param array<int, string> $args
1204
     * @return array<string>
1205
     */
1206
    public static function normalizeArguments(array $args): array
1207
    {
1208
        $normalized = [];
1209
1210
        foreach ($args as $arg) {
1211
            if (preg_match('/^\-\w=/', $arg)) {
1212
                $normalized = array_merge(
1213
                    $normalized,
1214
                    (array)explode('=', $arg)
1215
                );
1216
            } elseif (preg_match('/^\-\w{2,}/', $arg)) {
1217
                $splitArgs = implode(' -', str_split(ltrim($arg, '-')));
0 ignored issues
show
Bug introduced by
It seems like str_split(ltrim($arg, '-')) can also be of type boolean; however, parameter $pieces of implode() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1217
                $splitArgs = implode(' -', /** @scrutinizer ignore-type */ str_split(ltrim($arg, '-')));
Loading history...
1218
                $normalized = array_merge(
1219
                    $normalized,
1220
                    (array)explode(' ', '-' . $splitArgs)
1221
                );
1222
            } elseif (preg_match('/^\-\-([^\s\=]+)\=/', $arg)) {
1223
                $normalized = array_merge(
1224
                    $normalized,
1225
                    (array)explode('=', $arg)
1226
                );
1227
            } else {
1228
                $normalized[] = $arg;
1229
            }
1230
        }
1231
1232
        return $normalized;
1233
    }
1234
}
1235