Passed
Pull Request — master (#1)
by Zeeshan
01:34
created

Collections::doForEachRight()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 2
crap 2
1
<?php
2
3
namespace __\Traits;
4
5
trait Collections
6
{
7
    /**
8
     * Returns the values in the collection that pass the truth test.
9
     *
10
     * @param array    $array   array to filter
11
     * @param \Closure $closure closure to filter array based on
12
     *
13
     * @return array
14
     *
15
     */
16 1
    public static function filter(array $array = [], \Closure $closure)
0 ignored issues
show
Coding Style introduced by
Parameters which have default values should be placed at the end.

If you place a parameter with a default value before a parameter with a default value, the default value of the first parameter will never be used as it will always need to be passed anyway:

// $a must always be passed; it's default value is never used.
function someFunction($a = 5, $b) { }
Loading history...
17
    {
18 1
        if (!$closure) {
0 ignored issues
show
introduced by
The condition ! $closure can never be false.
Loading history...
19
            return \__::compact($array);
20
        } else {
21 1
            $result = [];
22
23 1
            foreach ($array as $key => $value) {
24 1
                if (\call_user_func($closure, $value)) {
25 1
                    $result[] = $value;
26
                }
27
            }
28
29 1
            return $result;
30
        }
31
    }
32
33
    /**
34
     * Gets the first element of an array. Passing n returns the first n elements.
35
     *
36
     * @param array $array of values
37
     * @param null  $take  number of values to return
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $take is correct as it would always require null to be passed?
Loading history...
38
     *
39
     * @return array|mixed
40
     *
41
     */
42 1
    public static function first($array, $take = null)
43
    {
44 1
        if (!$take) {
45
            return \array_shift($array);
46
        }
47
48 1
        return \array_splice($array, 0, $take, true);
49
    }
50
51
    /**
52
     * get item of an array by index , aceepting nested index
53
     *
54
     ** __::get(['foo' => ['bar' => 'ter']], 'foo.bar');
55
     ** // → 'ter'
56
     *
57
     * @param array  $collection array of values
58
     * @param string $key        key or index
59
     * @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...
60
     *
61
     * @return array|mixed|null
62
     *
63
     */
64 2
    public static function get($collection = [], $key = '', $default = null)
65
    {
66 2
        if (\__::isNull($key)) {
67
            return $collection;
68
        }
69
70 2
        if (!\__::isObject($collection) && isset($collection[$key])) {
71 1
            return $collection[$key];
72
        }
73
74 1
        foreach (\explode('.', $key) as $segment) {
75 1
            if (\__::isObject($collection)) {
76
                if (!isset($collection->{$segment})) {
77
                    return $default instanceof \Closure ? $default() : $default;
78
                } else {
79
                    $collection = $collection->{$segment};
80
                }
81
            } else {
82 1
                if (!isset($collection[$segment])) {
83
                    return $default instanceof \Closure ? $default() : $default;
84
                } else {
85 1
                    $collection = $collection[$segment];
86
                }
87
            }
88
        }
89
90 1
        return $collection;
91
    }
92
93
    /**
94
     * get last item(s) of an array
95
     *
96
     * @param array $array array of values
97
     * @param null  $take  number of returned values
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $take is correct as it would always require null to be passed?
Loading history...
98
     *
99
     * @return array|mixed
100
     *
101
     */
102 1
    public static function last($array, $take = null)
103
    {
104 1
        if (!$take) {
105
            return \array_pop($array);
106
        }
107
108 1
        return \array_splice($array, -$take);
109
    }
110
111
    /**
112
     * Returns an array of values by mapping each in collection through the iterator.
113
     *
114
     * @param array    $array   array of values
115
     * @param \Closure $closure closure to mapp based on
116
     *
117
     * @return array
118
     *
119
     */
120 1
    public static function map(array $array = [], \Closure $closure)
0 ignored issues
show
Coding Style introduced by
Parameters which have default values should be placed at the end.

If you place a parameter with a default value before a parameter with a default value, the default value of the first parameter will never be used as it will always need to be passed anyway:

// $a must always be passed; it's default value is never used.
function someFunction($a = 5, $b) { }
Loading history...
121
    {
122 1
        foreach ($array as $key => $value) {
123 1
            $array[$key] = $closure($value, $key);
124
        }
125
126 1
        return $array;
127
    }
128
129
    /**
130
     * Returns the maximum value from the collection. If passed an iterator, max will return max value returned by the
131
     * iterator.
132
     *
133
     * @param array $array array
134
     *
135
     * @return mixed maximum value
136
     *
137
     */
138 1
    public static function max(array $array = [])
139
    {
140 1
        return \max($array);
141
    }
142
143
    /**
144
     * Returns the minimum value from the collection. If passed an iterator, min will return min value returned by the
145
     * iterator.
146
     *
147
     * @param array $array array of values
148
     *
149
     * @return mixed
150
     *
151
     */
152 1
    public static function min(array $array = [])
153
    {
154 1
        return \min($array);
155
    }
156
157
    /**
158
     * Returns an array of values belonging to a given property of each item in a collection.
159
     *
160
     * @param array  $collection rray
161
     * @param string $property   property
162
     *
163
     * @return array|object
164
     *
165
     */
166 1
    public static function pluck($collection = [], $property = '')
167
    {
168 1
        $plucked = \array_map(
169 1
            function ($value) use ($property) {
170 1
                return \__::get($value, $property);
171 1
            }, (array)$collection
172
        );
173
174 1
        if (\__::isObject($collection)) {
175
            $plucked = (object)$plucked;
176
        }
177
178 1
        return $plucked;
179
    }
180
181
182
    /**
183
     * return data matching specific key value condition
184
     *
185
     **_::where($a, ['age' => 16]);
186
     ** // >> [['name' => 'maciej', 'age' => 16]]
187
     *
188
     * @param array $array    array of values
189
     * @param array $key      condition in format of ['KEY'=>'VALUE']
190
     * @param bool  $keepKeys keep original keys
191
     *
192
     * @return array
193
     *
194
     */
195 1
    public static function where(array $array = [], array $key = [], $keepKeys = false)
196
    {
197 1
        $result = [];
198
199 1
        foreach ($array as $k => $v) {
200 1
            $not = false;
201
202 1
            foreach ($key as $j => $w) {
203 1
                if (\__::isArray($w)) {
204
                    if (count(array_intersect($w, $v[$j])) == 0) {
205
                        $not = true;
206
                        break;
207
                    }
208
                } else {
209 1
                    if (!isset($v[$j]) || $v[$j] != $w) {
210 1
                        $not = true;
211 1
                        break;
212
                    }
213
                }
214
            }
215
216 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...
217 1
                if ($keepKeys) {
218
                    $result[$k] = $v;
219
                } else {
220 1
                    $result[] = $v;
221
                }
222
            }
223
        }
224
225 1
        return $result;
226
    }
227
228
    /**
229
     * Combines and merge collections provided with each others.
230
     *
231
     * If the collections have common keys, then the last passed keys override the
232
     * previous. If numerical indexes are passed, then last passed indexes override
233
     * the previous.
234
     *
235
     * For a recursive merge, see __::merge.
236
     *
237
     ** __::assign(['color' => ['favorite' => 'red', 5], 3], [10, 'color' => ['favorite' => 'green', 'blue']]);
238
     ** // >> ['color' => ['favorite' => 'green', 'blue'], 10]
239
     *
240
     * @param array|object $collection1 Collection to assign to.
241
     * @param array|object $collection2 Other collections to assign
242
     *
243
     * @return array|object Assigned collection.
244
     */
245
    public static function assign($collection1, $collection2)
246
    {
247
        return \__::reduceRight(func_get_args(), function ($source, $result) {
248
            \__::doForEach($source, function ($sourceValue, $key) use (&$result) {
249
                $result = \__::set($result, $key, $sourceValue);
250
            });
251
252
            return $result;
253
        }, []);
254
    }
255
256
    /**
257
     * Reduces $collection to a value which is the $accumulator result of running each
258
     * element in $collection - from right to left - thru $iteratee, where each
259
     * successive invocation is supplied the return value of the previous.
260
     *
261
     * If $accumulator is not given, the first element of $collection is used as the
262
     * initial value.
263
     *
264
     * The $iteratee is invoked with four arguments:
265
     * ($accumulator, $value, $index|$key, $collection).
266
     *
267
     ** __::reduceRight(['a', 'b', 'c'], function ($word, $char) {
268
     **     return $word . $char;
269
     ** }, '');
270
     ** // >> 'cba'
271
     *
272
     * @param array|object $collection The collection to iterate over.
273
     * @param \Closure     $iteratee   The function invoked per iteration.
274
     * @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...
275
     *
276
     * @return array|mixed|null (*): Returns the accumulated value.
277
     */
278
    public static function reduceRight($collection, \Closure $iteratee, $accumulator = null)
279
    {
280
        // TODO Factorize using iteratorReverse: make it a function. (See doForEachRight)
281
        if ($accumulator === null) {
282
            $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

282
            $accumulator = \__::first(/** @scrutinizer ignore-type */ $collection);
Loading history...
283
        }
284
        \__::doForEachRight(
285
            $collection,
286
            function ($value, $key, $collection) use (&$accumulator, $iteratee) {
287
                $accumulator = $iteratee($accumulator, $value, $key, $collection);
288
            }
289
        );
290
291
        return $accumulator;
292
    }
293
294
    /**
295
     * Iterate over elements of the collection, from right to left, and invokes iterate
296
     * for each element.
297
     *
298
     * The iterate is invoked with three arguments: (value, index|key, collection).
299
     * Iterate functions may exit iteration early by explicitly returning false.
300
     *
301
     ** __::doForEachRight([1, 2, 3], function ($value) { print_r($value) });
302
     ** // → (Side effect: print 3, 2, 1)
303
     *
304
     * @param array|object $collection The collection to iterate over.
305
     * @param \Closure     $iteratee   The function to call for each value.
306
     *
307
     * @return null
308
     */
309
    public static function doForEachRight($collection, \Closure $iteratee)
310
    {
311
        \__::doForEach(\__::iteratorReverse($collection), $iteratee);
312
    }
313
314
    /**
315
     * Iterate over elements of the collection and invokes iterate for each element.
316
     *
317
     * The iterate is invoked with three arguments: (value, index|key, collection).
318
     * Iterate functions may exit iteration early by explicitly returning false.
319
     *
320
     ** __::doForEach([1, 2, 3], function ($value) { print_r($value) });
321
     ** // → (Side effect: print 1, 2, 3)
322
     *
323
     * @param array|object $collection The collection to iterate over.
324
     * @param \Closure     $iteratee   The function to call for each value
325
     *
326
     * @return null
327
     */
328
    public static function doForEach($collection, \Closure $iteratee)
329
    {
330
        foreach ($collection as $key => $value) {
331
            if ($iteratee($value, $key, $collection) === false) {
332
                break;
333
            }
334
        }
335
    }
336
337
    public static function iteratorReverse($iterable)
338
    {
339
        for (end($iterable); ($key = key($iterable)) !== null; prev($iterable)) {
340
            yield $key => current($iterable);
341
        }
342
    }
343
344
    /**
345
     * Return a new collection with the item set at index to given value.
346
     * Index can be a path of nested indexes.
347
     *
348
     * If a portion of path doesn't exist, it's created. Arrays are created for missing
349
     * index in an array; objects are created for missing property in an object.
350
     *
351
     ** __::set(['foo' => ['bar' => 'ter']], 'foo.baz.ber', 'fer');
352
     ** // → '['foo' => ['bar' => 'ter', 'baz' => ['ber' => 'fer']]]'
353
     *
354
     * @param array|object $collection collection of values
355
     * @param string       $path       key or index
356
     * @param mixed        $value      the value to set at position $key
357
     *
358
     * @throws \Exception if the path consists of a non collection and strict is set to false
359
     *
360
     * @return array|object the new collection with the item set
361
     *
362
     */
363
    public static function set($collection, $path, $value = null)
364
    {
365
        if ($path === null) {
0 ignored issues
show
introduced by
The condition $path === null can never be true.
Loading history...
366
            return $collection;
367
        }
368
        $portions = \__::split($path, '.', 2);
0 ignored issues
show
Bug introduced by
The method split() does not exist on __. ( Ignorable by Annotation )

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

368
        /** @scrutinizer ignore-call */ 
369
        $portions = \__::split($path, '.', 2);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
369
        $key      = $portions[0];
370
        if (\count($portions) === 1) {
371
            return \__::universalSet($collection, $key, $value);
372
        }
373
        // Here we manage the case where the portion of the path points to nothing,
374
        // or to a value that does not match the type of the source collection
375
        // (e.g. the path portion 'foo.bar' points to an integer value, while we
376
        // want to set a string at 'foo.bar.fun'. We first set an object or array
377
        //  - following the current collection type - to 'for.bar' before setting
378
        // 'foo.bar.fun' to the specified value).
379
        if (
380
            !\__::has($collection, $key)
381
            || (\__::isObject($collection) && !\__::isObject(\__::get($collection, $key)))
0 ignored issues
show
Bug introduced by
It seems like $collection can also be of type object; however, parameter $collection of __::get() 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

381
            || (\__::isObject($collection) && !\__::isObject(\__::get(/** @scrutinizer ignore-type */ $collection, $key)))
Loading history...
382
            || (\__::isArray($collection) && !\__::isArray(\__::get($collection, $key)))
383
        ) {
384
            $collection = \__::universalSet($collection, $key, \__::isObject($collection) ? new \stdClass : []);
385
        }
386
387
        return \__::universalSet($collection, $key, set(\__::get($collection, $key), $portions[1], $value));
0 ignored issues
show
Bug introduced by
The function set was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

387
        return \__::universalSet($collection, $key, /** @scrutinizer ignore-call */ set(\__::get($collection, $key), $portions[1], $value));
Loading history...
388
    }
389
390
    public static function universalSet($collection, $key, $value)
391
    {
392
        $set_object = function ($object, $key, $value) {
393
            $newObject       = clone $object;
394
            $newObject->$key = $value;
395
396
            return $newObject;
397
        };
398
        $set_array  = function ($array, $key, $value) {
399
            $array[$key] = $value;
400
401
            return $array;
402
        };
403
        $setter     = \__::isObject($collection) ? $set_object : $set_array;
404
405
        return call_user_func_array($setter, [$collection, $key, $value]);
406
    }
407
408
    /**
409
     * Returns if $input contains all requested $keys. If $strict is true it also checks if $input exclusively contains
410
     * the given $keys.
411
     *
412
     ** __::hasKeys(['foo' => 'bar', 'foz' => 'baz'], ['foo', 'foz']);
413
     ** // → true
414
     *
415
     * @param array|object $collection of key values pairs
416
     * @param array        $keys       collection of keys to look for
417
     * @param boolean      $strict     to exclusively check
418
     *
419
     * @return boolean
420
     *
421
     */
422
    public static function hasKeys($collection = [], array $keys = [], $strict = false)
423
    {
424
        $keyCount = \count($keys);
425
        if ($strict && \count($collection) !== $keyCount) {
426
            return false;
427
        }
428
429
        return \__::every(
430
            \__::map($keys, function ($key) use ($collection) {
431
                return \__::has($collection, $key);
432
            }),
433
            function ($v) {
434
                return $v === true;
435
            }
436
        );
437
    }
438
439
    /**
440
     * Return true if $collection contains the requested $key.
441
     *
442
     * In constrast to isset(), __::has() returns true if the key exists but is null.
443
     *
444
     ** __::has(['foo' => ['bar' => 'num'], 'foz' => 'baz'], 'foo.bar');
445
     ** // → true
446
     *
447
     ** __::hasKeys((object) ['foo' => 'bar', 'foz' => 'baz'], 'bar');
448
     ** // → false
449
     *
450
     * @param array|object   $collection of key values pairs
451
     * @param string|integer $path       Path to look for.
452
     *
453
     * @return boolean
454
     *
455
     */
456
    public static function has($collection, $path)
457
    {
458
        $portions = \__::split($path, '.', 2);
459
        $key      = $portions[0];
460
        if (\count($portions) === 1) {
461
            return array_key_exists($key, (array)$collection);
462
        }
463
464
        return has(\__::get($collection, $key), $portions[1]);
0 ignored issues
show
Bug introduced by
The function has was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

464
        return /** @scrutinizer ignore-call */ has(\__::get($collection, $key), $portions[1]);
Loading history...
Bug introduced by
It seems like $collection can also be of type object; however, parameter $collection of __::get() 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

464
        return has(\__::get(/** @scrutinizer ignore-type */ $collection, $key), $portions[1]);
Loading history...
465
    }
466
467
    /**
468
     * Combines and concat collections provided with each others.
469
     *
470
     * If the collections have common keys, then the values are appended in an array.
471
     * If numerical indexes are passed, then values are appended.
472
     *
473
     * For a recursive merge, see __::merge.
474
     *
475
     ** __::concat(['color' => ['favorite' => 'red', 5], 3], [10, 'color' => ['favorite' => 'green', 'blue']]);
476
     ** // >> ['color' => ['favorite' => ['green'], 5, 'blue'], 3, 10]
477
     *
478
     * @param array|object $collection1 Collection to assign to.
479
     * @param array|object $collection2 Other collections to assign.
480
     *
481
     * @return array|object Assigned collection.
482
     */
483
    public static function concat($collection1, $collection2)
484
    {
485
        $isObject = \__::isObject($collection1);
486
487
        $args = \__::map(func_get_args(), function ($arg) {
488
            return (array)$arg;
489
        });
490
491
        $merged = call_user_func_array('array_merge', $args);
492
493
        return $isObject ? (object)$merged : $merged;
494
    }
495
496
    /**
497
     * Recursively combines and concat collections provided with each others.
498
     *
499
     * If the collections have common keys, then the values are appended in an array.
500
     * If numerical indexes are passed, then values are appended.
501
     *
502
     * For a non-recursive concat, see __::concat.
503
     *
504
     ** __::concatDeep(['color' => ['favorite' => 'red', 5], 3], [10, 'color' => ['favorite' => 'green', 'blue']]);
505
     ** // >> ['color' => ['favorite' => ['red', 'green'], 5, 'blue'], 3, 10]
506
     *
507
     * @param array|object $collection1 First collection to concatDeep.
508
     * @param array|object $collection2 other collections to concatDeep.
509
     *
510
     * @return array|object Concatened collection.
511
     *
512
     */
513
    public static function concatDeep($collection1, $collection2)
514
    {
515
        return \__::reduceRight(func_get_args(), function ($source, $result) {
516
            \__::doForEach($source, function ($sourceValue, $key) use (&$result) {
517
                if (!\__::has($result, $key)) {
518
                    $result = \__::set($result, $key, $sourceValue);
519
                } else {
520
                    if (is_numeric($key)) {
521
                        $result = \__::concat($result, [$sourceValue]);
522
                    } else {
523
                        $resultValue = \__::get($result, $key);
524
                        $result      = \__::set($result, $key, concatDeep(
0 ignored issues
show
Bug introduced by
The function concatDeep was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

524
                        $result      = \__::set($result, $key, /** @scrutinizer ignore-call */ concatDeep(
Loading history...
525
                            \__::isCollection($resultValue) ? $resultValue : (array)$resultValue,
0 ignored issues
show
Bug introduced by
The method isCollection() does not exist on __. ( Ignorable by Annotation )

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

525
                            \__::/** @scrutinizer ignore-call */ 
526
                                 isCollection($resultValue) ? $resultValue : (array)$resultValue,

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
526
                            \__::isCollection($sourceValue) ? $sourceValue : (array)$sourceValue
527
                        ));
528
                    }
529
                }
530
            });
531
532
            return $result;
533
        }, []);
534
    }
535
536
    /**
537
     * Flattens a complex collection by mapping each ending leafs value to a key consisting of all previous indexes.
538
     *
539
     * __::ease(['foo' => ['bar' => 'ter'], 'baz' => ['b', 'z']]);
540
     * // → '['foo.bar' => 'ter', 'baz.0' => 'b', , 'baz.1' => 'z']'
541
     *
542
     * @param array  $collection array of values
543
     * @param string $glue       glue between key path
544
     *
545
     * @return array flatten collection
546
     *
547
     */
548
    public static function ease(array $collection, $glue = '.')
549
    {
550
        $map = [];
551
        self::_ease($map, $collection, $glue);
552
553
        return $map;
554
    }
555
556
    /**
557
     * Inner function for collections::ease
558
     *
559
     * @param array  $map
560
     * @param array  $array
561
     * @param string $glue
562
     * @param string $prefix
563
     */
564
    private static function _ease(&$map, $array, $glue, $prefix = '')
565
    {
566
        foreach ($array as $index => $value) {
567
            if (\is_array($value)) {
568
                _ease($map, $value, $glue, $prefix . $index . $glue);
0 ignored issues
show
Bug introduced by
The function _ease was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

568
                /** @scrutinizer ignore-call */ 
569
                _ease($map, $value, $glue, $prefix . $index . $glue);
Loading history...
569
            } else {
570
                $map[$prefix . $index] = $value;
571
            }
572
        }
573
    }
574
575
    /**
576
     * Checks if predicate returns truthy for all elements of collection.
577
     *
578
     * Iteration is stopped once predicate returns falsey.
579
     * The predicate is invoked with three arguments: (value, index|key, collection).
580
     *
581
     ** __::every([1, 3, 4], function ($v) { return is_int($v); });
582
     ** // → true
583
     *
584
     * @param array|object $collection The collection to iterate over.
585
     * @param \Closure     $iteratee   The function to call for each value.
586
     *
587
     * @return bool
588
     */
589
    public static function every($collection, \Closure $iteratee)
590
    {
591
        $truthy = true;
592
        // We could use __::reduce(), but it won't allow us to return preliminarily.
593
        \__::doForEach(
594
            $collection,
595
            function ($value, $key, $collection) use (&$truthy, $iteratee) {
596
                $truthy = $truthy && $iteratee($value, $key, $collection);
597
                if (!$truthy) {
598
                    return false;
599
                }
600
            }
601
        );
602
603
        return $truthy;
604
    }
605
606
    /**
607
     * Returns an associative array where the keys are values of $key.
608
     *
609
     * Based on {@author Chauncey McAskill}'s
610
     * {@link https://gist.github.com/mcaskill/baaee44487653e1afc0d array_group_by()} function.
611
     *
612
     ** __::groupBy([
613
     **         ['state' => 'IN', 'city' => 'Indianapolis', 'object' => 'School bus'],
614
     **         ['state' => 'CA', 'city' => 'San Diego', 'object' => 'Light bulb'],
615
     **         ['state' => 'CA', 'city' => 'Mountain View', 'object' => 'Space pen'],
616
     **     ],
617
     **     'state'
618
     ** );
619
     ** // >> [
620
     ** //   'IN' => [
621
     ** //      ['state' => 'IN', 'city' => 'Indianapolis', 'object' => 'School bus'],
622
     ** //      ['state' => 'CA', 'city' => 'San Diego', 'object' => 'Light bulb'],
623
     ** //   ],
624
     ** //   'CA' => [
625
     ** //      ['state' => 'CA', 'city' => 'Mountain View', 'object' => 'Space pen']
626
     ** //   ]
627
     ** // ]
628
     *
629
     *
630
     ** __::groupBy([
631
     **         ['state' => 'IN', 'city' => 'Indianapolis', 'object' => 'School bus'],
632
     **         ['state' => 'IN', 'city' => 'Indianapolis', 'object' => 'Manhole'],
633
     **         ['state' => 'CA', 'city' => 'San Diego', 'object' => 'Light bulb'],
634
     **     ],
635
     **     function ($value) {
636
     **         return $value->city;
637
     **     }
638
     ** );
639
     ** // >> [
640
     ** //   'Indianapolis' => [
641
     ** //      ['state' => 'IN', 'city' => 'Indianapolis', 'object' => 'School bus'],
642
     ** //      ['state' => 'IN', 'city' => 'Indianapolis', 'object' => 'Manhole'],
643
     ** //   ],
644
     ** //   'San Diego' => [
645
     ** //      ['state' => 'CA', 'city' => 'San Diego', 'object' => 'Light bulb'],
646
     ** //   ]
647
     ** // ]
648
     *
649
     * @param array                     $array
650
     * @param int|float|string|\Closure $key
651
     *
652
     * @return array
653
     *
654
     */
655
    public static function groupBy(array $array, $key)
656
    {
657
        if (!\is_bool($key) && !\is_scalar($key) && !\is_callable($key)) {
658
            return $array;
659
        }
660
        $grouped = [];
661
        foreach ($array as $value) {
662
            $groupKey = null;
663
            if (\is_callable($key)) {
664
                $groupKey = call_user_func($key, $value);
665
            } elseif (\is_object($value) && \property_exists($value, $key)) {
666
                $groupKey = $value->{$key};
667
            } elseif (\is_array($value) && isset($value[$key])) {
668
                $groupKey = $value[$key];
669
            }
670
            if ($groupKey === null) {
671
                continue;
672
            }
673
            $grouped[$groupKey][] = $value;
674
        }
675
        if (($argCnt = func_num_args()) > 2) {
676
            $args = func_get_args();
677
            foreach ($grouped as $_key => $value) {
678
                $params         = array_merge([$value], array_slice($args, 2, $argCnt));
679
                $grouped[$_key] = call_user_func_array('__::groupBy', $params);
680
            }
681
        }
682
683
        return $grouped;
684
    }
685
686
    /**
687
     * Check if value is an empty array or object.
688
     *
689
     * We consider any non enumerable as empty.
690
     *
691
     ** __::isEmpty([]);
692
     ** // → true
693
     *
694
     * @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...
695
     *
696
     * @return boolean
697
     *
698
     */
699
    public static function isEmpty($value)
700
    {
701
        // TODO Create and use our own __::size(). (Manage object, etc.).
702
        return (!\__::isArray($value) && !\__::isObject($value)) || count((array)$value) === 0;
703
    }
704
705
    /**
706
     * Transforms the keys in a collection by running each key through the iterator
707
     *
708
     * @param array    $array   array of values
709
     * @param \Closure $closure closure to map the keys
710
     *
711
     * @throws \Exception           if closure doesn't return a valid key that can be used in PHP array
712
     *
713
     * @return array
714
     */
715
    public static function mapKeys(array $array, \Closure $closure = null)
716
    {
717
        if (is_null($closure)) {
718
            $closure = '__::identity';
719
        }
720
        $resultArray = [];
721
        foreach ($array as $key => $value) {
722
            $newKey = call_user_func_array($closure, [$key, $value, $array]);
723
            // key must be a number or string
724
            if (!is_numeric($newKey) && !is_string($newKey)) {
725
                throw new \Exception('closure must returns a number or string');
726
            }
727
            $resultArray[$newKey] = $value;
728
        }
729
730
        return $resultArray;
731
    }
732
733
    /**
734
     * Transforms the values in a collection by running each value through the iterator
735
     *
736
     * @param array    $array   array of values
737
     * @param \Closure $closure closure to map the values
738
     *
739
     * @return array
740
     */
741
    public static function mapValues(array $array, \Closure $closure = null)
742
    {
743
        if (is_null($closure)) {
744
            $closure = '__::identity';
745
        }
746
        $resultArray = [];
747
        foreach ($array as $key => $value) {
748
            $resultArray[$key] = call_user_func_array($closure, [$value, $key, $array]);
749
        }
750
751
        return $resultArray;
752
    }
753
754
    /**
755
     * Recursively combines and merge collections provided with each others.
756
     *
757
     * If the collections have common keys, then the last passed keys override the previous.
758
     * If numerical indexes are passed, then last passed indexes override the previous.
759
     *
760
     * For a non-recursive merge, see __::merge.
761
     *
762
     ** __::merge(['color' => ['favorite' => 'red', 'model' => 3, 5], 3], [10, 'color' => ['favorite' => 'green', 'blue']]);
763
     ** // >> ['color' => ['favorite' => 'green', 'model' => 3, 'blue'], 10]
764
     *
765
     * @param array|object $collection1 First collection to merge.
766
     * @param array|object $collection2 Other collections to merge.
767
     *
768
     * @return array|object Concatenated collection.
769
     *
770
     */
771
    public static function merge($collection1, $collection2)
772
    {
773
        return \__::reduceRight(func_get_args(), function ($source, $result) {
774
            \__::doForEach($source, function ($sourceValue, $key) use (&$result) {
775
                $value = $sourceValue;
776
                if (\__::isCollection($value)) {
777
                    $value = merge(\__::get($result, $key), $sourceValue);
0 ignored issues
show
Bug introduced by
The function merge was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

777
                    $value = /** @scrutinizer ignore-call */ merge(\__::get($result, $key), $sourceValue);
Loading history...
778
                }
779
                $result = \__::set($result, $key, $value);
780
            });
781
782
            return $result;
783
        }, []);
784
    }
785
786
    /**
787
     * Returns an array having only keys present in the given path list.
788
     *
789
     * Values for missing keys values will be filled with provided default value.
790
     *
791
     ** __::pick(['a' => 1, 'b' => ['c' => 3, 'd' => 4]], ['a', 'b.d']);
792
     ** // → ['a' => 1, 'b' => ['d' => 4]]
793
     *
794
     * @param array|object $collection The collection to iterate over.
795
     * @param array        $paths      array paths to pick
796
     *
797
     * @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...
798
     *
799
     * @return array
800
     */
801
    public static function pick($collection = [], array $paths = [], $default = null)
802
    {
803
        return \__::reduce($paths, function ($results, $path) use ($collection, $default) {
804
            return \__::set($results, $path, \__::get($collection, $path, $default));
0 ignored issues
show
Bug introduced by
It seems like $collection can also be of type object; however, parameter $collection of __::get() 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

804
            return \__::set($results, $path, \__::get(/** @scrutinizer ignore-type */ $collection, $path, $default));
Loading history...
805
        }, \__::isObject($collection) ? new \stdClass() : []);
806
    }
807
808
    /**
809
     * Reduces $collection to a value which is the $accumulator result of running each
810
     * element in $collection thru $iteratee, where each successive invocation is supplied
811
     * the return value of the previous.
812
     *
813
     * If $accumulator is not given, the first element of $collection is used as the
814
     * initial value.
815
     *
816
     * The $iteratee is invoked with four arguments:
817
     * ($accumulator, $value, $index|$key, $collection).
818
     *
819
     ** __::reduce([1, 2], function ($sum, $number) {
820
     **     return $sum + $number;
821
     ** }, 0);
822
     ** // >> 3
823
     *
824
     ** $a = [
825
     **     ['state' => 'IN', 'city' => 'Indianapolis', 'object' => 'School bus'],
826
     **     ['state' => 'IN', 'city' => 'Indianapolis', 'object' => 'Manhole'],
827
     **     ['state' => 'IN', 'city' => 'Plainfield', 'object' => 'Basketball'],
828
     **     ['state' => 'CA', 'city' => 'San Diego', 'object' => 'Light bulb'],
829
     **     ['state' => 'CA', 'city' => 'Mountain View', 'object' => 'Space pen'],
830
     ** ];
831
     ** $iteratee = function ($accumulator, $value) {
832
     **     if (isset($accumulator[$value['city']]))
833
     **         $accumulator[$value['city']]++;
834
     **     else
835
     **         $accumulator[$value['city']] = 1;
836
     **     return $accumulator;
837
     ** };
838
     ** __::reduce($c, $iteratee, []);
839
     ** // >> [
840
     ** // >>    'Indianapolis' => 2,
841
     ** // >>    'Plainfield' => 1,
842
     ** // >>    'San Diego' => 1,
843
     ** // >>    'Mountain View' => 1,
844
     ** // >> ]
845
     *
846
     ** $object = new \stdClass();
847
     ** $object->a = 1;
848
     ** $object->b = 2;
849
     ** $object->c = 1;
850
     ** __::reduce($object, function ($result, $value, $key) {
851
     **     if (!isset($result[$value]))
852
     **         $result[$value] = [];
853
     **     $result[$value][] = $key;
854
     **     return $result;
855
     ** }, [])
856
     ** // >> [
857
     ** // >>     '1' => ['a', 'c'],
858
     ** // >>     '2' => ['b']
859
     ** // >> ]
860
     *
861
     * @param array|object $collection The collection to iterate over.
862
     * @param \Closure     $iteratee   The function invoked per iteration.
863
     * @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...
864
     *
865
     * @return array|mixed|null (*): Returns the accumulated value.
866
     */
867
    public static function reduce($collection, \Closure $iteratee, $accumulator = null)
868
    {
869
        if ($accumulator === null) {
870
            $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

870
            $accumulator = \__::first(/** @scrutinizer ignore-type */ $collection);
Loading history...
871
        }
872
        \__::doForEach(
873
            $collection,
874
            function ($value, $key, $collection) use (&$accumulator, $iteratee) {
875
                $accumulator = $iteratee($accumulator, $value, $key, $collection);
876
            }
877
        );
878
879
        return $accumulator;
880
    }
881
882
    /**
883
     * Builds a multidimensional collection out of a hash map using the key as indicator where to put the value.
884
     *
885
     ** __::unease(['foo.bar' => 'ter', 'baz.0' => 'b', , 'baz.1' => 'z']);
886
     ** // → '['foo' => ['bar' => 'ter'], 'baz' => ['b', 'z']]'
887
     *
888
     * @param array  $collection hash map of values
889
     * @param string $separator  the glue used in the keys
890
     *
891
     * @return array
892
     * @throws \Exception
893
     */
894
    public static function unease(array $collection, $separator = '.')
895
    {
896
        $nonDefaultSeparator = $separator !== '.';
897
        $map                 = [];
898
        foreach ($collection as $key => $value) {
899
            $map = \__::set(
900
                $map,
901
                $nonDefaultSeparator ? \str_replace($separator, '.', $key) : $key,
902
                $value
903
            );
904
        }
905
906
        return $map;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $map also could return the type object which is incompatible with the documented return type array.
Loading history...
907
    }
908
}