Subversion-Projekte lars-tiefland.laravel_shop

Revision

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