Completed
Push — master ( 65bef2...0690b3 )
by Michael
05:16
created

Arrgh::__call()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 2
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 34 and the first side effect is on line 909.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
3
namespace Arrgh;
4
5
use \Closure;
6
use \Exception;
7
use \InvalidArgumentException;
8
9
/**
10
 * A chainable array API or a set of static functions, or both.
11
 *
12
 * Note: arr_* global functions are defined at the end of the file
13
 *
14
 * @method string getString()
15
 * @method void setInteger(integer $integer)
16
 * @method setString(integer $integer)
17
 * @method array collapse(array $input)
18
 * @method bool contains(array $haystack, string $needle, string $key)
19
 * @method array except(array $input, array|string $keys)
20
 * @method array only(array $input, array|string $keys)
21
 * @method array map_assoc(array $input, \Closure $callable)
22
 * @method array sort_by(array $input, string $key)
23
 * @method integer depth(array $input)
24
 * @method array even(array $input)
25
 * @method mixed first(array $input)
26
 * @method array get(array, $input, array|string, $path, bool $collapse)
27
 * @method mixed head(array $input)
28
 * @method bool is_collection(array $input)
29
 * @method mixed last(array $input)
30
 * @method array odd(array $input)
31
 * @method array partition(array $input, \Closure $callable)
32
 * @method array tail(array $input)
33
 */
34
class Arrgh implements \ArrayAccess, \Iterator
35
{
36
    const PHP_SORT_DIRECTION_56 = 1;
37
    const PHP_SORT_DIRECTION_7 = -1;
38
39
    private $array;
40
    private $array_position;
41
    private $original_array;
42
    private $terminate;
43
    private $keep_once;
44
    private $last_value;
45
46
    private static $php_version;
47
    private static $php_sort_direction;
48
49
    /* Creates a new arrgh array */
50
    public function __construct($array = [])
51
    {
52
        $this->array = $array;
53
        $this->array_position = 0;
54
        if ($array instanceof Arrgh) {
55
            $this->array = $array->toArray();
56
        }
57
        $this->original_array = $this->array;
58
        $this->terminate = true;
59
    }
60
61
    /* Starts object calls */
62
    public function __call($method, $args)
63
    {
64
        return self::invoke($method, $args, $this);
65
    }
66
67
    /* Returns an array */
68
    public function toArray()
69
    {
70
        $array = array_map(function($item) {
71
            if ($item instanceof Arrgh) {
72
                return $item->toArray();
73
            }
74
            return $item;
75
        }, $this->array);
76
        return $array;
77
    }
78
79
    public function keep()
80
    {
81
        return $this->keepChain(true);
82
    }
83
    public function keepOnce()
84
    {
85
        return $this->keepChain(true, true);
86
    }
87
    public function keepChain($value = true, $keep_once = false)
88
    {
89
        $this->terminate = !$value;
90
        $this->keep_once = $keep_once;
91
        return $this;
92
    }
93
    public function breakChain()
94
    {
95
        return $this->keepChain(false);
96
    }
97
98
    /* ArrayAccess */
99
    public function offsetExists($offset)
100
    {
101
        return isset($this->array[$offset]);
102
    }
103
104
    /* ArrayAccess */
105
    public function offsetGet($offset)
106
    {
107
        return isset($this->array[$offset]) ? $this->array[$offset] : null;
108
    }
109
110
    /* ArrayAccess */
111
    public function offsetSet($offset, $value)
112
    {
113
        if (is_null($offset)) {
114
            $this->array[] = $value;
115
        } else {
116
            $this->array[$offset] = $value;
117
        }
118
    }
119
120
    /* ArrayAccess */
121
    public function offsetUnset($offset)
122
    {
123
        unset($this->array[$offset]);
124
    }
125
126
    /* Iterator */
127
    public function current()
128
    {
129
        $value = $this->array[$this->array_position];
130
        if (is_array($value)) {
131
            return new Arrgh($value);
132
        }
133
        return $value;
134
    }
135
136
    /* Iterator */
137
    public function key()
138
    {
139
        return $this->array_position;
140
    }
141
142
    /* Iterator */
143
    public function next()
144
    {
145
        ++$this->array_position;
146
    }
147
148
    /* Iterator */
149
    public function rewind()
150
    {
151
        $this->array_position = 0;
152
    }
153
154
    /* Iterator */
155
    public function valid()
156
    {
157
        return isset($this->array[$this->array_position]);
158
    }
159
160
    /* Creates a new arr array. Synonym for: chain() */
161
    public static function arr($array = [])
162
    {
163
        return self::chain($array);
164
    }
165
166
    /* Creates a new arrgh array. Synonym for: arr() */
167
    public static function chain($array = [])
168
    {
169
        return new self($array);
170
    }
171
172
    /* Starts object calls */
173
    public static function __callStatic($method, $args)
174
    {
175
        if ($method[0] === "_") {
176
            $method = substr($method, 1);
177
            $_args = $args;
178
            $first_argument = array_shift($args);
179
            if (is_array($first_argument)) {
180
                return self::chain($first_argument)->$method(...$args);
181
            }
182
            return self::chain()->$method(...$_args);
183
        }
184
        return self::invoke($method, $args);
185
    }
186
187
    public static function allFunctions()
188
    {
189
        return [
190
            "_arrgh"        => self::$arr_functions,
191
            "_call"         => self::$simple_functions,
192
            "_rotateRight"  => self::$reverse_functions,
193
            "_swapTwoFirst" => self::$swapped_functions,
194
            "_copy"         => self::$mutable_functions,
195
            "_copyMultiple" => self::$mutable_functions_multiple,
196
            "_copyValue"    => self::$mutable_value_functions,
197
        ];
198
    }
199
200
    public static function getSortDirection($direction = null)
201
    {
202
        if (self::$php_version === null) {
203
            self::$php_version = explode(".", phpversion());
204
            self::$php_sort_direction = self::$php_version[0] >= 7 ? self::PHP_SORT_DIRECTION_7 : self::PHP_SORT_DIRECTION_56;
205
        }
206
        if ($direction === null || $direction === 0) {
207
            return self::$php_sort_direction;
208
        }
209
        return $direction;
210
    }
211
212
    /* Wraps a callable with the purpose of fixing bad PHP sort implementations */
213
    private static function wrapCallable(Closure $callable)
214
    {
215
        $direction = self::getSortDirection();
216
        return function($a, $b) use ($direction, $callable) {
217
            $result = $callable($a, $b);
218
            if ($result === 0) return $direction;
219
            return $result;
220
        };
221
    }
222
223
    /* Based on input method finds handler, function and post handler */
224
    private static function findFunction($method)
225
    {
226
        $snake = strtolower(preg_replace('/\B([A-Z])/', '_\1', $method));
227
        $function_name = $snake;
228
        $function_name_prefixed = stripos($method, "array_") === 0 ? $snake : "array_" . $snake;
229
230
        $all_function_names = [$function_name, $function_name_prefixed];
231
        $all_functions      = self::allFunctions();
232
233
        $matching_handler  = null;
234
        $matching_function = null;
235
        $post_handler      = null;
236
        foreach ($all_functions as $handler => $functions) {
237
            foreach ($all_function_names as $function) {
238
                if (in_array($function, $functions)) {
239
                    $matching_handler  = $handler;
240
                    $matching_function = $function;
241
                    break 2;
242
                }
243
            }
244
        }
245
246
        if ($matching_function === null) {
247
            throw new InvalidArgumentException("Method {$method} doesn't exist");
248
        }
249
        return [$matching_handler, $matching_function, $post_handler];
250
    }
251
252
    /* Transforms the incoming calls to native calls */
253
    private static function invoke($method, $args, $object = null)
254
    {
255
        self::getSortDirection();
256
257
        list($matching_handler, $matching_function, $post_handler) = self::findFunction($method);
258
259
        switch ($matching_function) {
260
            case "asort":
261
                self::handleCaseAsort($matching_handler, $matching_function, $post_handler, $args);
262
                break;
263
            case "array_column":
264
                self::handleCaseArrayColumn($matching_handler, $matching_function, $post_handler, $args);
265
                break;
266
            default:
267
                break;
268
        }
269
270
        // If chain unshift array onto argument stack
271
        if ($object && !in_array($matching_function, self::$starters)) {
272
            array_unshift($args, $object->array);
273
        }
274
275
        // If some arrays are Arrghs map to array or if callable, wrap it in
276
        // new callable with info about sort direction.
277
        $args = array_map(function($arg) use ($matching_function) {
278
            if ($arg instanceof Arrgh) {
279
                return $arg->array;
280
            } else if ($arg instanceof Closure) {
281
                if (in_array($matching_function, self::$reverse_result_functions) && self::$php_version[0] < 7) {
282
                    return self::wrapCallable($arg);
283
                }
284
            }
285
            return $arg;
286
        }, $args);
287
288
        // Invoke handler
289
        // (issue with strings that has been passed as reference when using
290
        // them as callable, see https://bugs.php.net/bug.php?id=71622)
1 ignored issue
show
Unused Code Comprehensibility introduced by
46% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
291
        $reldnah_gnihctam = $matching_handler;
292
        $result = self::$reldnah_gnihctam($matching_function, $args, $object);
293
294
        // If a post handler is registered let it modify the result
295
        if ($post_handler) {
296
            $result = $post_handler($result);
297
        }
298
299
        if ($object) {
300
            if (in_array($matching_function, self::$terminators)) {
301
                if ($object->terminate) {
302
                    if (is_array($result)) {
303
                        return new Arrgh($result);
304
                    }
305
                    return $result;
306
                }
307
                if ($object->keep_once) {
308
                    $object->terminate = true;
309
                    $object->keep_once = false;
310
                }
311
                $object->last_value = $result;
312
                return $object;
313
            }
314
            $object->array = $result;
315
            return $object;
316
        }
317
        return $result;
318
    }
319
320
    /* Handles special case: asort - In PHP5 reverses equals ("arsort" doen't mess up for some reason) */
321
    private static function handleCaseAsort(&$matching_handler, &$matching_function, &$post_handler, &$args)
2 ignored issues
show
Unused Code introduced by
The parameter $matching_handler is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $post_handler is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
322
    {
323
        $matching_function = "uasort";
324
        array_push($args, function($a, $b) { return strcasecmp($a, $b); });
325
    }
326
327
    /* Handles special case: array_column - Native array_column filters away null values.
328
     * That means you cannot use array_column for multisort since array size no longer matches.
329
     * This version of array_column returns null if the column is missing. */
330
    private static function handleCaseArrayColumn(&$matching_handler, &$matching_function, &$post_handler, &$args)
331
    {
332
        $matching_handler  = "_rotateRight";
333
        $matching_function = "array_map";
334
        $column_array = $args[0];
335
        $column_key   = $args[1];
336
        if (count($args) === 3) {
337
            $column_id = $args[2];
338
            $column_ids_new = array_map(function($item) use ($column_id) {
339
                return isset($item[$column_id]) ? $item[$column_id] : null;
340
            }, $column_array);
341
            $post_handler = function($result) use ($column_ids_new) {
342
                return array_combine($column_ids_new, $result);
343
            };
344
        }
345
        $args = [$column_array];
346
        array_push($args, function($item) use ($column_key) {
347
            return isset($item[$column_key]) ? $item[$column_key] : null;
348
        });
349
    }
350
351
    /* Calls the native function directly */
352
    private static function _call($function, $args)
353
    {
354
        return $function(...$args);
355
    }
356
357
    /* Shifts of the first argument (callable) and pushes it to the end */
358
    private static function _rotateRight($function, $args)
359
    {
360
        $first_argument = array_pop($args);
361
        array_unshift($args, $first_argument);
362
        return $function(...$args);
363
    }
364
365
    /* Swaps the first two args */
366
    private static function _swapTwoFirst($function, $args)
367
    {
368
        $first_argument = array_shift($args);
369
        $second_argument = array_shift($args);
370
        array_unshift($args, $first_argument);
371
        array_unshift($args, $second_argument);
372
        return $function(...$args);
373
    }
374
375
    /* Makes a copy of the array and returns it after invoking function */
376
    private static function _copy($function, $args)
377
    {
378
        $array = array_shift($args);
379
        $function($array, ...$args);
380
        return $array;
381
    }
382
383
    /* If multiple arrays are passed as arguments mulitple will be returned. Otherwise _copy is used */
384
    private static function _copyMultiple($function, $args)
385
    {
386
        $function(...$args);
387
        $arrays = [];
388
        foreach ($args as $arg) {
389
            if (is_array($arg)) {
390
                $arrays[] = $arg;
391
            }
392
        }
393
        if (count($arrays) === 1) {
394
            return $arrays[0];
395
        }
396
        return $arrays;
397
    }
398
399
    /* Makes a copy of the array and returns it after invoking function */
400
    private static function _copyValue($function, $args, $object = null)
401
    {
402
        $array = array_shift($args);
403
        $result = $function($array, ...$args);
404
        if ($object) {
405
            $object->array = $array;
406
        }
407
        return $result;
408
    }
409
410
    private static function _arrgh($function, $args)
411
    {
412
        $function = "arr_" . $function;
413
        return self::$function(...$args);
414
    }
415
416
    private static function arr_map_assoc($array, Closure $callable)
417
    {
418
        $keys = array_keys($array);
419
        return array_combine($keys, array_map($callable, $keys, $array));
420
    }
421
422
    /**
423
     * Sort an array of associative arrays by key. It checks the first two values for type
424
     * either sorts by number or using strcmp. If a key is missing entries are moved to the top
425
     * (or bottom depending on $direction)
426
     */
427
    private static function arr_sort_by($array, $key, $direction = "ASC")
428
    {
429
        $direction_int = strtoupper($direction) === "ASC" ? 1 : -1;
430
431
        if ($key instanceof Closure) {
432
            usort($array, self::wrapCallable($key));
433
            if ($direction_int === -1) {
434
                return array_reverse($array);
435
            }
436
            return $array;
437
        }
438
439
        $column = array_map(function($item) use ($key) {
440
            return isset($item[$key]) ? $item[$key] : null;
441
        }, $array);
442
        array_multisort($column, ($direction_int === 1 ? SORT_ASC : SORT_DESC), $array);
443
        return $array;
444
    }
445
446
    private static function arr_collapse($array)
447
    {
448
        return array_reduce($array, function($merged, $item) {
449
            if (is_array($item)) {
450
                return array_merge($merged, $item);
451
            }
452
            $merged[] = $item;
453
            return $merged;
454
        }, []);
455
    }
456
457
    private static function arr_contains($array, $search, $key = null)
458
    {
459
        $haystack = null;
1 ignored issue
show
Unused Code introduced by
$haystack is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
460
        if ($key) {
461
            $haystack = array_column($array, $key);
462
        } else {
463
            $haystack = array_reduce($array, function($merged, $item) {
464
                return array_merge($merged, array_values($item));
465
            }, []);
466
        }
467
        return array_search($search, $haystack) !== false;
468
    }
469
470
    private static function arr_except($array, $except)
471
    {
472
        if (is_string($except)) {
473
            $except = [$except];
474
        }
475
476
        $is_collection = self::arr_is_collection($array);
477
        $array = $is_collection ? $array : [$array];
478
479
        $result = array_map(function($item) use ($except) {
480
            foreach ($except as $key) {
481
                unset($item[$key]);
482
            }
483
            return $item;
484
        }, $array);
485
486
        if ($is_collection) {
487
            return $result;
488
        }
489
        return $result[0];
490
    }
491
492
    private static function arr_only($array, $only)
493
    {
494
        if (is_string($only)) {
495
            $only = [$only];
496
        }
497
498
        $is_collection = self::arr_is_collection($array);
499
        $array = $is_collection ? $array : [$array];
500
501
        $result = array_map(function($item) use ($only) {
502
            foreach ($item as $key => $value) {
503
                if (!in_array($key, $only)) {
504
                    unset($item[$key]);
505
                }
506
            }
507
            return $item;
508
        }, $array);
509
510
        if ($is_collection) {
511
            return $result;
512
        }
513
        return $result[0];
514
    }
515
516
    /**
517
     *  Get for multi-dimensional arrays
518
     *
519
     *  @param array      An array to query on
520
     *  @param path|array A string representing the path to traverse.
521
     *                    Optionally pass as [ $path, ...$functions ] if `!$` is used
522
     *  @param bool       Collapse resulting data-set
523
     *  @throws Exception Thrown when a path cannot be reached in case $array does
524
     *                    not correspond to path type. E.g. collection expected
525
     *                    but a simple value was encountered.
526
     */
527
    private static function arr_get($array, $path, $collapse = false)
528
    {
529
        $path_string = $path;
530
        if (is_array($path)) {
531
            $path_string = array_shift($path);
532
        }
533
        $path_segments = explode(".", $path_string);
534
        return self::_arr_get_traverse($array, $path_segments, $collapse, /* functions */ $path);
535
    }
536
537
    /* arr_get: Traverses path to get value */
538
    private static function _arr_get_traverse($data, $path, $collapse = false, $functions = [])
539
    {
540
        $next_key      = array_shift($path);
541
        $plug_index    = is_numeric($next_key) ? (int) $next_key : null;
542
        $is_collection = self::isCollection($data);
543
        $next_node = null;
1 ignored issue
show
Unused Code introduced by
$next_node is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
544
545
        // Apply custom function
546
        if ($next_key === '!$') {
547
            if ($is_collection) {
548
                list($data, $path, $functions, $next_key) = self::_arr_get_traverse_apply_custom_function($data, $functions, $path);
549
            } else {
550
                throw new Exception("Invalid path trying to invoke function on non-collection");
551
            }
552
        }
553
554
        // Select data either by index or key
555
        if ($plug_index === null) {
556
            $next_node = self::_arr_get_traverse_next_node_key($data, $is_collection, $next_key);
557
        } else {
558
            if ($is_collection) {
559
                $next_node = self::_arr_get_traverse_next_node_index($data, $plug_index);
560
            } else {
561
                throw new Exception("Invalid path trying to plug item but data is not a collection");
562
            }
563
        }
564
565
        // If nothing matched break path and return
566
        if (empty($next_node)) {
567
            return null;
568
        }
569
570
        // If path is at the end return
571
        if (count($path) === 0) {
572
            if (is_array($next_node) && $collapse) {
573
                return array_filter($next_node);
574
            }
575
            return $next_node;
576
        }
577
578
        // If path is not completed
579
        if (is_array($next_node)) {
580
581
            // Recurse
582
            $node_is_collection = self::arr_is_collection($next_node);
583
            $node_depth = self::arr_depth($next_node);
584
585
            if ($node_is_collection) {
586
                // Collapse collections
587
                if ($collapse                  // if enabled
588
                    && !is_numeric($path[0])   // if next path segment is not an index
589
                    && $path[0] !== "!$"       // if not the result of a custom function
590
                    && $node_depth > 0         // if array of arrays
591
                ) {
592
                    $next_node = self::arr_collapse($next_node);
593
                }
594
595
                if (is_numeric($path[0]) && $node_depth < 1) {
596
                    $result = self::_arr_get_traverse($next_node, $path, $collapse, $functions);
597
                } else {
598
                    // Collect data from sub-tree
599
                    $result = [];
600
                    foreach ($next_node as $node) {
601
                        if ($node === null) {
602
                            $result[] = null;
603
                        } else {
604
                            $partial = self::_arr_get_traverse($node, $path, $collapse, $functions);
605
                            if ($collapse) {
606
                                $result[] = $partial;
607
                            } else {
608
                                $result[] = [$partial];
609
                            }
610
                        }
611
                    }
612
                }
613
614
                // Since collection functions inject an array segment we must collapse the result
615
                if ($path[0] === "!$") {
616
                    $result = self::arr_collapse($result);
617
                }
618
            } else {
619
                $result = self::_arr_get_traverse($next_node, $path, $collapse, $functions);
620
            }
621
622
            // Collapse result if needed
623
            if (is_array($result)) {
624
                // Collapse collections greater than 1
625
                if (self::arr_depth($result) > 1) {
626
                    $result = self::arr_collapse($result);
627
                }
628
                return array_filter($result);
629
            }
630
            return $result;
631
        }
632
        throw new Exception("Next node in path is not an array");
633
    }
634
635
    /* arr_get: Find next node by index */
636
    private static function _arr_get_traverse_next_node_index($data, $plug_index)
637
    {
638
        // Adjust negative index
639
        if ($plug_index < 0) {
640
            $count = count($data);
641
            $plug_index = $count === 1 ? 0 : $count + ($plug_index % $count);
642
        }
643
644
        // Plug data
645
        if (isset($data[$plug_index])) {
646
            return $data[$plug_index];
647
        }
648
        return null;
649
    }
650
651
    /* arr_get: Find next node by key */
652
    private static function _arr_get_traverse_next_node_key($data, $is_collection, $next_key)
653
    {
654
        if ($next_key === null) {
655
            return $data;
656
        }
657
        if ($is_collection) {
658
            return array_map(function($item) use ($next_key) {
659
                if ($item !== null && array_key_exists($next_key, $item)) {
660
                    return $item[$next_key];
661
                }
662
                return null;
663
            }, $data);
664
        } else if (is_array($data)) {
665
            if (array_key_exists($next_key, $data)) {
666
                return $data[$next_key];
667
            }
668
            return null;
669
        }
670
        throw new Exception("Path ...$next_key does not exist");
671
    }
672
673
    /* arr_get: Invoke custom filter function on path */
674
    private static function _arr_get_traverse_apply_custom_function($data, $functions, $path)
675
    {
676
        $function  = array_shift($functions);
677
        $data      = array_values(array_filter($data, $function, ARRAY_FILTER_USE_BOTH));
678
        $next_key  = array_shift($path);
679
        return [$data, $path, $functions, $next_key];
680
    }
681
682
    private static function arr_is_collection($mixed)
683
    {
684
        return is_array($mixed) && array_values($mixed) === $mixed;
685
    }
686
687
    /**
688
     * Return the depth of a collection hiearchy. Zero based.
689
     *
690
     * @param array A collection
691
     * @return int `null` if $array is not a collection.
692
     */
693
    private static function arr_depth($array)
694
    {
695
        if (empty($array) && is_array($array)) return 0;
696
        if (!self::arr_is_collection($array)) return null;
697
698
        $depth = 0;
699
        $child = array_shift($array);
700
        while (self::arr_is_collection($child)) {
701
            $depth += 1;
702
            $child = array_shift($child);
703
        }
704
        return $depth;
705
    }
706
707
    /**
708
     * Partion the input based on the result of the callback function.
709
     *
710
     * @param array     $array    A collection
711
     * @param \Closeure $callable A callable returning true or false depending on which way to partion the element—left or right.
712
     * @return array An array with two arrays—left and right: [left, right]
713
     */
714
    private static function arr_partition($array, Closure $callable)
715
    {
716
        $left = [];
717
        $right = [];
718
        array_walk($array, function($item, $key) use (&$left, &$right, $callable) {
719
            if ($callable($item, $key)) {
720
                $left[] = $item;
721
            } else {
722
                $right[] = $item;
723
            }
724
        });
725
        return [$left, $right];
726
    }
727
728
    private static function arr_even($array)
729
    {
730
        return self::arr_partition($array, function($item, $key) { return $key % 2 === 0; })[0];
731
    }
732
733
    private static function arr_odd($array)
734
    {
735
        return self::arr_partition($array, function($item, $key) { return $key % 2 === 1; })[0];
736
    }
737
738
    /* Synonym of shift */
739
    private static function arr_head($array)
740
    {
741
        return self::shift($array);
742
    }
743
744
    private static function arr_first($array)
745
    {
746
        if (count($array)) {
747
            return $array[0];
748
        }
749
        return null;
750
    }
751
752
    private static function arr_last($array)
753
    {
754
        if (count($array)) {
755
            return $array[count($array) - 1];
756
        }
757
        return null;
758
    }
759
760
    private static function arr_tail($array)
761
    {
762
        return self::chain($array)->keep()->shift()->toArray();
0 ignored issues
show
Documentation Bug introduced by
The method shift does not exist on object<Arrgh\Arrgh>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
763
    }
764
765
    // _arrgh
766
    static private $arr_functions = [
767
        "collapse",
768
        "contains",
769
        "except",
770
        "map_assoc",
771
        "only",
772
        "sort_by",
773
        'depth',
774
        'even',
775
        'first',
776
        'get',
777
        'head',
778
        'is_collection',
779
        'last',
780
        'odd',
781
        'partition',
782
        'tail',
783
    ];
784
785
    // _call
786
    static private $simple_functions = [
787
        "array_change_key_case",
788
        "array_chunk",
789
        "array_column",
790
        "array_combine",
791
        "array_count_values",
792
        "array_diff",
793
        "array_diff_assoc",
794
        "array_diff_key",
795
        "array_diff_uassoc",
796
        "array_diff_ukey",
797
        "array_fill",
798
        "array_fill_keys",
799
        "array_filter",
800
        "array_flip",
801
        "array_intersect",
802
        "array_intersect_assoc",
803
        "array_intersect_key",
804
        "array_intersect_uassoc",
805
        "array_intersect_ukey",
806
        "array_keys",
807
        "array_merge",
808
        "array_merge_recursive",
809
        "array_pad",
810
        "array_product",
811
        "array_rand",
812
        "array_reduce",
813
        "array_replace",
814
        "array_replace_recursive",
815
        "array_reverse",
816
        "array_slice",
817
        "array_sum",
818
        "array_udiff",
819
        "array_udiff_assoc",
820
        "array_udiff_uassoc",
821
        "array_uintersect",
822
        "array_uintersect_assoc",
823
        "array_uintersect_uassoc",
824
        "array_unique",
825
        "array_values",
826
        "count",
827
        "max",
828
        "min",
829
        "range",
830
        "sizeof",
831
    ];
832
833
    // _copy
834
    static private $mutable_functions = [
835
        "array_push",
836
        "array_splice",
837
        "array_unshift",
838
        "array_walk",
839
        "array_walk_recursive",
840
        "arsort",
841
        "asort",
842
        "krsort",
843
        "ksort",
844
        "natcasesort",
845
        "natsort",
846
        "rsort",
847
        "shuffle",
848
        "sort",
849
        "uasort",
850
        "uksort",
851
        "usort",
852
    ];
853
854
    // _copyMultiple
855
    static private $mutable_functions_multiple = [
856
        "array_multisort",
857
    ];
858
859
    // _copyValue
860
    static private $mutable_value_functions = [
861
        "array_pop",
862
        "array_shift",
863
        "end",
864
    ];
865
866
    // _rotateRight
867
    static private $reverse_functions = [
868
        "array_map",
869
    ];
870
871
    // _swapTwoFirst
872
    static private $swapped_functions = [
873
        "array_key_exists",
874
        "array_search",
875
        "implode",
876
        "in_array",
877
        "join",
878
    ];
879
880
    static private $starters = [
881
        "array_fill",
882
        "array_fill_keys",
883
        "range",
884
    ];
885
886
    static private $terminators = [
887
        "array_pop",
888
        "array_shift",
889
        "array_sum",
890
        "count",
891
        "first",
892
        "head",
893
        "join",
894
        "last",
895
        "max",
896
        "min",
897
        "sizeof",
898
    ];
899
900
    static private $reverse_result_functions = [
901
        "uasort",
902
        "uksort",
903
        "usort",
904
        "asort",
905
    ];
906
}
907
908
if (defined("ARRGH")) {
909
    require __DIR__ . '/arrgh_functions.php';
910
}
911