Completed
Push — master ( 9221e6...f2a73b )
by Mohamed
01:39
created

Collections::_ease()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 7
ccs 5
cts 5
cp 1
rs 9.4285
cc 3
eloc 5
nc 3
nop 4
crap 3
1
<?php
2
3
namespace __\Traits;
4
5
use __;
6
7
trait Collections
8
{
9
    /**
10
     * Returns the values in the collection that pass the truth test.
11
     *
12
     * @param array    $array   array to filter
13
     * @param \Closure $closure closure to filter array based on
14
     *
15
     * @return array
16
     *
17
     */
18 1
    public static function filter(array $array = [], \Closure $closure = null)
19
    {
20 1
        if (!$closure) {
21 1
            return __::compact($array);
22
        } else {
23 1
            $result = [];
24
25 1
            foreach ($array as $key => $value) {
26 1
                if (\call_user_func($closure, $value)) {
27 1
                    $result[] = $value;
28
                }
29
            }
30
31 1
            return $result;
32
        }
33
    }
34
35
    /**
36
     * Gets the first element of an array. Passing n returns the first n elements.
37
     *
38
     * @param array $array of values
39
     * @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...
40
     *
41
     * @return array|mixed
42
     *
43
     */
44 8
    public static function first($array, $take = null)
45
    {
46 8
        if (!$take) {
47 7
            return \array_shift($array);
48
        }
49
50 1
        return \array_splice($array, 0, $take, true);
51
    }
52
53
    /**
54
     * get item of an array by index, aceepting nested index
55
     *
56
     ** __::get(['foo' => ['bar' => 'ter']], 'foo.bar');
57
     ** // → 'ter'
58
     *
59
     * @param array  $collection array of values
60
     * @param string $key        key or index
61
     * @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...
62
     *
63
     * @return array|mixed|null
64
     *
65
     */
66 15
    public static function get($collection = [], $key = '', $default = null)
67
    {
68 15
        if (__::isNull($key)) {
69
            return $collection;
70
        }
71
72 15
        if (!__::isObject($collection) && isset($collection[$key])) {
73 9
            return $collection[$key];
74
        }
75
76 8
        foreach (\explode('.', $key) as $segment) {
77 8
            if (__::isObject($collection)) {
78 5
                if (!isset($collection->{$segment})) {
79 2
                    return $default instanceof \Closure ? $default() : $default;
80
                } else {
81 5
                    $collection = $collection->{$segment};
82
                }
83
            } else {
84 3
                if (!isset($collection[$segment])) {
85 3
                    return $default instanceof \Closure ? $default() : $default;
86
                } else {
87 8
                    $collection = $collection[$segment];
88
                }
89
            }
90
        }
91
92 8
        return $collection;
93
    }
94
95
    /**
96
     * get last item(s) of an array
97
     *
98
     * @param array $array array of values
99
     * @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...
100
     *
101
     * @return array|mixed
102
     *
103
     */
104 1
    public static function last($array, $take = null)
105
    {
106 1
        if (!$take) {
107 1
            return \array_pop($array);
108
        }
109
110 1
        return \array_splice($array, -$take);
111
    }
112
113
    /**
114
     * Returns an array of values by mapping each in collection through the iteratee.
115
     *
116
     * The iteratee is invoked with three arguments: (value, index|key, collection).
117
     *
118
     * @param array|object $collection The collection of values to map over.
119
     * @param \Closure     $iteratee   The function to apply on each value.
120
     *
121
     * @return array
122
     */
123 8
    public static function map($collection, \Closure $iteratee)
124
    {
125 8
        $result = [];
126 8
        \__::doForEach($collection, function ($value, $key, $collection) use (&$result, $iteratee) {
127 8
            $result[] = $iteratee($value, $key, $collection);
128 8
        });
129
130 8
        return $result;
131
    }
132
133
    /**
134
     * Returns the maximum value from the collection. If passed an iterator, max will return max value returned by the
135
     * iterator.
136
     *
137
     * @param array $array array
138
     *
139
     * @return mixed maximum value
140
     *
141
     */
142 1
    public static function max(array $array = [])
143
    {
144 1
        return \max($array);
145
    }
146
147
    /**
148
     * Returns the minimum value from the collection. If passed an iterator, min will return min value returned by the
149
     * iterator.
150
     *
151
     * @param array $array array of values
152
     *
153
     * @return mixed
154
     *
155
     */
156 1
    public static function min(array $array = [])
157
    {
158 1
        return \min($array);
159
    }
160
161
    /**
162
     * Returns an array of values belonging to a given property of each item in a collection.
163
     *
164
     * @param array|object $collection array or object that can be converted to array
165
     * @param string       $property   property name
166
     *
167
     * @return array
168
     */
169
    public static function pluck($collection, $property)
170
    {
171 1
        $result = \array_map(function ($value) use ($property) {
172 1
            if (is_array($value) && isset($value[$property])) {
173 1
                return $value[$property];
174 1
            } elseif (\is_object($value) && isset($value->{$property})) {
175 1
                return $value->{$property};
176
            }
177 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...
178 1
                if (\is_object($value)) {
179 1
                    if (isset($value->{$segment})) {
180 1
                        $value = $value->{$segment};
181
                    } else {
182 1
                        return null;
183
                    }
184
                } else {
185 1
                    if (isset($value[$segment])) {
186 1
                        $value = $value[$segment];
187
                    } else {
188 1
                        return null;
189
                    }
190
                }
191
            }
192
193 1
            return $value;
194 1
        }, (array)$collection);
195
196 1
        return \array_values($result);
197
    }
198
199
200
    /**
201
     * return data matching specific key value condition
202
     *
203
     **_::where($a, ['age' => 16]);
204
     ** // >> [['name' => 'maciej', 'age' => 16]]
205
     *
206
     * @param array $array    array of values
207
     * @param array $key      condition in format of ['KEY'=>'VALUE']
208
     * @param bool  $keepKeys keep original keys
209
     *
210
     * @return array
211
     *
212
     */
213 1
    public static function where(array $array = [], array $key = [], $keepKeys = false)
214
    {
215 1
        $result = [];
216
217 1
        foreach ($array as $k => $v) {
218 1
            $not = false;
219
220 1
            foreach ($key as $j => $w) {
221 1
                if (__::isArray($w)) {
222
                    if (count(array_intersect($w, $v[$j])) == 0) {
223
                        $not = true;
224
                        break;
225
                    }
226
                } else {
227 1
                    if (!isset($v[$j]) || $v[$j] != $w) {
228 1
                        $not = true;
229 1
                        break;
230
                    }
231
                }
232
            }
233
234 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...
235 1
                if ($keepKeys) {
236
                    $result[$k] = $v;
237
                } else {
238 1
                    $result[] = $v;
239
                }
240
            }
241
        }
242
243 1
        return $result;
244
    }
245
246
    /**
247
     * Combines and merge collections provided with each others.
248
     *
249
     * If the collections have common keys, then the last passed keys override the
250
     * previous. If numerical indexes are passed, then last passed indexes override
251
     * the previous.
252
     *
253
     * For a recursive merge, see __::merge.
254
     *
255
     ** __::assign(['color' => ['favorite' => 'red', 5], 3], [10, 'color' => ['favorite' => 'green', 'blue']]);
256
     ** // >> ['color' => ['favorite' => 'green', 'blue'], 10]
257
     *
258
     * @param array|object $collection1 Collection to assign to.
259
     * @param array|object $collection2 Other collections to assign
260
     *
261
     * @return array|object Assigned collection.
262
     */
263
    public static function assign($collection1, $collection2)
264
    {
265
        return __::reduceRight(func_get_args(), function ($source, $result) {
266 2
            __::doForEach($source, function ($sourceValue, $key) use (&$result) {
267 2
                $result = __::set($result, $key, $sourceValue);
268 2
            });
269
270 2
            return $result;
271 2
        }, []);
272
    }
273
274
    /**
275
     * Reduces $collection to a value which is the $accumulator result of running each
276
     * element in $collection - from right to left - thru $iteratee, where each
277
     * successive invocation is supplied the return value of the previous.
278
     *
279
     * If $accumulator is not given, the first element of $collection is used as the
280
     * initial value.
281
     *
282
     * The $iteratee is invoked with four arguments:
283
     * ($accumulator, $value, $index|$key, $collection).
284
     *
285
     ** __::reduceRight(['a', 'b', 'c'], function ($word, $char) {
286
     **     return $word . $char;
287
     ** }, '');
288
     ** // >> 'cba'
289
     *
290
     * @param array|object $collection The collection to iterate over.
291
     * @param \Closure     $iteratee   The function invoked per iteration.
292
     * @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...
293
     *
294
     * @return array|mixed|null (*): Returns the accumulated value.
295
     */
296 7
    public static function reduceRight($collection, \Closure $iteratee, $accumulator = null)
297
    {
298
        // TODO Factorize using iteratorReverse: make it a function. (See doForEachRight)
299 7
        if ($accumulator === null) {
300
            $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

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

388
        if (\count(/** @scrutinizer ignore-type */ $portions) === 1) {
Loading history...
389 13
            return __::universalSet($collection, $key, $value);
390
        }
391
        // Here we manage the case where the portion of the path points to nothing,
392
        // or to a value that does not match the type of the source collection
393
        // (e.g. the path portion 'foo.bar' points to an integer value, while we
394
        // want to set a string at 'foo.bar.fun'. We first set an object or array
395
        //  - following the current collection type - to 'for.bar' before setting
396
        // 'foo.bar.fun' to the specified value).
397 6
        if (!__::has($collection, $key)
398 4
            || (__::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

398
            || (__::isObject($collection) && !__::isObject(__::get(/** @scrutinizer ignore-type */ $collection, $key)))
Loading history...
399 6
            || (__::isArray($collection) && !__::isArray(__::get($collection, $key)))
400
        ) {
401 6
            $collection = __::universalSet($collection, $key, __::isObject($collection) ? new \stdClass : []);
402
        }
403
404 6
        return __::universalSet($collection, $key, __::set(__::get($collection, $key), $portions[1], $value));
405
    }
406
407
    public static function universalSet($collection, $key, $value)
408
    {
409 13
        $set_object = function ($object, $key, $value) {
410 5
            $newObject       = clone $object;
411 5
            $newObject->$key = $value;
412
413 5
            return $newObject;
414 13
        };
415 13
        $set_array  = function ($array, $key, $value) {
416 8
            $array[$key] = $value;
417
418 8
            return $array;
419 13
        };
420 13
        $setter     = __::isObject($collection) ? $set_object : $set_array;
421
422 13
        return call_user_func_array($setter, [$collection, $key, $value]);
423
    }
424
425
    /**
426
     * Returns if $input contains all requested $keys. If $strict is true it also checks if $input exclusively contains
427
     * the given $keys.
428
     *
429
     ** __::hasKeys(['foo' => 'bar', 'foz' => 'baz'], ['foo', 'foz']);
430
     ** // → true
431
     *
432
     * @param array|object $collection of key values pairs
433
     * @param array        $keys       collection of keys to look for
434
     * @param boolean      $strict     to exclusively check
435
     *
436
     * @return boolean
437
     *
438
     */
439 2
    public static function hasKeys($collection = [], array $keys = [], $strict = false)
440
    {
441 2
        $keyCount = \count($keys);
442 2
        if ($strict && \count($collection) !== $keyCount) {
443 1
            return false;
444
        }
445
446 2
        return __::every(
447 2
            __::map($keys, function ($key) use ($collection) {
448 2
                return __::has($collection, $key);
449 2
            }),
450 2
            function ($v) {
451 2
                return $v === true;
452 2
            }
453
        );
454
    }
455
456
    /**
457
     * Return true if $collection contains the requested $key.
458
     *
459
     * In constrast to isset(), __::has() returns true if the key exists but is null.
460
     *
461
     ** __::has(['foo' => ['bar' => 'num'], 'foz' => 'baz'], 'foo.bar');
462
     ** // → true
463
     *
464
     ** __::hasKeys((object) ['foo' => 'bar', 'foz' => 'baz'], 'bar');
465
     ** // → false
466
     *
467
     * @param array|object   $collection of key values pairs
468
     * @param string|integer $path       Path to look for.
469
     *
470
     * @return boolean
471
     *
472
     */
473 11
    public static function has($collection, $path)
474
    {
475 11
        $portions = __::split($path, '.', 2);
476 11
        $key      = $portions[0];
477 11
        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

477
        if (\count(/** @scrutinizer ignore-type */ $portions) === 1) {
Loading history...
478 11
            return array_key_exists($key, (array)$collection);
479
        }
480
481 2
        return __::has(__::get($collection, $key), $portions[1]);
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

481
        return __::has(__::get(/** @scrutinizer ignore-type */ $collection, $key), $portions[1]);
Loading history...
482
    }
483
484
    /**
485
     * Combines and concat collections provided with each others.
486
     *
487
     * If the collections have common keys, then the values are appended in an array.
488
     * If numerical indexes are passed, then values are appended.
489
     *
490
     * For a recursive merge, see __::merge.
491
     *
492
     ** __::concat(['color' => ['favorite' => 'red', 5], 3], [10, 'color' => ['favorite' => 'green', 'blue']]);
493
     ** // >> ['color' => ['favorite' => ['green'], 5, 'blue'], 3, 10]
494
     *
495
     * @param array|object $collection1 Collection to assign to.
496
     * @param array|object $collection2 Other collections to assign.
497
     *
498
     * @return array|object Assigned collection.
499
     */
500 4
    public static function concat($collection1, $collection2)
501
    {
502 4
        $isObject = __::isObject($collection1);
503
504 4
        $args = __::map(func_get_args(), function ($arg) {
505 4
            return (array)$arg;
506 4
        });
507
508 4
        $merged = call_user_func_array('array_merge', $args);
509
510 4
        return $isObject ? (object)$merged : $merged;
511
    }
512
513
    /**
514
     * Recursively combines and concat collections provided with each others.
515
     *
516
     * If the collections have common keys, then the values are appended in an array.
517
     * If numerical indexes are passed, then values are appended.
518
     *
519
     * For a non-recursive concat, see __::concat.
520
     *
521
     ** __::concatDeep(['color' => ['favorite' => 'red', 5], 3], [10, 'color' => ['favorite' => 'green', 'blue']]);
522
     ** // >> ['color' => ['favorite' => ['red', 'green'], 5, 'blue'], 3, 10]
523
     *
524
     * @param array|object $collection1 First collection to concatDeep.
525
     * @param array|object $collection2 other collections to concatDeep.
526
     *
527
     * @return array|object Concatened collection.
528
     *
529
     */
530
    public static function concatDeep($collection1, $collection2)
531
    {
532
        return __::reduceRight(func_get_args(), function ($source, $result) {
533 2
            __::doForEach($source, function ($sourceValue, $key) use (&$result) {
534 2
                if (!__::has($result, $key)) {
535 2
                    $result = __::set($result, $key, $sourceValue);
536
                } else {
537 2
                    if (is_numeric($key)) {
538 2
                        $result = __::concat($result, [$sourceValue]);
539
                    } else {
540 2
                        $resultValue = __::get($result, $key);
541 2
                        $result      = __::set($result, $key, __::concatDeep(
542 2
                            __::isCollection($resultValue) ? $resultValue : (array)$resultValue,
543 2
                            __::isCollection($sourceValue) ? $sourceValue : (array)$sourceValue
544
                        ));
545
                    }
546
                }
547 2
            });
548
549 2
            return $result;
550 2
        }, []);
551
    }
552
553
    /**
554
     * Flattens a complex collection by mapping each ending leafs value to a key consisting of all previous indexes.
555
     *
556
     * __::ease(['foo' => ['bar' => 'ter'], 'baz' => ['b', 'z']]);
557
     * // → '['foo.bar' => 'ter', 'baz.0' => 'b', , 'baz.1' => 'z']'
558
     *
559
     * @param array  $collection array of values
560
     * @param string $glue       glue between key path
561
     *
562
     * @return array flatten collection
563
     *
564
     */
565 1
    public static function ease(array $collection, $glue = '.')
566
    {
567 1
        $map = [];
568 1
        __::_ease($map, $collection, $glue);
569
570 1
        return $map;
571
    }
572
573
    /**
574
     * Inner function for collections::ease
575
     *
576
     * @param array  $map
577
     * @param array  $array
578
     * @param string $glue
579
     * @param string $prefix
580
     */
581 1
    public static function _ease(&$map, $array, $glue, $prefix = '')
582
    {
583 1
        foreach ($array as $index => $value) {
584 1
            if (\is_array($value)) {
585 1
                __::_ease($map, $value, $glue, $prefix . $index . $glue);
586
            } else {
587 1
                $map[$prefix . $index] = $value;
588
            }
589
        }
590 1
    }
591
592
    /**
593
     * Checks if predicate returns truthy for all elements of collection.
594
     *
595
     * Iteration is stopped once predicate returns falsey.
596
     * The predicate is invoked with three arguments: (value, index|key, collection).
597
     *
598
     ** __::every([1, 3, 4], function ($v) { return is_int($v); });
599
     ** // → true
600
     *
601
     * @param array|object $collection The collection to iterate over.
602
     * @param \Closure     $iteratee   The function to call for each value.
603
     *
604
     * @return bool
605
     */
606 3
    public static function every($collection, \Closure $iteratee)
607
    {
608 3
        $truthy = true;
609
        // We could use __::reduce(), but it won't allow us to return preliminarily.
610 3
        __::doForEach(
611 3
            $collection,
612 3
            function ($value, $key, $collection) use (&$truthy, $iteratee) {
613 3
                $truthy = $truthy && $iteratee($value, $key, $collection);
614 3
                if (!$truthy) {
615 3
                    return false;
616
                }
617 3
            }
618
        );
619
620 3
        return $truthy;
621
    }
622
623
    /**
624
     * Returns an associative array where the keys are values of $key.
625
     *
626
     * Based on {@author Chauncey McAskill}'s
627
     * {@link https://gist.github.com/mcaskill/baaee44487653e1afc0d array_group_by()} function.
628
     *
629
     ** __::groupBy([
630
     **         ['state' => 'IN', 'city' => 'Indianapolis', 'object' => 'School bus'],
631
     **         ['state' => 'CA', 'city' => 'San Diego', 'object' => 'Light bulb'],
632
     **         ['state' => 'CA', 'city' => 'Mountain View', 'object' => 'Space pen'],
633
     **     ],
634
     **     'state'
635
     ** );
636
     ** // >> [
637
     ** //   'IN' => [
638
     ** //      ['state' => 'IN', 'city' => 'Indianapolis', 'object' => 'School bus'],
639
     ** //      ['state' => 'CA', 'city' => 'San Diego', 'object' => 'Light bulb'],
640
     ** //   ],
641
     ** //   'CA' => [
642
     ** //      ['state' => 'CA', 'city' => 'Mountain View', 'object' => 'Space pen']
643
     ** //   ]
644
     ** // ]
645
     *
646
     *
647
     ** __::groupBy([
648
     **         ['state' => 'IN', 'city' => 'Indianapolis', 'object' => 'School bus'],
649
     **         ['state' => 'IN', 'city' => 'Indianapolis', 'object' => 'Manhole'],
650
     **         ['state' => 'CA', 'city' => 'San Diego', 'object' => 'Light bulb'],
651
     **     ],
652
     **     function ($value) {
653
     **         return $value->city;
654
     **     }
655
     ** );
656
     ** // >> [
657
     ** //   'Indianapolis' => [
658
     ** //      ['state' => 'IN', 'city' => 'Indianapolis', 'object' => 'School bus'],
659
     ** //      ['state' => 'IN', 'city' => 'Indianapolis', 'object' => 'Manhole'],
660
     ** //   ],
661
     ** //   'San Diego' => [
662
     ** //      ['state' => 'CA', 'city' => 'San Diego', 'object' => 'Light bulb'],
663
     ** //   ]
664
     ** // ]
665
     *
666
     * @param array                     $array
667
     * @param int|float|string|\Closure $key
668
     *
669
     * @return array
670
     *
671
     */
672 5
    public static function groupBy(array $array, $key)
673
    {
674 5
        if (!\is_bool($key) && !\is_scalar($key) && !\is_callable($key)) {
675
            return $array;
676
        }
677 5
        $grouped = [];
678 5
        foreach ($array as $value) {
679 5
            $groupKey = null;
680 5
            if (\is_callable($key)) {
681 1
                $groupKey = call_user_func($key, $value);
682 4
            } elseif (\is_object($value) && \property_exists($value, $key)) {
683 1
                $groupKey = $value->{$key};
684 3
            } elseif (\is_array($value) && isset($value[$key])) {
685 3
                $groupKey = $value[$key];
686
            }
687 5
            if ($groupKey === null) {
688
                continue;
689
            }
690 5
            $grouped[$groupKey][] = $value;
691
        }
692 5
        if (($argCnt = func_num_args()) > 2) {
693 1
            $args = func_get_args();
694 1
            foreach ($grouped as $_key => $value) {
695 1
                $params         = array_merge([$value], array_slice($args, 2, $argCnt));
696 1
                $grouped[$_key] = call_user_func_array('__::groupBy', $params);
697
            }
698
        }
699
700 5
        return $grouped;
701
    }
702
703
    /**
704
     * Check if value is an empty array or object.
705
     *
706
     * We consider any non enumerable as empty.
707
     *
708
     ** __::isEmpty([]);
709
     ** // → true
710
     *
711
     * @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...
712
     *
713
     * @return boolean
714
     *
715
     */
716 1
    public static function isEmpty($value)
717
    {
718
        // TODO Create and use our own __::size(). (Manage object, etc.).
719 1
        return (!__::isArray($value) && !__::isObject($value)) || count((array)$value) === 0;
720
    }
721
722
    /**
723
     * Transforms the keys in a collection by running each key through the iterator
724
     *
725
     * @param array    $array   array of values
726
     * @param \Closure $closure closure to map the keys
727
     *
728
     * @throws \Exception           if closure doesn't return a valid key that can be used in PHP array
729
     *
730
     * @return array
731
     */
732 2
    public static function mapKeys(array $array, \Closure $closure = null)
733
    {
734 2
        if (is_null($closure)) {
735 1
            $closure = '__::identity';
736
        }
737 2
        $resultArray = [];
738 2
        foreach ($array as $key => $value) {
739 2
            $newKey = call_user_func_array($closure, [$key, $value, $array]);
740
            // key must be a number or string
741 2
            if (!is_numeric($newKey) && !is_string($newKey)) {
742 1
                throw new \Exception('closure must returns a number or string');
743
            }
744 1
            $resultArray[$newKey] = $value;
745
        }
746
747 1
        return $resultArray;
748
    }
749
750
    /**
751
     * Transforms the values in a collection by running each value through the iterator
752
     *
753
     * @param array    $array   array of values
754
     * @param \Closure $closure closure to map the values
755
     *
756
     * @return array
757
     */
758 1
    public static function mapValues(array $array, \Closure $closure = null)
759
    {
760 1
        if (is_null($closure)) {
761 1
            $closure = '__::identity';
762
        }
763 1
        $resultArray = [];
764 1
        foreach ($array as $key => $value) {
765 1
            $resultArray[$key] = call_user_func_array($closure, [$value, $key, $array]);
766
        }
767
768 1
        return $resultArray;
769
    }
770
771
    /**
772
     * Recursively combines and merge collections provided with each others.
773
     *
774
     * If the collections have common keys, then the last passed keys override the previous.
775
     * If numerical indexes are passed, then last passed indexes override the previous.
776
     *
777
     * For a non-recursive merge, see __::merge.
778
     *
779
     ** __::merge(['color' => ['favorite' => 'red', 'model' => 3, 5], 3], [10, 'color' => ['favorite' => 'green', 'blue']]);
780
     ** // >> ['color' => ['favorite' => 'green', 'model' => 3, 'blue'], 10]
781
     *
782
     * @param array|object $collection1 First collection to merge.
783
     * @param array|object $collection2 Other collections to merge.
784
     *
785
     * @return array|object Concatenated collection.
786
     *
787
     */
788
    public static function merge($collection1, $collection2)
789
    {
790
        return __::reduceRight(func_get_args(), function ($source, $result) {
791 2
            __::doForEach($source, function ($sourceValue, $key) use (&$result) {
792 2
                $value = $sourceValue;
793 2
                if (__::isCollection($value)) {
794 2
                    $value = __::merge(__::get($result, $key), $sourceValue);
795
                }
796 2
                $result = __::set($result, $key, $value);
797 2
            });
798
799 2
            return $result;
800 2
        }, []);
801
    }
802
803
    /**
804
     * Returns an array having only keys present in the given path list.
805
     *
806
     * Values for missing keys values will be filled with provided default value.
807
     *
808
     ** __::pick(['a' => 1, 'b' => ['c' => 3, 'd' => 4]], ['a', 'b.d']);
809
     ** // → ['a' => 1, 'b' => ['d' => 4]]
810
     *
811
     * @param array|object $collection The collection to iterate over.
812
     * @param array        $paths      array paths to pick
813
     *
814
     * @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...
815
     *
816
     * @return array
817
     */
818
    public static function pick($collection = [], array $paths = [], $default = null)
819
    {
820 3
        return __::reduce($paths, function ($results, $path) use ($collection, $default) {
821 3
            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

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

887
            $accumulator = __::first(/** @scrutinizer ignore-type */ $collection);
Loading history...
888
        }
889 5
        __::doForEach(
890 5
            $collection,
891 5
            function ($value, $key, $collection) use (&$accumulator, $iteratee) {
892 5
                $accumulator = $iteratee($accumulator, $value, $key, $collection);
893 5
            }
894
        );
895
896 5
        return $accumulator;
897
    }
898
899
    /**
900
     * Builds a multidimensional collection out of a hash map using the key as indicator where to put the value.
901
     *
902
     ** __::unease(['foo.bar' => 'ter', 'baz.0' => 'b', , 'baz.1' => 'z']);
903
     ** // → '['foo' => ['bar' => 'ter'], 'baz' => ['b', 'z']]'
904
     *
905
     * @param array  $collection hash map of values
906
     * @param string $separator  the glue used in the keys
907
     *
908
     * @return array
909
     * @throws \Exception
910
     */
911 1
    public static function unease(array $collection, $separator = '.')
912
    {
913 1
        $nonDefaultSeparator = $separator !== '.';
914 1
        $map                 = [];
915 1
        foreach ($collection as $key => $value) {
916 1
            $map = __::set(
917 1
                $map,
918 1
                $nonDefaultSeparator ? \str_replace($separator, '.', $key) : $key,
919 1
                $value
920
            );
921
        }
922
923 1
        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...
924
    }
925
}
926