Completed
Push — master ( 16bda6...f53e58 )
by Mohamed
43:31 queued 41:47
created

Collections::where()   D

Complexity

Conditions 9
Paths 16

Size

Total Lines 31
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 10.0551

Importance

Changes 0
Metric Value
dl 0
loc 31
ccs 13
cts 17
cp 0.7647
rs 4.909
c 0
b 0
f 0
cc 9
eloc 18
nc 16
nop 3
crap 10.0551
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 2
    public static function first($array, $take = null)
45
    {
46 2
        if (!$take) {
47 1
            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
        if (
398 6
            !__::has($collection, $key)
399 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

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

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

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

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

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