Completed
Pull Request — master (#10)
by Zeeshan
01:46
created

Collections::isEmpty()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 1
cts 1
cp 1
rs 10
c 0
b 0
f 0
cc 3
eloc 1
nc 3
nop 1
crap 3
1
<?php
2
3
namespace __\Traits;
4
5
use __;
6
use Closure;
7
use Exception;
8
use stdClass;
9
10
trait Collections
11
{
12
    /**
13
     * Returns the values in the collection that pass the truth test.
14
     *
15
     * @param array    $array   array to filter
16
     * @param \Closure $closure closure to filter array based on
17
     *
18
     * @return array
19
     *
20
     */
21 1
    public static function filter(array $array = [], Closure $closure = null): array
22
    {
23 1
        if ($closure) {
24 1
            $result = [];
25
26 1
            foreach ($array as $key => $value) {
27 1
                if (\call_user_func($closure, $value)) {
28 1
                    $result[] = $value;
29
                }
30
            }
31
32 1
            return $result;
33
34
        }
35
36 1
        return __::compact($array);
37
    }
38
39
    /**
40
     * Gets the first element of an array. Passing n returns the first n elements.
41
     *
42
     * __::first([1, 2, 3]);
43
     * >> // => 1
44
     *
45
     * @param array    $array of values
46
     * @param int|null $take  number of values to return
47
     *
48
     * @return array|mixed
49
     *
50
     */
51 8
    public static function first(array $array, $take = null)
52
    {
53 8
        if (!$take) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $take of type null|integer is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
54 7
            return array_shift($array);
55
        }
56
57 1
        return array_splice($array, 0, $take, true);
58
    }
59
60
    /**
61
     * Get item of an array by index, accepting nested index
62
     *
63
     ** __::get(['foo' => ['bar' => 'ter']], 'foo.bar');
64
     ** // >> 'ter'
65
     *
66
     * @param array|object  $collection array of values
67
     * @param string $key        key or index
68
     * @param null   $default    default value to return if index not exist
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $default is correct as it would always require null to be passed?
Loading history...
69
     *
70
     * @return array|mixed|null
71
     *
72
     */
73 15
    public static function get($collection = [], string $key = '', $default = null)
74
    {
75 15
        if (__::isNull($key)) {
76
            return $collection;
77
        }
78
79 15
        if (!__::isObject($collection) && isset($collection[$key])) {
80 9
            return $collection[$key];
81
        }
82
83 8
        foreach (\explode('.', $key) as $segment) {
84 8
            if (__::isObject($collection)) {
85 5
                if (!isset($collection->{$segment})) {
86 2
                    return $default instanceof Closure ? $default() : $default;
87
                } else {
88 5
                    $collection = $collection->{$segment};
89
                }
90
            } else {
91 3
                if (!isset($collection[$segment])) {
92 3
                    return $default instanceof Closure ? $default() : $default;
93
                } else {
94 8
                    $collection = $collection[$segment];
95
                }
96
            }
97
        }
98
99 8
        return $collection;
100
    }
101
102
    /**
103
     * Get last item(s) of an array
104
     *
105
     * __::last([1, 2, 3, 4, 5], 2);
106
     * // >> [4, 5]
107
     *
108
     * @param array    $array array of values
109
     * @param int|null $take  number of returned values
110
     *
111
     * @return array|mixed
112
     *
113
     */
114 1
    public static function last(array $array, $take = null)
115
    {
116 1
        if (!$take) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $take of type null|integer is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
117 1
            return array_pop($array);
118
        }
119
120 1
        return array_splice($array, -$take);
121
    }
122
123
    /**
124
     * Returns an array of values by mapping each in collection through the iteratee. The iteratee is invoked with
125
     * three arguments: (value, index|key, collection).
126
     *
127
     * __::map([1, 2, 3], function($n) {
128
     *     return $n * 3;
129
     * });
130
     * // >> [3, 6, 9]
131
     *
132
     * @param array|object $collection The collection of values to map over.
133
     * @param \Closure     $iteratee   The function to apply on each value.
134
     *
135
     * @return array
136
     */
137 8
    public static function map($collection, Closure $iteratee): array
138
    {
139 8
        $result = [];
140
141 8
        __::doForEach($collection, function ($value, $key, $collection) use (&$result, $iteratee) {
142 8
            $result[] = $iteratee($value, $key, $collection);
143 8
        });
144
145 8
        return $result;
146
    }
147
148
    /**
149
     * Returns the maximum value from the collection. If passed an iterator, max will return max value returned by the
150
     * iterator.
151
     *
152
     * __::max([1, 2, 3]);
153
     * // >> 3
154
     *
155
     * @param array $array The array to iterate over
156
     *
157
     * @return mixed Returns the maximum value
158
     *
159
     */
160 1
    public static function max(array $array = [])
161
    {
162 1
        return max($array);
163
    }
164
165
    /**
166
     * Returns the minimum value from the collection. If passed an iterator, min will return min value returned by the
167
     * iterator.
168
     *
169
     * __::min([1, 2, 3]);
170
     * // >> 1
171
     *
172
     * @param array $array array of values
173
     *
174
     * @return mixed
175
     *
176
     */
177 1
    public static function min(array $array = [])
178
    {
179 1
        return min($array);
180
    }
181
182
    /**
183
     * Returns an array of values belonging to a given property of each item in a collection.
184
     *
185
     ** $a = [
186
     **    ['foo' => 'bar',  'bis' => 'ter' ],
187
     **    ['foo' => 'bar2', 'bis' => 'ter2'],
188
     ** ];
189
     **
190
     ** __::pluck($a, 'foo');
191
     ** // >> ['bar', 'bar2']
192
     *
193
     * @param array|object $collection array or object that can be converted to array
194
     * @param string       $property   property name
195
     *
196
     * @return array
197
     */
198
    public static function pluck($collection, string $property): array
199
    {
200 1
        $result = array_map(function ($value) use ($property) {
201 1
            if (is_array($value) && isset($value[$property])) {
202 1
                return $value[$property];
203 1
            } elseif (is_object($value) && isset($value->{$property})) {
204 1
                return $value->{$property};
205
            }
206 1
            foreach (__::split($property, '.') as $segment) {
0 ignored issues
show
Bug introduced by
The expression __::split($property, '.') of type string is not traversable.
Loading history...
207 1
                if (is_object($value)) {
208 1
                    if (isset($value->{$segment})) {
209 1
                        $value = $value->{$segment};
210
                    } else {
211 1
                        return null;
212
                    }
213
                } else {
214 1
                    if (isset($value[$segment])) {
215 1
                        $value = $value[$segment];
216
                    } else {
217 1
                        return null;
218
                    }
219
                }
220
            }
221
222 1
            return $value;
223 1
        }, (array)$collection);
224
225 1
        return array_values($result);
226
    }
227
228
229
    /**
230
     * Return data matching specific key value condition
231
     *
232
     **_::where($a, ['age' => 16]);
233
     ** // >> [['name' => 'maciej', 'age' => 16]]
234
     *
235
     * @param array $array    array of values
236
     * @param array $key      condition in format of ['KEY'=>'VALUE']
237
     * @param bool  $keepKeys keep original keys
238
     *
239
     * @return array
240
     *
241
     */
242 1
    public static function where(array $array = [], array $key = [], bool $keepKeys = false): array
243
    {
244 1
        $result = [];
245
246 1
        foreach ($array as $k => $v) {
247 1
            $not = false;
248
249 1
            foreach ($key as $j => $w) {
250 1
                if (__::isArray($w)) {
251
                    if (count(array_intersect($w, $v[$j])) == 0) {
252
                        $not = true;
253
                        break;
254
                    }
255
                } else {
256 1
                    if (!isset($v[$j]) || $v[$j] != $w) {
257 1
                        $not = true;
258 1
                        break;
259
                    }
260
                }
261
            }
262
263 1
            if ($not == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
264 1
                if ($keepKeys) {
265
                    $result[$k] = $v;
266
                } else {
267 1
                    $result[] = $v;
268
                }
269
            }
270
        }
271
272 1
        return $result;
273
    }
274
275
    /**
276
     * Combines and merge collections provided with each others.
277
     *
278
     * If the collections have common keys, then the last passed keys override the
279
     * previous. If numerical indexes are passed, then last passed indexes override
280
     * the previous.
281
     *
282
     * For a recursive merge, see __::merge.
283
     *
284
     ** __::assign(['color' => ['favorite' => 'red', 5], 3], [10, 'color' => ['favorite' => 'green', 'blue']]);
285
     ** // >> ['color' => ['favorite' => 'green', 'blue'], 10]
286
     *
287
     * @param array|object $collection1 Collection to assign to.
288
     * @param array|object $collection2 Other collections to assign
289
     *
290
     * @return array|object Assigned collection.
291
     */
292
    public static function assign($collection1, $collection2)
293
    {
294
        return __::reduceRight(func_get_args(), function ($source, $result) {
295 2
            __::doForEach($source, function ($sourceValue, $key) use (&$result) {
296 2
                $result = __::set($result, $key, $sourceValue);
297 2
            });
298
299 2
            return $result;
300 2
        }, []);
301
    }
302
303
    /**
304
     * Reduces $collection to a value which is the $accumulator result of running each
305
     * element in $collection - from right to left - thru $iteratee, where each
306
     * successive invocation is supplied the return value of the previous.
307
     *
308
     * If $accumulator is not given, the first element of $collection is used as the
309
     * initial value.
310
     *
311
     * The $iteratee is invoked with four arguments:
312
     * ($accumulator, $value, $index|$key, $collection).
313
     *
314
     ** __::reduceRight(['a', 'b', 'c'], function ($word, $char) {
315
     **     return $word . $char;
316
     ** }, '');
317
     ** // >> 'cba'
318
     *
319
     * @param array|object $collection The collection to iterate over.
320
     * @param \Closure     $iteratee   The function invoked per iteration.
321
     * @param null         $accumulator
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $accumulator is correct as it would always require null to be passed?
Loading history...
322
     *
323
     * @return array|mixed|null (*): Returns the accumulated value.
324
     */
325 7
    public static function reduceRight($collection, Closure $iteratee, $accumulator = null)
326
    {
327 7
        if ($accumulator === null) {
328
            $accumulator = __::first($collection);
0 ignored issues
show
Bug introduced by
It seems like $collection can also be of type object; however, parameter $array of __::first() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

328
            $accumulator = __::first(/** @scrutinizer ignore-type */ $collection);
Loading history...
329
        }
330
331 7
        __::doForEachRight(
332 7
            $collection,
333 7
            function ($value, $key, $collection) use (&$accumulator, $iteratee) {
334 7
                $accumulator = $iteratee($accumulator, $value, $key, $collection);
335 7
            }
336
        );
337
338 7
        return $accumulator;
339
    }
340
341
    /**
342
     * Iterate over elements of the collection, from right to left, and invokes iterate
343
     * for each element.
344
     *
345
     * The iterate is invoked with three arguments: (value, index|key, collection).
346
     * Iterate functions may exit iteration early by explicitly returning false.
347
     *
348
     ** __::doForEachRight([1, 2, 3], function ($value) { print_r($value) });
349
     ** // >> (Side effect: print 3, 2, 1)
350
     *
351
     * @param array|object $collection The collection to iterate over.
352
     * @param \Closure     $iteratee   The function to call for each value.
353
     *
354
     * @return null
355
     */
356 8
    public static function doForEachRight($collection, Closure $iteratee)
357
    {
358 8
        __::doForEach(__::iteratorReverse($collection), $iteratee);
359 8
    }
360
361
    /**
362
     * Iterate over elements of the collection and invokes iterate for each element.
363
     *
364
     * The iterate is invoked with three arguments: (value, index|key, collection).
365
     * Iterate functions may exit iteration early by explicitly returning false.
366
     *
367
     ** __::doForEach([1, 2, 3], function ($value) { print_r($value) });
368
     ** // >> (Side effect: print 1, 2, 3)
369
     *
370
     * @param array|object $collection The collection to iterate over.
371
     * @param \Closure     $iteratee   The function to call for each value
372
     *
373
     * @return null
374
     */
375 22
    public static function doForEach($collection, Closure $iteratee)
376
    {
377 22
        foreach ($collection as $key => $value) {
378 22
            if ($iteratee($value, $key, $collection) === false) {
379 22
                break;
380
            }
381
        }
382 22
    }
383
384 8
    public static function iteratorReverse($iterable)
385
    {
386 8
        for (end($iterable); ($key = key($iterable)) !== null; prev($iterable)) {
387 8
            yield $key => current($iterable);
388
        }
389 8
    }
390
391
    /**
392
     * Return a new collection with the item set at index to given value.
393
     * Index can be a path of nested indexes.
394
     *
395
     * If a portion of path doesn't exist, it's created. Arrays are created for missing
396
     * index in an array; objects are created for missing property in an object.
397
     *
398
     ** __::set(['foo' => ['bar' => 'ter']], 'foo.baz.ber', 'fer');
399
     ** // >> '['foo' => ['bar' => 'ter', 'baz' => ['ber' => 'fer']]]'
400
     *
401
     * @param array|object $collection collection of values
402
     * @param string       $path       key or index
403
     * @param mixed        $value      the value to set at position $key
404
     *
405
     * @throws \Exception if the path consists of a non collection and strict is set to false
406
     *
407
     * @return array|object the new collection with the item set
408
     *
409
     */
410 13
    public static function set($collection, string $path, $value = null)
411
    {
412 13
        if ($path === null) {
0 ignored issues
show
introduced by
The condition $path === null can never be true.
Loading history...
413
            return $collection;
414
        }
415 13
        $portions = __::split($path, '.', 2);
416 13
        $key      = $portions[0];
417 13
        if (\count($portions) === 1) {
0 ignored issues
show
Bug introduced by
$portions of type string is incompatible with the type Countable|array expected by parameter $var of count(). ( Ignorable by Annotation )

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

417
        if (\count(/** @scrutinizer ignore-type */ $portions) === 1) {
Loading history...
418 13
            return __::universalSet($collection, $key, $value);
419
        }
420
        // Here we manage the case where the portion of the path points to nothing,
421
        // or to a value that does not match the type of the source collection
422
        // (e.g. the path portion 'foo.bar' points to an integer value, while we
423
        // want to set a string at 'foo.bar.fun'. We first set an object or array
424
        //  - following the current collection type - to 'for.bar' before setting
425
        // 'foo.bar.fun' to the specified value).
426
        if (!__::has($collection, $key)
427 6
            || (__::isObject($collection) && !__::isObject(__::get($collection, $key)))
428 4
            || (__::isArray($collection) && !__::isArray(__::get($collection, $key)))
429 6
        ) {
430
            $collection = __::universalSet($collection, $key, __::isObject($collection) ? new stdClass : []);
431 6
        }
432
433
        return __::universalSet($collection, $key, __::set(__::get($collection, $key), $portions[1], $value));
434 6
    }
435
436
    public static function universalSet($collection, $key, $value)
437
    {
438
        $set_object = function ($object, $key, $value) {
439 13
            $newObject       = clone $object;
440 5
            $newObject->$key = $value;
441 5
442
            return $newObject;
443 5
        };
444 13
        $set_array  = function ($array, $key, $value) {
445 13
            $array[$key] = $value;
446 8
447
            return $array;
448 8
        };
449 13
        $setter     = __::isObject($collection) ? $set_object : $set_array;
450 13
451
        return call_user_func_array($setter, [$collection, $key, $value]);
452 13
    }
453
454
    /**
455
     * Returns if $input contains all requested $keys. If $strict is true it also checks if $input exclusively contains
456
     * the given $keys.
457
     *
458
     ** __::hasKeys(['foo' => 'bar', 'foz' => 'baz'], ['foo', 'foz']);
459
     ** // >> true
460
     *
461
     * @param array|object $collection of key values pairs
462
     * @param array        $keys       collection of keys to look for
463
     * @param boolean      $strict     to exclusively check
464
     *
465
     * @return boolean
466
     *
467
     */
468
    public static function hasKeys($collection = [], array $keys = [], bool $strict = false): bool
469 2
    {
470
        $keyCount = count($keys);
471 2
        if ($strict && count($collection) !== $keyCount) {
472 2
            return false;
473 1
        }
474
475
        return __::every(
476 2
            __::map($keys, function ($key) use ($collection) {
477 2
                return __::has($collection, $key);
478 2
            }),
479 2
            function ($v) {
480 2
                return $v === true;
481 2
            }
482 2
        );
483
    }
484
485
    /**
486
     * Return true if $collection contains the requested $key.
487
     *
488
     * In constrast to isset(), __::has() returns true if the key exists but is null.
489
     *
490
     ** __::has(['foo' => ['bar' => 'num'], 'foz' => 'baz'], 'foo.bar');
491
     ** // >> true
492
     *
493
     ** __::hasKeys((object) ['foo' => 'bar', 'foz' => 'baz'], 'bar');
494
     ** // >> false
495
     *
496
     * @param array|object   $collection of key values pairs
497
     * @param string|integer $path       Path to look for.
498
     *
499
     * @return boolean
500
     *
501
     */
502
    public static function has($collection, $path): bool
503 11
    {
504
        $portions = __::split($path, '.', 2);
505 11
        $key      = $portions[0];
506 11
507
        if (count($portions) === 1) {
0 ignored issues
show
Bug introduced by
$portions of type string is incompatible with the type Countable|array expected by parameter $var of count(). ( Ignorable by Annotation )

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

507
        if (count(/** @scrutinizer ignore-type */ $portions) === 1) {
Loading history...
508 11
            return array_key_exists($key, (array)$collection);
509 11
        }
510
511
        return __::has(__::get($collection, $key), $portions[1]);
512 2
    }
513
514
    /**
515
     * Combines and concat collections provided with each others.
516
     *
517
     * If the collections have common keys, then the values are appended in an array.
518
     * If numerical indexes are passed, then values are appended.
519
     *
520
     * For a recursive merge, see __::merge.
521
     *
522
     ** __::concat(['color' => ['favorite' => 'red', 5], 3], [10, 'color' => ['favorite' => 'green', 'blue']]);
523
     ** // >> ['color' => ['favorite' => ['green'], 5, 'blue'], 3, 10]
524
     *
525
     * @param array|object $collection1 Collection to assign to.
526
     * @param array|object $collection2 Other collections to assign.
527
     *
528
     * @return array|object Assigned collection.
529
     */
530
    public static function concat($collection1, $collection2)
531 4
    {
532
        $isObject = __::isObject($collection1);
533 4
534
        $args = __::map(func_get_args(), function ($arg) {
535 4
            return (array)$arg;
536 4
        });
537 4
538
        $merged = call_user_func_array('array_merge', $args);
539 4
540
        return $isObject ? (object)$merged : $merged;
541 4
    }
542
543
    /**
544
     * Recursively combines and concat collections provided with each others.
545
     *
546
     * If the collections have common keys, then the values are appended in an array.
547
     * If numerical indexes are passed, then values are appended.
548
     *
549
     * For a non-recursive concat, see __::concat.
550
     *
551
     ** __::concatDeep(['color' => ['favorite' => 'red', 5], 3], [10, 'color' => ['favorite' => 'green', 'blue']]);
552
     ** // >> ['color' => ['favorite' => ['red', 'green'], 5, 'blue'], 3, 10]
553
     *
554
     * @param array|object $collection1 First collection to concatDeep.
555
     * @param array|object $collection2 other collections to concatDeep.
556
     *
557
     * @return array|object Concatenated collection.
558
     *
559
     */
560
    public static function concatDeep($collection1, $collection2)
561
    {
562
        return __::reduceRight(func_get_args(), function ($source, $result) {
563
            __::doForEach($source, function ($sourceValue, $key) use (&$result) {
564 2
                if (!__::has($result, $key)) {
565 2
                    $result = __::set($result, $key, $sourceValue);
566 2
                } else {
567
                    if (is_numeric($key)) {
568 2
                        $result = __::concat($result, [$sourceValue]);
569 2
                    } else {
570
                        $resultValue = __::get($result, $key);
571 2
                        $result      = __::set($result, $key, __::concatDeep(
572 2
                            __::isCollection($resultValue) ? $resultValue : (array)$resultValue,
573 2
                            __::isCollection($sourceValue) ? $sourceValue : (array)$sourceValue
574 2
                        ));
575
                    }
576
                }
577
            });
578 2
579
            return $result;
580 2
        }, []);
581 2
    }
582
583
    /**
584
     * Flattens a complex collection by mapping each ending leafs value to a key consisting of all previous indexes.
585
     *
586
     * __::ease(['foo' => ['bar' => 'ter'], 'baz' => ['b', 'z']]);
587
     * // >> '['foo.bar' => 'ter', 'baz.0' => 'b', , 'baz.1' => 'z']'
588
     *
589
     * @param array  $collection array of values
590
     * @param string $glue       glue between key path
591
     *
592
     * @return array flatten collection
593
     *
594
     */
595
    public static function ease(array $collection, string $glue = '.'): array
596 1
    {
597
        $map = [];
598 1
        __::_ease($map, $collection, $glue);
599 1
600
        return $map;
601 1
    }
602
603
    /**
604
     * Inner function for collections::ease
605
     *
606
     * @param array  $map
607
     * @param array  $array
608
     * @param string $glue
609
     * @param string $prefix
610
     */
611
    public static function _ease(array &$map, array $array, string $glue, string $prefix = '')
612 1
    {
613
        foreach ($array as $index => $value) {
614 1
            if (is_array($value)) {
615 1
                __::_ease($map, $value, $glue, $prefix . $index . $glue);
616 1
            } else {
617
                $map[$prefix . $index] = $value;
618 1
            }
619
        }
620
    }
621 1
622
    /**
623
     * Checks if predicate returns truthy for all elements of collection.
624
     *
625
     * Iteration is stopped once predicate returns falsey.
626
     * The predicate is invoked with three arguments: (value, index|key, collection).
627
     *
628
     ** __::every([1, 3, 4], function ($v) { return is_int($v); });
629
     ** // → true
630
     *
631
     * @param array|object $collection The collection to iterate over.
632
     * @param \Closure     $iteratee   The function to call for each value.
633
     *
634
     * @return bool
635
     */
636
    public static function every($collection, Closure $iteratee): bool
637 3
    {
638
        $truthy = true;
639 3
640
        __::doForEach(
641 3
            $collection,
642 3
            function ($value, $key, $collection) use (&$truthy, $iteratee) {
643 3
                $truthy = $truthy && $iteratee($value, $key, $collection);
644 3
                if (!$truthy) {
645 3
                    return false;
646 3
                }
647
            }
648 3
        );
649
650
        return $truthy;
651 3
    }
652
653
    /**
654
     * Returns an associative array where the keys are values of $key.
655
     *
656
     * @author Chauncey McAskill
657
     * @link   https://gist.github.com/mcaskill/baaee44487653e1afc0d array_group_by() function.
658
     *
659
     ** __::groupBy([
660
     **         ['state' => 'IN', 'city' => 'Indianapolis', 'object' => 'School bus'],
661
     **         ['state' => 'CA', 'city' => 'San Diego', 'object' => 'Light bulb'],
662
     **         ['state' => 'CA', 'city' => 'Mountain View', 'object' => 'Space pen'],
663
     **     ],
664
     **     'state'
665
     ** );
666
     ** // >> [
667
     ** //   'IN' => [
668
     ** //      ['state' => 'IN', 'city' => 'Indianapolis', 'object' => 'School bus'],
669
     ** //      ['state' => 'CA', 'city' => 'San Diego', 'object' => 'Light bulb'],
670
     ** //   ],
671
     ** //   'CA' => [
672
     ** //      ['state' => 'CA', 'city' => 'Mountain View', 'object' => 'Space pen']
673
     ** //   ]
674
     ** // ]
675
     *
676
     *
677
     ** __::groupBy([
678
     **         ['state' => 'IN', 'city' => 'Indianapolis', 'object' => 'School bus'],
679
     **         ['state' => 'IN', 'city' => 'Indianapolis', 'object' => 'Manhole'],
680
     **         ['state' => 'CA', 'city' => 'San Diego', 'object' => 'Light bulb'],
681
     **     ],
682
     **     function ($value) {
683
     **         return $value->city;
684
     **     }
685
     ** );
686
     ** // >> [
687
     ** //   'Indianapolis' => [
688
     ** //      ['state' => 'IN', 'city' => 'Indianapolis', 'object' => 'School bus'],
689
     ** //      ['state' => 'IN', 'city' => 'Indianapolis', 'object' => 'Manhole'],
690
     ** //   ],
691
     ** //   'San Diego' => [
692
     ** //      ['state' => 'CA', 'city' => 'San Diego', 'object' => 'Light bulb'],
693
     ** //   ]
694
     ** // ]
695
     *
696
     * @param array                     $array
697
     * @param int|float|string|\Closure $key
698
     *
699
     * @return array
700
     *
701
     */
702
    public static function groupBy(array $array, $key): array
703 5
    {
704
        if (!is_bool($key) && !is_scalar($key) && !is_callable($key)) {
705 5
            return $array;
706
        }
707
        $grouped = [];
708 5
        foreach ($array as $value) {
709 5
            $groupKey = null;
710 5
            if (is_callable($key)) {
711 5
                $groupKey = call_user_func($key, $value);
712 1
            } elseif (is_object($value) && property_exists($value, $key)) {
713 4
                $groupKey = $value->{$key};
714 1
            } elseif (is_array($value) && isset($value[$key])) {
715 3
                $groupKey = $value[$key];
716 3
            }
717
            if ($groupKey === null) {
718 5
                continue;
719
            }
720
            $grouped[$groupKey][] = $value;
721 5
        }
722
        if (($argCnt = func_num_args()) > 2) {
723 5
            $args = func_get_args();
724 1
            foreach ($grouped as $_key => $value) {
725 1
                $params         = array_merge([$value], array_slice($args, 2, $argCnt));
726 1
                $grouped[$_key] = call_user_func_array('__::groupBy', $params);
727 1
            }
728
        }
729
730
        return $grouped;
731 5
    }
732
733
    /**
734
     * Check if value is an empty array or object.
735
     *
736
     * We consider any non enumerable as empty.
737
     *
738
     ** __::isEmpty([]);
739
     ** // >> true
740
     *
741
     * @param $value The value to check for emptiness.
0 ignored issues
show
Bug introduced by
The type __\Traits\The was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
742
     *
743
     * @return bool
744
     *
745
     */
746
    public static function isEmpty($value): bool
747 1
    {
748
        return (!__::isArray($value) && !__::isObject($value)) || count((array)$value) === 0;
749 1
    }
750
751
    /**
752
     * Transforms the keys in a collection by running each key through the iterator
753
     *
754
     * @param array    $array   array of values
755
     * @param \Closure $closure closure to map the keys
756
     *
757
     * @throws \Exception           if closure doesn't return a valid key that can be used in PHP array
758
     *
759
     * @return array
760
     */
761
    public static function mapKeys(array $array, Closure $closure = null): array
762 2
    {
763
        if (is_null($closure)) {
764 2
            $closure = '__::identity';
765 1
        }
766
        $resultArray = [];
767 2
        foreach ($array as $key => $value) {
768 2
            $newKey = call_user_func_array($closure, [$key, $value, $array]);
769 2
            // key must be a number or string
770
            if (!is_numeric($newKey) && !is_string($newKey)) {
771 2
                throw new Exception('closure must returns a number or string');
772 1
            }
773
            $resultArray[$newKey] = $value;
774 1
        }
775
776
        return $resultArray;
777 1
    }
778
779
    /**
780
     * Transforms the values in a collection by running each value through the iterator
781
     *
782
     * @param array    $array   array of values
783
     * @param \Closure $closure closure to map the values
784
     *
785
     * @return array
786
     */
787
    public static function mapValues(array $array, Closure $closure = null): array
788 1
    {
789
        if (is_null($closure)) {
790 1
            $closure = '__::identity';
791 1
        }
792
        $resultArray = [];
793 1
        foreach ($array as $key => $value) {
794 1
            $resultArray[$key] = call_user_func_array($closure, [$value, $key, $array]);
795 1
        }
796
797
        return $resultArray;
798 1
    }
799
800
    /**
801
     * Recursively combines and merge collections provided with each others.
802
     *
803
     * If the collections have common keys, then the last passed keys override the previous.
804
     * If numerical indexes are passed, then last passed indexes override the previous.
805
     *
806
     * For a non-recursive merge, see __::merge.
807
     *
808
     ** __::merge(['color' => ['favorite' => 'red', 'model' => 3, 5], 3], [10, 'color' => ['favorite' => 'green', 'blue']]);
809
     ** // >> ['color' => ['favorite' => 'green', 'model' => 3, 'blue'], 10]
810
     *
811
     * @param array|object $collection1 First collection to merge.
812
     * @param array|object $collection2 Other collections to merge.
813
     *
814
     * @return array|object Concatenated collection.
815
     *
816
     */
817
    public static function merge($collection1, $collection2)
818
    {
819
        return __::reduceRight(func_get_args(), function ($source, $result) {
820
            __::doForEach($source, function ($sourceValue, $key) use (&$result) {
821 2
                $value = $sourceValue;
822 2
                if (__::isCollection($value)) {
823 2
                    $value = __::merge(__::get($result, $key), $sourceValue);
824 2
                }
825
                $result = __::set($result, $key, $value);
826 2
            });
827 2
828
            return $result;
829 2
        }, []);
830 2
    }
831
832
    /**
833
     * Returns an array having only keys present in the given path list.
834
     *
835
     * Values for missing keys values will be filled with provided default value.
836
     *
837
     ** __::pick(['a' => 1, 'b' => ['c' => 3, 'd' => 4]], ['a', 'b.d']);
838
     ** // → ['a' => 1, 'b' => ['d' => 4]]
839
     *
840
     * @param array|object $collection The collection to iterate over.
841
     * @param array        $paths      array paths to pick
842
     *
843
     * @param null         $default
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $default is correct as it would always require null to be passed?
Loading history...
844
     *
845
     * @return array|object
846
     */
847
    public static function pick($collection = [], array $paths = [], $default = null)
848
    {
849
        return __::reduce($paths, function ($results, $path) use ($collection, $default) {
850 3
            return __::set($results, $path, __::get($collection, $path, $default));
851 3
        }, __::isObject($collection) ? new stdClass() : []);
852 3
    }
853
854
    /**
855
     * Reduces $collection to a value which is the $accumulator result of running each
856
     * element in $collection thru $iteratee, where each successive invocation is supplied
857
     * the return value of the previous.
858
     *
859
     * If $accumulator is not given, the first element of $collection is used as the
860
     * initial value.
861
     *
862
     * The $iteratee is invoked with four arguments:
863
     * ($accumulator, $value, $index|$key, $collection).
864
     *
865
     ** __::reduce([1, 2], function ($sum, $number) {
866
     **     return $sum + $number;
867
     ** }, 0);
868
     ** // >> 3
869
     *
870
     ** $a = [
871
     **     ['state' => 'IN', 'city' => 'Indianapolis', 'object' => 'School bus'],
872
     **     ['state' => 'IN', 'city' => 'Indianapolis', 'object' => 'Manhole'],
873
     **     ['state' => 'IN', 'city' => 'Plainfield', 'object' => 'Basketball'],
874
     **     ['state' => 'CA', 'city' => 'San Diego', 'object' => 'Light bulb'],
875
     **     ['state' => 'CA', 'city' => 'Mountain View', 'object' => 'Space pen'],
876
     ** ];
877
     ** $iteratee = function ($accumulator, $value) {
878
     **     if (isset($accumulator[$value['city']]))
879
     **         $accumulator[$value['city']]++;
880
     **     else
881
     **         $accumulator[$value['city']] = 1;
882
     **     return $accumulator;
883
     ** };
884
     ** __::reduce($c, $iteratee, []);
885
     ** // >> [
886
     ** // >>    'Indianapolis' => 2,
887
     ** // >>    'Plainfield' => 1,
888
     ** // >>    'San Diego' => 1,
889
     ** // >>    'Mountain View' => 1,
890
     ** // >> ]
891
     *
892
     ** $object = new \stdClass();
893
     ** $object->a = 1;
894
     ** $object->b = 2;
895
     ** $object->c = 1;
896
     ** __::reduce($object, function ($result, $value, $key) {
897
     **     if (!isset($result[$value]))
898
     **         $result[$value] = [];
899
     **     $result[$value][] = $key;
900
     **     return $result;
901
     ** }, [])
902
     ** // >> [
903
     ** // >>     '1' => ['a', 'c'],
904
     ** // >>     '2' => ['b']
905
     ** // >> ]
906
     *
907
     * @param array|object $collection The collection to iterate over.
908
     * @param \Closure     $iteratee   The function invoked per iteration.
909
     * @param null         $accumulator
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $accumulator is correct as it would always require null to be passed?
Loading history...
910
     *
911
     * @return array|mixed|null (*): Returns the accumulated value.
912
     */
913
    public static function reduce($collection, Closure $iteratee, $accumulator = null)
914 5
    {
915
        if ($accumulator === null) {
916 5
            $accumulator = __::first($collection);
0 ignored issues
show
Bug introduced by
It seems like $collection can also be of type object; however, parameter $array of __::first() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

916
            $accumulator = __::first(/** @scrutinizer ignore-type */ $collection);
Loading history...
917 1
        }
918
        __::doForEach(
919 5
            $collection,
920 5
            function ($value, $key, $collection) use (&$accumulator, $iteratee) {
921 5
                $accumulator = $iteratee($accumulator, $value, $key, $collection);
922 5
            }
923 5
        );
924
925
        return $accumulator;
926 5
    }
927
928
    /**
929
     * Builds a multidimensional collection out of a hash map using the key as indicator where to put the value.
930
     *
931
     ** __::unease(['foo.bar' => 'ter', 'baz.0' => 'b', , 'baz.1' => 'z']);
932
     ** // → '['foo' => ['bar' => 'ter'], 'baz' => ['b', 'z']]'
933
     *
934
     * @param array  $collection hash map of values
935
     * @param string $separator  the glue used in the keys
936
     *
937
     * @return array
938
     * @throws \Exception
939
     */
940
    public static function unease(array $collection, string $separator = '.'): array
941 1
    {
942
        $nonDefaultSeparator = $separator !== '.';
943 1
        $map                 = [];
944 1
945
        foreach ($collection as $key => $value) {
946 1
            $map = __::set(
947 1
                $map,
948 1
                $nonDefaultSeparator ? str_replace($separator, '.', $key) : $key,
949 1
                $value
950 1
            );
951
        }
952
953
        return $map;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $map could return the type object which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
954 1
    }
955
}
956