Passed
Push — develop ( 05b73e...53ba47 )
by Glynn
03:58 queued 01:22
created

pick()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Array functions.
5
 *
6
 * This file is part of PinkCrab Function Constructors.
7
 *
8
 * PinkCrab Function Constructors is free software: you can redistribute it and/or modify it under the terms of the
9
 * GNU General Public License as published by the Free Software Foundation, either version 2
10
 * of the License, or (at your option) any later version.
11
 *
12
 * PinkCrab Function Constructors is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
13
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14
 * See the GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License along with PinkCrab Function Constructors.
17
 * If not, see <https://www.gnu.org/licenses/>.
18
 *
19
 * @author Glynn Quelch <[email protected]>
20
 * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
21
 * @package PinkCrab\FunctionConstructors
22
 * @since 0.0.1
23
 *
24
 * @template Number of int|float
25
 * @phpstan-template Number of int|float
26
 * @psalm-template Number of int|float
27
 */
28
29
declare(strict_types=1);
30
31
namespace PinkCrab\FunctionConstructors\Arrays;
32
33
use Closure;
34
use stdClass;
35
use PinkCrab\FunctionConstructors\Comparisons as Comp;
36
37
/**
38
 * Returns a Closure for appending a value to an array.
39
 *
40
 * @param mixed $value
41
 * @return Closure(array<int|string, mixed>):array<int|string, mixed>
42
 */
43
function append($value): Closure
44
{
45
    /**
46
     * @param array<int|string, mixed> $array
47
     * @return array<int|string, mixed>
48
     */
49
    return function (array $array) use ($value): array {
50
        $array[] = $value;
51
        return $array;
52
    };
53
}
54
55
/**
56
 * Returns a Closure for prepending a value to an array.
57
 *
58
 * @param mixed $value
59
 * @return Closure(array<int|string, mixed>):array<int|string, mixed>
60
 */
61
function prepend($value): Closure
62
{
63
    /**
64
     * @param array<int|string, mixed> $array
65
     * @return array<int|string, mixed>
66
     */
67
    return function (array $array) use ($value): array {
68
        array_unshift($array, $value);
69
        return $array;
70
    };
71
}
72
73
/**
74
 * Returns a Closure for pushing a value to the head of an array
75
 *
76
 * @param array<int|string, mixed> $array
77
 * @return Closure(mixed):array<int|string, mixed>
78
 * @deprecated 0.3.0 Use prepend() instead.
79
 * @codeCoverageIgnore
80
 */
81
function pushHead(array $array): Closure
82
{
83
    trigger_error('Deprecated function called. This function will be removed in later versions.', E_USER_DEPRECATED);
84
    /**
85
     * @param mixed $value Adds value start of array.
86
     * @return array New array with value on head.
87
     */
88
    return function ($value) use ($array): array {
89
        array_unshift($array, $value);
90
        return $array;
91
    };
92
}
93
94
/**
95
 * Returns a Closure for pushing a value to the head of an array
96
 *
97
 * @param array<int|string, mixed> $array
98
 * @return Closure(mixed):array<int|string, mixed>
99
 * @deprecated 0.3.0 Use append() instead.
100
 * @codeCoverageIgnore
101
 */
102
function pushTail(array $array): Closure
103
{
104
    trigger_error('Deprecated function called. This function will be removed in later versions.', E_USER_DEPRECATED);
105
    /**
106
     * @param mixed $value Adds value end of array.
107
     * @return array<int|string, mixed> New array with value on tail.
108
     */
109
    return function ($value) use ($array): array {
110
        $array[] = $value;
111
        return $array;
112
    };
113
}
114
115
/**
116
 * Gets the first value from an array.
117
 *
118
 * @param array<int|string, mixed> $array The array.
119
 * @return mixed Will return the first value is array is not empty, else null.
120
 */
121
function head(array $array)
122
{
123
    return !empty($array) ? array_values($array)[0] : null;
124
}
125
126
/**
127
 * Gets the last value from an array.
128
 *
129
 * @param array<int|string, mixed> $array The array.
130
 * @return mixed Will return the last value is array is not empty, else null.
131
 */
132
function last(array $array)
133
{
134
    return !empty($array) ? array_reverse($array, false)[0] : null;
135
}
136
137
/**
138
 * Gets the remainder values from an array, after first item removed.
139
 *
140
 * @param array<int|string, mixed> $array
141
 * @return array<int|string, mixed>|null Will return the first value is array is not empty, else null.
142
 */
143
function tail(array $array)
144
{
145
    // Return null if empty.
146
    if (empty($array)) {
147
        return null;
148
    }
149
150
    // Remove the first item from the array.
151
    array_shift($array);
152
    return $array;
153
}
154
155
156
/**
157
 * Creates a Closure for concatenating arrays with a defined glue.
158
 *
159
 * @param string|null $glue The string to join each element. If null, will be no separation between elements.
160
 * @return Closure(array<int|string, mixed>):string
161
 *
162
 */
163
function toString(?string $glue = null): Closure
164
{
165
    /**
166
     * @param array<int|string, mixed> $array Array join
167
     * @return string
168
     */
169
    return function (array $array) use ($glue): string {
170
        return $glue ? \join($glue, $array) : \join($array);
171
    };
172
}
173
174
/**
175
 * Creates a Closure for zipping 2 arrays.
176
 *
177
 * @param array<mixed> $additional Values with the same key will be paired.
178
 * @param mixed $default The fallback value if the additional array doesn't have the same length
179
 * @return Closure(array<mixed>):array<array{mixed, mixed}>
180
 *
181
 */
182
function zip(array $additional, $default = null): Closure
183
{
184
    $additional = array_values($additional);
185
    return function (array $array) use ($additional, $default) {
186
        $array = array_values($array);
187
        return array_reduce(
188
            array_keys($array),
189
            function ($carry, $key) use ($array, $additional, $default): array {
190
                $carry[] = array(
191
                    $array[$key],
192
                    array_key_exists($key, $additional) ? $additional[$key] : $default,
193
                );
194
                return $carry;
195
            },
196
            array()
197
        );
198
    };
199
}
200
201
202
/*
203
 *                                ********************
204
 *                                * Filter Compilers *
205
 *                                ********************
206
 */
207
208
209
/**
210
 * Compiles an array if a value is passed.
211
 * Returns the array if nothing passed.
212
 *
213
 * @param mixed[] $inital Sets up the inner value.
214
 * @return Closure
215
 */
216
function arrayCompiler(array $inital = array()): Closure
217
{
218
    /**
219
     * @param mixed $value Adds value to inner array if value set, else returns.
220
     * @return mixed[]|Closure
221
     */
222
    return function ($value = null) use ($inital) {
223
        if ($value) {
224
            $inital[] = $value;
225
        }
226
        return !is_null($value) ? arrayCompiler($inital) : $inital;
227
    };
228
}
229
230
/**
231
 * Creates a typed array compiler.
232
 * All values which do not pass the validator are not added.
233
 *
234
 * @param Closure(mixed):bool $validator Used to validate values before adding to array.
235
 * @param mixed[] $inital The inital data to start with
236
 * @return Closure
237
 */
238
function arrayCompilerTyped(callable $validator, array $inital = array()): Closure
239
{
240
    // Ensure all is validated from initial.
241
    $inital = array_filter($inital, $validator);
242
243
    /**
244
     * @param mixed $value
245
     * @return mixed[]|Closure
246
     */
247
    return function ($value = null) use ($validator, $inital) {
248
        if (!is_null($value) && $validator($value)) {
249
            $inital[] = $value;
250
        }
251
        return !is_null($value) ? arrayCompilerTyped($validator, $inital) : $inital;
252
    };
253
}
254
255
256
257
/*
258
 *                                ********************
259
 *                                * Filter Functions *
260
 *                                ********************
261
 */
262
263
264
/**
265
 * Created a Closure for filtering an array.
266
 *
267
 * @param callable $callable The function to apply to the array.
268
 * @return Closure(array<int|string, mixed>):array<int|string, mixed>
269
 */
270
function filter(callable $callable): Closure
271
{
272
    /**
273
     * @param array<int|string, mixed> $source Array to filter
274
     * @return array<int|string, mixed> Filtered array.
275
     */
276
    return function (array $source) use ($callable): array {
277
        return array_filter($source, $callable);
278
    };
279
}
280
281
/**
282
 * Create a Closure for filtering an array by a key.
283
 *
284
 * @param callable $callable The function to apply to the array.
285
 * @return Closure(array<int|string, mixed>):array<int|string, mixed>
286
 */
287
function filterKey(callable $callable): Closure
288
{
289
    /**
290
     * @param array<int|string, mixed> $source Array to filter
291
     * @return array<int|string, mixed> Filtered array.
292
     */
293
    return function (array $source) use ($callable): array {
294
        return array_filter($source, $callable, \ARRAY_FILTER_USE_KEY);
295
    };
296
}
297
298
/**
299
 * Creates a Closure for running an array through various callbacks for all true response.
300
 * Wrapper for creating a AND group of callbacks and running through array filter.
301
 *
302
 * @param callable ...$callables
303
 * @return Closure(array<int|string, mixed>):array<int|string, mixed>
304
 */
305
function filterAnd(callable ...$callables): Closure
306
{
307
    /**
308
     * @param array<int|string, mixed> $source Array to filter
309
     * @return array<int|string, mixed> Filtered array.
310
     */
311
    return function (array $source) use ($callables): array {
312
        return array_filter($source, Comp\groupAnd(...$callables));
313
    };
314
}
315
316
/**
317
 * Creates a Closure for running an array through various callbacks for any true response.
318
 * Wrapper for creating a OR group of callbacks and running through array filter.
319
 *
320
 * @param callable ...$callables
321
 * @return Closure(array<int|string, mixed>):array<int|string, mixed>
322
 */
323
function filterOr(callable ...$callables): Closure
324
{
325
    /**
326
     * @param array<int|string, mixed> $source Array to filter
327
     * @return array<int|string, mixed> Filtered array.
328
     */
329
    return function (array $source) use ($callables): array {
330
        return array_filter($source, Comp\groupOr(...$callables));
331
    };
332
}
333
334
/**
335
 * Creates a Closure for running array filter and getting the first value.
336
 *
337
 * @param callable $func
338
 * @return Closure(array<int|string, mixed>):?mixed
339
 */
340
function filterFirst(callable $func): Closure
341
{
342
    /**
343
     * @param array<int|string, mixed> $array The array to filter
344
     * @return mixed|null The first element from the filtered array or null if filter returns empty
345
     */
346
    return function (array $array) use ($func) {
347
        foreach ($array as $value) {
348
            $result = $func($value);
349
            if (\is_bool($result) && $result) {
350
                return $value;
351
            }
352
        }
353
    };
354
}
355
356
/**
357
 * Creates a Closure for running array filter and getting the last value.
358
 *
359
 * @param callable $func
360
 * @return Closure(array<int|string, mixed>):?mixed
361
 */
362
function filterLast(callable $func): Closure
363
{
364
    /**
365
     * @param array<int|string, mixed> $array The array to filter
366
     * @return mixed|null The last element from the filtered array.
367
     */
368
    return function (array $array) use ($func) {
369
        while ($value = array_pop($array)) {
370
            $result = $func($value);
371
            if (\is_bool($result) && $result) {
372
                return $value;
373
            }
374
        }
375
    };
376
}
377
378
/**
379
 * Creates a Closure which takes an array, applies a filter, then maps the
380
 * results of the map.
381
 *
382
 *
383
 * @param callable(mixed):bool $filter Function to of filter contents
384
 * @param callable(mixed):mixed $map Function to map results of filter function.
385
 * @return Closure(array<int|string, mixed>):array<int|string, mixed>
386
 */
387
function filterMap(callable $filter, callable $map): Closure
388
{
389
    /**
390
     * @param array<int|string, mixed> $array The array to filter then map.
391
     * @return array<int|string, mixed>
392
     */
393
    return function (array $array) use ($filter, $map): array {
394
        return array_map($map, array_filter($array, $filter));
395
    };
396
}
397
398
/**
399
 * Runs an array through a filters, returns the total count of true
400
 *
401
 * @param callable $function
402
 * @return Closure(array<int|string, mixed>):int
403
 */
404
function filterCount(callable $function): Closure
405
{
406
    /**
407
     * @param array<int|string, mixed> $array
408
     * @return int Count
409
     */
410
    return function (array $array) use ($function) {
411
        return count(array_filter($array, $function));
412
    };
413
}
414
415
/**
416
 * Returns a Closure for partitioning an array based
417
 * on the results of a filter type function.
418
 * Callable will be cast to a bool, if truthy will be listed under 1 key, else 0 for falsey
419
 *
420
 * @param callable(mixed):(bool|int) $function
421
 * @return Closure(mixed[]):array{0:mixed[], 1:mixed[]}
422
 */
423
function partition(callable $function): Closure
424
{
425
    /**
426
     * @param mixed[] $array
427
     * @return array{0:mixed[], 1:mixed[]}
428
     */
429
    return function (array $array) use ($function): array {
430
        return array_reduce(
431
            $array,
432
            /**
433
             * @param array{0:mixed[], 1:mixed[]} $carry
434
             * @param mixed $element
435
             * @return array{0:mixed[], 1:mixed[]}
436
             */
437
            function ($carry, $element) use ($function): array {
438
                $key             = (bool) $function($element) ? 1 : 0;
439
                $carry[$key][] = $element;
440
                return $carry;
441
            },
442
            array(array(), array())
443
        );
444
    };
445
}
446
447
/**
448
 * Returns a closure for checking all elements pass a filter.
449
 *
450
 * @param callable(mixed):bool $function
451
 * @return Closure(mixed[]):bool
452
 */
453
function filterAll(callable $function): Closure
454
{
455
    /**
456
     * @param mixed[] $array
457
     * @return bool
458
     */
459
    return function (array $array) use ($function): bool {
460
        foreach ($array as $value) {
461
            if (false === $function($value)) {
462
                return false;
463
            }
464
        }
465
        return true;
466
    };
467
}
468
469
470
/**
471
 * Returns a closure for checking any elements pass a filter.
472
 *
473
 * @param callable(mixed):bool $function
474
 * @return Closure(mixed[]):bool
475
 */
476
function filterAny(callable $function): Closure
477
{
478
    /**
479
     * @param mixed[] $array
480
     * @return bool
481
     */
482
    return function (array $array) use ($function): bool {
483
        foreach ($array as $value) {
484
            if (true === $function($value)) {
485
                return true;
486
            }
487
        }
488
        return false;
489
    };
490
}
491
492
493
/*
494
 *                           *****************
495
 *                           * Map Functions *
496
 *                           *****************
497
 */
498
499
500
501
/**
502
 * Returns a Closure which can be passed an array.
503
 *
504
 * @param callable(mixed):mixed $func Callback to apply to each element in array.
505
 * @return Closure(mixed[]):mixed[]
506
 */
507
function map(callable $func): Closure
508
{
509
    /**
510
     * @param mixed[] $array The array to map
511
     * @return mixed[]
512
     */
513
    return function (array $array) use ($func): array {
514
        return array_map($func, $array);
515
    };
516
}
517
518
/**
519
 * Returns a Closure for mapping of an arrays keys.
520
 * Setting the key to an existing index will overwrite the current value at same index.
521
 *
522
 * @param callable $func
523
 * @return Closure(mixed[]):mixed[]
524
 */
525
function mapKey(callable $func): Closure
526
{
527
    /**
528
     * @param mixed[] $array The array to map
529
     * @return mixed[]
530
     */
531
    return function (array $array) use ($func): array {
532
        return array_reduce(
533
            array_keys($array),
534
            function ($carry, $key) use ($func, $array) {
535
                $carry[$func($key)] = $array[$key];
536
                return $carry;
537
            },
538
            array()
539
        );
540
    };
541
}
542
543
/**
544
 * Returns a Closure for mapping an array with additional data.
545
 *
546
 * @param callable(mixed ...$a):mixed $func
547
 * @param mixed ...$data
548
 * @return Closure(mixed[]):mixed[]
549
 */
550
function mapWith(callable $func, ...$data): Closure
551
{
552
    /**
553
     * @param mixed[] $array The array to map
554
     * @return mixed[]
555
     */
556
    return function (array $array) use ($func, $data): array {
557
        return array_map(
558
            function ($e) use ($data, $func) {
559
                return $func($e, ...$data);
560
            },
561
            $array
562
        );
563
    };
564
}
565
566
/**
567
 * Returns a Closure for mapping an array with access to value and key.
568
 *
569
 * @param callable(int|string $key, mixed $value):mixed $func
570
 * @return Closure(mixed[]):mixed[]
571
 */
572
function mapWithKey(callable $func): Closure
573
{
574
    /**
575
     * @param mixed[] $array The array to map
576
     * @return mixed[]
577
     */
578
    return function (array $array) use ($func): array {
579
        return array_map(
580
            function ($key, $value) use ($func) {
581
                return $func($value, $key);
582
            },
583
            $array,
584
            array_keys($array)
585
        );
586
    };
587
}
588
589
/**
590
 * Returns a Closure foreaching over an array
591
 *
592
 * @param callable(int|string $key, mixed $value):void $func
593
 * @return Closure(mixed[]):void
594
 */
595
function each(callable $func): Closure
596
{
597
    /**
598
     * @param mixed[] $array The array to map
599
     * @return void
600
     */
601
    return function (array $array) use ($func): void {
602
        array_map(
603
            function ($key, $value) use ($func) {
604
                $func($key, $value);
605
            },
606
            array_keys($array),
607
            $array
608
        );
609
    };
610
}
611
612
/**
613
 * Returns a Closure for flattening and mapping an array
614
 *
615
 * @param callable(mixed):mixed $function The function to map the element. (Will no be called if resolves to array)
616
 * @param int|null $n Depth of nodes to flatten. If null will flatten to n
617
 * @return Closure(mixed[]):mixed[]
618
 */
619
function flatMap(callable $function, ?int $n = null): Closure
620
{
621
    /**
622
     * @param mixed[] $array
623
     * @return mixed[]
624
     */
625
    return function (array $array) use ($n, $function): array {
626
        return array_reduce(
627
            $array,
628
            /**
629
             * @param mixed[] $carry
630
             * @param mixed $element
631
             * @return mixed[]
632
             */
633
            function (array $carry, $element) use ($n, $function): array {
634
                if (is_array($element) && (is_null($n) || $n > 0)) {
635
                    $carry = array_merge($carry, flatMap($function, $n ? $n - 1 : null)($element));
636
                } else {
637
                    $carry[] = is_array($element) ? $element : $function($element);
638
                }
639
                return $carry;
640
            },
641
            array()
642
        );
643
    };
644
}
645
646
/*
647
 *                         **********************
648
 *                         * General Operations *
649
 *                         **********************
650
 */
651
652
653
/**
654
 * Creates a Closure for grouping an array.
655
 *
656
 * @param callable(mixed):(string|int) $function The function to group by.
657
 * @return Closure(mixed):mixed[]
658
 */
659
function groupBy(callable $function): Closure
660
{
661
    /**
662
     * @param mixed[] $array The array to be grouped
663
     * @return mixed[] Grouped array.
664
     */
665
    return function (array $array) use ($function): array {
666
        return array_reduce(
667
            $array,
668
            /**
669
             * @param mixed[] $carry
670
             * @param mixed $element
671
             * @return mixed[]
672
             */
673
            function ($carry, $item) use ($function): array {
674
                $carry[call_user_func($function, $item)][] = $item;
675
                return $carry;
676
            },
677
            array()
678
        );
679
    };
680
}
681
682
/**
683
 * Creates a Closure for chunking an array to set a limit.
684
 *
685
 * @param int $count The max size of each chunk. Must not be less than 1!
686
 * @param bool $preserveKeys Should inital keys be kept. Default false.
687
 * @return Closure(mixed[]):mixed[]
688
 */
689
function chunk(int $count, bool $preserveKeys = false): Closure
690
{
691
    /**
692
     * @param mixed[] $array Array to chunk
693
     * @return mixed[]
694
     */
695
    return function (array $array) use ($count, $preserveKeys): array {
696
        return array_chunk($array, max(1, $count), $preserveKeys);
697
    };
698
}
699
700
/**
701
 * Create callback for extracting a single column from an array.
702
 *
703
 * @param string $column Column to retrieve.
704
 * @param string $key Use column for assigning as the index. defaults to numeric keys if null.
705
 * @return Closure(mixed[]):mixed[]
706
 */
707
function column(string $column, ?string $key = null): Closure
708
{
709
    /**
710
     * @param mixed[] $array
711
     * @return mixed[]
712
     */
713
    return function (array $array) use ($column, $key): array {
714
        return array_column($array, $column, $key);
715
    };
716
}
717
718
/**
719
 * Returns a Closure for flattening an array to a defined depth
720
 *
721
 * @param int|null $n Depth of nodes to flatten. If null will flatten to n
722
 * @return Closure(mixed[] $var): mixed[]
723
 */
724
function flattenByN(?int $n = null): Closure
725
{
726
    /**
727
     * @param mixed[] $array Array to flatten
728
     * @return mixed[]
729
     */
730
    return function (array $array) use ($n): array {
731
        return array_reduce(
732
            $array,
733
            /**
734
             * @param array<int|string, mixed> $carry
735
             * @param mixed|mixed[] $element
736
             * @return array<int|string, mixed>
737
             */
738
            function (array $carry, $element) use ($n): array {
739
                // Remove empty arrays.
740
                if (is_array($element) && empty($element)) {
741
                    return $carry;
742
                }
743
                // If the element is an array and we are still flattening, call again
744
                if (is_array($element) && (is_null($n) || $n > 0)) { // @phpstan-ignore-line
745
                    $carry = array_merge($carry, flattenByN($n ? $n - 1 : null)($element));
746
                } else {
747
                    // Else just add the element.
748
                    $carry[] = $element;
749
                }
750
                return $carry;
751
            },
752
            array()
753
        );
754
    };
755
}
756
757
/**
758
 * Returns a closure for recursively changing values in an array.
759
 *
760
 * @param mixed[] ...$with The array values to replace with
761
 * @return Closure(mixed[]):mixed[]
762
 */
763
function replaceRecursive(array ...$with): Closure
764
{
765
    /**
766
     * @param mixed[] $array The array to have elements replaced from.
767
     * @return mixed[] Array with replacements.
768
     */
769
    return function (array $array) use ($with): array {
770
        return array_replace_recursive($array, ...$with);
771
    };
772
}
773
774
/**
775
 * Returns a Closure for changing all values in a flat array, based on key.
776
 *
777
 * @param mixed[] ...$with Array with values to replace with, must have matching key with base array.
778
 * @return Closure(mixed[]):mixed[]
779
 */
780
function replace(array ...$with): Closure
781
{
782
    /**
783
     * @param mixed[] $array The array to have elements replaced from.
784
     * @return mixed[] Array with replacements.
785
     */
786
    return function (array $array) use ($with): array {
787
        return array_replace($array, ...$with);
788
    };
789
}
790
791
/**
792
 * Returns a Closure for doing array_sum with the results of a function/expression.
793
 *
794
 * @param callable(mixed):Number $function The function to return the value for array sum
795
 * @return Closure(mixed[]):Number
796
 */
797
function sumWhere(callable $function): Closure
798
{
799
    /**
800
     * @param mixed[] $array Array to do sum() on.
801
     * @return Number The total.
802
     */
803
    return function (array $array) use ($function) {
804
        return array_sum(array_map($function, $array));
805
    };
806
}
807
808
/**
809
 * Creates a closure for casting an array to an object.
810
 * Assumed all properties are public
811
 * None existing properties will be set as dynamic properties.
812
 *
813
 * @param object|null $object The object to cast to, defaults to stdClass
814
 * @return Closure(mixed[]):object
815
 * @throws \InvalidArgumentException If property does not exist or is not public.
816
 */
817
function toObject($object = null): Closure
818
{
819
    $object = $object ?? new stdClass();
820
821
    // Throws an exception if $object is not an object.
822
    if (!is_object($object)) {
823
        throw new \InvalidArgumentException('Object must be an object.');
824
    }
825
826
    /**
827
     * @param mixed[] $array
828
     * @return object
829
     */
830
    return function (array $array) use ($object) {
831
        foreach ($array as $key => $value) {
832
            // If key is not a string or numerical, skip it.
833
            if (!is_string($key) || is_numeric($key)) {
834
                continue;
835
            }
836
837
            try {
838
                $object->{$key} = $value;
839
            } catch (\Throwable $th) {
0 ignored issues
show
Unused Code introduced by
catch (\Throwable $th) is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
840
                throw new \InvalidArgumentException("Property {$key} does not exist or is not public.");
841
            }
842
        }
843
        return $object;
844
    };
845
}
846
847
/**
848
 * Creates a closure for encoding json with defined flags/depth
849
 *
850
 * @param int $flags json_encode flags (default = 0)
851
 * @param int $depth Nodes deep to encode (default = 512)
852
 * @return \Closure(mixed):?string
853
 * @constants JSON_FORCE_OBJECT, JSON_HEX_QUOT, JSON_HEX_TAG, JSON_HEX_AMP,
854
 *            JSON_HEX_APOS, JSON_INVALID_UTF8_IGNORE,
855
 *            JSON_INVALID_UTF8_SUBSTITUTE, JSON_NUMERIC_CHECK,
856
 *            JSON_PARTIAL_OUTPUT_ON_ERROR, JSON_PRESERVE_ZERO_FRACTION,
857
 *            JSON_PRETTY_PRINT, JSON_UNESCAPED_LINE_TERMINATORS,
858
 *            JSON_UNESCAPED_SLASHES, JSON_UNESCAPED_UNICODE, JSON_THROW_ON_ERROR
859
 */
860
function toJson(int $flags = 0, int $depth = 512): Closure
861
{
862
    /**
863
     * @param mixed $data
864
     * @return string|null
865
     */
866
    return function ($data) use ($flags, $depth): ?string {
867
        return \json_encode($data, $flags, max(1, $depth)) ?: null;
868
    };
869
}
870
871
872
/*
873
 *                         ****************
874
 *                         *  Array Sort  *
875
 *                         ****************
876
 */
877
878
879
/**
880
 * Returns a Closure for doing regular SORT against an array.
881
 * Doesn't maintain keys.
882
 *
883
 * @param int $flag Uses php stock sort constants or numerical values.
884
 * @return Closure(mixed[]):mixed[]
885
 */
886
function sort(int $flag = SORT_REGULAR): Closure
887
{
888
    /**
889
     *  @param mixed[]$array The array to sort
890
     *  @return mixed[] The sorted array (new array)
891
     */
892
    return function (array $array) use ($flag) {
893
        \sort($array, $flag);
894
        return $array;
895
    };
896
}
897
898
/**
899
 * Returns a Closure for doing regular Reverse SORT against an array.
900
 * Doesn't maintain keys.
901
 *
902
 * @param int $flag Uses php stock sort constants or numerical values.
903
 * @return Closure(mixed[]):mixed[]
904
 */
905
function rsort(int $flag = SORT_REGULAR): Closure
906
{
907
    /**
908
     *  @param mixed[]$array The array to sort
909
     *  @return mixed[] The sorted array (new array)
910
     */
911
    return function (array $array) use ($flag) {
912
        \rsort($array, $flag);
913
        return $array;
914
    };
915
}
916
917
918
/**
919
 * Returns a Closure for sorting an array by key in ascending order.
920
 *
921
 * @param int $flag Uses php stock sort constants or numerical values.
922
 * @return Closure(mixed[]):mixed[]
923
 */
924
function ksort(int $flag = SORT_REGULAR): Closure
925
{
926
    /**
927
     *  @param mixed[]$array The array to sort
928
     *  @return mixed[] The sorted array (new array)
929
     */
930
    return function (array $array) use ($flag) {
931
        \ksort($array, $flag);
932
        return $array;
933
    };
934
}
935
936
/**
937
 * Returns a Closure for sorting an array by key in descending (reverse) order.
938
 *
939
 * @param int $flag Uses php stock sort constants or numerical values.
940
 * @return Closure(mixed[]):mixed[]
941
 */
942
function krsort(int $flag = SORT_REGULAR): Closure
943
{
944
    /**
945
     *  @param mixed[]$array The array to sort
946
     *  @return mixed[] The sorted array (new array)
947
     */
948
    return function (array $array) use ($flag) {
949
        \krsort($array, $flag);
950
        return $array;
951
    };
952
}
953
954
/**
955
 * Returns a Closure for sorting an array by value in ascending order.
956
 * Maintain keys.
957
 *
958
 * @param int $flag Uses php stock sort constants or numerical values.
959
 * @return Closure(mixed[]):mixed[]
960
 */
961
function asort(int $flag = SORT_REGULAR): Closure
962
{
963
    /**
964
     *  @param mixed[]$array The array to sort
965
     *  @return mixed[] The sorted array (new array)
966
     */
967
    return function (array $array) use ($flag) {
968
        \asort($array, $flag);
969
        return $array;
970
    };
971
}
972
973
/**
974
 * Returns a Closure for sorting an array by value in descending (reverse) order.
975
 * Maintain keys.
976
 *
977
 * @param int $flag Uses php stock sort constants or numerical values.
978
 * @return Closure(mixed[]):mixed[]
979
 */
980
function arsort(int $flag = SORT_REGULAR): Closure
981
{
982
    /**
983
     *  @param mixed[]$array The array to sort
984
     *  @return mixed[] The sorted array (new array)
985
     */
986
    return function (array $array) use ($flag) {
987
        \arsort($array, $flag);
988
        return $array;
989
    };
990
}
991
992
/**
993
 * Returns a Closure for sorting an array using a "natural order" algorithm
994
 *
995
 * @return Closure(mixed[]):mixed[]
996
 */
997
function natsort(): Closure
998
{
999
    /**
1000
     *  @param mixed[]$array The array to sort
1001
     *  @return mixed[] The sorted array (new array)
1002
     */
1003
    return function (array $array) {
1004
        \natsort($array);
1005
        return $array;
1006
    };
1007
}
1008
1009
/**
1010
 * Returns a Closure for sorting an array using a case insensitive "natural order" algorithm
1011
 *
1012
 * @return Closure(mixed[]):mixed[]
1013
 */
1014
function natcasesort(): Closure
1015
{
1016
    /**
1017
     *  @param mixed[]$array The array to sort
1018
     *  @return mixed[] The sorted array (new array)
1019
     */
1020
    return function (array $array) {
1021
        \natcasesort($array);
1022
        return $array;
1023
    };
1024
}
1025
1026
/**
1027
 * Returns a Closure for sorting an array by key using a custom comparison function
1028
 *
1029
 * @param callable(mixed $a, mixed $b): int $function
1030
 * @return Closure(mixed[]):mixed[]
1031
 */
1032
function uksort(callable $function): Closure
1033
{
1034
    /**
1035
     *  @param mixed[] $array The array to sort
1036
     *  @return mixed[] The sorted array (new array)
1037
     */
1038
    return function (array $array) use ($function) {
1039
        \uksort($array, $function);
1040
        return $array;
1041
    };
1042
}
1043
1044
/**
1045
 * Returns a Closure for sorting an array using a custom comparison function
1046
 * Maintain keys.
1047
 *
1048
 * @param callable(mixed $a, mixed $b): int $function
1049
 * @return Closure(mixed[]):mixed[]
1050
 */
1051
function uasort(callable $function): Closure
1052
{
1053
    /**
1054
     *  @param mixed[]$array The array to sort
1055
     *  @return mixed[] The sorted array (new array)
1056
     */
1057
    return function (array $array) use ($function) {
1058
        \uasort($array, $function);
1059
        return $array;
1060
    };
1061
}
1062
1063
1064
/**
1065
 * Returns a Closure for sorting an array using a custom comparison function
1066
 * Doesn't maintain keys.
1067
 *
1068
 * @param callable(mixed $a, mixed $b): int $function
1069
 * @return Closure(mixed[]):mixed[]
1070
 */
1071
function usort(callable $function): Closure
1072
{
1073
    /**
1074
     *  @param mixed[]$array The array to sort
1075
     *  @return mixed[] The sorted array (new array)
1076
     */
1077
    return function (array $array) use ($function) {
1078
        \usort($array, $function);
1079
        return $array;
1080
    };
1081
}
1082
1083
1084
/**
1085
 * Returns a Closure for applying a function to every element of an array
1086
 *
1087
 * @param callable(mixed $carry, mixed $value):mixed $function
1088
 * @param mixed $initialValue
1089
 * @return Closure(mixed[]):mixed[]
1090
 */
1091
function scan(callable $function, $initialValue): Closure
1092
{
1093
    return function (array $array) use ($function, $initialValue) {
1094
        $carry[] = $initialValue;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$carry was never initialized. Although not strictly required by PHP, it is generally a good practice to add $carry = array(); before regardless.
Loading history...
1095
        foreach ($array as $key => $value) {
1096
            $initialValue = $function($initialValue, $value);
1097
            $carry[]      = $initialValue;
1098
        }
1099
        return $carry;
1100
    };
1101
}
1102
1103
/**
1104
 * Returns a Closure for applying a function to every element of an array
1105
 *
1106
 * @param callable(mixed $carry, mixed $value):mixed $function
1107
 * @param mixed $initialValue
1108
 * @return Closure(mixed[]):mixed[]
1109
 */
1110
function scanR(callable $function, $initialValue): Closure
1111
{
1112
    return function (array $array) use ($function, $initialValue) {
1113
        $carry[] = $initialValue;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$carry was never initialized. Although not strictly required by PHP, it is generally a good practice to add $carry = array(); before regardless.
Loading history...
1114
        foreach (array_reverse($array) as $key => $value) {
1115
            $initialValue = $function($initialValue, $value);
1116
            $carry[]      = $initialValue;
1117
        }
1118
        return \array_reverse($carry);
1119
    };
1120
}
1121
1122
/**
1123
 * Creates a function for defining the callback and initial for reduce/fold
1124
 *
1125
 * @param callable(mixed $carry, mixed $value): mixed $callable
1126
 * @param mixed $initial
1127
 * @return Closure(mixed[]):mixed
1128
 */
1129
function fold(callable $callable, $initial = array()): Closure
1130
{
1131
    /**
1132
     * @param mixed[] $array
1133
     * @return mixed
1134
     */
1135
    return function (array $array) use ($callable, $initial) {
1136
        return array_reduce($array, $callable, $initial);
1137
    };
1138
}
1139
1140
/**
1141
 * Creates a function for defining the callback and initial for reduce/fold
1142
 *
1143
 * @param callable(mixed $carry, mixed $value): mixed $callable
1144
 * @param mixed $initial
1145
 * @return Closure(mixed[]):mixed
1146
 */
1147
function foldR(callable $callable, $initial = array()): Closure
1148
{
1149
    /**
1150
     * @param mixed[] $array
1151
     * @return mixed
1152
     */
1153
    return function (array $array) use ($callable, $initial) {
1154
        return array_reduce(\array_reverse($array), $callable, $initial);
1155
    };
1156
}
1157
1158
/**
1159
 * Creates a function for defining the callback and initial for reduce/fold, with the key
1160
 * also passed to the callback.
1161
 *
1162
 * @param callable(mixed $carry, int|string $key, mixed $value): mixed $callable
1163
 * @param mixed $initial
1164
 * @return Closure(mixed[]):mixed
1165
 */
1166
function foldKeys(callable $callable, $initial = array()): Closure
1167
{
1168
    /**
1169
     * @param mixed[] $array
1170
     * @return mixed
1171
     */
1172
    return function (array $array) use ($callable, $initial) {
1173
        foreach ($array as $key => $value) {
1174
            $initial = $callable($initial, $key, $value);
1175
        }
1176
        return $initial;
1177
    };
1178
}
1179
1180
/**
1181
 * Creates a function which takes the first n elements from an array
1182
 *
1183
 * @param int $count
1184
 * @return Closure(mixed[]):mixed[]
1185
 * @throws \InvalidArgumentException if count is negative
1186
 */
1187
function take(int $count = 1): Closure
1188
{
1189
    // throw InvalidArgumentException if count is negative
1190
    if ($count < 0) {
1191
        throw new \InvalidArgumentException(__FUNCTION__ . ' count must be greater than or equal to 0');
1192
    }
1193
1194
    /**
1195
     * @param mixed[] $array
1196
     * @return mixed[]
1197
     */
1198
    return function (array $array) use ($count) {
1199
        return \array_slice($array, 0, $count);
1200
    };
1201
}
1202
1203
/**
1204
 * Creates a function which takes the last n elements from an array
1205
 *
1206
 * @param int $count
1207
 * @return Closure(mixed[]):mixed[]
1208
 * @throws \InvalidArgumentException if count is negative
1209
 */
1210
function takeLast(int $count = 1): Closure
1211
{
1212
    // throw InvalidArgumentException if count is negative
1213
    if ($count < 0) {
1214
        throw new \InvalidArgumentException(__FUNCTION__ . ' count must be greater than or equal to 0');
1215
    }
1216
1217
    // If count is 0, return an empty array
1218
    if ($count === 0) {
1219
        return function (array $array) {
1220
            return array();
1221
        };
1222
    }
1223
1224
    /**
1225
     * @param mixed[] $array
1226
     * @return mixed[]
1227
     */
1228
    return function (array $array) use ($count) {
1229
        return \array_slice($array, -$count);
1230
    };
1231
}
1232
1233
/**
1234
 * Creates a function that allows you to take a slice of an array until the passed conditional
1235
 * returns true.
1236
 *
1237
 * @param callable(mixed): bool $conditional
1238
 * @return Closure(mixed[]):mixed[]
1239
 */
1240
function takeUntil(callable $conditional): Closure
1241
{
1242
    /**
1243
     * @param mixed[] $array
1244
     * @return mixed[]
1245
     */
1246
    return function (array $array) use ($conditional) {
1247
        $carry = array();
1248
        foreach ($array as $key => $value) {
1249
            if (true === $conditional($value)) {
1250
                break;
1251
            }
1252
            $carry[$key] = $value;
1253
        }
1254
        return $carry;
1255
    };
1256
}
1257
1258
/**
1259
 * Creates a function that allows you to take a slice of an array until the passed conditional
1260
 * returns false.
1261
 *
1262
 * @param callable(mixed): bool $conditional
1263
 * @return Closure(mixed[]):mixed[]
1264
 */
1265
function takeWhile(callable $conditional): Closure
1266
{
1267
    /**
1268
     * @param mixed[] $array
1269
     * @return mixed[]
1270
     */
1271
    return function (array $array) use ($conditional) {
1272
        $carry = array();
1273
        foreach ($array as $key => $value) {
1274
            if (false === $conditional($value)) {
1275
                break;
1276
            }
1277
            $carry[$key] = $value;
1278
        }
1279
        return $carry;
1280
    };
1281
}
1282
1283
/**
1284
 * Picks selected indexes from an array
1285
 *
1286
 * @param string ...$indexes
1287
 * @return Closure(mixed[]):mixed[]
1288
 */
1289
function pick(string ...$indexes): Closure
1290
{
1291
    /**
1292
     * @param mixed[] $array
1293
     * @return mixed[]
1294
     */
1295
    return function (array $array) use ($indexes) {
1296
        return array_intersect_key($array, array_flip($indexes));
1297
    };
1298
}
1299