EnumeratesValues   F
last analyzed

Complexity

Total Complexity 114

Size/Duplication

Total Lines 944
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 225
c 2
b 0
f 0
dl 0
loc 944
rs 2
wmc 114

60 Methods

Rating   Name   Duplication   Size   Complexity  
A average() 0 3 1
A make() 0 3 1
A containsStrict() 0 19 5
A unwrap() 0 3 2
A wrap() 0 5 2
A some() 0 3 1
A when() 0 13 4
A toJson() 0 3 1
A useAsCallable() 0 3 2
A pipe() 0 3 1
A __get() 0 7 2
A whereNotInStrict() 0 3 1
A flatMap() 0 3 1
A reject() 0 8 2
A dd() 0 3 1
A whenNotEmpty() 0 3 1
A whereStrict() 0 3 1
A min() 0 10 3
A mapInto() 0 4 1
A toArray() 0 5 2
A getCachingIterator() 0 3 1
A whereInstanceOf() 0 4 1
A partition() 0 18 4
A until() 0 3 1
A max() 0 10 3
A isNotEmpty() 0 3 1
A whereNull() 0 3 1
A equality() 0 4 1
A tap() 0 5 1
A mapToGroups() 0 5 1
A firstWhere() 0 3 1
A collect() 0 3 1
A dump() 0 9 1
A unlessEmpty() 0 3 1
A where() 0 3 1
A eachSpread() 0 6 1
A whereBetween() 0 3 1
A proxy() 0 3 1
A mapSpread() 0 6 1
A whereNotBetween() 0 4 2
A sum() 0 9 2
A whereInStrict() 0 3 1
A each() 0 9 3
A whenEmpty() 0 3 1
A unless() 0 3 1
A every() 0 15 4
B getArrayableItems() 0 17 7
A valueRetriever() 0 8 2
A negate() 0 4 1
A whereIn() 0 6 1
A unlessNotEmpty() 0 3 1
A identity() 0 4 1
C operatorForWhere() 0 45 17
A jsonSerialize() 0 13 4
A whereNotIn() 0 6 1
A whereNotNull() 0 3 1
A uniqueStrict() 0 3 1
A unique() 0 12 2
A __toString() 0 3 1
A forPage() 0 5 1

How to fix   Complexity   

Complex Class

Complex classes like EnumeratesValues often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use EnumeratesValues, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace MuCTS\Collections\Traits;
4
5
use CachingIterator;
6
use Closure;
7
use Exception;
8
use MuCTS\Contracts\Collections\Enumerable;
9
use MuCTS\Contracts\Support\Arrayable;
10
use MuCTS\Contracts\Support\Jsonable;
11
use MuCTS\Collections\Arr;
12
use MuCTS\Collections\Collection;
13
use MuCTS\Collections\HigherOrderCollectionProxy;
14
use MuCTS\Collections\HigherOrderWhenProxy;
15
use JsonSerializable;
16
use Symfony\Component\VarDumper\VarDumper;
17
use Traversable;
18
19
/**
20
 * @property-read HigherOrderCollectionProxy $average
21
 * @property-read HigherOrderCollectionProxy $avg
22
 * @property-read HigherOrderCollectionProxy $contains
23
 * @property-read HigherOrderCollectionProxy $each
24
 * @property-read HigherOrderCollectionProxy $every
25
 * @property-read HigherOrderCollectionProxy $filter
26
 * @property-read HigherOrderCollectionProxy $first
27
 * @property-read HigherOrderCollectionProxy $flatMap
28
 * @property-read HigherOrderCollectionProxy $groupBy
29
 * @property-read HigherOrderCollectionProxy $keyBy
30
 * @property-read HigherOrderCollectionProxy $map
31
 * @property-read HigherOrderCollectionProxy $max
32
 * @property-read HigherOrderCollectionProxy $min
33
 * @property-read HigherOrderCollectionProxy $partition
34
 * @property-read HigherOrderCollectionProxy $reject
35
 * @property-read HigherOrderCollectionProxy $some
36
 * @property-read HigherOrderCollectionProxy $sortBy
37
 * @property-read HigherOrderCollectionProxy $sortByDesc
38
 * @property-read HigherOrderCollectionProxy $sum
39
 * @property-read HigherOrderCollectionProxy $unique
40
 * @property-read HigherOrderCollectionProxy $until
41
 */
42
trait EnumeratesValues
43
{
44
    /**
45
     * The methods that can be proxied.
46
     *
47
     * @var array
48
     */
49
    protected static array $proxies = [
50
        'average',
51
        'avg',
52
        'contains',
53
        'each',
54
        'every',
55
        'filter',
56
        'first',
57
        'flatMap',
58
        'groupBy',
59
        'keyBy',
60
        'map',
61
        'max',
62
        'min',
63
        'partition',
64
        'reject',
65
        'skipUntil',
66
        'skipWhile',
67
        'some',
68
        'sortBy',
69
        'sortByDesc',
70
        'sum',
71
        'takeUntil',
72
        'takeWhile',
73
        'unique',
74
        'until',
75
    ];
76
77
    /**
78
     * Create a new collection instance if the value isn't one already.
79
     *
80
     * @param mixed $items
81
     * @return static
82
     */
83
    public static function make($items = [])
84
    {
85
        return new static($items);
0 ignored issues
show
Unused Code introduced by
The call to MuCTS\Collections\Traits...esValues::__construct() has too many arguments starting with $items. ( Ignorable by Annotation )

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

85
        return /** @scrutinizer ignore-call */ new static($items);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
86
    }
87
88
    /**
89
     * Wrap the given value in a collection if applicable.
90
     *
91
     * @param mixed $value
92
     * @return static
93
     */
94
    public static function wrap($value)
95
    {
96
        return $value instanceof Enumerable
97
            ? new static($value)
0 ignored issues
show
Unused Code introduced by
The call to MuCTS\Collections\Traits...esValues::__construct() has too many arguments starting with $value. ( Ignorable by Annotation )

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

97
            ? /** @scrutinizer ignore-call */ new static($value)

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
98
            : new static(Arr::wrap($value));
99
    }
100
101
    /**
102
     * Get the underlying items from the given collection if applicable.
103
     *
104
     * @param array|static $value
105
     * @return array
106
     */
107
    public static function unwrap($value)
108
    {
109
        return $value instanceof Enumerable ? $value->all() : $value;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $value instanceof... $value->all() : $value also could return the type MuCTS\Collections\Traits\EnumeratesValues which is incompatible with the documented return type array.
Loading history...
110
    }
111
112
    /**
113
     * Alias for the "avg" method.
114
     *
115
     * @param callable|string|null $callback
116
     * @return mixed
117
     */
118
    public function average($callback = null)
119
    {
120
        return $this->avg($callback);
0 ignored issues
show
Bug introduced by
It seems like avg() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

120
        return $this->/** @scrutinizer ignore-call */ avg($callback);
Loading history...
121
    }
122
123
    /**
124
     * Alias for the "contains" method.
125
     *
126
     * @param mixed $key
127
     * @param mixed $operator
128
     * @param mixed $value
129
     * @return bool
130
     */
131
    public function some($key, $operator = null, $value = null)
132
    {
133
        return $this->contains(...func_get_args());
0 ignored issues
show
Bug introduced by
The method contains() does not exist on MuCTS\Collections\Traits\EnumeratesValues. Did you maybe mean containsStrict()? ( Ignorable by Annotation )

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

133
        return $this->/** @scrutinizer ignore-call */ contains(...func_get_args());

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...
134
    }
135
136
    /**
137
     * Determine if an item exists, using strict comparison.
138
     *
139
     * @param mixed $key
140
     * @param mixed $value
141
     * @return bool
142
     */
143
    public function containsStrict($key, $value = null)
144
    {
145
        if (func_num_args() === 2) {
146
            return $this->contains(function ($item) use ($key, $value) {
147
                return data_get($item, $key) === $value;
148
            });
149
        }
150
151
        if ($this->useAsCallable($key)) {
152
            return !is_null($this->first($key));
0 ignored issues
show
Bug introduced by
The method first() does not exist on MuCTS\Collections\Traits\EnumeratesValues. Did you maybe mean firstWhere()? ( Ignorable by Annotation )

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

152
            return !is_null($this->/** @scrutinizer ignore-call */ first($key));

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...
153
        }
154
155
        foreach ($this as $item) {
156
            if ($item === $key) {
157
                return true;
158
            }
159
        }
160
161
        return false;
162
    }
163
164
    /**
165
     * Dump the items and end the script.
166
     *
167
     * @param mixed ...$args
168
     * @return void
169
     */
170
    public function dd(...$args)
171
    {
172
        call_user_func_array([$this, 'dump'], $args);
173
    }
174
175
    /**
176
     * Dump the items.
177
     *
178
     * @return $this
179
     */
180
    public function dump()
181
    {
182
        (new static(func_get_args()))
0 ignored issues
show
Unused Code introduced by
The call to MuCTS\Collections\Traits...esValues::__construct() has too many arguments starting with func_get_args(). ( Ignorable by Annotation )

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

182
        (/** @scrutinizer ignore-call */ new static(func_get_args()))

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
183
            ->push($this)
0 ignored issues
show
Bug introduced by
It seems like push() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

183
            ->/** @scrutinizer ignore-call */ push($this)
Loading history...
184
            ->each(function ($item) {
185
                VarDumper::dump($item);
186
            });
187
188
        return $this;
189
    }
190
191
    /**
192
     * Execute a callback over each item.
193
     *
194
     * @param callable $callback
195
     * @return $this
196
     */
197
    public function each(callable $callback)
198
    {
199
        foreach ($this as $key => $item) {
200
            if ($callback($item, $key) === false) {
201
                break;
202
            }
203
        }
204
205
        return $this;
206
    }
207
208
    /**
209
     * Execute a callback over each nested chunk of items.
210
     *
211
     * @param callable $callback
212
     * @return static
213
     */
214
    public function eachSpread(callable $callback)
215
    {
216
        return $this->each(function ($chunk, $key) use ($callback) {
217
            $chunk[] = $key;
218
219
            return $callback(...$chunk);
220
        });
221
    }
222
223
    /**
224
     * Determine if all items pass the given truth test.
225
     *
226
     * @param string|callable $key
227
     * @param mixed $operator
228
     * @param mixed $value
229
     * @return bool
230
     */
231
    public function every($key, $operator = null, $value = null)
232
    {
233
        if (func_num_args() === 1) {
234
            $callback = $this->valueRetriever($key);
235
236
            foreach ($this as $k => $v) {
237
                if (!$callback($v, $k)) {
238
                    return false;
239
                }
240
            }
241
242
            return true;
243
        }
244
245
        return $this->every($this->operatorForWhere(...func_get_args()));
0 ignored issues
show
Bug introduced by
func_get_args() is expanded, but the parameter $key of MuCTS\Collections\Traits...ues::operatorForWhere() does not expect variable arguments. ( Ignorable by Annotation )

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

245
        return $this->every($this->operatorForWhere(/** @scrutinizer ignore-type */ ...func_get_args()));
Loading history...
246
    }
247
248
    /**
249
     * Get the first item by the given key value pair.
250
     *
251
     * @param string $key
252
     * @param mixed $operator
253
     * @param mixed $value
254
     * @return mixed
255
     */
256
    public function firstWhere($key, $operator = null, $value = null)
257
    {
258
        return $this->first($this->operatorForWhere(...func_get_args()));
0 ignored issues
show
Bug introduced by
func_get_args() is expanded, but the parameter $key of MuCTS\Collections\Traits...ues::operatorForWhere() does not expect variable arguments. ( Ignorable by Annotation )

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

258
        return $this->first($this->operatorForWhere(/** @scrutinizer ignore-type */ ...func_get_args()));
Loading history...
259
    }
260
261
    /**
262
     * Determine if the collection is not empty.
263
     *
264
     * @return bool
265
     */
266
    public function isNotEmpty()
267
    {
268
        return !$this->isEmpty();
0 ignored issues
show
Bug introduced by
It seems like isEmpty() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

268
        return !$this->/** @scrutinizer ignore-call */ isEmpty();
Loading history...
269
    }
270
271
    /**
272
     * Run a map over each nested chunk of items.
273
     *
274
     * @param callable $callback
275
     * @return static
276
     */
277
    public function mapSpread(callable $callback)
278
    {
279
        return $this->map(function ($chunk, $key) use ($callback) {
0 ignored issues
show
Bug introduced by
It seems like map() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

279
        return $this->/** @scrutinizer ignore-call */ map(function ($chunk, $key) use ($callback) {
Loading history...
280
            $chunk[] = $key;
281
282
            return $callback(...$chunk);
283
        });
284
    }
285
286
    /**
287
     * Run a grouping map over the items.
288
     *
289
     * The callback should return an associative array with a single key/value pair.
290
     *
291
     * @param callable $callback
292
     * @return static
293
     */
294
    public function mapToGroups(callable $callback)
295
    {
296
        $groups = $this->mapToDictionary($callback);
0 ignored issues
show
Bug introduced by
It seems like mapToDictionary() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

296
        /** @scrutinizer ignore-call */ 
297
        $groups = $this->mapToDictionary($callback);
Loading history...
297
298
        return $groups->map([$this, 'make']);
299
    }
300
301
    /**
302
     * Map a collection and flatten the result by a single level.
303
     *
304
     * @param callable $callback
305
     * @return static
306
     */
307
    public function flatMap(callable $callback)
308
    {
309
        return $this->map($callback)->collapse();
310
    }
311
312
    /**
313
     * Map the values into a new class.
314
     *
315
     * @param string $class
316
     * @return static
317
     */
318
    public function mapInto($class)
319
    {
320
        return $this->map(function ($value, $key) use ($class) {
321
            return new $class($value, $key);
322
        });
323
    }
324
325
    /**
326
     * Get the min value of a given key.
327
     *
328
     * @param callable|string|null $callback
329
     * @return mixed
330
     */
331
    public function min($callback = null)
332
    {
333
        $callback = $this->valueRetriever($callback);
334
335
        return $this->map(function ($value) use ($callback) {
336
            return $callback($value);
337
        })->filter(function ($value) {
338
            return !is_null($value);
339
        })->reduce(function ($result, $value) {
340
            return is_null($result) || $value < $result ? $value : $result;
341
        });
342
    }
343
344
    /**
345
     * Get the max value of a given key.
346
     *
347
     * @param callable|string|null $callback
348
     * @return mixed
349
     */
350
    public function max($callback = null)
351
    {
352
        $callback = $this->valueRetriever($callback);
353
354
        return $this->filter(function ($value) {
0 ignored issues
show
Bug introduced by
It seems like filter() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

354
        return $this->/** @scrutinizer ignore-call */ filter(function ($value) {
Loading history...
355
            return !is_null($value);
356
        })->reduce(function ($result, $item) use ($callback) {
357
            $value = $callback($item);
358
359
            return is_null($result) || $value > $result ? $value : $result;
360
        });
361
    }
362
363
    /**
364
     * "Paginate" the collection by slicing it into a smaller collection.
365
     *
366
     * @param int $page
367
     * @param int $perPage
368
     * @return static
369
     */
370
    public function forPage($page, $perPage)
371
    {
372
        $offset = max(0, ($page - 1) * $perPage);
373
374
        return $this->slice($offset, $perPage);
0 ignored issues
show
Bug introduced by
It seems like slice() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

374
        return $this->/** @scrutinizer ignore-call */ slice($offset, $perPage);
Loading history...
375
    }
376
377
    /**
378
     * Partition the collection into two arrays using the given callback or key.
379
     *
380
     * @param callable|string $key
381
     * @param mixed $operator
382
     * @param mixed $value
383
     * @return static
384
     */
385
    public function partition($key, $operator = null, $value = null)
386
    {
387
        $passed = [];
388
        $failed = [];
389
390
        $callback = func_num_args() === 1
391
            ? $this->valueRetriever($key)
392
            : $this->operatorForWhere(...func_get_args());
0 ignored issues
show
Bug introduced by
func_get_args() is expanded, but the parameter $key of MuCTS\Collections\Traits...ues::operatorForWhere() does not expect variable arguments. ( Ignorable by Annotation )

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

392
            : $this->operatorForWhere(/** @scrutinizer ignore-type */ ...func_get_args());
Loading history...
393
394
        foreach ($this as $key => $item) {
0 ignored issues
show
introduced by
$key is overwriting one of the parameters of this function.
Loading history...
395
            if ($callback($item, $key)) {
396
                $passed[$key] = $item;
397
            } else {
398
                $failed[$key] = $item;
399
            }
400
        }
401
402
        return new static([new static($passed), new static($failed)]);
0 ignored issues
show
Unused Code introduced by
The call to MuCTS\Collections\Traits...esValues::__construct() has too many arguments starting with $passed. ( Ignorable by Annotation )

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

402
        return new static([/** @scrutinizer ignore-call */ new static($passed), new static($failed)]);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
403
    }
404
405
    /**
406
     * Get the sum of the given values.
407
     *
408
     * @param callable|string|null $callback
409
     * @return mixed
410
     */
411
    public function sum($callback = null)
412
    {
413
        $callback = is_null($callback)
414
            ? $this->identity()
415
            : $this->valueRetriever($callback);
416
417
        return $this->reduce(function ($result, $item) use ($callback) {
0 ignored issues
show
Bug introduced by
It seems like reduce() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

417
        return $this->/** @scrutinizer ignore-call */ reduce(function ($result, $item) use ($callback) {
Loading history...
418
            return $result + $callback($item);
419
        }, 0);
420
    }
421
422
    /**
423
     * Apply the callback if the value is truthy.
424
     *
425
     * @param bool|mixed $value
426
     * @param callable|null $callback
427
     * @param callable|null $default
428
     * @return static|mixed
429
     */
430
    public function when($value, callable $callback = null, callable $default = null)
431
    {
432
        if (!$callback) {
433
            return new HigherOrderWhenProxy($this, $value);
0 ignored issues
show
Bug introduced by
$this of type MuCTS\Collections\Traits\EnumeratesValues is incompatible with the type MuCTS\Contracts\Collections\Enumerable expected by parameter $collection of MuCTS\Collections\Higher...henProxy::__construct(). ( Ignorable by Annotation )

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

433
            return new HigherOrderWhenProxy(/** @scrutinizer ignore-type */ $this, $value);
Loading history...
434
        }
435
436
        if ($value) {
437
            return $callback($this, $value);
438
        } elseif ($default) {
439
            return $default($this, $value);
440
        }
441
442
        return $this;
443
    }
444
445
    /**
446
     * Apply the callback if the collection is empty.
447
     *
448
     * @param callable $callback
449
     * @param callable|null $default
450
     * @return static|mixed
451
     */
452
    public function whenEmpty(callable $callback, callable $default = null)
453
    {
454
        return $this->when($this->isEmpty(), $callback, $default);
455
    }
456
457
    /**
458
     * Apply the callback if the collection is not empty.
459
     *
460
     * @param callable $callback
461
     * @param callable|null $default
462
     * @return static|mixed
463
     */
464
    public function whenNotEmpty(callable $callback, callable $default = null)
465
    {
466
        return $this->when($this->isNotEmpty(), $callback, $default);
467
    }
468
469
    /**
470
     * Apply the callback if the value is falsy.
471
     *
472
     * @param bool $value
473
     * @param callable $callback
474
     * @param callable|null $default
475
     * @return static|mixed
476
     */
477
    public function unless($value, callable $callback, callable $default = null)
478
    {
479
        return $this->when(!$value, $callback, $default);
480
    }
481
482
    /**
483
     * Apply the callback unless the collection is empty.
484
     *
485
     * @param callable $callback
486
     * @param callable|null $default
487
     * @return static|mixed
488
     */
489
    public function unlessEmpty(callable $callback, callable $default = null)
490
    {
491
        return $this->whenNotEmpty($callback, $default);
492
    }
493
494
    /**
495
     * Apply the callback unless the collection is not empty.
496
     *
497
     * @param callable $callback
498
     * @param callable|null $default
499
     * @return static|mixed
500
     */
501
    public function unlessNotEmpty(callable $callback, callable $default = null)
502
    {
503
        return $this->whenEmpty($callback, $default);
504
    }
505
506
    /**
507
     * Filter items by the given key value pair.
508
     *
509
     * @param string $key
510
     * @param mixed $operator
511
     * @param mixed $value
512
     * @return static
513
     */
514
    public function where($key, $operator = null, $value = null)
515
    {
516
        return $this->filter($this->operatorForWhere(...func_get_args()));
0 ignored issues
show
Bug introduced by
func_get_args() is expanded, but the parameter $key of MuCTS\Collections\Traits...ues::operatorForWhere() does not expect variable arguments. ( Ignorable by Annotation )

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

516
        return $this->filter($this->operatorForWhere(/** @scrutinizer ignore-type */ ...func_get_args()));
Loading history...
517
    }
518
519
    /**
520
     * Filter items where the value for the given key is null.
521
     *
522
     * @param string|null $key
523
     * @return static
524
     */
525
    public function whereNull($key = null)
526
    {
527
        return $this->whereStrict($key, null);
528
    }
529
530
    /**
531
     * Filter items where the value for the given key is not null.
532
     *
533
     * @param string|null $key
534
     * @return static
535
     */
536
    public function whereNotNull($key = null)
537
    {
538
        return $this->where($key, '!==', null);
539
    }
540
541
    /**
542
     * Filter items by the given key value pair using strict comparison.
543
     *
544
     * @param string $key
545
     * @param mixed $value
546
     * @return static
547
     */
548
    public function whereStrict($key, $value)
549
    {
550
        return $this->where($key, '===', $value);
551
    }
552
553
    /**
554
     * Filter items by the given key value pair.
555
     *
556
     * @param string $key
557
     * @param mixed $values
558
     * @param bool $strict
559
     * @return static
560
     */
561
    public function whereIn($key, $values, $strict = false)
562
    {
563
        $values = $this->getArrayableItems($values);
564
565
        return $this->filter(function ($item) use ($key, $values, $strict) {
566
            return in_array(data_get($item, $key), $values, $strict);
567
        });
568
    }
569
570
    /**
571
     * Filter items by the given key value pair using strict comparison.
572
     *
573
     * @param string $key
574
     * @param mixed $values
575
     * @return static
576
     */
577
    public function whereInStrict($key, $values)
578
    {
579
        return $this->whereIn($key, $values, true);
580
    }
581
582
    /**
583
     * Filter items such that the value of the given key is between the given values.
584
     *
585
     * @param string $key
586
     * @param array $values
587
     * @return static
588
     */
589
    public function whereBetween($key, $values)
590
    {
591
        return $this->where($key, '>=', reset($values))->where($key, '<=', end($values));
592
    }
593
594
    /**
595
     * Filter items such that the value of the given key is not between the given values.
596
     *
597
     * @param string $key
598
     * @param array $values
599
     * @return static
600
     */
601
    public function whereNotBetween($key, $values)
602
    {
603
        return $this->filter(function ($item) use ($key, $values) {
604
            return data_get($item, $key) < reset($values) || data_get($item, $key) > end($values);
605
        });
606
    }
607
608
    /**
609
     * Filter items by the given key value pair.
610
     *
611
     * @param string $key
612
     * @param mixed $values
613
     * @param bool $strict
614
     * @return static
615
     */
616
    public function whereNotIn($key, $values, $strict = false)
617
    {
618
        $values = $this->getArrayableItems($values);
619
620
        return $this->reject(function ($item) use ($key, $values, $strict) {
621
            return in_array(data_get($item, $key), $values, $strict);
622
        });
623
    }
624
625
    /**
626
     * Filter items by the given key value pair using strict comparison.
627
     *
628
     * @param string $key
629
     * @param mixed $values
630
     * @return static
631
     */
632
    public function whereNotInStrict($key, $values)
633
    {
634
        return $this->whereNotIn($key, $values, true);
635
    }
636
637
    /**
638
     * Filter the items, removing any items that don't match the given type.
639
     *
640
     * @param string $type
641
     * @return static
642
     */
643
    public function whereInstanceOf($type)
644
    {
645
        return $this->filter(function ($value) use ($type) {
646
            return $value instanceof $type;
647
        });
648
    }
649
650
    /**
651
     * Pass the collection to the given callback and return the result.
652
     *
653
     * @param callable $callback
654
     * @return mixed
655
     */
656
    public function pipe(callable $callback)
657
    {
658
        return $callback($this);
659
    }
660
661
    /**
662
     * Pass the collection to the given callback and then return it.
663
     *
664
     * @param callable $callback
665
     * @return $this
666
     */
667
    public function tap(callable $callback)
668
    {
669
        $callback(clone $this);
670
671
        return $this;
672
    }
673
674
    /**
675
     * Create a collection of all elements that do not pass a given truth test.
676
     *
677
     * @param callable|mixed $callback
678
     * @return static
679
     */
680
    public function reject($callback = true)
681
    {
682
        $useAsCallable = $this->useAsCallable($callback);
683
684
        return $this->filter(function ($value, $key) use ($callback, $useAsCallable) {
685
            return $useAsCallable
686
                ? !$callback($value, $key)
687
                : $value != $callback;
688
        });
689
    }
690
691
    /**
692
     * Return only unique items from the collection array.
693
     *
694
     * @param string|callable|null $key
695
     * @param bool $strict
696
     * @return static
697
     */
698
    public function unique($key = null, $strict = false)
699
    {
700
        $callback = $this->valueRetriever($key);
701
702
        $exists = [];
703
704
        return $this->reject(function ($item, $key) use ($callback, $strict, &$exists) {
705
            if (in_array($id = $callback($item, $key), $exists, $strict)) {
706
                return true;
707
            }
708
709
            $exists[] = $id;
710
        });
711
    }
712
713
    /**
714
     * Return only unique items from the collection array using strict comparison.
715
     *
716
     * @param string|callable|null $key
717
     * @return static
718
     */
719
    public function uniqueStrict($key = null)
720
    {
721
        return $this->unique($key, true);
722
    }
723
724
    /**
725
     * Take items in the collection until the given condition is met.
726
     *
727
     * This is an alias to the "takeUntil" method.
728
     *
729
     * @param mixed $value
730
     * @return static
731
     *
732
     * @deprecated Use the "takeUntil" method directly.
733
     */
734
    public function until($value)
735
    {
736
        return $this->takeUntil($value);
0 ignored issues
show
Bug introduced by
It seems like takeUntil() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

736
        return $this->/** @scrutinizer ignore-call */ takeUntil($value);
Loading history...
737
    }
738
739
    /**
740
     * Collect the values into a collection.
741
     *
742
     * @return \MuCTS\Collections\Collection
743
     */
744
    public function collect()
745
    {
746
        return new Collection($this->all());
0 ignored issues
show
Bug introduced by
It seems like all() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

746
        return new Collection($this->/** @scrutinizer ignore-call */ all());
Loading history...
747
    }
748
749
    /**
750
     * Get the collection of items as a plain array.
751
     *
752
     * @return array
753
     */
754
    public function toArray()
755
    {
756
        return $this->map(function ($value) {
757
            return $value instanceof Arrayable ? $value->toArray() : $value;
758
        })->all();
759
    }
760
761
    /**
762
     * Convert the object into something JSON serializable.
763
     *
764
     * @return array
765
     */
766
    public function jsonSerialize()
767
    {
768
        return array_map(function ($value) {
769
            if ($value instanceof JsonSerializable) {
770
                return $value->jsonSerialize();
771
            } elseif ($value instanceof Jsonable) {
772
                return json_decode($value->toJson(), true);
773
            } elseif ($value instanceof Arrayable) {
774
                return $value->toArray();
775
            }
776
777
            return $value;
778
        }, $this->all());
779
    }
780
781
    /**
782
     * Get the collection of items as JSON.
783
     *
784
     * @param int $options
785
     * @return string
786
     */
787
    public function toJson($options = 0)
788
    {
789
        return json_encode($this->jsonSerialize(), $options);
790
    }
791
792
    /**
793
     * Get a CachingIterator instance.
794
     *
795
     * @param int $flags
796
     * @return \CachingIterator
797
     */
798
    public function getCachingIterator($flags = CachingIterator::CALL_TOSTRING)
799
    {
800
        return new CachingIterator($this->getIterator(), $flags);
0 ignored issues
show
Bug introduced by
It seems like getIterator() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

800
        return new CachingIterator($this->/** @scrutinizer ignore-call */ getIterator(), $flags);
Loading history...
801
    }
802
803
    /**
804
     * Convert the collection to its string representation.
805
     *
806
     * @return string
807
     */
808
    public function __toString()
809
    {
810
        return $this->toJson();
811
    }
812
813
    /**
814
     * Add a method to the list of proxied methods.
815
     *
816
     * @param string $method
817
     * @return void
818
     */
819
    public static function proxy($method)
820
    {
821
        static::$proxies[] = $method;
822
    }
823
824
    /**
825
     * Dynamically access collection proxies.
826
     *
827
     * @param string $key
828
     * @return mixed
829
     *
830
     * @throws Exception
831
     */
832
    public function __get($key)
833
    {
834
        if (!in_array($key, static::$proxies)) {
835
            throw new Exception("Property [{$key}] does not exist on this collection instance.");
836
        }
837
838
        return new HigherOrderCollectionProxy($this, $key);
0 ignored issues
show
Bug introduced by
$this of type MuCTS\Collections\Traits\EnumeratesValues is incompatible with the type MuCTS\Contracts\Collections\Enumerable expected by parameter $collection of MuCTS\Collections\Higher...ionProxy::__construct(). ( Ignorable by Annotation )

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

838
        return new HigherOrderCollectionProxy(/** @scrutinizer ignore-type */ $this, $key);
Loading history...
839
    }
840
841
    /**
842
     * Results array of items from Collection or Arrayable.
843
     *
844
     * @param mixed $items
845
     * @return array
846
     */
847
    protected function getArrayableItems($items)
848
    {
849
        if (is_array($items)) {
850
            return $items;
851
        } elseif ($items instanceof Enumerable) {
852
            return $items->all();
853
        } elseif ($items instanceof Arrayable) {
854
            return $items->toArray();
855
        } elseif ($items instanceof Jsonable) {
856
            return json_decode($items->toJson(), true);
857
        } elseif ($items instanceof JsonSerializable) {
858
            return (array)$items->jsonSerialize();
859
        } elseif ($items instanceof Traversable) {
860
            return iterator_to_array($items);
861
        }
862
863
        return (array)$items;
864
    }
865
866
    /**
867
     * Get an operator checker callback.
868
     *
869
     * @param string $key
870
     * @param string|null $operator
871
     * @param mixed $value
872
     * @return Closure
873
     */
874
    protected function operatorForWhere($key, $operator = null, $value = null)
875
    {
876
        if (func_num_args() === 1) {
877
            $value = true;
878
879
            $operator = '=';
880
        }
881
882
        if (func_num_args() === 2) {
883
            $value = $operator;
884
885
            $operator = '=';
886
        }
887
888
        return function ($item) use ($key, $operator, $value) {
889
            $retrieved = data_get($item, $key);
890
891
            $strings = array_filter([$retrieved, $value], function ($value) {
892
                return is_string($value) || (is_object($value) && method_exists($value, '__toString'));
893
            });
894
895
            if (count($strings) < 2 && count(array_filter([$retrieved, $value], 'is_object')) == 1) {
896
                return in_array($operator, ['!=', '<>', '!==']);
897
            }
898
899
            switch ($operator) {
900
                default:
901
                case '=':
902
                case '==':
903
                    return $retrieved == $value;
904
                case '!=':
905
                case '<>':
906
                    return $retrieved != $value;
907
                case '<':
908
                    return $retrieved < $value;
909
                case '>':
910
                    return $retrieved > $value;
911
                case '<=':
912
                    return $retrieved <= $value;
913
                case '>=':
914
                    return $retrieved >= $value;
915
                case '===':
916
                    return $retrieved === $value;
917
                case '!==':
918
                    return $retrieved !== $value;
919
            }
920
        };
921
    }
922
923
    /**
924
     * Determine if the given value is callable, but not a string.
925
     *
926
     * @param mixed $value
927
     * @return bool
928
     */
929
    protected function useAsCallable($value)
930
    {
931
        return !is_string($value) && is_callable($value);
932
    }
933
934
    /**
935
     * Get a value retrieving callback.
936
     *
937
     * @param callable|string|null $value
938
     * @return callable
939
     */
940
    protected function valueRetriever($value)
941
    {
942
        if ($this->useAsCallable($value)) {
943
            return $value;
944
        }
945
946
        return function ($item) use ($value) {
947
            return data_get($item, $value);
948
        };
949
    }
950
951
    /**
952
     * Make a function to check an item's equality.
953
     *
954
     * @param mixed $value
955
     * @return Closure
956
     */
957
    protected function equality($value)
958
    {
959
        return function ($item) use ($value) {
960
            return $item === $value;
961
        };
962
    }
963
964
    /**
965
     * Make a function using another function, by negating its result.
966
     *
967
     * @param Closure $callback
968
     * @return Closure
969
     */
970
    protected function negate(Closure $callback)
971
    {
972
        return function (...$params) use ($callback) {
973
            return !$callback(...$params);
974
        };
975
    }
976
977
    /**
978
     * Make a function that returns what's passed to it.
979
     *
980
     * @return Closure
981
     */
982
    protected function identity()
983
    {
984
        return function ($value) {
985
            return $value;
986
        };
987
    }
988
}
989