Passed
Push — develop ( c09191...4b5082 )
by nguereza
02:18
created

Arr::isArrayable()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 1
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 3
rs 10
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 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 $key
219
     * @param mixed $default
220
     *
221
     * @return mixed|null
222
     */
223
    public static function remove(array &$array, string $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<string>|string $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<string>|string $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
            $parts = explode('.', $key);
275
276
            $array = &$original;
277
            while (count($parts) > 1) {
278
                $part = array_shift($parts);
279
                if (isset($array[$part]) && is_array($array[$part])) {
280
                    $array = &$array[$part];
281
                } else {
282
                    continue 2;
283
                }
284
            }
285
286
            unset($array[array_shift($parts)]);
287
        }
288
    }
289
290
    /**
291
     * Get a value from the array, and remove it.
292
     * @param array<mixed> $array
293
     * @param string $key
294
     * @param mixed $default
295
     * @return mixed
296
     */
297
    public static function pull(array &$array, string $key, $default = null)
298
    {
299
        $value = static::get($array, $key, $default);
300
301
        static::forget($array, $key);
302
303
        return $value;
304
    }
305
306
    /**
307
     * Indexes and/or groups the array according to a specified key.
308
     * The input should be either multidimensional array or an array of objects.
309
     *
310
     * The $key can be either a key name of the sub-array, a property
311
     * name of object, or an anonymous function that must return the value
312
     * that will be used as a key.
313
     *
314
     * $groups is an array of keys, that will be used to group the input
315
     * array into one or more sub-arrays based on keys specified.
316
     *
317
     * If the `$key` is specified as `null` or a value of an element
318
     * corresponding to the key is `null` in addition to `$groups` not
319
     * specified then the element is discarded.
320
     *
321
     * @param array<mixed> $array
322
     * @param string|Closure|null   $key
323
     * @param string|string[]|Closure[]|null $groups
324
     * @return array<mixed> the indexed and/or grouped array
325
     */
326
    public static function index(array $array, $key, $groups = []): array
327
    {
328
        $result = [];
329
        if (!is_array($groups)) {
330
            $groups = (array) $groups;
331
        }
332
333
        foreach ($array as $element) {
334
            $lastArray = &$result;
335
            foreach ($groups as $group) {
336
                /** @var int|string $value */
337
                $value = static::getValue($element, $group);
338
                if (!empty($lastArray) && !array_key_exists($value, $lastArray)) {
339
                    $lastArray[$value] = [];
340
                }
341
                $lastArray = &$lastArray[$value];
342
            }
343
344
            if ($key === null) {
345
                if (!empty($groups)) {
346
                    $lastArray[] = $element;
347
                }
348
            } else {
349
                $value = static::getValue($element, $key);
350
                if ($value !== null) {
351
                    if (is_float($value)) {
352
                        $value = (string) $value;
353
                    }
354
                    $lastArray[$value] = $element;
355
                }
356
            }
357
            unset($lastArray);
358
        }
359
360
        return $result;
361
    }
362
363
    /**
364
     * Returns the values of a specified column in an array.
365
     * The input array should be multidimensional or an array of objects.
366
     * @param array<mixed> $array
367
     * @param string|Closure $name
368
     * @param bool $keepKeys
369
     * @return array<mixed>
370
     */
371
    public static function getColumn(array $array, $name, bool $keepKeys = true): array
372
    {
373
        $result = [];
374
        if ($keepKeys) {
375
            foreach ($array as $key => $element) {
376
                $result[$key] = static::getValue($element, $name);
377
            }
378
        } else {
379
            foreach ($array as $element) {
380
                $result[] = static::getValue($element, $name);
381
            }
382
        }
383
384
        return $result;
385
    }
386
387
    /**
388
     * Builds a map (key-value pairs) from a multidimensional array or
389
     * an array of objects.
390
     * The `$from` and `$to` parameters specify the key names or property
391
     * names to set up the map.
392
     * Optionally, one can further group the map according to a
393
     * grouping field `$group`.
394
     *
395
     * @param array<mixed> $array
396
     * @param string|Closure $from
397
     * @param string|Closure $to
398
     * @param string|Closure|null $group
399
     * @return array<mixed>
400
     */
401
    public static function map(array $array, $from, $to, $group = null): array
402
    {
403
        $result = [];
404
        foreach ($array as $element) {
405
            $key = static::getValue($element, $from);
406
            $value = static::getValue($element, $to);
407
            if ($group !== null) {
408
                $result[static::getValue($element, $group)][$key] = $value;
409
            } else {
410
                $result[$key] = $value;
411
            }
412
        }
413
414
        return $result;
415
    }
416
417
    /**
418
     * Checks if the given array contains the specified key.
419
     * This method enhances the `array_key_exists()` function by supporting
420
     * case-insensitive key comparison.
421
     *
422
     * @param string $key
423
     * @param array<mixed> $array
424
     * @param bool $caseSensative
425
     * @return bool
426
     */
427
    public static function keyExists(
428
        string $key,
429
        array $array,
430
        bool $caseSensative = true
431
    ): bool {
432
        if ($caseSensative) {
433
            return array_key_exists($key, $array);
434
        }
435
436
        foreach (array_keys($array) as $k) {
437
            if (strcasecmp($key, $k) === 0) {
438
                return true;
439
            }
440
        }
441
442
        return false;
443
    }
444
445
    /**
446
     * Sorts an array of objects or arrays (with the same structure) by one
447
     * or several keys.
448
     * @param array<mixed> $array
449
     * @param string|Closure|array<string> $key the key(s) to be sorted by.
450
     *     This refers to a key name of the sub-array elements, a property name
451
     *      of the objects, or an anonymous function returning the values
452
     *   for comparison purpose. The anonymous function signature
453
     * should be: `function($item)`.
454
     * @param int|array<int> $direction
455
     * @param int|array<int> $sortFlag
456
     * @return void
457
     */
458
    public static function multisort(
459
        array &$array,
460
        $key,
461
        $direction = SORT_ASC,
462
        $sortFlag = SORT_REGULAR
463
    ): void {
464
        $keys = is_array($key) ? $key : [$key];
465
466
        if (empty($keys) || empty($array)) {
467
            return;
468
        }
469
470
        $count  = count($keys);
471
        if (is_scalar($direction)) {
472
            $direction = array_fill(0, $count, $direction);
473
        } elseif (count($direction) !== $count) {
474
            throw new InvalidArgumentException(
475
                'The length of the sort direction must be the same '
476
                    . 'as that of sort keys.'
477
            );
478
        }
479
        if (is_scalar($sortFlag)) {
480
            $sortFlag = array_fill(0, $count, $sortFlag);
481
        } elseif (count($sortFlag) !== $count) {
482
            throw new InvalidArgumentException(
483
                'The length of the sort flag must be the same '
484
                    . 'as that of sort keys.'
485
            );
486
        }
487
488
        /** @var array<int, mixed> $args */
489
        $args = [];
490
        foreach ($keys as $i => $k) {
491
            $flag = $sortFlag[$i];
492
            $args[] = static::getColumn($array, $k);
493
            $args[] = $direction[$i];
494
            $args[] = $flag;
495
        }
496
497
        // This fix is used for cases when main sorting specified by
498
        // columns has equal values
499
        // Without it it will lead to Fatal Error: Nesting level
500
        // too deep - recursive dependency?
501
        $args[] = range(1, count($array));
502
        $args[] = SORT_ASC;
503
        $args[] = SORT_NUMERIC;
504
        $args[] = &$array;
505
506
        array_multisort(...$args);
507
    }
508
509
    /**
510
     * Check whether the given array is an associative array.
511
     *
512
     * An array is associative if all its keys are strings.
513
     * If `$allStrings` is false, then an array will be treated as associative
514
     * if at least one of its keys is a string.
515
     *
516
     * Note that an empty array will NOT be considered associative.
517
     *
518
     * @param array<mixed> $array
519
     * @param bool $allStrings
520
     * @return bool
521
     */
522
    public static function isAssoc(array $array, bool $allStrings = true): bool
523
    {
524
        if (empty($array)) {
525
            return false;
526
        }
527
528
        if ($allStrings) {
529
            foreach ($array as $key => $value) {
530
                if (!is_string($key)) {
531
                    return false;
532
                }
533
            }
534
535
            return true;
536
        } else {
537
            foreach ($array as $key => $value) {
538
                if (is_string($key)) {
539
                    return true;
540
                }
541
            }
542
543
            return false;
544
        }
545
    }
546
547
    /**
548
     * Whether the given array is an indexed array.
549
     *
550
     * An array is indexed if all its keys are integers.
551
     * If `$consecutive` is true, then the array keys must be a consecutive
552
     * sequence starting from 0.
553
     *
554
     * Note that an empty array will be considered indexed.
555
     *
556
     * @param array<mixed> $array
557
     * @param bool $consecutive
558
     * @return bool
559
     */
560
    public static function isIndexed(array $array, bool $consecutive = false): bool
561
    {
562
        if (empty($array)) {
563
            return true;
564
        }
565
566
        if ($consecutive) {
567
            return array_keys($array) === range(0, count($array) - 1);
568
        } else {
569
            foreach ($array as $key => $value) {
570
                if (!is_int($key)) {
571
                    return false;
572
                }
573
            }
574
575
            return true;
576
        }
577
    }
578
579
    /**
580
     * Check whether an array or Traversable contains an element.
581
     *
582
     * This method does the same as the PHP function in_array()
583
     * but additionally works for objects that implement the
584
     * Traversable interface.
585
     *
586
     * @param mixed $needle
587
     * @param array<mixed>|Traversable<int|string, mixed> $array
588
     * @param bool $strict
589
     * @return bool
590
     */
591
    public static function isIn($needle, $array, bool $strict = false): bool
592
    {
593
        if ($array instanceof Traversable) {
594
            $array = iterator_to_array($array);
595
            foreach ($array as $value) {
596
                if ($needle == $value && (!$strict || $needle === $value)) {
597
                    return true;
598
                }
599
            }
600
        }
601
602
        return in_array($needle, $array, $strict);
603
    }
604
605
    /**
606
     * Checks whether a variable is an array or Traversable.
607
     * @param mixed $var
608
     * @return bool
609
     */
610
    public static function isTraversable($var): bool
611
    {
612
        return is_array($var) || $var instanceof Traversable;
613
    }
614
615
    /**
616
     * Checks whether an array or Traversable is a subset of another array
617
     * or Traversable.
618
     *
619
     * This method will return `true`, if all elements of `$needles`
620
     * are contained in `$array`. If at least one element is missing,
621
     * `false` will be returned.
622
     *
623
     * @param array<mixed>|Traversable<int|string, mixed> $needles
624
     * @param array<mixed>|Traversable<int|string, mixed> $array
625
     * @param bool $strict
626
     * @return bool
627
     */
628
    public static function isSubset($needles, $array, bool $strict = false): bool
629
    {
630
        foreach ($needles as $needle) {
631
            if (!static::isIn($needle, $array, $strict)) {
632
                return false;
633
            }
634
        }
635
636
        return true;
637
    }
638
639
    /**
640
     * Filters array according to rules specified.
641
     * @param array<mixed> $array
642
     * @param array<string> $filters Rules that define array keys which should
643
     *  be left or removed from results.
644
     *         Each rule is:
645
     *            - `var` - `$array['var']` will be left in result.
646
     *            - `var.key` = only `$array['var']['key'] will be left in result.
647
     *            - `!var.key` = `$array['var']['key'] will be removed from result.
648
     * @return array<mixed>
649
     */
650
    public static function filter(array $array, array $filters): array
651
    {
652
        $result = [];
653
        $tobeRemoved = [];
654
655
        foreach ($filters as $filter) {
656
            $keys = explode('.', $filter);
657
            $globalKey = $keys[0];
658
            $localkey = $keys[1] ?? null;
659
            if ($globalKey[0] === '!') {
660
                $tobeRemoved[] = [
661
                    substr($globalKey, 1),
662
                    $localkey
663
                ];
664
665
                continue;
666
            }
667
668
            if (empty($array[$globalKey])) {
669
                continue;
670
            }
671
672
            if ($localkey === null) {
673
                $result[$globalKey] = $array[$globalKey];
674
                continue;
675
            }
676
677
            if (!isset($array[$globalKey][$localkey])) {
678
                continue;
679
            }
680
681
            if (array_key_exists($globalKey, $result)) {
682
                $result[$globalKey] = [];
683
            }
684
685
            $result[$globalKey][$localkey] = $array[$globalKey][$localkey];
686
        }
687
688
        foreach ($tobeRemoved as $value) {
689
            [$globalKey, $localkey] = $value;
690
            if (array_key_exists($globalKey, $result)) {
691
                unset($result[$globalKey][$localkey]);
692
            }
693
        }
694
695
        return $result;
696
    }
697
698
    /**
699
     * Checks whether a variable is an array accessible.
700
     * @param mixed $var
701
     * @return bool
702
     */
703
    public static function isAccessible($var): bool
704
    {
705
        return is_array($var) || $var instanceof ArrayAccess;
706
    }
707
708
    /**
709
     * Checks whether a variable is an array or instance of Arrayable.
710
     * @param mixed $var
711
     * @return bool
712
     */
713
    public static function isArrayable($var): bool
714
    {
715
        return is_array($var) || $var instanceof Arrayable;
716
    }
717
718
    /**
719
     * If the given value is not an array and not null, wrap it in one.
720
     * @param mixed $var
721
     * @return array<mixed>
722
     */
723
    public static function wrap($var): array
724
    {
725
        if ($var === null) {
726
            return [];
727
        }
728
        return is_array($var) ? $var : [$var];
729
    }
730
731
    /**
732
     * Check whether the given key exists in the provided array.
733
     *
734
     * @param array<mixed>|ArrayAccess<string|int, mixed> $array
735
     * @param string|int $key
736
     * @return bool
737
     */
738
    public static function exists($array, $key): bool
739
    {
740
        if (is_array($array)) {
741
            return array_key_exists($key, $array);
742
        }
743
744
        return $array->offsetExists($key);
745
    }
746
747
    /**
748
     * Get an item from an array using "dot" notation.
749
     *
750
     * @param array<mixed>|ArrayAccess<string|int, mixed> $array
751
     * @param string|null $key
752
     * @param mixed $default
753
     * @return mixed
754
     */
755
    public static function get($array, ?string $key = null, $default = null)
756
    {
757
        if ($key === null) {
758
            return $array;
759
        }
760
761
        if (isset($array[$key])) {
762
            return $array[$key];
763
        }
764
765
        // Fix: If is int, stop continue find.
766
        if (!is_string($key)) {
0 ignored issues
show
introduced by
The condition is_string($key) is always true.
Loading history...
767
            return $default;
768
        }
769
770
        foreach (explode('.', $key) as $segment) {
771
            if (static::isAccessible($array) && static::exists($array, $segment)) {
772
                $array = $array[$segment];
773
            } else {
774
                return $default;
775
            }
776
        }
777
778
        return $array;
779
    }
780
781
    /**
782
     * Check if an item exists in an array using "dot" notation.
783
     *
784
     * @param array<mixed>|ArrayAccess<string|int, mixed> $array
785
     * @param string $key
786
     * @return bool
787
     */
788
    public static function has($array, string $key): bool
789
    {
790
        if (empty($array)) {
791
            return false;
792
        }
793
794
        if (
795
            is_array($array) && array_key_exists($key, $array)
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (is_array($array) && arr...ray->offsetExists($key), Probably Intended Meaning: is_array($array) && (arr...ay->offsetExists($key))
Loading history...
796
            || $array instanceof ArrayAccess && $array->offsetExists($key)
797
        ) {
798
            return true;
799
        }
800
801
        foreach (explode('.', $key) as $segment) {
802
            if (
803
                ((is_array($array) && array_key_exists($segment, $array))
804
                || ($array instanceof ArrayAccess
805
                        && $array->offsetExists($segment)))
806
            ) {
807
                $array = $array[$segment];
808
            } else {
809
                return false;
810
            }
811
        }
812
813
        return true;
814
    }
815
816
    /**
817
     *
818
     * @param array<mixed> $array
819
     * @param string|null $key
820
     * @param mixed $value
821
     * @return array<mixed>
822
     */
823
    public static function set(array &$array, ?string $key, $value): array
824
    {
825
        if ($key === null) {
826
            return $array = $value;
827
        }
828
829
        $keys = explode('.', $key);
830
831
        while (count($keys) > 1) {
832
            $key = array_shift($keys);
833
834
            // If the key doesn't exist at this depth, we will just create
835
            // an empty array hold the next value, allowing us to create
836
            // the arrays to hold final values at the correct depth.
837
            // Then we'll keep digging into the array.
838
            if (!isset($array[$key]) || !is_array($array[$key])) {
839
                $array[$key] = [];
840
            }
841
842
            $array = &$array[$key];
843
        }
844
845
        $array[array_shift($keys)] = $value;
846
847
        return $array;
848
    }
849
850
    /**
851
     * Insert one array to another array
852
     *
853
     * @param array<mixed> $array
854
     * @param int $index
855
     * @param array<mixed> ...$inserts
856
     * @return void
857
     */
858
    public static function insert(
859
        array &$array,
860
        int $index,
861
        array ...$inserts
862
    ): void {
863
        $first = array_splice($array, 0, $index);
864
        $array = array_merge($first, $inserts, $array);
865
    }
866
867
    /**
868
     * Flatten a multi-dimensional array into a single level.
869
     * @param array<mixed> $array
870
     * @param int $depth
871
     * @return array<mixed>
872
     */
873
    public static function flatten(array $array, int $depth = PHP_INT_MAX): array
874
    {
875
        $result = [];
876
        foreach ($array as $item) {
877
            if (!is_array($item)) {
878
                $result[] = $item;
879
            } elseif ($depth === 1) {
880
                $result = array_merge($result, array_values($item));
881
            } else {
882
                $result = array_merge(
883
                    $result,
884
                    static::flatten($item, $depth - 1)
885
                );
886
            }
887
        }
888
889
        return $result;
890
    }
891
892
    /**
893
     * find similar text from an array
894
     *
895
     * @param string $need
896
     * @param array<int, string>|Traversable<int|string, mixed> $array
897
     * @param int $percentage
898
     * @return array<int, string>
899
     */
900
    public static function similar(
901
        string $need,
902
        $array,
903
        int $percentage = 45
904
    ): array {
905
        if (empty($need)) {
906
            return [];
907
        }
908
909
        $similar = [];
910
        $percent = 0;
911
        foreach ($array as $name) {
912
            similar_text($need, $name, $percent);
913
            if ($percentage <= (int) $percent) {
914
                $similar[] = $name;
915
            }
916
        }
917
918
        return $similar;
919
    }
920
921
    /**
922
     * Return the array key max width
923
     * @param array<int|string, mixed> $array
924
     * @param bool $expectInt
925
     * @return int
926
     */
927
    public static function getKeyMaxWidth(array $array, bool $expectInt = false): int
928
    {
929
        $max = 0;
930
        foreach ($array as $key => $value) {
931
            if (!$expectInt || !is_numeric($key)) {
932
                $width = mb_strlen((string)$key, 'UTF-8');
933
                if ($width > $max) {
934
                    $max = $width;
935
                }
936
            }
937
        }
938
939
        return $max;
940
    }
941
942
    /**
943
     * Return the first element in an array passing a given truth test.
944
     * @param array<mixed> $array
945
     * @param callable $callable
946
     * @param mixed $default
947
     *
948
     * @return mixed
949
     */
950
    public static function first(
951
        array $array,
952
        callable $callable = null,
953
        $default = null
954
    ) {
955
        if ($callable === null) {
956
            if (empty($array)) {
957
                return $default;
958
            }
959
960
            foreach ($array as $value) {
961
                return $value;
962
            }
963
        }
964
965
        foreach ($array as $key => $value) {
966
            if ($callable($value, $key)) {
967
                return $value;
968
            }
969
        }
970
971
        return $default;
972
    }
973
974
    /**
975
     * Return the last element in an array passing a given truth test.
976
     * @param array<mixed> $array
977
     * @param callable $callable
978
     * @param mixed $default
979
     *
980
     * @return mixed
981
     */
982
    public static function last(
983
        array $array,
984
        callable $callable = null,
985
        $default = null
986
    ) {
987
        if ($callable === null) {
988
            if (empty($array)) {
989
                return $default;
990
            }
991
992
            return end($array);
993
        }
994
995
        return static::first(array_reverse($array, true), $callable, $default);
996
    }
997
998
    /**
999
     * Filter the array using the given callback.
1000
     * @param array<mixed> $array
1001
     * @param callable $callable
1002
     * @return array<mixed>
1003
     */
1004
    public static function where(array $array, callable $callable): array
1005
    {
1006
        return array_filter($array, $callable, ARRAY_FILTER_USE_BOTH);
1007
    }
1008
1009
    /**
1010
     * Convert the array into a query string.
1011
     * @param array<mixed> $array
1012
     * @return string
1013
     */
1014
    public static function query(array $array): string
1015
    {
1016
        return http_build_query($array, '', '&', PHP_QUERY_RFC3986);
1017
    }
1018
1019
    /**
1020
     * Get a subset of the items from the given array.
1021
     * @param array<mixed> $array
1022
     * @param array<int, int|string> $keys
1023
     * @return array<mixed>
1024
     */
1025
    public static function only(array $array, array $keys): array
1026
    {
1027
        return array_intersect_key($array, array_flip($keys));
1028
    }
1029
1030
    /**
1031
     * Pluck an array of values from an array.
1032
     * @param array<int, array<mixed>> $array
1033
     * @param string $value
1034
     * @param string|null $key
1035
     * @return array<mixed>
1036
     */
1037
    public static function pluck(array $array, string $value, ?string $key = null): array
1038
    {
1039
        $results = [];
1040
        foreach ($array as $item) {
1041
            $itemValue = static::get($item, $value);
1042
1043
            // If the key is "null", we will just append the value to the array
1044
            // and keep looping. Otherwise we will key the array using
1045
            // the value of the key we received from the developer.
1046
            // Then we'll return the final array form.
1047
            if ($key === null) {
1048
                $results[] = $itemValue;
1049
            } else {
1050
                $itemKey = static::get($item, $key);
1051
                if (is_object($itemKey) && method_exists($itemKey, '__toString')) {
1052
                    $itemKey = (string)$itemKey;
1053
                }
1054
1055
                $results[$itemKey] = $itemValue;
1056
            }
1057
        }
1058
1059
        return $results;
1060
    }
1061
1062
    /**
1063
     * Collapse an array of arrays into a single array.
1064
     * @param array<mixed> $array
1065
     * @return array<mixed>
1066
     */
1067
    public static function collapse(array $array): array
1068
    {
1069
        $results = [];
1070
        foreach ($array as $values) {
1071
            if (!is_array($values)) {
1072
                continue;
1073
            }
1074
1075
            $results = array_merge($results, $values);
1076
        }
1077
1078
        return $results;
1079
    }
1080
1081
    /**
1082
     * Cross join the given arrays, returning all possible permutations.
1083
     * @param array<mixed> ...$arrays
1084
     * @return array<mixed>
1085
     */
1086
    public static function crossJoin(array ...$arrays): array
1087
    {
1088
        $results = [[]];
1089
        foreach ($arrays as $index => $array) {
1090
            $append = [];
1091
1092
            foreach ($results as $product) {
1093
                foreach ($array as $item) {
1094
                    $product[$index] = $item;
1095
1096
                    $append[] = $product;
1097
                }
1098
            }
1099
1100
            $results = $append;
1101
        }
1102
1103
        return $results;
1104
    }
1105
1106
    /**
1107
     *
1108
     * @param array<int|string, mixed> $array
1109
     * @param mixed $value
1110
     * @param mixed|null $key
1111
     * @return array<mixed>
1112
     */
1113
    public static function prepend(array $array, $value, $key = null): array
1114
    {
1115
        if ($key === null) {
1116
            array_unshift($array, $value);
1117
        } else {
1118
            $array = [$key => $value] + $array;
1119
        }
1120
1121
        return $array;
1122
    }
1123
1124
    /**
1125
     * Get one or a specified number of random values from an array.
1126
     * @param array<mixed> $array
1127
     * @param int|null $number
1128
     *
1129
     * @return mixed
1130
     */
1131
    public static function random(array $array, ?int $number = null)
1132
    {
1133
        $requested = $number === null ? 1 : $number;
1134
        $count = count($array);
1135
1136
        if ($requested > $count) {
1137
            throw new InvalidArgumentException(sprintf(
1138
                'You requested %d items, but there are only %d items available',
1139
                $requested,
1140
                $count
1141
            ));
1142
        }
1143
1144
        if ($number === null) {
1145
            return $array[array_rand($array)];
1146
        }
1147
1148
        if ($number === 0) {
1149
            return [];
1150
        }
1151
1152
        $keys = array_rand($array, $number);
1153
1154
        $results = [];
1155
        foreach ((array)$keys as $key) {
1156
            $results[] = $array[$key];
1157
        }
1158
1159
        return $results;
1160
    }
1161
1162
    /**
1163
     * Convert an array to string
1164
     * @param array<mixed> $array
1165
     * @param string $glue
1166
     * @return string
1167
     */
1168
    public static function toString(array $array, string $glue = '_'): string
1169
    {
1170
        return implode($glue, $array);
1171
    }
1172
1173
    /**
1174
     * Shuffle the given array and return the result.
1175
     * @param array<mixed> $array
1176
     * @param int|null $seed
1177
     * @return array<mixed>
1178
     */
1179
    public static function shuffle(array $array, ?int $seed = null): array
1180
    {
1181
        if ($seed === null) {
1182
            shuffle($array);
1183
        } else {
1184
            mt_srand($seed);
1185
            shuffle($array);
1186
            mt_srand();
1187
        }
1188
1189
        return $array;
1190
    }
1191
1192
    /**
1193
     * Normalize command line arguments like splitting "-abc" and "--xyz=...".
1194
     * @param array<int, string> $args
1195
     * @return array<string>
1196
     */
1197
    public static function normalizeArguments(array $args): array
1198
    {
1199
        $normalized = [];
1200
1201
        foreach ($args as $arg) {
1202
            if (preg_match('/^\-\w=/', $arg)) {
1203
                $normalized = array_merge(
1204
                    $normalized,
1205
                    (array)explode('=', $arg)
1206
                );
1207
            } elseif (preg_match('/^\-\w{2,}/', $arg)) {
1208
                $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 true; 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

1208
                $splitArgs = implode(' -', /** @scrutinizer ignore-type */ str_split(ltrim($arg, '-')));
Loading history...
1209
                $normalized = array_merge(
1210
                    $normalized,
1211
                    (array)explode(' ', '-' . $splitArgs)
1212
                );
1213
            } elseif (preg_match('/^\-\-([^\s\=]+)\=/', $arg)) {
1214
                $normalized = array_merge(
1215
                    $normalized,
1216
                    (array)explode('=', $arg)
1217
                );
1218
            } else {
1219
                $normalized[] = $arg;
1220
            }
1221
        }
1222
1223
        return $normalized;
1224
    }
1225
}
1226