Passed
Pull Request — develop (#86)
by Glynn
02:39
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
 */
80
function pushHead(array $array): Closure
81
{
82
    trigger_error('Deprecated function called. This function will be removed in later versions.', E_USER_DEPRECATED);
83
    /**
84
     * @param mixed $value Adds value start of array.
85
     * @return array New array with value on head.
86
     */
87
    // codecov:miss:next
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
 */
101
function pushTail(array $array): Closure
102
{
103
    trigger_error('Deprecated function called. This function will be removed in later versions.', E_USER_DEPRECATED);
104
    /**
105
     * @param mixed $value Adds value end of array.
106
     * @return array<int|string, mixed> New array with value on tail.
107
     */
108
    return function ($value) use ($array): array {
109
        $array[] = $value;
110
        return $array;
111
    };
112
}
113
114
/**
115
 * Gets the first value from an array.
116
 *
117
 * @param array<int|string, mixed> $array The array.
118
 * @return mixed Will return the first value is array is not empty, else null.
119
 */
120
function head(array $array)
121
{
122
    return !empty($array) ? array_values($array)[0] : null;
123
}
124
125
/**
126
 * Gets the last value from an array.
127
 *
128
 * @param array<int|string, mixed> $array The array.
129
 * @return mixed Will return the last value is array is not empty, else null.
130
 */
131
function last(array $array)
132
{
133
    return !empty($array) ? array_reverse($array, false)[0] : null;
134
}
135
136
/**
137
 * Gets the remainder values from an array, after first item removed.
138
 *
139
 * @param array<int|string, mixed> $array
140
 * @return array<int|string, mixed>|null Will return the first value is array is not empty, else null.
141
 */
142
function tail(array $array)
143
{
144
    // Return null if empty.
145
    if (empty($array)) {
146
        return null;
147
    }
148
149
    // Remove the first item from the array.
150
    array_shift($array);
151
    return $array;
152
}
153
154
155
/**
156
 * Creates a Closure for concatenating arrays with a defined glue.
157
 *
158
 * @param string|null $glue The string to join each element. If null, will be no separation between elements.
159
 * @return Closure(array<int|string, mixed>):string
160
 *
161
 */
162
function toString(?string $glue = null): Closure
163
{
164
    /**
165
     * @param array<int|string, mixed> $array Array join
166
     * @return string
167
     */
168
    return function (array $array) use ($glue): string {
169
        return $glue ? \join($glue, $array) : \join($array);
170
    };
171
}
172
173
/**
174
 * Creates a Closure for zipping 2 arrays.
175
 *
176
 * @param array<mixed> $additional Values with the same key will be paired.
177
 * @param mixed $default The fallback value if the additional array doesn't have the same length
178
 * @return Closure(array<mixed>):array<array{mixed, mixed}>
179
 *
180
 */
181
function zip(array $additional, $default = null): Closure
182
{
183
    $additional = array_values($additional);
184
    return function (array $array) use ($additional, $default) {
185
        $array = array_values($array);
186
        return array_reduce(
187
            array_keys($array),
188
            function ($carry, $key) use ($array, $additional, $default): array {
189
                $carry[] = array(
190
                    $array[$key],
191
                    array_key_exists($key, $additional) ? $additional[$key] : $default,
192
                );
193
                return $carry;
194
            },
195
            array()
196
        );
197
    };
198
}
199
200
201
/*
202
 *                                ********************
203
 *                                * Filter Compilers *
204
 *                                ********************
205
 */
206
207
208
/**
209
 * Compiles an array if a value is passed.
210
 * Returns the array if nothing passed.
211
 *
212
 * @param mixed[] $inital Sets up the inner value.
213
 * @return Closure
214
 */
215
function arrayCompiler(array $inital = array()): Closure
216
{
217
    /**
218
     * @param mixed $value Adds value to inner array if value set, else returns.
219
     * @return mixed[]|Closure
220
     */
221
    return function ($value = null) use ($inital) {
222
        if ($value) {
223
            $inital[] = $value;
224
        }
225
        return !is_null($value) ? arrayCompiler($inital) : $inital;
226
    };
227
}
228
229
/**
230
 * Creates a typed array compiler.
231
 * All values which do not pass the validator are not added.
232
 *
233
 * @param Closure(mixed):bool $validator Used to validate values before adding to array.
234
 * @param mixed[] $inital The inital data to start with
235
 * @return Closure
236
 */
237
function arrayCompilerTyped(callable $validator, array $inital = array()): Closure
238
{
239
    // Ensure all is validated from initial.
240
    $inital = array_filter($inital, $validator);
241
242
    /**
243
     * @param mixed $value
244
     * @return mixed[]|Closure
245
     */
246
    return function ($value = null) use ($validator, $inital) {
247
        if (!is_null($value) && $validator($value)) {
248
            $inital[] = $value;
249
        }
250
        return !is_null($value) ? arrayCompilerTyped($validator, $inital) : $inital;
251
    };
252
}
253
254
255
256
/*
257
 *                                ********************
258
 *                                * Filter Functions *
259
 *                                ********************
260
 */
261
262
263
/**
264
 * Created a Closure for filtering an array.
265
 *
266
 * @param callable $callable The function to apply to the array.
267
 * @return Closure(array<int|string, mixed>):array<int|string, mixed>
268
 */
269
function filter(callable $callable): Closure
270
{
271
    /**
272
     * @param array<int|string, mixed> $source Array to filter
273
     * @return array<int|string, mixed> Filtered array.
274
     */
275
    return function (array $source) use ($callable): array {
276
        return array_filter($source, $callable);
277
    };
278
}
279
280
/**
281
 * Create a Closure for filtering an array by a key.
282
 *
283
 * @param callable $callable The function to apply to the array.
284
 * @return Closure(array<int|string, mixed>):array<int|string, mixed>
285
 */
286
function filterKey(callable $callable): Closure
287
{
288
    /**
289
     * @param array<int|string, mixed> $source Array to filter
290
     * @return array<int|string, mixed> Filtered array.
291
     */
292
    return function (array $source) use ($callable): array {
293
        return array_filter($source, $callable, \ARRAY_FILTER_USE_KEY);
294
    };
295
}
296
297
/**
298
 * Creates a Closure for running an array through various callbacks for all true response.
299
 * Wrapper for creating a AND group of callbacks and running through array filter.
300
 *
301
 * @param callable ...$callables
302
 * @return Closure(array<int|string, mixed>):array<int|string, mixed>
303
 */
304
function filterAnd(callable ...$callables): Closure
305
{
306
    /**
307
     * @param array<int|string, mixed> $source Array to filter
308
     * @return array<int|string, mixed> Filtered array.
309
     */
310
    return function (array $source) use ($callables): array {
311
        return array_filter($source, Comp\groupAnd(...$callables));
312
    };
313
}
314
315
/**
316
 * Creates a Closure for running an array through various callbacks for any true response.
317
 * Wrapper for creating a OR group of callbacks and running through array filter.
318
 *
319
 * @param callable ...$callables
320
 * @return Closure(array<int|string, mixed>):array<int|string, mixed>
321
 */
322
function filterOr(callable ...$callables): Closure
323
{
324
    /**
325
     * @param array<int|string, mixed> $source Array to filter
326
     * @return array<int|string, mixed> Filtered array.
327
     */
328
    return function (array $source) use ($callables): array {
329
        return array_filter($source, Comp\groupOr(...$callables));
330
    };
331
}
332
333
/**
334
 * Creates a Closure for running array filter and getting the first value.
335
 *
336
 * @param callable $func
337
 * @return Closure(array<int|string, mixed>):?mixed
338
 */
339
function filterFirst(callable $func): Closure
340
{
341
    /**
342
     * @param array<int|string, mixed> $array The array to filter
343
     * @return mixed|null The first element from the filtered array or null if filter returns empty
344
     */
345
    return function (array $array) use ($func) {
346
        foreach ($array as $value) {
347
            $result = $func($value);
348
            if (\is_bool($result) && $result) {
349
                return $value;
350
            }
351
        }
352
    };
353
}
354
355
/**
356
 * Creates a Closure for running array filter and getting the last value.
357
 *
358
 * @param callable $func
359
 * @return Closure(array<int|string, mixed>):?mixed
360
 */
361
function filterLast(callable $func): Closure
362
{
363
    /**
364
     * @param array<int|string, mixed> $array The array to filter
365
     * @return mixed|null The last element from the filtered array.
366
     */
367
    return function (array $array) use ($func) {
368
        while ($value = array_pop($array)) {
369
            $result = $func($value);
370
            if (\is_bool($result) && $result) {
371
                return $value;
372
            }
373
        }
374
    };
375
}
376
377
/**
378
 * Creates a Closure which takes an array, applies a filter, then maps the
379
 * results of the map.
380
 *
381
 *
382
 * @param callable(mixed):bool $filter Function to of filter contents
383
 * @param callable(mixed):mixed $map Function to map results of filter function.
384
 * @return Closure(array<int|string, mixed>):array<int|string, mixed>
385
 */
386
function filterMap(callable $filter, callable $map): Closure
387
{
388
    /**
389
     * @param array<int|string, mixed> $array The array to filter then map.
390
     * @return array<int|string, mixed>
391
     */
392
    return function (array $array) use ($filter, $map): array {
393
        return array_map($map, array_filter($array, $filter));
394
    };
395
}
396
397
/**
398
 * Runs an array through a filters, returns the total count of true
399
 *
400
 * @param callable $function
401
 * @return Closure(array<int|string, mixed>):int
402
 */
403
function filterCount(callable $function): Closure
404
{
405
    /**
406
     * @param array<int|string, mixed> $array
407
     * @return int Count
408
     */
409
    return function (array $array) use ($function) {
410
        return count(array_filter($array, $function));
411
    };
412
}
413
414
/**
415
 * Returns a Closure for partitioning an array based
416
 * on the results of a filter type function.
417
 * Callable will be cast to a bool, if truthy will be listed under 1 key, else 0 for falsey
418
 *
419
 * @param callable(mixed):(bool|int) $function
420
 * @return Closure(mixed[]):array{0:mixed[], 1:mixed[]}
421
 */
422
function partition(callable $function): Closure
423
{
424
    /**
425
     * @param mixed[] $array
426
     * @return array{0:mixed[], 1:mixed[]}
427
     */
428
    return function (array $array) use ($function): array {
429
        return array_reduce(
430
            $array,
431
            /**
432
             * @param array{0:mixed[], 1:mixed[]} $carry
433
             * @param mixed $element
434
             * @return array{0:mixed[], 1:mixed[]}
435
             */
436
            function ($carry, $element) use ($function): array {
437
                $key             = (bool) $function($element) ? 1 : 0;
438
                $carry[$key][] = $element;
439
                return $carry;
440
            },
441
            array(array(), array())
442
        );
443
    };
444
}
445
446
/**
447
 * Returns a closure for checking all elements pass a filter.
448
 *
449
 * @param callable(mixed):bool $function
450
 * @return Closure(mixed[]):bool
451
 */
452
function filterAll(callable $function): Closure
453
{
454
    /**
455
     * @param mixed[] $array
456
     * @return bool
457
     */
458
    return function (array $array) use ($function): bool {
459
        foreach ($array as $value) {
460
            if (false === $function($value)) {
461
                return false;
462
            }
463
        }
464
        return true;
465
    };
466
}
467
468
469
/**
470
 * Returns a closure for checking any elements pass a filter.
471
 *
472
 * @param callable(mixed):bool $function
473
 * @return Closure(mixed[]):bool
474
 */
475
function filterAny(callable $function): Closure
476
{
477
    /**
478
     * @param mixed[] $array
479
     * @return bool
480
     */
481
    return function (array $array) use ($function): bool {
482
        foreach ($array as $value) {
483
            if (true === $function($value)) {
484
                return true;
485
            }
486
        }
487
        return false;
488
    };
489
}
490
491
492
/*
493
 *                           *****************
494
 *                           * Map Functions *
495
 *                           *****************
496
 */
497
498
499
500
/**
501
 * Returns a Closure which can be passed an array.
502
 *
503
 * @param callable(mixed):mixed $func Callback to apply to each element in array.
504
 * @return Closure(mixed[]):mixed[]
505
 */
506
function map(callable $func): Closure
507
{
508
    /**
509
     * @param mixed[] $array The array to map
510
     * @return mixed[]
511
     */
512
    return function (array $array) use ($func): array {
513
        return array_map($func, $array);
514
    };
515
}
516
517
/**
518
 * Returns a Closure for mapping of an arrays keys.
519
 * Setting the key to an existing index will overwrite the current value at same index.
520
 *
521
 * @param callable $func
522
 * @return Closure(mixed[]):mixed[]
523
 */
524
function mapKey(callable $func): Closure
525
{
526
    /**
527
     * @param mixed[] $array The array to map
528
     * @return mixed[]
529
     */
530
    return function (array $array) use ($func): array {
531
        return array_reduce(
532
            array_keys($array),
533
            function ($carry, $key) use ($func, $array) {
534
                $carry[$func($key)] = $array[$key];
535
                return $carry;
536
            },
537
            array()
538
        );
539
    };
540
}
541
542
/**
543
 * Returns a Closure for mapping an array with additional data.
544
 *
545
 * @param callable(mixed ...$a):mixed $func
546
 * @param mixed ...$data
547
 * @return Closure(mixed[]):mixed[]
548
 */
549
function mapWith(callable $func, ...$data): Closure
550
{
551
    /**
552
     * @param mixed[] $array The array to map
553
     * @return mixed[]
554
     */
555
    return function (array $array) use ($func, $data): array {
556
        return array_map(
557
            function ($e) use ($data, $func) {
558
                return $func($e, ...$data);
559
            },
560
            $array
561
        );
562
    };
563
}
564
565
/**
566
 * Returns a Closure for mapping an array with access to value and key.
567
 *
568
 * @param callable(int|string $key, mixed $value):mixed $func
569
 * @return Closure(mixed[]):mixed[]
570
 */
571
function mapWithKey(callable $func): Closure
572
{
573
    /**
574
     * @param mixed[] $array The array to map
575
     * @return mixed[]
576
     */
577
    return function (array $array) use ($func): array {
578
        return array_map(
579
            function ($key, $value) use ($func) {
580
                return $func($value, $key);
581
            },
582
            $array,
583
            array_keys($array)
584
        );
585
    };
586
}
587
588
/**
589
 * Returns a Closure foreaching over an array
590
 *
591
 * @param callable(int|string $key, mixed $value):void $func
592
 * @return Closure(mixed[]):void
593
 */
594
function each(callable $func): Closure
595
{
596
    /**
597
     * @param mixed[] $array The array to map
598
     * @return void
599
     */
600
    return function (array $array) use ($func): void {
601
        array_map(
602
            function ($key, $value) use ($func) {
603
                $func($key, $value);
604
            },
605
            array_keys($array),
606
            $array
607
        );
608
    };
609
}
610
611
/**
612
 * Returns a Closure for flattening and mapping an array
613
 *
614
 * @param callable(mixed):mixed $function The function to map the element. (Will no be called if resolves to array)
615
 * @param int|null $n Depth of nodes to flatten. If null will flatten to n
616
 * @return Closure(mixed[]):mixed[]
617
 */
618
function flatMap(callable $function, ?int $n = null): Closure
619
{
620
    /**
621
     * @param mixed[] $array
622
     * @return mixed[]
623
     */
624
    return function (array $array) use ($n, $function): array {
625
        return array_reduce(
626
            $array,
627
            /**
628
             * @param mixed[] $carry
629
             * @param mixed $element
630
             * @return mixed[]
631
             */
632
            function (array $carry, $element) use ($n, $function): array {
633
                if (is_array($element) && (is_null($n) || $n > 0)) {
634
                    $carry = array_merge($carry, flatMap($function, $n ? $n - 1 : null)($element));
635
                } else {
636
                    $carry[] = is_array($element) ? $element : $function($element);
637
                }
638
                return $carry;
639
            },
640
            array()
641
        );
642
    };
643
}
644
645
/*
646
 *                         **********************
647
 *                         * General Operations *
648
 *                         **********************
649
 */
650
651
652
/**
653
 * Creates a Closure for grouping an array.
654
 *
655
 * @param callable(mixed):(string|int) $function The function to group by.
656
 * @return Closure(mixed):mixed[]
657
 */
658
function groupBy(callable $function): Closure
659
{
660
    /**
661
     * @param mixed[] $array The array to be grouped
662
     * @return mixed[] Grouped array.
663
     */
664
    return function (array $array) use ($function): array {
665
        return array_reduce(
666
            $array,
667
            /**
668
             * @param mixed[] $carry
669
             * @param mixed $element
670
             * @return mixed[]
671
             */
672
            function ($carry, $item) use ($function): array {
673
                $carry[call_user_func($function, $item)][] = $item;
674
                return $carry;
675
            },
676
            array()
677
        );
678
    };
679
}
680
681
/**
682
 * Creates a Closure for chunking an array to set a limit.
683
 *
684
 * @param int $count The max size of each chunk. Must not be less than 1!
685
 * @param bool $preserveKeys Should inital keys be kept. Default false.
686
 * @return Closure(mixed[]):mixed[]
687
 */
688
function chunk(int $count, bool $preserveKeys = false): Closure
689
{
690
    /**
691
     * @param mixed[] $array Array to chunk
692
     * @return mixed[]
693
     */
694
    return function (array $array) use ($count, $preserveKeys): array {
695
        return array_chunk($array, max(1, $count), $preserveKeys);
696
    };
697
}
698
699
/**
700
 * Create callback for extracting a single column from an array.
701
 *
702
 * @param string $column Column to retrieve.
703
 * @param string $key Use column for assigning as the index. defaults to numeric keys if null.
704
 * @return Closure(mixed[]):mixed[]
705
 */
706
function column(string $column, ?string $key = null): Closure
707
{
708
    /**
709
     * @param mixed[] $array
710
     * @return mixed[]
711
     */
712
    return function (array $array) use ($column, $key): array {
713
        return array_column($array, $column, $key);
714
    };
715
}
716
717
/**
718
 * Returns a Closure for flattening an array to a defined depth
719
 *
720
 * @param int|null $n Depth of nodes to flatten. If null will flatten to n
721
 * @return Closure(mixed[] $var): mixed[]
722
 */
723
function flattenByN(?int $n = null): Closure
724
{
725
    /**
726
     * @param mixed[] $array Array to flatten
727
     * @return mixed[]
728
     */
729
    return function (array $array) use ($n): array {
730
        return array_reduce(
731
            $array,
732
            /**
733
             * @param array<int|string, mixed> $carry
734
             * @param mixed|mixed[] $element
735
             * @return array<int|string, mixed>
736
             */
737
            function (array $carry, $element) use ($n): array {
738
                // Remove empty arrays.
739
                if (is_array($element) && empty($element)) {
740
                    return $carry;
741
                }
742
                // If the element is an array and we are still flattening, call again
743
                if (is_array($element) && (is_null($n) || $n > 0)) { // @phpstan-ignore-line
744
                    $carry = array_merge($carry, flattenByN($n ? $n - 1 : null)($element));
745
                } else {
746
                    // Else just add the element.
747
                    $carry[] = $element;
748
                }
749
                return $carry;
750
            },
751
            array()
752
        );
753
    };
754
}
755
756
/**
757
 * Returns a closure for recursively changing values in an array.
758
 *
759
 * @param mixed[] ...$with The array values to replace with
760
 * @return Closure(mixed[]):mixed[]
761
 */
762
function replaceRecursive(array ...$with): Closure
763
{
764
    /**
765
     * @param mixed[] $array The array to have elements replaced from.
766
     * @return mixed[] Array with replacements.
767
     */
768
    return function (array $array) use ($with): array {
769
        return array_replace_recursive($array, ...$with);
770
    };
771
}
772
773
/**
774
 * Returns a Closure for changing all values in a flat array, based on key.
775
 *
776
 * @param mixed[] ...$with Array with values to replace with, must have matching key with base array.
777
 * @return Closure(mixed[]):mixed[]
778
 */
779
function replace(array ...$with): Closure
780
{
781
    /**
782
     * @param mixed[] $array The array to have elements replaced from.
783
     * @return mixed[] Array with replacements.
784
     */
785
    return function (array $array) use ($with): array {
786
        return array_replace($array, ...$with);
787
    };
788
}
789
790
/**
791
 * Returns a Closure for doing array_sum with the results of a function/expression.
792
 *
793
 * @param callable(mixed):Number $function The function to return the value for array sum
794
 * @return Closure(mixed[]):Number
795
 */
796
function sumWhere(callable $function): Closure
797
{
798
    /**
799
     * @param mixed[] $array Array to do sum() on.
800
     * @return Number The total.
801
     */
802
    return function (array $array) use ($function) {
803
        return array_sum(array_map($function, $array));
804
    };
805
}
806
807
/**
808
 * Creates a closure for casting an array to an object.
809
 * Assumed all properties are public
810
 * None existing properties will be set as dynamic properties.
811
 *
812
 * @param object|null $object The object to cast to, defaults to stdClass
813
 * @return Closure(mixed[]):object
814
 * @throws \InvalidArgumentException If property does not exist or is not public.
815
 */
816
function toObject($object = null): Closure
817
{
818
    $object = $object ?? new stdClass();
819
820
    // Throws an exception if $object is not an object.
821
    if (!is_object($object)) {
822
        throw new \InvalidArgumentException('Object must be an object.');
823
    }
824
825
    /**
826
     * @param mixed[] $array
827
     * @return object
828
     */
829
    return function (array $array) use ($object) {
830
        foreach ($array as $key => $value) {
831
            // If key is not a string or numerical, skip it.
832
            if (!is_string($key) || is_numeric($key)) {
833
                continue;
834
            }
835
836
            try {
837
                $object->{$key} = $value;
838
            } 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...
839
                throw new \InvalidArgumentException("Property {$key} does not exist or is not public.");
840
            }
841
        }
842
        return $object;
843
    };
844
}
845
846
/**
847
 * Creates a closure for encoding json with defined flags/depth
848
 *
849
 * @param int $flags json_encode flags (default = 0)
850
 * @param int $depth Nodes deep to encode (default = 512)
851
 * @return \Closure(mixed):?string
852
 * @constants JSON_FORCE_OBJECT, JSON_HEX_QUOT, JSON_HEX_TAG, JSON_HEX_AMP,
853
 *            JSON_HEX_APOS, JSON_INVALID_UTF8_IGNORE,
854
 *            JSON_INVALID_UTF8_SUBSTITUTE, JSON_NUMERIC_CHECK,
855
 *            JSON_PARTIAL_OUTPUT_ON_ERROR, JSON_PRESERVE_ZERO_FRACTION,
856
 *            JSON_PRETTY_PRINT, JSON_UNESCAPED_LINE_TERMINATORS,
857
 *            JSON_UNESCAPED_SLASHES, JSON_UNESCAPED_UNICODE, JSON_THROW_ON_ERROR
858
 */
859
function toJson(int $flags = 0, int $depth = 512): Closure
860
{
861
    /**
862
     * @param mixed $data
863
     * @return string|null
864
     */
865
    return function ($data) use ($flags, $depth): ?string {
866
        return \json_encode($data, $flags, max(1, $depth)) ?: null;
867
    };
868
}
869
870
871
/*
872
 *                         ****************
873
 *                         *  Array Sort  *
874
 *                         ****************
875
 */
876
877
878
/**
879
 * Returns a Closure for doing regular SORT against an array.
880
 * Doesn't maintain keys.
881
 *
882
 * @param int $flag Uses php stock sort constants or numerical values.
883
 * @return Closure(mixed[]):mixed[]
884
 */
885
function sort(int $flag = SORT_REGULAR): Closure
886
{
887
    /**
888
     *  @param mixed[]$array The array to sort
889
     *  @return mixed[] The sorted array (new array)
890
     */
891
    return function (array $array) use ($flag) {
892
        \sort($array, $flag);
893
        return $array;
894
    };
895
}
896
897
/**
898
 * Returns a Closure for doing regular Reverse SORT against an array.
899
 * Doesn't maintain keys.
900
 *
901
 * @param int $flag Uses php stock sort constants or numerical values.
902
 * @return Closure(mixed[]):mixed[]
903
 */
904
function rsort(int $flag = SORT_REGULAR): Closure
905
{
906
    /**
907
     *  @param mixed[]$array The array to sort
908
     *  @return mixed[] The sorted array (new array)
909
     */
910
    return function (array $array) use ($flag) {
911
        \rsort($array, $flag);
912
        return $array;
913
    };
914
}
915
916
917
/**
918
 * Returns a Closure for sorting an array by key in ascending order.
919
 *
920
 * @param int $flag Uses php stock sort constants or numerical values.
921
 * @return Closure(mixed[]):mixed[]
922
 */
923
function ksort(int $flag = SORT_REGULAR): Closure
924
{
925
    /**
926
     *  @param mixed[]$array The array to sort
927
     *  @return mixed[] The sorted array (new array)
928
     */
929
    return function (array $array) use ($flag) {
930
        \ksort($array, $flag);
931
        return $array;
932
    };
933
}
934
935
/**
936
 * Returns a Closure for sorting an array by key in descending (reverse) order.
937
 *
938
 * @param int $flag Uses php stock sort constants or numerical values.
939
 * @return Closure(mixed[]):mixed[]
940
 */
941
function krsort(int $flag = SORT_REGULAR): Closure
942
{
943
    /**
944
     *  @param mixed[]$array The array to sort
945
     *  @return mixed[] The sorted array (new array)
946
     */
947
    return function (array $array) use ($flag) {
948
        \krsort($array, $flag);
949
        return $array;
950
    };
951
}
952
953
/**
954
 * Returns a Closure for sorting an array by value in ascending order.
955
 * Maintain keys.
956
 *
957
 * @param int $flag Uses php stock sort constants or numerical values.
958
 * @return Closure(mixed[]):mixed[]
959
 */
960
function asort(int $flag = SORT_REGULAR): Closure
961
{
962
    /**
963
     *  @param mixed[]$array The array to sort
964
     *  @return mixed[] The sorted array (new array)
965
     */
966
    return function (array $array) use ($flag) {
967
        \asort($array, $flag);
968
        return $array;
969
    };
970
}
971
972
/**
973
 * Returns a Closure for sorting an array by value in descending (reverse) order.
974
 * Maintain keys.
975
 *
976
 * @param int $flag Uses php stock sort constants or numerical values.
977
 * @return Closure(mixed[]):mixed[]
978
 */
979
function arsort(int $flag = SORT_REGULAR): Closure
980
{
981
    /**
982
     *  @param mixed[]$array The array to sort
983
     *  @return mixed[] The sorted array (new array)
984
     */
985
    return function (array $array) use ($flag) {
986
        \arsort($array, $flag);
987
        return $array;
988
    };
989
}
990
991
/**
992
 * Returns a Closure for sorting an array using a "natural order" algorithm
993
 *
994
 * @return Closure(mixed[]):mixed[]
995
 */
996
function natsort(): Closure
997
{
998
    /**
999
     *  @param mixed[]$array The array to sort
1000
     *  @return mixed[] The sorted array (new array)
1001
     */
1002
    return function (array $array) {
1003
        \natsort($array);
1004
        return $array;
1005
    };
1006
}
1007
1008
/**
1009
 * Returns a Closure for sorting an array using a case insensitive "natural order" algorithm
1010
 *
1011
 * @return Closure(mixed[]):mixed[]
1012
 */
1013
function natcasesort(): Closure
1014
{
1015
    /**
1016
     *  @param mixed[]$array The array to sort
1017
     *  @return mixed[] The sorted array (new array)
1018
     */
1019
    return function (array $array) {
1020
        \natcasesort($array);
1021
        return $array;
1022
    };
1023
}
1024
1025
/**
1026
 * Returns a Closure for sorting an array by key using a custom comparison function
1027
 *
1028
 * @param callable(mixed $a, mixed $b): int $function
1029
 * @return Closure(mixed[]):mixed[]
1030
 */
1031
function uksort(callable $function): Closure
1032
{
1033
    /**
1034
     *  @param mixed[] $array The array to sort
1035
     *  @return mixed[] The sorted array (new array)
1036
     */
1037
    return function (array $array) use ($function) {
1038
        \uksort($array, $function);
1039
        return $array;
1040
    };
1041
}
1042
1043
/**
1044
 * Returns a Closure for sorting an array using a custom comparison function
1045
 * Maintain keys.
1046
 *
1047
 * @param callable(mixed $a, mixed $b): int $function
1048
 * @return Closure(mixed[]):mixed[]
1049
 */
1050
function uasort(callable $function): Closure
1051
{
1052
    /**
1053
     *  @param mixed[]$array The array to sort
1054
     *  @return mixed[] The sorted array (new array)
1055
     */
1056
    return function (array $array) use ($function) {
1057
        \uasort($array, $function);
1058
        return $array;
1059
    };
1060
}
1061
1062
1063
/**
1064
 * Returns a Closure for sorting an array using a custom comparison function
1065
 * Doesn't maintain keys.
1066
 *
1067
 * @param callable(mixed $a, mixed $b): int $function
1068
 * @return Closure(mixed[]):mixed[]
1069
 */
1070
function usort(callable $function): Closure
1071
{
1072
    /**
1073
     *  @param mixed[]$array The array to sort
1074
     *  @return mixed[] The sorted array (new array)
1075
     */
1076
    return function (array $array) use ($function) {
1077
        \usort($array, $function);
1078
        return $array;
1079
    };
1080
}
1081
1082
1083
/**
1084
 * Returns a Closure for applying a function to every element of an array
1085
 *
1086
 * @param callable(mixed $carry, mixed $value):mixed $function
1087
 * @param mixed $initialValue
1088
 * @return Closure(mixed[]):mixed[]
1089
 */
1090
function scan(callable $function, $initialValue): Closure
1091
{
1092
    return function (array $array) use ($function, $initialValue) {
1093
        $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...
1094
        foreach ($array as $key => $value) {
1095
            $initialValue = $function($initialValue, $value);
1096
            $carry[]      = $initialValue;
1097
        }
1098
        return $carry;
1099
    };
1100
}
1101
1102
/**
1103
 * Returns a Closure for applying a function to every element of an array
1104
 *
1105
 * @param callable(mixed $carry, mixed $value):mixed $function
1106
 * @param mixed $initialValue
1107
 * @return Closure(mixed[]):mixed[]
1108
 */
1109
function scanR(callable $function, $initialValue): Closure
1110
{
1111
    return function (array $array) use ($function, $initialValue) {
1112
        $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...
1113
        foreach (array_reverse($array) as $key => $value) {
1114
            $initialValue = $function($initialValue, $value);
1115
            $carry[]      = $initialValue;
1116
        }
1117
        return \array_reverse($carry);
1118
    };
1119
}
1120
1121
/**
1122
 * Creates a function for defining the callback and initial for reduce/fold
1123
 *
1124
 * @param callable(mixed $carry, mixed $value): mixed $callable
1125
 * @param mixed $initial
1126
 * @return Closure(mixed[]):mixed
1127
 */
1128
function fold(callable $callable, $initial = array()): Closure
1129
{
1130
    /**
1131
     * @param mixed[] $array
1132
     * @return mixed
1133
     */
1134
    return function (array $array) use ($callable, $initial) {
1135
        return array_reduce($array, $callable, $initial);
1136
    };
1137
}
1138
1139
/**
1140
 * Creates a function for defining the callback and initial for reduce/fold
1141
 *
1142
 * @param callable(mixed $carry, mixed $value): mixed $callable
1143
 * @param mixed $initial
1144
 * @return Closure(mixed[]):mixed
1145
 */
1146
function foldR(callable $callable, $initial = array()): Closure
1147
{
1148
    /**
1149
     * @param mixed[] $array
1150
     * @return mixed
1151
     */
1152
    return function (array $array) use ($callable, $initial) {
1153
        return array_reduce(\array_reverse($array), $callable, $initial);
1154
    };
1155
}
1156
1157
/**
1158
 * Creates a function for defining the callback and initial for reduce/fold, with the key
1159
 * also passed to the callback.
1160
 *
1161
 * @param callable(mixed $carry, int|string $key, mixed $value): mixed $callable
1162
 * @param mixed $initial
1163
 * @return Closure(mixed[]):mixed
1164
 */
1165
function foldKeys(callable $callable, $initial = array()): Closure
1166
{
1167
    /**
1168
     * @param mixed[] $array
1169
     * @return mixed
1170
     */
1171
    return function (array $array) use ($callable, $initial) {
1172
        foreach ($array as $key => $value) {
1173
            $initial = $callable($initial, $key, $value);
1174
        }
1175
        return $initial;
1176
    };
1177
}
1178
1179
/**
1180
 * Creates a function which takes the first n elements from an array
1181
 *
1182
 * @param int $count
1183
 * @return Closure(mixed[]):mixed[]
1184
 * @throws \InvalidArgumentException if count is negative
1185
 */
1186
function take(int $count = 1): Closure
1187
{
1188
    // throw InvalidArgumentException if count is negative
1189
    if ($count < 0) {
1190
        throw new \InvalidArgumentException(__FUNCTION__ . ' count must be greater than or equal to 0');
1191
    }
1192
1193
    /**
1194
     * @param mixed[] $array
1195
     * @return mixed[]
1196
     */
1197
    return function (array $array) use ($count) {
1198
        return \array_slice($array, 0, $count);
1199
    };
1200
}
1201
1202
/**
1203
 * Creates a function which takes the last n elements from an array
1204
 *
1205
 * @param int $count
1206
 * @return Closure(mixed[]):mixed[]
1207
 * @throws \InvalidArgumentException if count is negative
1208
 */
1209
function takeLast(int $count = 1): Closure
1210
{
1211
    // throw InvalidArgumentException if count is negative
1212
    if ($count < 0) {
1213
        throw new \InvalidArgumentException(__FUNCTION__ . ' count must be greater than or equal to 0');
1214
    }
1215
1216
    // If count is 0, return an empty array
1217
    if ($count === 0) {
1218
        return function (array $array) {
1219
            return array();
1220
        };
1221
    }
1222
1223
    /**
1224
     * @param mixed[] $array
1225
     * @return mixed[]
1226
     */
1227
    return function (array $array) use ($count) {
1228
        return \array_slice($array, -$count);
1229
    };
1230
}
1231
1232
/**
1233
 * Creates a function that allows you to take a slice of an array until the passed conditional
1234
 * returns true.
1235
 *
1236
 * @param callable(mixed): bool $conditional
1237
 * @return Closure(mixed[]):mixed[]
1238
 */
1239
function takeUntil(callable $conditional): Closure
1240
{
1241
    /**
1242
     * @param mixed[] $array
1243
     * @return mixed[]
1244
     */
1245
    return function (array $array) use ($conditional) {
1246
        $carry = array();
1247
        foreach ($array as $key => $value) {
1248
            if (true === $conditional($value)) {
1249
                break;
1250
            }
1251
            $carry[$key] = $value;
1252
        }
1253
        return $carry;
1254
    };
1255
}
1256
1257
/**
1258
 * Creates a function that allows you to take a slice of an array until the passed conditional
1259
 * returns false.
1260
 *
1261
 * @param callable(mixed): bool $conditional
1262
 * @return Closure(mixed[]):mixed[]
1263
 */
1264
function takeWhile(callable $conditional): Closure
1265
{
1266
    /**
1267
     * @param mixed[] $array
1268
     * @return mixed[]
1269
     */
1270
    return function (array $array) use ($conditional) {
1271
        $carry = array();
1272
        foreach ($array as $key => $value) {
1273
            if (false === $conditional($value)) {
1274
                break;
1275
            }
1276
            $carry[$key] = $value;
1277
        }
1278
        return $carry;
1279
    };
1280
}
1281
1282
/**
1283
 * Picks selected indexes from an array
1284
 *
1285
 * @param string ...$indexes
1286
 * @return Closure(mixed[]):mixed[]
1287
 */
1288
function pick(string ...$indexes): Closure
1289
{
1290
    /**
1291
     * @param mixed[] $array
1292
     * @return mixed[]
1293
     */
1294
    return function (array $array) use ($indexes) {
1295
        return array_intersect_key($array, array_flip($indexes));
1296
    };
1297
}
1298