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); |
|
|
|
|
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) |
|
|
|
|
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; |
|
|
|
|
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); |
|
|
|
|
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()); |
|
|
|
|
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)); |
|
|
|
|
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())) |
|
|
|
|
183
|
|
|
->push($this) |
|
|
|
|
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())); |
|
|
|
|
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())); |
|
|
|
|
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(); |
|
|
|
|
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) { |
|
|
|
|
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); |
|
|
|
|
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) { |
|
|
|
|
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); |
|
|
|
|
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()); |
|
|
|
|
393
|
|
|
|
394
|
|
|
foreach ($this as $key => $item) { |
|
|
|
|
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)]); |
|
|
|
|
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) { |
|
|
|
|
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); |
|
|
|
|
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())); |
|
|
|
|
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); |
|
|
|
|
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()); |
|
|
|
|
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); |
|
|
|
|
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); |
|
|
|
|
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
|
|
|
|
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.