blob: 2600a2238e91581938928d084a202efc76b41c7a [file] [log] [blame]
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001<?php
2
3namespace Tightenco\Collect\Support\Traits;
4
5use CachingIterator;
6use Closure;
7use Exception;
8use Tightenco\Collect\Contracts\Support\Arrayable;
9use Tightenco\Collect\Contracts\Support\Jsonable;
10use Tightenco\Collect\Support\Arr;
11use Tightenco\Collect\Support\Collection;
12use Tightenco\Collect\Support\Enumerable;
13use Tightenco\Collect\Support\HigherOrderCollectionProxy;
14use Tightenco\Collect\Support\HigherOrderWhenProxy;
15use JsonSerializable;
16use Symfony\Component\VarDumper\VarDumper;
17use Traversable;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010018use UnexpectedValueException;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020019
20/**
21 * @property-read HigherOrderCollectionProxy $average
22 * @property-read HigherOrderCollectionProxy $avg
23 * @property-read HigherOrderCollectionProxy $contains
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010024 * @property-read HigherOrderCollectionProxy $doesntContain
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020025 * @property-read HigherOrderCollectionProxy $each
26 * @property-read HigherOrderCollectionProxy $every
27 * @property-read HigherOrderCollectionProxy $filter
28 * @property-read HigherOrderCollectionProxy $first
29 * @property-read HigherOrderCollectionProxy $flatMap
30 * @property-read HigherOrderCollectionProxy $groupBy
31 * @property-read HigherOrderCollectionProxy $keyBy
32 * @property-read HigherOrderCollectionProxy $map
33 * @property-read HigherOrderCollectionProxy $max
34 * @property-read HigherOrderCollectionProxy $min
35 * @property-read HigherOrderCollectionProxy $partition
36 * @property-read HigherOrderCollectionProxy $reject
37 * @property-read HigherOrderCollectionProxy $some
38 * @property-read HigherOrderCollectionProxy $sortBy
39 * @property-read HigherOrderCollectionProxy $sortByDesc
40 * @property-read HigherOrderCollectionProxy $skipUntil
41 * @property-read HigherOrderCollectionProxy $skipWhile
42 * @property-read HigherOrderCollectionProxy $sum
43 * @property-read HigherOrderCollectionProxy $takeUntil
44 * @property-read HigherOrderCollectionProxy $takeWhile
45 * @property-read HigherOrderCollectionProxy $unique
46 * @property-read HigherOrderCollectionProxy $until
47 */
48trait EnumeratesValues
49{
50 /**
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010051 * Indicates that the object's string representation should be escaped when __toString is invoked.
52 *
53 * @var bool
54 */
55 protected $escapeWhenCastingToString = false;
56
57 /**
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020058 * The methods that can be proxied.
59 *
60 * @var string[]
61 */
62 protected static $proxies = [
63 'average',
64 'avg',
65 'contains',
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010066 'doesntContain',
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020067 'each',
68 'every',
69 'filter',
70 'first',
71 'flatMap',
72 'groupBy',
73 'keyBy',
74 'map',
75 'max',
76 'min',
77 'partition',
78 'reject',
79 'skipUntil',
80 'skipWhile',
81 'some',
82 'sortBy',
83 'sortByDesc',
84 'sum',
85 'takeUntil',
86 'takeWhile',
87 'unique',
88 'until',
89 ];
90
91 /**
92 * Create a new collection instance if the value isn't one already.
93 *
94 * @param mixed $items
95 * @return static
96 */
97 public static function make($items = [])
98 {
99 return new static($items);
100 }
101
102 /**
103 * Wrap the given value in a collection if applicable.
104 *
105 * @param mixed $value
106 * @return static
107 */
108 public static function wrap($value)
109 {
110 return $value instanceof Enumerable
111 ? new static($value)
112 : new static(Arr::wrap($value));
113 }
114
115 /**
116 * Get the underlying items from the given collection if applicable.
117 *
118 * @param array|static $value
119 * @return array
120 */
121 public static function unwrap($value)
122 {
123 return $value instanceof Enumerable ? $value->all() : $value;
124 }
125
126 /**
127 * Create a new instance with no items.
128 *
129 * @return static
130 */
131 public static function empty()
132 {
133 return new static([]);
134 }
135
136 /**
137 * Create a new collection by invoking the callback a given amount of times.
138 *
139 * @param int $number
140 * @param callable|null $callback
141 * @return static
142 */
143 public static function times($number, callable $callback = null)
144 {
145 if ($number < 1) {
146 return new static;
147 }
148
149 return static::range(1, $number)
150 ->when($callback)
151 ->map($callback);
152 }
153
154 /**
155 * Alias for the "avg" method.
156 *
157 * @param callable|string|null $callback
158 * @return mixed
159 */
160 public function average($callback = null)
161 {
162 return $this->avg($callback);
163 }
164
165 /**
166 * Alias for the "contains" method.
167 *
168 * @param mixed $key
169 * @param mixed $operator
170 * @param mixed $value
171 * @return bool
172 */
173 public function some($key, $operator = null, $value = null)
174 {
175 return $this->contains(...func_get_args());
176 }
177
178 /**
179 * Determine if an item exists, using strict comparison.
180 *
181 * @param mixed $key
182 * @param mixed $value
183 * @return bool
184 */
185 public function containsStrict($key, $value = null)
186 {
187 if (func_num_args() === 2) {
188 return $this->contains(function ($item) use ($key, $value) {
189 return data_get($item, $key) === $value;
190 });
191 }
192
193 if ($this->useAsCallable($key)) {
194 return ! is_null($this->first($key));
195 }
196
197 foreach ($this as $item) {
198 if ($item === $key) {
199 return true;
200 }
201 }
202
203 return false;
204 }
205
206 /**
207 * Dump the items and end the script.
208 *
209 * @param mixed ...$args
210 * @return void
211 */
212 public function dd(...$args)
213 {
214 $this->dump(...$args);
215
216 exit(1);
217 }
218
219 /**
220 * Dump the items.
221 *
222 * @return $this
223 */
224 public function dump()
225 {
226 (new Collection(func_get_args()))
227 ->push($this->all())
228 ->each(function ($item) {
229 VarDumper::dump($item);
230 });
231
232 return $this;
233 }
234
235 /**
236 * Execute a callback over each item.
237 *
238 * @param callable $callback
239 * @return $this
240 */
241 public function each(callable $callback)
242 {
243 foreach ($this as $key => $item) {
244 if ($callback($item, $key) === false) {
245 break;
246 }
247 }
248
249 return $this;
250 }
251
252 /**
253 * Execute a callback over each nested chunk of items.
254 *
255 * @param callable $callback
256 * @return static
257 */
258 public function eachSpread(callable $callback)
259 {
260 return $this->each(function ($chunk, $key) use ($callback) {
261 $chunk[] = $key;
262
263 return $callback(...$chunk);
264 });
265 }
266
267 /**
268 * Determine if all items pass the given truth test.
269 *
270 * @param string|callable $key
271 * @param mixed $operator
272 * @param mixed $value
273 * @return bool
274 */
275 public function every($key, $operator = null, $value = null)
276 {
277 if (func_num_args() === 1) {
278 $callback = $this->valueRetriever($key);
279
280 foreach ($this as $k => $v) {
281 if (! $callback($v, $k)) {
282 return false;
283 }
284 }
285
286 return true;
287 }
288
289 return $this->every($this->operatorForWhere(...func_get_args()));
290 }
291
292 /**
293 * Get the first item by the given key value pair.
294 *
295 * @param string $key
296 * @param mixed $operator
297 * @param mixed $value
298 * @return mixed
299 */
300 public function firstWhere($key, $operator = null, $value = null)
301 {
302 return $this->first($this->operatorForWhere(...func_get_args()));
303 }
304
305 /**
306 * Determine if the collection is not empty.
307 *
308 * @return bool
309 */
310 public function isNotEmpty()
311 {
312 return ! $this->isEmpty();
313 }
314
315 /**
316 * Run a map over each nested chunk of items.
317 *
318 * @param callable $callback
319 * @return static
320 */
321 public function mapSpread(callable $callback)
322 {
323 return $this->map(function ($chunk, $key) use ($callback) {
324 $chunk[] = $key;
325
326 return $callback(...$chunk);
327 });
328 }
329
330 /**
331 * Run a grouping map over the items.
332 *
333 * The callback should return an associative array with a single key/value pair.
334 *
335 * @param callable $callback
336 * @return static
337 */
338 public function mapToGroups(callable $callback)
339 {
340 $groups = $this->mapToDictionary($callback);
341
342 return $groups->map([$this, 'make']);
343 }
344
345 /**
346 * Map a collection and flatten the result by a single level.
347 *
348 * @param callable $callback
349 * @return static
350 */
351 public function flatMap(callable $callback)
352 {
353 return $this->map($callback)->collapse();
354 }
355
356 /**
357 * Map the values into a new class.
358 *
359 * @param string $class
360 * @return static
361 */
362 public function mapInto($class)
363 {
364 return $this->map(function ($value, $key) use ($class) {
365 return new $class($value, $key);
366 });
367 }
368
369 /**
370 * Get the min value of a given key.
371 *
372 * @param callable|string|null $callback
373 * @return mixed
374 */
375 public function min($callback = null)
376 {
377 $callback = $this->valueRetriever($callback);
378
379 return $this->map(function ($value) use ($callback) {
380 return $callback($value);
381 })->filter(function ($value) {
382 return ! is_null($value);
383 })->reduce(function ($result, $value) {
384 return is_null($result) || $value < $result ? $value : $result;
385 });
386 }
387
388 /**
389 * Get the max value of a given key.
390 *
391 * @param callable|string|null $callback
392 * @return mixed
393 */
394 public function max($callback = null)
395 {
396 $callback = $this->valueRetriever($callback);
397
398 return $this->filter(function ($value) {
399 return ! is_null($value);
400 })->reduce(function ($result, $item) use ($callback) {
401 $value = $callback($item);
402
403 return is_null($result) || $value > $result ? $value : $result;
404 });
405 }
406
407 /**
408 * "Paginate" the collection by slicing it into a smaller collection.
409 *
410 * @param int $page
411 * @param int $perPage
412 * @return static
413 */
414 public function forPage($page, $perPage)
415 {
416 $offset = max(0, ($page - 1) * $perPage);
417
418 return $this->slice($offset, $perPage);
419 }
420
421 /**
422 * Partition the collection into two arrays using the given callback or key.
423 *
424 * @param callable|string $key
425 * @param mixed $operator
426 * @param mixed $value
427 * @return static
428 */
429 public function partition($key, $operator = null, $value = null)
430 {
431 $passed = [];
432 $failed = [];
433
434 $callback = func_num_args() === 1
435 ? $this->valueRetriever($key)
436 : $this->operatorForWhere(...func_get_args());
437
438 foreach ($this as $key => $item) {
439 if ($callback($item, $key)) {
440 $passed[$key] = $item;
441 } else {
442 $failed[$key] = $item;
443 }
444 }
445
446 return new static([new static($passed), new static($failed)]);
447 }
448
449 /**
450 * Get the sum of the given values.
451 *
452 * @param callable|string|null $callback
453 * @return mixed
454 */
455 public function sum($callback = null)
456 {
457 $callback = is_null($callback)
458 ? $this->identity()
459 : $this->valueRetriever($callback);
460
461 return $this->reduce(function ($result, $item) use ($callback) {
462 return $result + $callback($item);
463 }, 0);
464 }
465
466 /**
467 * Apply the callback if the value is truthy.
468 *
469 * @param bool|mixed $value
470 * @param callable|null $callback
471 * @param callable|null $default
472 * @return static|mixed
473 */
474 public function when($value, callable $callback = null, callable $default = null)
475 {
476 if (! $callback) {
477 return new HigherOrderWhenProxy($this, $value);
478 }
479
480 if ($value) {
481 return $callback($this, $value);
482 } elseif ($default) {
483 return $default($this, $value);
484 }
485
486 return $this;
487 }
488
489 /**
490 * Apply the callback if the collection is empty.
491 *
492 * @param callable $callback
493 * @param callable|null $default
494 * @return static|mixed
495 */
496 public function whenEmpty(callable $callback, callable $default = null)
497 {
498 return $this->when($this->isEmpty(), $callback, $default);
499 }
500
501 /**
502 * Apply the callback if the collection is not empty.
503 *
504 * @param callable $callback
505 * @param callable|null $default
506 * @return static|mixed
507 */
508 public function whenNotEmpty(callable $callback, callable $default = null)
509 {
510 return $this->when($this->isNotEmpty(), $callback, $default);
511 }
512
513 /**
514 * Apply the callback if the value is falsy.
515 *
516 * @param bool $value
517 * @param callable $callback
518 * @param callable|null $default
519 * @return static|mixed
520 */
521 public function unless($value, callable $callback, callable $default = null)
522 {
523 return $this->when(! $value, $callback, $default);
524 }
525
526 /**
527 * Apply the callback unless the collection is empty.
528 *
529 * @param callable $callback
530 * @param callable|null $default
531 * @return static|mixed
532 */
533 public function unlessEmpty(callable $callback, callable $default = null)
534 {
535 return $this->whenNotEmpty($callback, $default);
536 }
537
538 /**
539 * Apply the callback unless the collection is not empty.
540 *
541 * @param callable $callback
542 * @param callable|null $default
543 * @return static|mixed
544 */
545 public function unlessNotEmpty(callable $callback, callable $default = null)
546 {
547 return $this->whenEmpty($callback, $default);
548 }
549
550 /**
551 * Filter items by the given key value pair.
552 *
553 * @param string $key
554 * @param mixed $operator
555 * @param mixed $value
556 * @return static
557 */
558 public function where($key, $operator = null, $value = null)
559 {
560 return $this->filter($this->operatorForWhere(...func_get_args()));
561 }
562
563 /**
564 * Filter items where the value for the given key is null.
565 *
566 * @param string|null $key
567 * @return static
568 */
569 public function whereNull($key = null)
570 {
571 return $this->whereStrict($key, null);
572 }
573
574 /**
575 * Filter items where the value for the given key is not null.
576 *
577 * @param string|null $key
578 * @return static
579 */
580 public function whereNotNull($key = null)
581 {
582 return $this->where($key, '!==', null);
583 }
584
585 /**
586 * Filter items by the given key value pair using strict comparison.
587 *
588 * @param string $key
589 * @param mixed $value
590 * @return static
591 */
592 public function whereStrict($key, $value)
593 {
594 return $this->where($key, '===', $value);
595 }
596
597 /**
598 * Filter items by the given key value pair.
599 *
600 * @param string $key
601 * @param mixed $values
602 * @param bool $strict
603 * @return static
604 */
605 public function whereIn($key, $values, $strict = false)
606 {
607 $values = $this->getArrayableItems($values);
608
609 return $this->filter(function ($item) use ($key, $values, $strict) {
610 return in_array(data_get($item, $key), $values, $strict);
611 });
612 }
613
614 /**
615 * Filter items by the given key value pair using strict comparison.
616 *
617 * @param string $key
618 * @param mixed $values
619 * @return static
620 */
621 public function whereInStrict($key, $values)
622 {
623 return $this->whereIn($key, $values, true);
624 }
625
626 /**
627 * Filter items such that the value of the given key is between the given values.
628 *
629 * @param string $key
630 * @param array $values
631 * @return static
632 */
633 public function whereBetween($key, $values)
634 {
635 return $this->where($key, '>=', reset($values))->where($key, '<=', end($values));
636 }
637
638 /**
639 * Filter items such that the value of the given key is not between the given values.
640 *
641 * @param string $key
642 * @param array $values
643 * @return static
644 */
645 public function whereNotBetween($key, $values)
646 {
647 return $this->filter(function ($item) use ($key, $values) {
648 return data_get($item, $key) < reset($values) || data_get($item, $key) > end($values);
649 });
650 }
651
652 /**
653 * Filter items by the given key value pair.
654 *
655 * @param string $key
656 * @param mixed $values
657 * @param bool $strict
658 * @return static
659 */
660 public function whereNotIn($key, $values, $strict = false)
661 {
662 $values = $this->getArrayableItems($values);
663
664 return $this->reject(function ($item) use ($key, $values, $strict) {
665 return in_array(data_get($item, $key), $values, $strict);
666 });
667 }
668
669 /**
670 * Filter items by the given key value pair using strict comparison.
671 *
672 * @param string $key
673 * @param mixed $values
674 * @return static
675 */
676 public function whereNotInStrict($key, $values)
677 {
678 return $this->whereNotIn($key, $values, true);
679 }
680
681 /**
682 * Filter the items, removing any items that don't match the given type(s).
683 *
684 * @param string|string[] $type
685 * @return static
686 */
687 public function whereInstanceOf($type)
688 {
689 return $this->filter(function ($value) use ($type) {
690 if (is_array($type)) {
691 foreach ($type as $classType) {
692 if ($value instanceof $classType) {
693 return true;
694 }
695 }
696
697 return false;
698 }
699
700 return $value instanceof $type;
701 });
702 }
703
704 /**
705 * Pass the collection to the given callback and return the result.
706 *
707 * @param callable $callback
708 * @return mixed
709 */
710 public function pipe(callable $callback)
711 {
712 return $callback($this);
713 }
714
715 /**
716 * Pass the collection into a new class.
717 *
718 * @param string $class
719 * @return mixed
720 */
721 public function pipeInto($class)
722 {
723 return new $class($this);
724 }
725
726 /**
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100727 * Pass the collection through a series of callable pipes and return the result.
728 *
729 * @param array<callable> $pipes
730 * @return mixed
731 */
732 public function pipeThrough($pipes)
733 {
734 return static::make($pipes)->reduce(
735 function ($carry, $pipe) {
736 return $pipe($carry);
737 },
738 $this,
739 );
740 }
741
742 /**
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200743 * Pass the collection to the given callback and then return it.
744 *
745 * @param callable $callback
746 * @return $this
747 */
748 public function tap(callable $callback)
749 {
750 $callback(clone $this);
751
752 return $this;
753 }
754
755 /**
756 * Reduce the collection to a single value.
757 *
758 * @param callable $callback
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100759 * @param mixed $initial
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200760 * @return mixed
761 */
762 public function reduce(callable $callback, $initial = null)
763 {
764 $result = $initial;
765
766 foreach ($this as $key => $value) {
767 $result = $callback($result, $value, $key);
768 }
769
770 return $result;
771 }
772
773 /**
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100774 * Reduce the collection to multiple aggregate values.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200775 *
776 * @param callable $callback
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100777 * @param mixed ...$initial
778 * @return array
779 *
780 * @deprecated Use "reduceSpread" instead
781 *
782 * @throws \UnexpectedValueException
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200783 */
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100784 public function reduceMany(callable $callback, ...$initial)
785 {
786 return $this->reduceSpread($callback, ...$initial);
787 }
788
789 /**
790 * Reduce the collection to multiple aggregate values.
791 *
792 * @param callable $callback
793 * @param mixed ...$initial
794 * @return array
795 *
796 * @throws \UnexpectedValueException
797 */
798 public function reduceSpread(callable $callback, ...$initial)
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200799 {
800 $result = $initial;
801
802 foreach ($this as $key => $value) {
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100803 $result = call_user_func_array($callback, array_merge($result, [$value, $key]));
804
805 if (! is_array($result)) {
806 throw new UnexpectedValueException(sprintf(
807 "%s::reduceMany expects reducer to return an array, but got a '%s' instead.",
808 class_basename(static::class), gettype($result)
809 ));
810 }
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200811 }
812
813 return $result;
814 }
815
816 /**
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100817 * Reduce an associative collection to a single value.
818 *
819 * @param callable $callback
820 * @param mixed $initial
821 * @return mixed
822 */
823 public function reduceWithKeys(callable $callback, $initial = null)
824 {
825 return $this->reduce($callback, $initial);
826 }
827
828 /**
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200829 * Create a collection of all elements that do not pass a given truth test.
830 *
831 * @param callable|mixed $callback
832 * @return static
833 */
834 public function reject($callback = true)
835 {
836 $useAsCallable = $this->useAsCallable($callback);
837
838 return $this->filter(function ($value, $key) use ($callback, $useAsCallable) {
839 return $useAsCallable
840 ? ! $callback($value, $key)
841 : $value != $callback;
842 });
843 }
844
845 /**
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200846 * Return only unique items from the collection array using strict comparison.
847 *
848 * @param string|callable|null $key
849 * @return static
850 */
851 public function uniqueStrict($key = null)
852 {
853 return $this->unique($key, true);
854 }
855
856 /**
857 * Collect the values into a collection.
858 *
859 * @return \Tightenco\Collect\Support\Collection
860 */
861 public function collect()
862 {
863 return new Collection($this->all());
864 }
865
866 /**
867 * Get the collection of items as a plain array.
868 *
869 * @return array
870 */
871 public function toArray()
872 {
873 return $this->map(function ($value) {
874 return $value instanceof Arrayable ? $value->toArray() : $value;
875 })->all();
876 }
877
878 /**
879 * Convert the object into something JSON serializable.
880 *
881 * @return array
882 */
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100883 #[\ReturnTypeWillChange]
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200884 public function jsonSerialize()
885 {
886 return array_map(function ($value) {
887 if ($value instanceof JsonSerializable) {
888 return $value->jsonSerialize();
889 } elseif ($value instanceof Jsonable) {
890 return json_decode($value->toJson(), true);
891 } elseif ($value instanceof Arrayable) {
892 return $value->toArray();
893 }
894
895 return $value;
896 }, $this->all());
897 }
898
899 /**
900 * Get the collection of items as JSON.
901 *
902 * @param int $options
903 * @return string
904 */
905 public function toJson($options = 0)
906 {
907 return json_encode($this->jsonSerialize(), $options);
908 }
909
910 /**
911 * Get a CachingIterator instance.
912 *
913 * @param int $flags
914 * @return \CachingIterator
915 */
916 public function getCachingIterator($flags = CachingIterator::CALL_TOSTRING)
917 {
918 return new CachingIterator($this->getIterator(), $flags);
919 }
920
921 /**
922 * Convert the collection to its string representation.
923 *
924 * @return string
925 */
926 public function __toString()
927 {
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100928 return $this->escapeWhenCastingToString
929 ? e($this->toJson())
930 : $this->toJson();
931 }
932
933 /**
934 * Indicate that the model's string representation should be escaped when __toString is invoked.
935 *
936 * @param bool $escape
937 * @return $this
938 */
939 public function escapeWhenCastingToString($escape = true)
940 {
941 $this->escapeWhenCastingToString = $escape;
942
943 return $this;
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200944 }
945
946 /**
947 * Add a method to the list of proxied methods.
948 *
949 * @param string $method
950 * @return void
951 */
952 public static function proxy($method)
953 {
954 static::$proxies[] = $method;
955 }
956
957 /**
958 * Dynamically access collection proxies.
959 *
960 * @param string $key
961 * @return mixed
962 *
963 * @throws \Exception
964 */
965 public function __get($key)
966 {
967 if (! in_array($key, static::$proxies)) {
968 throw new Exception("Property [{$key}] does not exist on this collection instance.");
969 }
970
971 return new HigherOrderCollectionProxy($this, $key);
972 }
973
974 /**
975 * Results array of items from Collection or Arrayable.
976 *
977 * @param mixed $items
978 * @return array
979 */
980 protected function getArrayableItems($items)
981 {
982 if (is_array($items)) {
983 return $items;
984 } elseif ($items instanceof Enumerable) {
985 return $items->all();
986 } elseif ($items instanceof Arrayable) {
987 return $items->toArray();
988 } elseif ($items instanceof Jsonable) {
989 return json_decode($items->toJson(), true);
990 } elseif ($items instanceof JsonSerializable) {
991 return (array) $items->jsonSerialize();
992 } elseif ($items instanceof Traversable) {
993 return iterator_to_array($items);
994 }
995
996 return (array) $items;
997 }
998
999 /**
1000 * Get an operator checker callback.
1001 *
1002 * @param string $key
1003 * @param string|null $operator
1004 * @param mixed $value
1005 * @return \Closure
1006 */
1007 protected function operatorForWhere($key, $operator = null, $value = null)
1008 {
1009 if (func_num_args() === 1) {
1010 $value = true;
1011
1012 $operator = '=';
1013 }
1014
1015 if (func_num_args() === 2) {
1016 $value = $operator;
1017
1018 $operator = '=';
1019 }
1020
1021 return function ($item) use ($key, $operator, $value) {
1022 $retrieved = data_get($item, $key);
1023
1024 $strings = array_filter([$retrieved, $value], function ($value) {
1025 return is_string($value) || (is_object($value) && method_exists($value, '__toString'));
1026 });
1027
1028 if (count($strings) < 2 && count(array_filter([$retrieved, $value], 'is_object')) == 1) {
1029 return in_array($operator, ['!=', '<>', '!==']);
1030 }
1031
1032 switch ($operator) {
1033 default:
1034 case '=':
1035 case '==': return $retrieved == $value;
1036 case '!=':
1037 case '<>': return $retrieved != $value;
1038 case '<': return $retrieved < $value;
1039 case '>': return $retrieved > $value;
1040 case '<=': return $retrieved <= $value;
1041 case '>=': return $retrieved >= $value;
1042 case '===': return $retrieved === $value;
1043 case '!==': return $retrieved !== $value;
1044 }
1045 };
1046 }
1047
1048 /**
1049 * Determine if the given value is callable, but not a string.
1050 *
1051 * @param mixed $value
1052 * @return bool
1053 */
1054 protected function useAsCallable($value)
1055 {
1056 return ! is_string($value) && is_callable($value);
1057 }
1058
1059 /**
1060 * Get a value retrieving callback.
1061 *
1062 * @param callable|string|null $value
1063 * @return callable
1064 */
1065 protected function valueRetriever($value)
1066 {
1067 if ($this->useAsCallable($value)) {
1068 return $value;
1069 }
1070
1071 return function ($item) use ($value) {
1072 return data_get($item, $value);
1073 };
1074 }
1075
1076 /**
1077 * Make a function to check an item's equality.
1078 *
1079 * @param mixed $value
1080 * @return \Closure
1081 */
1082 protected function equality($value)
1083 {
1084 return function ($item) use ($value) {
1085 return $item === $value;
1086 };
1087 }
1088
1089 /**
1090 * Make a function using another function, by negating its result.
1091 *
1092 * @param \Closure $callback
1093 * @return \Closure
1094 */
1095 protected function negate(Closure $callback)
1096 {
1097 return function (...$params) use ($callback) {
1098 return ! $callback(...$params);
1099 };
1100 }
1101
1102 /**
1103 * Make a function that returns what's passed to it.
1104 *
1105 * @return \Closure
1106 */
1107 protected function identity()
1108 {
1109 return function ($value) {
1110 return $value;
1111 };
1112 }
1113}