Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 150 | Zur aktuellen Revision | Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Illuminate\Database\Eloquent;
4
 
5
use Illuminate\Contracts\Queue\QueueableCollection;
6
use Illuminate\Contracts\Queue\QueueableEntity;
7
use Illuminate\Contracts\Support\Arrayable;
8
use Illuminate\Support\Arr;
9
use Illuminate\Support\Collection as BaseCollection;
10
use LogicException;
11
 
12
/**
13
 * @template TKey of array-key
14
 * @template TModel of \Illuminate\Database\Eloquent\Model
15
 *
16
 * @extends \Illuminate\Support\Collection<TKey, TModel>
17
 */
18
class Collection extends BaseCollection implements QueueableCollection
19
{
20
    /**
21
     * Find a model in the collection by key.
22
     *
23
     * @template TFindDefault
24
     *
25
     * @param  mixed  $key
26
     * @param  TFindDefault  $default
27
     * @return static<TKey|TModel>|TModel|TFindDefault
28
     */
29
    public function find($key, $default = null)
30
    {
31
        if ($key instanceof Model) {
32
            $key = $key->getKey();
33
        }
34
 
35
        if ($key instanceof Arrayable) {
36
            $key = $key->toArray();
37
        }
38
 
39
        if (is_array($key)) {
40
            if ($this->isEmpty()) {
41
                return new static;
42
            }
43
 
44
            return $this->whereIn($this->first()->getKeyName(), $key);
45
        }
46
 
47
        return Arr::first($this->items, function ($model) use ($key) {
48
            return $model->getKey() == $key;
49
        }, $default);
50
    }
51
 
52
    /**
53
     * Load a set of relationships onto the collection.
54
     *
55
     * @param  array<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string>|string  $relations
56
     * @return $this
57
     */
58
    public function load($relations)
59
    {
60
        if ($this->isNotEmpty()) {
61
            if (is_string($relations)) {
62
                $relations = func_get_args();
63
            }
64
 
65
            $query = $this->first()->newQueryWithoutRelationships()->with($relations);
66
 
67
            $this->items = $query->eagerLoadRelations($this->items);
68
        }
69
 
70
        return $this;
71
    }
72
 
73
    /**
74
     * Load a set of aggregations over relationship's column onto the collection.
75
     *
76
     * @param  array<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string>|string  $relations
77
     * @param  string  $column
78
     * @param  string|null  $function
79
     * @return $this
80
     */
81
    public function loadAggregate($relations, $column, $function = null)
82
    {
83
        if ($this->isEmpty()) {
84
            return $this;
85
        }
86
 
87
        $models = $this->first()->newModelQuery()
88
            ->whereKey($this->modelKeys())
89
            ->select($this->first()->getKeyName())
90
            ->withAggregate($relations, $column, $function)
91
            ->get()
92
            ->keyBy($this->first()->getKeyName());
93
 
94
        $attributes = Arr::except(
95
            array_keys($models->first()->getAttributes()),
96
            $models->first()->getKeyName()
97
        );
98
 
99
        $this->each(function ($model) use ($models, $attributes) {
100
            $extraAttributes = Arr::only($models->get($model->getKey())->getAttributes(), $attributes);
101
 
102
            $model->forceFill($extraAttributes)
103
                ->syncOriginalAttributes($attributes)
104
                ->mergeCasts($models->get($model->getKey())->getCasts());
105
        });
106
 
107
        return $this;
108
    }
109
 
110
    /**
111
     * Load a set of relationship counts onto the collection.
112
     *
113
     * @param  array<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string>|string  $relations
114
     * @return $this
115
     */
116
    public function loadCount($relations)
117
    {
118
        return $this->loadAggregate($relations, '*', 'count');
119
    }
120
 
121
    /**
122
     * Load a set of relationship's max column values onto the collection.
123
     *
124
     * @param  array<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string>|string  $relations
125
     * @param  string  $column
126
     * @return $this
127
     */
128
    public function loadMax($relations, $column)
129
    {
130
        return $this->loadAggregate($relations, $column, 'max');
131
    }
132
 
133
    /**
134
     * Load a set of relationship's min column values onto the collection.
135
     *
136
     * @param  array<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string>|string  $relations
137
     * @param  string  $column
138
     * @return $this
139
     */
140
    public function loadMin($relations, $column)
141
    {
142
        return $this->loadAggregate($relations, $column, 'min');
143
    }
144
 
145
    /**
146
     * Load a set of relationship's column summations onto the collection.
147
     *
148
     * @param  array<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string>|string  $relations
149
     * @param  string  $column
150
     * @return $this
151
     */
152
    public function loadSum($relations, $column)
153
    {
154
        return $this->loadAggregate($relations, $column, 'sum');
155
    }
156
 
157
    /**
158
     * Load a set of relationship's average column values onto the collection.
159
     *
160
     * @param  array<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string>|string  $relations
161
     * @param  string  $column
162
     * @return $this
163
     */
164
    public function loadAvg($relations, $column)
165
    {
166
        return $this->loadAggregate($relations, $column, 'avg');
167
    }
168
 
169
    /**
170
     * Load a set of related existences onto the collection.
171
     *
172
     * @param  array<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string>|string  $relations
173
     * @return $this
174
     */
175
    public function loadExists($relations)
176
    {
177
        return $this->loadAggregate($relations, '*', 'exists');
178
    }
179
 
180
    /**
181
     * Load a set of relationships onto the collection if they are not already eager loaded.
182
     *
183
     * @param  array<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string>|string  $relations
184
     * @return $this
185
     */
186
    public function loadMissing($relations)
187
    {
188
        if (is_string($relations)) {
189
            $relations = func_get_args();
190
        }
191
 
192
        foreach ($relations as $key => $value) {
193
            if (is_numeric($key)) {
194
                $key = $value;
195
            }
196
 
197
            $segments = explode('.', explode(':', $key)[0]);
198
 
199
            if (str_contains($key, ':')) {
200
                $segments[count($segments) - 1] .= ':'.explode(':', $key)[1];
201
            }
202
 
203
            $path = [];
204
 
205
            foreach ($segments as $segment) {
206
                $path[] = [$segment => $segment];
207
            }
208
 
209
            if (is_callable($value)) {
210
                $path[count($segments) - 1][end($segments)] = $value;
211
            }
212
 
213
            $this->loadMissingRelation($this, $path);
214
        }
215
 
216
        return $this;
217
    }
218
 
219
    /**
220
     * Load a relationship path if it is not already eager loaded.
221
     *
222
     * @param  \Illuminate\Database\Eloquent\Collection  $models
223
     * @param  array  $path
224
     * @return void
225
     */
226
    protected function loadMissingRelation(self $models, array $path)
227
    {
228
        $relation = array_shift($path);
229
 
230
        $name = explode(':', key($relation))[0];
231
 
232
        if (is_string(reset($relation))) {
233
            $relation = reset($relation);
234
        }
235
 
236
        $models->filter(function ($model) use ($name) {
237
            return ! is_null($model) && ! $model->relationLoaded($name);
238
        })->load($relation);
239
 
240
        if (empty($path)) {
241
            return;
242
        }
243
 
244
        $models = $models->pluck($name)->whereNotNull();
245
 
246
        if ($models->first() instanceof BaseCollection) {
247
            $models = $models->collapse();
248
        }
249
 
250
        $this->loadMissingRelation(new static($models), $path);
251
    }
252
 
253
    /**
254
     * Load a set of relationships onto the mixed relationship collection.
255
     *
256
     * @param  string  $relation
257
     * @param  array<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string> $relations
258
     * @return $this
259
     */
260
    public function loadMorph($relation, $relations)
261
    {
262
        $this->pluck($relation)
263
            ->filter()
264
            ->groupBy(function ($model) {
265
                return get_class($model);
266
            })
267
            ->each(function ($models, $className) use ($relations) {
268
                static::make($models)->load($relations[$className] ?? []);
269
            });
270
 
271
        return $this;
272
    }
273
 
274
    /**
275
     * Load a set of relationship counts onto the mixed relationship collection.
276
     *
277
     * @param  string  $relation
278
     * @param  array<array-key, (callable(\Illuminate\Database\Eloquent\Builder): mixed)|string> $relations
279
     * @return $this
280
     */
281
    public function loadMorphCount($relation, $relations)
282
    {
283
        $this->pluck($relation)
284
            ->filter()
285
            ->groupBy(function ($model) {
286
                return get_class($model);
287
            })
288
            ->each(function ($models, $className) use ($relations) {
289
                static::make($models)->loadCount($relations[$className] ?? []);
290
            });
291
 
292
        return $this;
293
    }
294
 
295
    /**
296
     * Determine if a key exists in the collection.
297
     *
298
     * @param  (callable(TModel, TKey): bool)|TModel|string|int  $key
299
     * @param  mixed  $operator
300
     * @param  mixed  $value
301
     * @return bool
302
     */
303
    public function contains($key, $operator = null, $value = null)
304
    {
305
        if (func_num_args() > 1 || $this->useAsCallable($key)) {
306
            return parent::contains(...func_get_args());
307
        }
308
 
309
        if ($key instanceof Model) {
310
            return parent::contains(function ($model) use ($key) {
311
                return $model->is($key);
312
            });
313
        }
314
 
315
        return parent::contains(function ($model) use ($key) {
316
            return $model->getKey() == $key;
317
        });
318
    }
319
 
320
    /**
321
     * Get the array of primary keys.
322
     *
323
     * @return array<int, array-key>
324
     */
325
    public function modelKeys()
326
    {
327
        return array_map(function ($model) {
328
            return $model->getKey();
329
        }, $this->items);
330
    }
331
 
332
    /**
333
     * Merge the collection with the given items.
334
     *
335
     * @param  iterable<array-key, TModel>  $items
336
     * @return static
337
     */
338
    public function merge($items)
339
    {
340
        $dictionary = $this->getDictionary();
341
 
342
        foreach ($items as $item) {
343
            $dictionary[$item->getKey()] = $item;
344
        }
345
 
346
        return new static(array_values($dictionary));
347
    }
348
 
349
    /**
350
     * Run a map over each of the items.
351
     *
352
     * @template TMapValue
353
     *
354
     * @param  callable(TModel, TKey): TMapValue  $callback
355
     * @return \Illuminate\Support\Collection<TKey, TMapValue>|static<TKey, TMapValue>
356
     */
357
    public function map(callable $callback)
358
    {
359
        $result = parent::map($callback);
360
 
361
        return $result->contains(function ($item) {
362
            return ! $item instanceof Model;
363
        }) ? $result->toBase() : $result;
364
    }
365
 
366
    /**
367
     * Run an associative map over each of the items.
368
     *
369
     * The callback should return an associative array with a single key / value pair.
370
     *
371
     * @template TMapWithKeysKey of array-key
372
     * @template TMapWithKeysValue
373
     *
374
     * @param  callable(TModel, TKey): array<TMapWithKeysKey, TMapWithKeysValue>  $callback
375
     * @return \Illuminate\Support\Collection<TMapWithKeysKey, TMapWithKeysValue>|static<TMapWithKeysKey, TMapWithKeysValue>
376
     */
377
    public function mapWithKeys(callable $callback)
378
    {
379
        $result = parent::mapWithKeys($callback);
380
 
381
        return $result->contains(function ($item) {
382
            return ! $item instanceof Model;
383
        }) ? $result->toBase() : $result;
384
    }
385
 
386
    /**
387
     * Reload a fresh model instance from the database for all the entities.
388
     *
389
     * @param  array<array-key, string>|string  $with
390
     * @return static
391
     */
392
    public function fresh($with = [])
393
    {
394
        if ($this->isEmpty()) {
395
            return new static;
396
        }
397
 
398
        $model = $this->first();
399
 
400
        $freshModels = $model->newQueryWithoutScopes()
401
            ->with(is_string($with) ? func_get_args() : $with)
402
            ->whereIn($model->getKeyName(), $this->modelKeys())
403
            ->get()
404
            ->getDictionary();
405
 
406
        return $this->filter(function ($model) use ($freshModels) {
407
            return $model->exists && isset($freshModels[$model->getKey()]);
408
        })
409
        ->map(function ($model) use ($freshModels) {
410
            return $freshModels[$model->getKey()];
411
        });
412
    }
413
 
414
    /**
415
     * Diff the collection with the given items.
416
     *
417
     * @param  iterable<array-key, TModel>  $items
418
     * @return static
419
     */
420
    public function diff($items)
421
    {
422
        $diff = new static;
423
 
424
        $dictionary = $this->getDictionary($items);
425
 
426
        foreach ($this->items as $item) {
427
            if (! isset($dictionary[$item->getKey()])) {
428
                $diff->add($item);
429
            }
430
        }
431
 
432
        return $diff;
433
    }
434
 
435
    /**
436
     * Intersect the collection with the given items.
437
     *
438
     * @param  iterable<array-key, TModel>  $items
439
     * @return static
440
     */
441
    public function intersect($items)
442
    {
443
        $intersect = new static;
444
 
445
        if (empty($items)) {
446
            return $intersect;
447
        }
448
 
449
        $dictionary = $this->getDictionary($items);
450
 
451
        foreach ($this->items as $item) {
452
            if (isset($dictionary[$item->getKey()])) {
453
                $intersect->add($item);
454
            }
455
        }
456
 
457
        return $intersect;
458
    }
459
 
460
    /**
461
     * Return only unique items from the collection.
462
     *
463
     * @param  (callable(TModel, TKey): mixed)|string|null  $key
464
     * @param  bool  $strict
465
     * @return static<int, TModel>
466
     */
467
    public function unique($key = null, $strict = false)
468
    {
469
        if (! is_null($key)) {
470
            return parent::unique($key, $strict);
471
        }
472
 
473
        return new static(array_values($this->getDictionary()));
474
    }
475
 
476
    /**
477
     * Returns only the models from the collection with the specified keys.
478
     *
479
     * @param  array<array-key, mixed>|null  $keys
480
     * @return static<int, TModel>
481
     */
482
    public function only($keys)
483
    {
484
        if (is_null($keys)) {
485
            return new static($this->items);
486
        }
487
 
488
        $dictionary = Arr::only($this->getDictionary(), $keys);
489
 
490
        return new static(array_values($dictionary));
491
    }
492
 
493
    /**
494
     * Returns all models in the collection except the models with specified keys.
495
     *
496
     * @param  array<array-key, mixed>|null  $keys
497
     * @return static<int, TModel>
498
     */
499
    public function except($keys)
500
    {
501
        $dictionary = Arr::except($this->getDictionary(), $keys);
502
 
503
        return new static(array_values($dictionary));
504
    }
505
 
506
    /**
507
     * Make the given, typically visible, attributes hidden across the entire collection.
508
     *
509
     * @param  array<array-key, string>|string  $attributes
510
     * @return $this
511
     */
512
    public function makeHidden($attributes)
513
    {
514
        return $this->each->makeHidden($attributes);
515
    }
516
 
517
    /**
518
     * Make the given, typically hidden, attributes visible across the entire collection.
519
     *
520
     * @param  array<array-key, string>|string  $attributes
521
     * @return $this
522
     */
523
    public function makeVisible($attributes)
524
    {
525
        return $this->each->makeVisible($attributes);
526
    }
527
 
528
    /**
529
     * Set the visible attributes across the entire collection.
530
     *
531
     * @param  array<int, string>  $visible
532
     * @return $this
533
     */
534
    public function setVisible($visible)
535
    {
536
        return $this->each->setVisible($visible);
537
    }
538
 
539
    /**
540
     * Set the hidden attributes across the entire collection.
541
     *
542
     * @param  array<int, string>  $hidden
543
     * @return $this
544
     */
545
    public function setHidden($hidden)
546
    {
547
        return $this->each->setHidden($hidden);
548
    }
549
 
550
    /**
551
     * Append an attribute across the entire collection.
552
     *
553
     * @param  array<array-key, string>|string  $attributes
554
     * @return $this
555
     */
556
    public function append($attributes)
557
    {
558
        return $this->each->append($attributes);
559
    }
560
 
561
    /**
562
     * Get a dictionary keyed by primary keys.
563
     *
564
     * @param  iterable<array-key, TModel>|null  $items
565
     * @return array<array-key, TModel>
566
     */
567
    public function getDictionary($items = null)
568
    {
569
        $items = is_null($items) ? $this->items : $items;
570
 
571
        $dictionary = [];
572
 
573
        foreach ($items as $value) {
574
            $dictionary[$value->getKey()] = $value;
575
        }
576
 
577
        return $dictionary;
578
    }
579
 
580
    /**
581
     * The following methods are intercepted to always return base collections.
582
     */
583
 
584
    /**
585
     * Get an array with the values of a given key.
586
     *
587
     * @param  string|array<array-key, string>  $value
588
     * @param  string|null  $key
589
     * @return \Illuminate\Support\Collection<array-key, mixed>
590
     */
591
    public function pluck($value, $key = null)
592
    {
593
        return $this->toBase()->pluck($value, $key);
594
    }
595
 
596
    /**
597
     * Get the keys of the collection items.
598
     *
599
     * @return \Illuminate\Support\Collection<int, TKey>
600
     */
601
    public function keys()
602
    {
603
        return $this->toBase()->keys();
604
    }
605
 
606
    /**
607
     * Zip the collection together with one or more arrays.
608
     *
609
     * @template TZipValue
610
     *
611
     * @param  \Illuminate\Contracts\Support\Arrayable<array-key, TZipValue>|iterable<array-key, TZipValue>  ...$items
612
     * @return \Illuminate\Support\Collection<int, \Illuminate\Support\Collection<int, TModel|TZipValue>>
613
     */
614
    public function zip($items)
615
    {
616
        return $this->toBase()->zip(...func_get_args());
617
    }
618
 
619
    /**
620
     * Collapse the collection of items into a single array.
621
     *
622
     * @return \Illuminate\Support\Collection<int, mixed>
623
     */
624
    public function collapse()
625
    {
626
        return $this->toBase()->collapse();
627
    }
628
 
629
    /**
630
     * Get a flattened array of the items in the collection.
631
     *
632
     * @param  int  $depth
633
     * @return \Illuminate\Support\Collection<int, mixed>
634
     */
635
    public function flatten($depth = INF)
636
    {
637
        return $this->toBase()->flatten($depth);
638
    }
639
 
640
    /**
641
     * Flip the items in the collection.
642
     *
643
     * @return \Illuminate\Support\Collection<TModel, TKey>
644
     */
645
    public function flip()
646
    {
647
        return $this->toBase()->flip();
648
    }
649
 
650
    /**
651
     * Pad collection to the specified length with a value.
652
     *
653
     * @template TPadValue
654
     *
655
     * @param  int  $size
656
     * @param  TPadValue  $value
657
     * @return \Illuminate\Support\Collection<int, TModel|TPadValue>
658
     */
659
    public function pad($size, $value)
660
    {
661
        return $this->toBase()->pad($size, $value);
662
    }
663
 
664
    /**
665
     * Get the comparison function to detect duplicates.
666
     *
667
     * @param  bool  $strict
668
     * @return callable(TValue, TValue): bool
669
     */
670
    protected function duplicateComparator($strict)
671
    {
672
        return function ($a, $b) {
673
            return $a->is($b);
674
        };
675
    }
676
 
677
    /**
678
     * Get the type of the entities being queued.
679
     *
680
     * @return string|null
681
     *
682
     * @throws \LogicException
683
     */
684
    public function getQueueableClass()
685
    {
686
        if ($this->isEmpty()) {
687
            return;
688
        }
689
 
690
        $class = $this->getQueueableModelClass($this->first());
691
 
692
        $this->each(function ($model) use ($class) {
693
            if ($this->getQueueableModelClass($model) !== $class) {
694
                throw new LogicException('Queueing collections with multiple model types is not supported.');
695
            }
696
        });
697
 
698
        return $class;
699
    }
700
 
701
    /**
702
     * Get the queueable class name for the given model.
703
     *
704
     * @param  \Illuminate\Database\Eloquent\Model  $model
705
     * @return string
706
     */
707
    protected function getQueueableModelClass($model)
708
    {
709
        return method_exists($model, 'getQueueableClassName')
710
                ? $model->getQueueableClassName()
711
                : get_class($model);
712
    }
713
 
714
    /**
715
     * Get the identifiers for all of the entities.
716
     *
717
     * @return array<int, mixed>
718
     */
719
    public function getQueueableIds()
720
    {
721
        if ($this->isEmpty()) {
722
            return [];
723
        }
724
 
725
        return $this->first() instanceof QueueableEntity
726
                    ? $this->map->getQueueableId()->all()
727
                    : $this->modelKeys();
728
    }
729
 
730
    /**
731
     * Get the relationships of the entities being queued.
732
     *
733
     * @return array<int, string>
734
     */
735
    public function getQueueableRelations()
736
    {
737
        if ($this->isEmpty()) {
738
            return [];
739
        }
740
 
741
        $relations = $this->map->getQueueableRelations()->all();
742
 
743
        if (count($relations) === 0 || $relations === [[]]) {
744
            return [];
745
        } elseif (count($relations) === 1) {
746
            return reset($relations);
747
        } else {
748
            return array_intersect(...array_values($relations));
749
        }
750
    }
751
 
752
    /**
753
     * Get the connection of the entities being queued.
754
     *
755
     * @return string|null
756
     *
757
     * @throws \LogicException
758
     */
759
    public function getQueueableConnection()
760
    {
761
        if ($this->isEmpty()) {
762
            return;
763
        }
764
 
765
        $connection = $this->first()->getConnectionName();
766
 
767
        $this->each(function ($model) use ($connection) {
768
            if ($model->getConnectionName() !== $connection) {
769
                throw new LogicException('Queueing collections with multiple model connections is not supported.');
770
            }
771
        });
772
 
773
        return $connection;
774
    }
775
 
776
    /**
777
     * Get the Eloquent query builder from the collection.
778
     *
779
     * @return \Illuminate\Database\Eloquent\Builder
780
     *
781
     * @throws \LogicException
782
     */
783
    public function toQuery()
784
    {
785
        $model = $this->first();
786
 
787
        if (! $model) {
788
            throw new LogicException('Unable to create query for empty collection.');
789
        }
790
 
791
        $class = get_class($model);
792
 
793
        if ($this->filter(function ($model) use ($class) {
794
            return ! $model instanceof $class;
795
        })->isNotEmpty()) {
796
            throw new LogicException('Unable to create query for collection with mixed types.');
797
        }
798
 
799
        return $model->newModelQuery()->whereKey($this->modelKeys());
800
    }
801
}