Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 148 | 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 BadMethodCallException;
6
use Closure;
7
use Exception;
8
use Illuminate\Contracts\Database\Eloquent\Builder as BuilderContract;
9
use Illuminate\Contracts\Support\Arrayable;
10
use Illuminate\Database\Concerns\BuildsQueries;
11
use Illuminate\Database\Eloquent\Concerns\QueriesRelationships;
12
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
13
use Illuminate\Database\Eloquent\Relations\Relation;
14
use Illuminate\Database\Query\Builder as QueryBuilder;
15
use Illuminate\Database\RecordsNotFoundException;
16
use Illuminate\Pagination\Paginator;
17
use Illuminate\Support\Arr;
18
use Illuminate\Support\Str;
19
use Illuminate\Support\Traits\ForwardsCalls;
20
use ReflectionClass;
21
use ReflectionMethod;
22
 
23
/**
24
 * @property-read HigherOrderBuilderProxy $orWhere
25
 * @property-read HigherOrderBuilderProxy $whereNot
26
 * @property-read HigherOrderBuilderProxy $orWhereNot
27
 *
28
 * @mixin \Illuminate\Database\Query\Builder
29
 */
30
class Builder implements BuilderContract
31
{
32
    use BuildsQueries, ForwardsCalls, QueriesRelationships {
33
        BuildsQueries::sole as baseSole;
34
    }
35
 
36
    /**
37
     * The base query builder instance.
38
     *
39
     * @var \Illuminate\Database\Query\Builder
40
     */
41
    protected $query;
42
 
43
    /**
44
     * The model being queried.
45
     *
46
     * @var \Illuminate\Database\Eloquent\Model
47
     */
48
    protected $model;
49
 
50
    /**
51
     * The relationships that should be eager loaded.
52
     *
53
     * @var array
54
     */
55
    protected $eagerLoad = [];
56
 
57
    /**
58
     * All of the globally registered builder macros.
59
     *
60
     * @var array
61
     */
62
    protected static $macros = [];
63
 
64
    /**
65
     * All of the locally registered builder macros.
66
     *
67
     * @var array
68
     */
69
    protected $localMacros = [];
70
 
71
    /**
72
     * A replacement for the typical delete function.
73
     *
74
     * @var \Closure
75
     */
76
    protected $onDelete;
77
 
78
    /**
79
     * The properties that should be returned from query builder.
80
     *
81
     * @var string[]
82
     */
83
    protected $propertyPassthru = [
84
        'from',
85
    ];
86
 
87
    /**
88
     * The methods that should be returned from query builder.
89
     *
90
     * @var string[]
91
     */
92
    protected $passthru = [
93
        'aggregate',
94
        'average',
95
        'avg',
96
        'count',
97
        'dd',
98
        'doesntExist',
99
        'doesntExistOr',
100
        'dump',
101
        'exists',
102
        'existsOr',
103
        'explain',
104
        'getBindings',
105
        'getConnection',
106
        'getGrammar',
107
        'implode',
108
        'insert',
109
        'insertGetId',
110
        'insertOrIgnore',
111
        'insertUsing',
112
        'max',
113
        'min',
114
        'raw',
115
        'rawValue',
116
        'sum',
117
        'toSql',
118
    ];
119
 
120
    /**
121
     * Applied global scopes.
122
     *
123
     * @var array
124
     */
125
    protected $scopes = [];
126
 
127
    /**
128
     * Removed global scopes.
129
     *
130
     * @var array
131
     */
132
    protected $removedScopes = [];
133
 
134
    /**
135
     * Create a new Eloquent query builder instance.
136
     *
137
     * @param  \Illuminate\Database\Query\Builder  $query
138
     * @return void
139
     */
140
    public function __construct(QueryBuilder $query)
141
    {
142
        $this->query = $query;
143
    }
144
 
145
    /**
146
     * Create and return an un-saved model instance.
147
     *
148
     * @param  array  $attributes
149
     * @return \Illuminate\Database\Eloquent\Model|static
150
     */
151
    public function make(array $attributes = [])
152
    {
153
        return $this->newModelInstance($attributes);
154
    }
155
 
156
    /**
157
     * Register a new global scope.
158
     *
159
     * @param  string  $identifier
160
     * @param  \Illuminate\Database\Eloquent\Scope|\Closure  $scope
161
     * @return $this
162
     */
163
    public function withGlobalScope($identifier, $scope)
164
    {
165
        $this->scopes[$identifier] = $scope;
166
 
167
        if (method_exists($scope, 'extend')) {
168
            $scope->extend($this);
169
        }
170
 
171
        return $this;
172
    }
173
 
174
    /**
175
     * Remove a registered global scope.
176
     *
177
     * @param  \Illuminate\Database\Eloquent\Scope|string  $scope
178
     * @return $this
179
     */
180
    public function withoutGlobalScope($scope)
181
    {
182
        if (! is_string($scope)) {
183
            $scope = get_class($scope);
184
        }
185
 
186
        unset($this->scopes[$scope]);
187
 
188
        $this->removedScopes[] = $scope;
189
 
190
        return $this;
191
    }
192
 
193
    /**
194
     * Remove all or passed registered global scopes.
195
     *
196
     * @param  array|null  $scopes
197
     * @return $this
198
     */
199
    public function withoutGlobalScopes(array $scopes = null)
200
    {
201
        if (! is_array($scopes)) {
202
            $scopes = array_keys($this->scopes);
203
        }
204
 
205
        foreach ($scopes as $scope) {
206
            $this->withoutGlobalScope($scope);
207
        }
208
 
209
        return $this;
210
    }
211
 
212
    /**
213
     * Get an array of global scopes that were removed from the query.
214
     *
215
     * @return array
216
     */
217
    public function removedScopes()
218
    {
219
        return $this->removedScopes;
220
    }
221
 
222
    /**
223
     * Add a where clause on the primary key to the query.
224
     *
225
     * @param  mixed  $id
226
     * @return $this
227
     */
228
    public function whereKey($id)
229
    {
230
        if ($id instanceof Model) {
231
            $id = $id->getKey();
232
        }
233
 
234
        if (is_array($id) || $id instanceof Arrayable) {
235
            if (in_array($this->model->getKeyType(), ['int', 'integer'])) {
236
                $this->query->whereIntegerInRaw($this->model->getQualifiedKeyName(), $id);
237
            } else {
238
                $this->query->whereIn($this->model->getQualifiedKeyName(), $id);
239
            }
240
 
241
            return $this;
242
        }
243
 
244
        if ($id !== null && $this->model->getKeyType() === 'string') {
245
            $id = (string) $id;
246
        }
247
 
248
        return $this->where($this->model->getQualifiedKeyName(), '=', $id);
249
    }
250
 
251
    /**
252
     * Add a where clause on the primary key to the query.
253
     *
254
     * @param  mixed  $id
255
     * @return $this
256
     */
257
    public function whereKeyNot($id)
258
    {
259
        if ($id instanceof Model) {
260
            $id = $id->getKey();
261
        }
262
 
263
        if (is_array($id) || $id instanceof Arrayable) {
264
            if (in_array($this->model->getKeyType(), ['int', 'integer'])) {
265
                $this->query->whereIntegerNotInRaw($this->model->getQualifiedKeyName(), $id);
266
            } else {
267
                $this->query->whereNotIn($this->model->getQualifiedKeyName(), $id);
268
            }
269
 
270
            return $this;
271
        }
272
 
273
        if ($id !== null && $this->model->getKeyType() === 'string') {
274
            $id = (string) $id;
275
        }
276
 
277
        return $this->where($this->model->getQualifiedKeyName(), '!=', $id);
278
    }
279
 
280
    /**
281
     * Add a basic where clause to the query.
282
     *
283
     * @param  \Closure|string|array|\Illuminate\Database\Query\Expression  $column
284
     * @param  mixed  $operator
285
     * @param  mixed  $value
286
     * @param  string  $boolean
287
     * @return $this
288
     */
289
    public function where($column, $operator = null, $value = null, $boolean = 'and')
290
    {
291
        if ($column instanceof Closure && is_null($operator)) {
292
            $column($query = $this->model->newQueryWithoutRelationships());
293
 
294
            $this->query->addNestedWhereQuery($query->getQuery(), $boolean);
295
        } else {
296
            $this->query->where(...func_get_args());
297
        }
298
 
299
        return $this;
300
    }
301
 
302
    /**
303
     * Add a basic where clause to the query, and return the first result.
304
     *
305
     * @param  \Closure|string|array|\Illuminate\Database\Query\Expression  $column
306
     * @param  mixed  $operator
307
     * @param  mixed  $value
308
     * @param  string  $boolean
309
     * @return \Illuminate\Database\Eloquent\Model|static|null
310
     */
311
    public function firstWhere($column, $operator = null, $value = null, $boolean = 'and')
312
    {
313
        return $this->where(...func_get_args())->first();
314
    }
315
 
316
    /**
317
     * Add an "or where" clause to the query.
318
     *
319
     * @param  \Closure|array|string|\Illuminate\Database\Query\Expression  $column
320
     * @param  mixed  $operator
321
     * @param  mixed  $value
322
     * @return $this
323
     */
324
    public function orWhere($column, $operator = null, $value = null)
325
    {
326
        [$value, $operator] = $this->query->prepareValueAndOperator(
327
            $value, $operator, func_num_args() === 2
328
        );
329
 
330
        return $this->where($column, $operator, $value, 'or');
331
    }
332
 
333
    /**
334
     * Add a basic "where not" clause to the query.
335
     *
336
     * @param  \Closure|string|array|\Illuminate\Database\Query\Expression  $column
337
     * @param  mixed  $operator
338
     * @param  mixed  $value
339
     * @param  string  $boolean
340
     * @return $this
341
     */
342
    public function whereNot($column, $operator = null, $value = null, $boolean = 'and')
343
    {
344
        return $this->where($column, $operator, $value, $boolean.' not');
345
    }
346
 
347
    /**
348
     * Add an "or where not" clause to the query.
349
     *
350
     * @param  \Closure|array|string|\Illuminate\Database\Query\Expression  $column
351
     * @param  mixed  $operator
352
     * @param  mixed  $value
353
     * @return $this
354
     */
355
    public function orWhereNot($column, $operator = null, $value = null)
356
    {
357
        return $this->whereNot($column, $operator, $value, 'or');
358
    }
359
 
360
    /**
361
     * Add an "order by" clause for a timestamp to the query.
362
     *
363
     * @param  string|\Illuminate\Database\Query\Expression  $column
364
     * @return $this
365
     */
366
    public function latest($column = null)
367
    {
368
        if (is_null($column)) {
369
            $column = $this->model->getCreatedAtColumn() ?? 'created_at';
370
        }
371
 
372
        $this->query->latest($column);
373
 
374
        return $this;
375
    }
376
 
377
    /**
378
     * Add an "order by" clause for a timestamp to the query.
379
     *
380
     * @param  string|\Illuminate\Database\Query\Expression  $column
381
     * @return $this
382
     */
383
    public function oldest($column = null)
384
    {
385
        if (is_null($column)) {
386
            $column = $this->model->getCreatedAtColumn() ?? 'created_at';
387
        }
388
 
389
        $this->query->oldest($column);
390
 
391
        return $this;
392
    }
393
 
394
    /**
395
     * Create a collection of models from plain arrays.
396
     *
397
     * @param  array  $items
398
     * @return \Illuminate\Database\Eloquent\Collection
399
     */
400
    public function hydrate(array $items)
401
    {
402
        $instance = $this->newModelInstance();
403
 
404
        return $instance->newCollection(array_map(function ($item) use ($items, $instance) {
405
            $model = $instance->newFromBuilder($item);
406
 
407
            if (count($items) > 1) {
408
                $model->preventsLazyLoading = Model::preventsLazyLoading();
409
            }
410
 
411
            return $model;
412
        }, $items));
413
    }
414
 
415
    /**
416
     * Create a collection of models from a raw query.
417
     *
418
     * @param  string  $query
419
     * @param  array  $bindings
420
     * @return \Illuminate\Database\Eloquent\Collection
421
     */
422
    public function fromQuery($query, $bindings = [])
423
    {
424
        return $this->hydrate(
425
            $this->query->getConnection()->select($query, $bindings)
426
        );
427
    }
428
 
429
    /**
430
     * Find a model by its primary key.
431
     *
432
     * @param  mixed  $id
433
     * @param  array|string  $columns
434
     * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|static[]|static|null
435
     */
436
    public function find($id, $columns = ['*'])
437
    {
438
        if (is_array($id) || $id instanceof Arrayable) {
439
            return $this->findMany($id, $columns);
440
        }
441
 
442
        return $this->whereKey($id)->first($columns);
443
    }
444
 
445
    /**
446
     * Find multiple models by their primary keys.
447
     *
448
     * @param  \Illuminate\Contracts\Support\Arrayable|array  $ids
449
     * @param  array|string  $columns
450
     * @return \Illuminate\Database\Eloquent\Collection
451
     */
452
    public function findMany($ids, $columns = ['*'])
453
    {
454
        $ids = $ids instanceof Arrayable ? $ids->toArray() : $ids;
455
 
456
        if (empty($ids)) {
457
            return $this->model->newCollection();
458
        }
459
 
460
        return $this->whereKey($ids)->get($columns);
461
    }
462
 
463
    /**
464
     * Find a model by its primary key or throw an exception.
465
     *
466
     * @param  mixed  $id
467
     * @param  array|string  $columns
468
     * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|static|static[]
469
     *
470
     * @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model>
471
     */
472
    public function findOrFail($id, $columns = ['*'])
473
    {
474
        $result = $this->find($id, $columns);
475
 
476
        $id = $id instanceof Arrayable ? $id->toArray() : $id;
477
 
478
        if (is_array($id)) {
479
            if (count($result) !== count(array_unique($id))) {
480
                throw (new ModelNotFoundException)->setModel(
481
                    get_class($this->model), array_diff($id, $result->modelKeys())
482
                );
483
            }
484
 
485
            return $result;
486
        }
487
 
488
        if (is_null($result)) {
489
            throw (new ModelNotFoundException)->setModel(
490
                get_class($this->model), $id
491
            );
492
        }
493
 
494
        return $result;
495
    }
496
 
497
    /**
498
     * Find a model by its primary key or return fresh model instance.
499
     *
500
     * @param  mixed  $id
501
     * @param  array|string  $columns
502
     * @return \Illuminate\Database\Eloquent\Model|static
503
     */
504
    public function findOrNew($id, $columns = ['*'])
505
    {
506
        if (! is_null($model = $this->find($id, $columns))) {
507
            return $model;
508
        }
509
 
510
        return $this->newModelInstance();
511
    }
512
 
513
    /**
514
     * Find a model by its primary key or call a callback.
515
     *
516
     * @param  mixed  $id
517
     * @param  \Closure|array|string  $columns
518
     * @param  \Closure|null  $callback
519
     * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|static[]|static|mixed
520
     */
521
    public function findOr($id, $columns = ['*'], Closure $callback = null)
522
    {
523
        if ($columns instanceof Closure) {
524
            $callback = $columns;
525
 
526
            $columns = ['*'];
527
        }
528
 
529
        if (! is_null($model = $this->find($id, $columns))) {
530
            return $model;
531
        }
532
 
533
        return $callback();
534
    }
535
 
536
    /**
537
     * Get the first record matching the attributes or instantiate it.
538
     *
539
     * @param  array  $attributes
540
     * @param  array  $values
541
     * @return \Illuminate\Database\Eloquent\Model|static
542
     */
543
    public function firstOrNew(array $attributes = [], array $values = [])
544
    {
545
        if (! is_null($instance = $this->where($attributes)->first())) {
546
            return $instance;
547
        }
548
 
549
        return $this->newModelInstance(array_merge($attributes, $values));
550
    }
551
 
552
    /**
553
     * Get the first record matching the attributes or create it.
554
     *
555
     * @param  array  $attributes
556
     * @param  array  $values
557
     * @return \Illuminate\Database\Eloquent\Model|static
558
     */
559
    public function firstOrCreate(array $attributes = [], array $values = [])
560
    {
561
        if (! is_null($instance = $this->where($attributes)->first())) {
562
            return $instance;
563
        }
564
 
565
        return tap($this->newModelInstance(array_merge($attributes, $values)), function ($instance) {
566
            $instance->save();
567
        });
568
    }
569
 
570
    /**
571
     * Create or update a record matching the attributes, and fill it with values.
572
     *
573
     * @param  array  $attributes
574
     * @param  array  $values
575
     * @return \Illuminate\Database\Eloquent\Model|static
576
     */
577
    public function updateOrCreate(array $attributes, array $values = [])
578
    {
579
        return tap($this->firstOrNew($attributes), function ($instance) use ($values) {
580
            $instance->fill($values)->save();
581
        });
582
    }
583
 
584
    /**
585
     * Execute the query and get the first result or throw an exception.
586
     *
587
     * @param  array|string  $columns
588
     * @return \Illuminate\Database\Eloquent\Model|static
589
     *
590
     * @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model>
591
     */
592
    public function firstOrFail($columns = ['*'])
593
    {
594
        if (! is_null($model = $this->first($columns))) {
595
            return $model;
596
        }
597
 
598
        throw (new ModelNotFoundException)->setModel(get_class($this->model));
599
    }
600
 
601
    /**
602
     * Execute the query and get the first result or call a callback.
603
     *
604
     * @param  \Closure|array|string  $columns
605
     * @param  \Closure|null  $callback
606
     * @return \Illuminate\Database\Eloquent\Model|static|mixed
607
     */
608
    public function firstOr($columns = ['*'], Closure $callback = null)
609
    {
610
        if ($columns instanceof Closure) {
611
            $callback = $columns;
612
 
613
            $columns = ['*'];
614
        }
615
 
616
        if (! is_null($model = $this->first($columns))) {
617
            return $model;
618
        }
619
 
620
        return $callback();
621
    }
622
 
623
    /**
624
     * Execute the query and get the first result if it's the sole matching record.
625
     *
626
     * @param  array|string  $columns
627
     * @return \Illuminate\Database\Eloquent\Model
628
     *
629
     * @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model>
630
     * @throws \Illuminate\Database\MultipleRecordsFoundException
631
     */
632
    public function sole($columns = ['*'])
633
    {
634
        try {
635
            return $this->baseSole($columns);
636
        } catch (RecordsNotFoundException $exception) {
637
            throw (new ModelNotFoundException)->setModel(get_class($this->model));
638
        }
639
    }
640
 
641
    /**
642
     * Get a single column's value from the first result of a query.
643
     *
644
     * @param  string|\Illuminate\Database\Query\Expression  $column
645
     * @return mixed
646
     */
647
    public function value($column)
648
    {
649
        if ($result = $this->first([$column])) {
650
            return $result->{Str::afterLast($column, '.')};
651
        }
652
    }
653
 
654
    /**
655
     * Get a single column's value from the first result of a query if it's the sole matching record.
656
     *
657
     * @param  string|\Illuminate\Database\Query\Expression  $column
658
     * @return mixed
659
     *
660
     * @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model>
661
     * @throws \Illuminate\Database\MultipleRecordsFoundException
662
     */
663
    public function soleValue($column)
664
    {
665
        return $this->sole([$column])->{Str::afterLast($column, '.')};
666
    }
667
 
668
    /**
669
     * Get a single column's value from the first result of the query or throw an exception.
670
     *
671
     * @param  string|\Illuminate\Database\Query\Expression  $column
672
     * @return mixed
673
     *
674
     * @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model>
675
     */
676
    public function valueOrFail($column)
677
    {
678
        return $this->firstOrFail([$column])->{Str::afterLast($column, '.')};
679
    }
680
 
681
    /**
682
     * Execute the query as a "select" statement.
683
     *
684
     * @param  array|string  $columns
685
     * @return \Illuminate\Database\Eloquent\Collection|static[]
686
     */
687
    public function get($columns = ['*'])
688
    {
689
        $builder = $this->applyScopes();
690
 
691
        // If we actually found models we will also eager load any relationships that
692
        // have been specified as needing to be eager loaded, which will solve the
693
        // n+1 query issue for the developers to avoid running a lot of queries.
694
        if (count($models = $builder->getModels($columns)) > 0) {
695
            $models = $builder->eagerLoadRelations($models);
696
        }
697
 
698
        return $builder->getModel()->newCollection($models);
699
    }
700
 
701
    /**
702
     * Get the hydrated models without eager loading.
703
     *
704
     * @param  array|string  $columns
705
     * @return \Illuminate\Database\Eloquent\Model[]|static[]
706
     */
707
    public function getModels($columns = ['*'])
708
    {
709
        return $this->model->hydrate(
710
            $this->query->get($columns)->all()
711
        )->all();
712
    }
713
 
714
    /**
715
     * Eager load the relationships for the models.
716
     *
717
     * @param  array  $models
718
     * @return array
719
     */
720
    public function eagerLoadRelations(array $models)
721
    {
722
        foreach ($this->eagerLoad as $name => $constraints) {
723
            // For nested eager loads we'll skip loading them here and they will be set as an
724
            // eager load on the query to retrieve the relation so that they will be eager
725
            // loaded on that query, because that is where they get hydrated as models.
726
            if (! str_contains($name, '.')) {
727
                $models = $this->eagerLoadRelation($models, $name, $constraints);
728
            }
729
        }
730
 
731
        return $models;
732
    }
733
 
734
    /**
735
     * Eagerly load the relationship on a set of models.
736
     *
737
     * @param  array  $models
738
     * @param  string  $name
739
     * @param  \Closure  $constraints
740
     * @return array
741
     */
742
    protected function eagerLoadRelation(array $models, $name, Closure $constraints)
743
    {
744
        // First we will "back up" the existing where conditions on the query so we can
745
        // add our eager constraints. Then we will merge the wheres that were on the
746
        // query back to it in order that any where conditions might be specified.
747
        $relation = $this->getRelation($name);
748
 
749
        $relation->addEagerConstraints($models);
750
 
751
        $constraints($relation);
752
 
753
        // Once we have the results, we just match those back up to their parent models
754
        // using the relationship instance. Then we just return the finished arrays
755
        // of models which have been eagerly hydrated and are readied for return.
756
        return $relation->match(
757
            $relation->initRelation($models, $name),
758
            $relation->getEager(), $name
759
        );
760
    }
761
 
762
    /**
763
     * Get the relation instance for the given relation name.
764
     *
765
     * @param  string  $name
766
     * @return \Illuminate\Database\Eloquent\Relations\Relation
767
     */
768
    public function getRelation($name)
769
    {
770
        // We want to run a relationship query without any constrains so that we will
771
        // not have to remove these where clauses manually which gets really hacky
772
        // and error prone. We don't want constraints because we add eager ones.
773
        $relation = Relation::noConstraints(function () use ($name) {
774
            try {
775
                return $this->getModel()->newInstance()->$name();
776
            } catch (BadMethodCallException $e) {
777
                throw RelationNotFoundException::make($this->getModel(), $name);
778
            }
779
        });
780
 
781
        $nested = $this->relationsNestedUnder($name);
782
 
783
        // If there are nested relationships set on the query, we will put those onto
784
        // the query instances so that they can be handled after this relationship
785
        // is loaded. In this way they will all trickle down as they are loaded.
786
        if (count($nested) > 0) {
787
            $relation->getQuery()->with($nested);
788
        }
789
 
790
        return $relation;
791
    }
792
 
793
    /**
794
     * Get the deeply nested relations for a given top-level relation.
795
     *
796
     * @param  string  $relation
797
     * @return array
798
     */
799
    protected function relationsNestedUnder($relation)
800
    {
801
        $nested = [];
802
 
803
        // We are basically looking for any relationships that are nested deeper than
804
        // the given top-level relationship. We will just check for any relations
805
        // that start with the given top relations and adds them to our arrays.
806
        foreach ($this->eagerLoad as $name => $constraints) {
807
            if ($this->isNestedUnder($relation, $name)) {
808
                $nested[substr($name, strlen($relation.'.'))] = $constraints;
809
            }
810
        }
811
 
812
        return $nested;
813
    }
814
 
815
    /**
816
     * Determine if the relationship is nested.
817
     *
818
     * @param  string  $relation
819
     * @param  string  $name
820
     * @return bool
821
     */
822
    protected function isNestedUnder($relation, $name)
823
    {
824
        return str_contains($name, '.') && str_starts_with($name, $relation.'.');
825
    }
826
 
827
    /**
828
     * Get a lazy collection for the given query.
829
     *
830
     * @return \Illuminate\Support\LazyCollection
831
     */
832
    public function cursor()
833
    {
834
        return $this->applyScopes()->query->cursor()->map(function ($record) {
835
            return $this->newModelInstance()->newFromBuilder($record);
836
        });
837
    }
838
 
839
    /**
840
     * Add a generic "order by" clause if the query doesn't already have one.
841
     *
842
     * @return void
843
     */
844
    protected function enforceOrderBy()
845
    {
846
        if (empty($this->query->orders) && empty($this->query->unionOrders)) {
847
            $this->orderBy($this->model->getQualifiedKeyName(), 'asc');
848
        }
849
    }
850
 
851
    /**
399 lars 852
     * Get a collection with the values of a given column.
148 lars 853
     *
854
     * @param  string|\Illuminate\Database\Query\Expression  $column
855
     * @param  string|null  $key
856
     * @return \Illuminate\Support\Collection
857
     */
858
    public function pluck($column, $key = null)
859
    {
860
        $results = $this->toBase()->pluck($column, $key);
861
 
862
        // If the model has a mutator for the requested column, we will spin through
863
        // the results and mutate the values so that the mutated version of these
864
        // columns are returned as you would expect from these Eloquent models.
865
        if (! $this->model->hasGetMutator($column) &&
866
            ! $this->model->hasCast($column) &&
867
            ! in_array($column, $this->model->getDates())) {
868
            return $results;
869
        }
870
 
871
        return $results->map(function ($value) use ($column) {
872
            return $this->model->newFromBuilder([$column => $value])->{$column};
873
        });
874
    }
875
 
876
    /**
877
     * Paginate the given query.
878
     *
879
     * @param  int|null|\Closure  $perPage
880
     * @param  array|string  $columns
881
     * @param  string  $pageName
882
     * @param  int|null  $page
883
     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
884
     *
885
     * @throws \InvalidArgumentException
886
     */
887
    public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
888
    {
889
        $page = $page ?: Paginator::resolveCurrentPage($pageName);
890
 
891
        $total = $this->toBase()->getCountForPagination();
892
 
893
        $perPage = ($perPage instanceof Closure
894
            ? $perPage($total)
895
            : $perPage
896
        ) ?: $this->model->getPerPage();
897
 
898
        $results = $total
899
            ? $this->forPage($page, $perPage)->get($columns)
900
            : $this->model->newCollection();
901
 
902
        return $this->paginator($results, $total, $perPage, $page, [
903
            'path' => Paginator::resolveCurrentPath(),
904
            'pageName' => $pageName,
905
        ]);
906
    }
907
 
908
    /**
909
     * Paginate the given query into a simple paginator.
910
     *
911
     * @param  int|null  $perPage
912
     * @param  array|string  $columns
913
     * @param  string  $pageName
914
     * @param  int|null  $page
915
     * @return \Illuminate\Contracts\Pagination\Paginator
916
     */
917
    public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
918
    {
919
        $page = $page ?: Paginator::resolveCurrentPage($pageName);
920
 
921
        $perPage = $perPage ?: $this->model->getPerPage();
922
 
923
        // Next we will set the limit and offset for this query so that when we get the
924
        // results we get the proper section of results. Then, we'll create the full
925
        // paginator instances for these results with the given page and per page.
926
        $this->skip(($page - 1) * $perPage)->take($perPage + 1);
927
 
928
        return $this->simplePaginator($this->get($columns), $perPage, $page, [
929
            'path' => Paginator::resolveCurrentPath(),
930
            'pageName' => $pageName,
931
        ]);
932
    }
933
 
934
    /**
935
     * Paginate the given query into a cursor paginator.
936
     *
937
     * @param  int|null  $perPage
938
     * @param  array|string  $columns
939
     * @param  string  $cursorName
940
     * @param  \Illuminate\Pagination\Cursor|string|null  $cursor
941
     * @return \Illuminate\Contracts\Pagination\CursorPaginator
942
     */
943
    public function cursorPaginate($perPage = null, $columns = ['*'], $cursorName = 'cursor', $cursor = null)
944
    {
945
        $perPage = $perPage ?: $this->model->getPerPage();
946
 
947
        return $this->paginateUsingCursor($perPage, $columns, $cursorName, $cursor);
948
    }
949
 
950
    /**
951
     * Ensure the proper order by required for cursor pagination.
952
     *
953
     * @param  bool  $shouldReverse
954
     * @return \Illuminate\Support\Collection
955
     */
956
    protected function ensureOrderForCursorPagination($shouldReverse = false)
957
    {
958
        if (empty($this->query->orders) && empty($this->query->unionOrders)) {
959
            $this->enforceOrderBy();
960
        }
961
 
962
        if ($shouldReverse) {
963
            $this->query->orders = collect($this->query->orders)->map(function ($order) {
964
                $order['direction'] = $order['direction'] === 'asc' ? 'desc' : 'asc';
965
 
966
                return $order;
967
            })->toArray();
968
        }
969
 
970
        if ($this->query->unionOrders) {
971
            return collect($this->query->unionOrders);
972
        }
973
 
974
        return collect($this->query->orders);
975
    }
976
 
977
    /**
978
     * Save a new model and return the instance.
979
     *
980
     * @param  array  $attributes
981
     * @return \Illuminate\Database\Eloquent\Model|$this
982
     */
983
    public function create(array $attributes = [])
984
    {
985
        return tap($this->newModelInstance($attributes), function ($instance) {
986
            $instance->save();
987
        });
988
    }
989
 
990
    /**
991
     * Save a new model and return the instance. Allow mass-assignment.
992
     *
993
     * @param  array  $attributes
994
     * @return \Illuminate\Database\Eloquent\Model|$this
995
     */
996
    public function forceCreate(array $attributes)
997
    {
998
        return $this->model->unguarded(function () use ($attributes) {
999
            return $this->newModelInstance()->create($attributes);
1000
        });
1001
    }
1002
 
1003
    /**
1004
     * Update records in the database.
1005
     *
1006
     * @param  array  $values
1007
     * @return int
1008
     */
1009
    public function update(array $values)
1010
    {
1011
        return $this->toBase()->update($this->addUpdatedAtColumn($values));
1012
    }
1013
 
1014
    /**
1015
     * Insert new records or update the existing ones.
1016
     *
1017
     * @param  array  $values
1018
     * @param  array|string  $uniqueBy
1019
     * @param  array|null  $update
1020
     * @return int
1021
     */
1022
    public function upsert(array $values, $uniqueBy, $update = null)
1023
    {
1024
        if (empty($values)) {
1025
            return 0;
1026
        }
1027
 
1028
        if (! is_array(reset($values))) {
1029
            $values = [$values];
1030
        }
1031
 
1032
        if (is_null($update)) {
1033
            $update = array_keys(reset($values));
1034
        }
1035
 
1036
        return $this->toBase()->upsert(
1037
            $this->addTimestampsToUpsertValues($values),
1038
            $uniqueBy,
1039
            $this->addUpdatedAtToUpsertColumns($update)
1040
        );
1041
    }
1042
 
1043
    /**
1044
     * Update the column's update timestamp.
1045
     *
1046
     * @param  string|null  $column
1047
     * @return int|false
1048
     */
1049
    public function touch($column = null)
1050
    {
1051
        $time = $this->model->freshTimestamp();
1052
 
1053
        if ($column) {
1054
            return $this->toBase()->update([$column => $time]);
1055
        }
1056
 
1057
        $column = $this->model->getUpdatedAtColumn();
1058
 
1059
        if (! $this->model->usesTimestamps() || is_null($column)) {
1060
            return false;
1061
        }
1062
 
1063
        return $this->toBase()->update([$column => $time]);
1064
    }
1065
 
1066
    /**
1067
     * Increment a column's value by a given amount.
1068
     *
1069
     * @param  string|\Illuminate\Database\Query\Expression  $column
1070
     * @param  float|int  $amount
1071
     * @param  array  $extra
1072
     * @return int
1073
     */
1074
    public function increment($column, $amount = 1, array $extra = [])
1075
    {
1076
        return $this->toBase()->increment(
1077
            $column, $amount, $this->addUpdatedAtColumn($extra)
1078
        );
1079
    }
1080
 
1081
    /**
1082
     * Decrement a column's value by a given amount.
1083
     *
1084
     * @param  string|\Illuminate\Database\Query\Expression  $column
1085
     * @param  float|int  $amount
1086
     * @param  array  $extra
1087
     * @return int
1088
     */
1089
    public function decrement($column, $amount = 1, array $extra = [])
1090
    {
1091
        return $this->toBase()->decrement(
1092
            $column, $amount, $this->addUpdatedAtColumn($extra)
1093
        );
1094
    }
1095
 
1096
    /**
1097
     * Add the "updated at" column to an array of values.
1098
     *
1099
     * @param  array  $values
1100
     * @return array
1101
     */
1102
    protected function addUpdatedAtColumn(array $values)
1103
    {
1104
        if (! $this->model->usesTimestamps() ||
1105
            is_null($this->model->getUpdatedAtColumn())) {
1106
            return $values;
1107
        }
1108
 
1109
        $column = $this->model->getUpdatedAtColumn();
1110
 
1111
        $values = array_merge(
1112
            [$column => $this->model->freshTimestampString()],
1113
            $values
1114
        );
1115
 
1116
        $segments = preg_split('/\s+as\s+/i', $this->query->from);
1117
 
1118
        $qualifiedColumn = end($segments).'.'.$column;
1119
 
1120
        $values[$qualifiedColumn] = Arr::get($values, $qualifiedColumn, $values[$column]);
1121
 
1122
        unset($values[$column]);
1123
 
1124
        return $values;
1125
    }
1126
 
1127
    /**
1128
     * Add timestamps to the inserted values.
1129
     *
1130
     * @param  array  $values
1131
     * @return array
1132
     */
1133
    protected function addTimestampsToUpsertValues(array $values)
1134
    {
1135
        if (! $this->model->usesTimestamps()) {
1136
            return $values;
1137
        }
1138
 
1139
        $timestamp = $this->model->freshTimestampString();
1140
 
1141
        $columns = array_filter([
1142
            $this->model->getCreatedAtColumn(),
1143
            $this->model->getUpdatedAtColumn(),
1144
        ]);
1145
 
1146
        foreach ($columns as $column) {
1147
            foreach ($values as &$row) {
1148
                $row = array_merge([$column => $timestamp], $row);
1149
            }
1150
        }
1151
 
1152
        return $values;
1153
    }
1154
 
1155
    /**
1156
     * Add the "updated at" column to the updated columns.
1157
     *
1158
     * @param  array  $update
1159
     * @return array
1160
     */
1161
    protected function addUpdatedAtToUpsertColumns(array $update)
1162
    {
1163
        if (! $this->model->usesTimestamps()) {
1164
            return $update;
1165
        }
1166
 
1167
        $column = $this->model->getUpdatedAtColumn();
1168
 
1169
        if (! is_null($column) &&
1170
            ! array_key_exists($column, $update) &&
1171
            ! in_array($column, $update)) {
1172
            $update[] = $column;
1173
        }
1174
 
1175
        return $update;
1176
    }
1177
 
1178
    /**
1179
     * Delete records from the database.
1180
     *
1181
     * @return mixed
1182
     */
1183
    public function delete()
1184
    {
1185
        if (isset($this->onDelete)) {
1186
            return call_user_func($this->onDelete, $this);
1187
        }
1188
 
1189
        return $this->toBase()->delete();
1190
    }
1191
 
1192
    /**
1193
     * Run the default delete function on the builder.
1194
     *
1195
     * Since we do not apply scopes here, the row will actually be deleted.
1196
     *
1197
     * @return mixed
1198
     */
1199
    public function forceDelete()
1200
    {
1201
        return $this->query->delete();
1202
    }
1203
 
1204
    /**
1205
     * Register a replacement for the default delete function.
1206
     *
1207
     * @param  \Closure  $callback
1208
     * @return void
1209
     */
1210
    public function onDelete(Closure $callback)
1211
    {
1212
        $this->onDelete = $callback;
1213
    }
1214
 
1215
    /**
1216
     * Determine if the given model has a scope.
1217
     *
1218
     * @param  string  $scope
1219
     * @return bool
1220
     */
1221
    public function hasNamedScope($scope)
1222
    {
1223
        return $this->model && $this->model->hasNamedScope($scope);
1224
    }
1225
 
1226
    /**
1227
     * Call the given local model scopes.
1228
     *
1229
     * @param  array|string  $scopes
1230
     * @return static|mixed
1231
     */
1232
    public function scopes($scopes)
1233
    {
1234
        $builder = $this;
1235
 
1236
        foreach (Arr::wrap($scopes) as $scope => $parameters) {
1237
            // If the scope key is an integer, then the scope was passed as the value and
1238
            // the parameter list is empty, so we will format the scope name and these
1239
            // parameters here. Then, we'll be ready to call the scope on the model.
1240
            if (is_int($scope)) {
1241
                [$scope, $parameters] = [$parameters, []];
1242
            }
1243
 
1244
            // Next we'll pass the scope callback to the callScope method which will take
1245
            // care of grouping the "wheres" properly so the logical order doesn't get
1246
            // messed up when adding scopes. Then we'll return back out the builder.
1247
            $builder = $builder->callNamedScope(
1248
                $scope, Arr::wrap($parameters)
1249
            );
1250
        }
1251
 
1252
        return $builder;
1253
    }
1254
 
1255
    /**
1256
     * Apply the scopes to the Eloquent builder instance and return it.
1257
     *
1258
     * @return static
1259
     */
1260
    public function applyScopes()
1261
    {
1262
        if (! $this->scopes) {
1263
            return $this;
1264
        }
1265
 
1266
        $builder = clone $this;
1267
 
1268
        foreach ($this->scopes as $identifier => $scope) {
1269
            if (! isset($builder->scopes[$identifier])) {
1270
                continue;
1271
            }
1272
 
1273
            $builder->callScope(function (self $builder) use ($scope) {
1274
                // If the scope is a Closure we will just go ahead and call the scope with the
1275
                // builder instance. The "callScope" method will properly group the clauses
1276
                // that are added to this query so "where" clauses maintain proper logic.
1277
                if ($scope instanceof Closure) {
1278
                    $scope($builder);
1279
                }
1280
 
1281
                // If the scope is a scope object, we will call the apply method on this scope
1282
                // passing in the builder and the model instance. After we run all of these
1283
                // scopes we will return back the builder instance to the outside caller.
1284
                if ($scope instanceof Scope) {
1285
                    $scope->apply($builder, $this->getModel());
1286
                }
1287
            });
1288
        }
1289
 
1290
        return $builder;
1291
    }
1292
 
1293
    /**
1294
     * Apply the given scope on the current builder instance.
1295
     *
1296
     * @param  callable  $scope
1297
     * @param  array  $parameters
1298
     * @return mixed
1299
     */
1300
    protected function callScope(callable $scope, array $parameters = [])
1301
    {
1302
        array_unshift($parameters, $this);
1303
 
1304
        $query = $this->getQuery();
1305
 
1306
        // We will keep track of how many wheres are on the query before running the
1307
        // scope so that we can properly group the added scope constraints in the
1308
        // query as their own isolated nested where statement and avoid issues.
1309
        $originalWhereCount = is_null($query->wheres)
1310
                    ? 0 : count($query->wheres);
1311
 
1312
        $result = $scope(...$parameters) ?? $this;
1313
 
1314
        if (count((array) $query->wheres) > $originalWhereCount) {
1315
            $this->addNewWheresWithinGroup($query, $originalWhereCount);
1316
        }
1317
 
1318
        return $result;
1319
    }
1320
 
1321
    /**
1322
     * Apply the given named scope on the current builder instance.
1323
     *
1324
     * @param  string  $scope
1325
     * @param  array  $parameters
1326
     * @return mixed
1327
     */
1328
    protected function callNamedScope($scope, array $parameters = [])
1329
    {
1330
        return $this->callScope(function (...$parameters) use ($scope) {
1331
            return $this->model->callNamedScope($scope, $parameters);
1332
        }, $parameters);
1333
    }
1334
 
1335
    /**
1336
     * Nest where conditions by slicing them at the given where count.
1337
     *
1338
     * @param  \Illuminate\Database\Query\Builder  $query
1339
     * @param  int  $originalWhereCount
1340
     * @return void
1341
     */
1342
    protected function addNewWheresWithinGroup(QueryBuilder $query, $originalWhereCount)
1343
    {
1344
        // Here, we totally remove all of the where clauses since we are going to
1345
        // rebuild them as nested queries by slicing the groups of wheres into
1346
        // their own sections. This is to prevent any confusing logic order.
1347
        $allWheres = $query->wheres;
1348
 
1349
        $query->wheres = [];
1350
 
1351
        $this->groupWhereSliceForScope(
1352
            $query, array_slice($allWheres, 0, $originalWhereCount)
1353
        );
1354
 
1355
        $this->groupWhereSliceForScope(
1356
            $query, array_slice($allWheres, $originalWhereCount)
1357
        );
1358
    }
1359
 
1360
    /**
1361
     * Slice where conditions at the given offset and add them to the query as a nested condition.
1362
     *
1363
     * @param  \Illuminate\Database\Query\Builder  $query
1364
     * @param  array  $whereSlice
1365
     * @return void
1366
     */
1367
    protected function groupWhereSliceForScope(QueryBuilder $query, $whereSlice)
1368
    {
1369
        $whereBooleans = collect($whereSlice)->pluck('boolean');
1370
 
1371
        // Here we'll check if the given subset of where clauses contains any "or"
1372
        // booleans and in this case create a nested where expression. That way
1373
        // we don't add any unnecessary nesting thus keeping the query clean.
1374
        if ($whereBooleans->contains('or')) {
1375
            $query->wheres[] = $this->createNestedWhere(
1376
                $whereSlice, $whereBooleans->first()
1377
            );
1378
        } else {
1379
            $query->wheres = array_merge($query->wheres, $whereSlice);
1380
        }
1381
    }
1382
 
1383
    /**
1384
     * Create a where array with nested where conditions.
1385
     *
1386
     * @param  array  $whereSlice
1387
     * @param  string  $boolean
1388
     * @return array
1389
     */
1390
    protected function createNestedWhere($whereSlice, $boolean = 'and')
1391
    {
1392
        $whereGroup = $this->getQuery()->forNestedWhere();
1393
 
1394
        $whereGroup->wheres = $whereSlice;
1395
 
1396
        return ['type' => 'Nested', 'query' => $whereGroup, 'boolean' => $boolean];
1397
    }
1398
 
1399
    /**
1400
     * Set the relationships that should be eager loaded.
1401
     *
1402
     * @param  string|array  $relations
1403
     * @param  string|\Closure|null  $callback
1404
     * @return $this
1405
     */
1406
    public function with($relations, $callback = null)
1407
    {
1408
        if ($callback instanceof Closure) {
1409
            $eagerLoad = $this->parseWithRelations([$relations => $callback]);
1410
        } else {
1411
            $eagerLoad = $this->parseWithRelations(is_string($relations) ? func_get_args() : $relations);
1412
        }
1413
 
1414
        $this->eagerLoad = array_merge($this->eagerLoad, $eagerLoad);
1415
 
1416
        return $this;
1417
    }
1418
 
1419
    /**
1420
     * Prevent the specified relations from being eager loaded.
1421
     *
1422
     * @param  mixed  $relations
1423
     * @return $this
1424
     */
1425
    public function without($relations)
1426
    {
1427
        $this->eagerLoad = array_diff_key($this->eagerLoad, array_flip(
1428
            is_string($relations) ? func_get_args() : $relations
1429
        ));
1430
 
1431
        return $this;
1432
    }
1433
 
1434
    /**
1435
     * Set the relationships that should be eager loaded while removing any previously added eager loading specifications.
1436
     *
1437
     * @param  mixed  $relations
1438
     * @return $this
1439
     */
1440
    public function withOnly($relations)
1441
    {
1442
        $this->eagerLoad = [];
1443
 
1444
        return $this->with($relations);
1445
    }
1446
 
1447
    /**
1448
     * Create a new instance of the model being queried.
1449
     *
1450
     * @param  array  $attributes
1451
     * @return \Illuminate\Database\Eloquent\Model|static
1452
     */
1453
    public function newModelInstance($attributes = [])
1454
    {
1455
        return $this->model->newInstance($attributes)->setConnection(
1456
            $this->query->getConnection()->getName()
1457
        );
1458
    }
1459
 
1460
    /**
1461
     * Parse a list of relations into individuals.
1462
     *
1463
     * @param  array  $relations
1464
     * @return array
1465
     */
1466
    protected function parseWithRelations(array $relations)
1467
    {
1468
        if ($relations === []) {
1469
            return [];
1470
        }
1471
 
1472
        $results = [];
1473
 
1474
        foreach ($this->prepareNestedWithRelationships($relations) as $name => $constraints) {
1475
            // We need to separate out any nested includes, which allows the developers
1476
            // to load deep relationships using "dots" without stating each level of
1477
            // the relationship with its own key in the array of eager-load names.
1478
            $results = $this->addNestedWiths($name, $results);
1479
 
1480
            $results[$name] = $constraints;
1481
        }
1482
 
1483
        return $results;
1484
    }
1485
 
1486
    /**
1487
     * Prepare nested with relationships.
1488
     *
1489
     * @param  array  $relations
1490
     * @param  string  $prefix
1491
     * @return array
1492
     */
1493
    protected function prepareNestedWithRelationships($relations, $prefix = '')
1494
    {
1495
        $preparedRelationships = [];
1496
 
1497
        if ($prefix !== '') {
1498
            $prefix .= '.';
1499
        }
1500
 
1501
        // If any of the relationships are formatted with the [$attribute => array()]
1502
        // syntax, we shall loop over the nested relations and prepend each key of
1503
        // this array while flattening into the traditional dot notation format.
1504
        foreach ($relations as $key => $value) {
1505
            if (! is_string($key) || ! is_array($value)) {
1506
                continue;
1507
            }
1508
 
1509
            [$attribute, $attributeSelectConstraint] = $this->parseNameAndAttributeSelectionConstraint($key);
1510
 
1511
            $preparedRelationships = array_merge(
1512
                $preparedRelationships,
1513
                ["{$prefix}{$attribute}" => $attributeSelectConstraint],
1514
                $this->prepareNestedWithRelationships($value, "{$prefix}{$attribute}"),
1515
            );
1516
 
1517
            unset($relations[$key]);
1518
        }
1519
 
1520
        // We now know that the remaining relationships are in a dot notation format
1521
        // and may be a string or Closure. We'll loop over them and ensure all of
1522
        // the present Closures are merged + strings are made into constraints.
1523
        foreach ($relations as $key => $value) {
1524
            if (is_numeric($key) && is_string($value)) {
1525
                [$key, $value] = $this->parseNameAndAttributeSelectionConstraint($value);
1526
            }
1527
 
1528
            $preparedRelationships[$prefix.$key] = $this->combineConstraints([
1529
                $value,
1530
                $preparedRelationships[$prefix.$key] ?? static function () {
1531
                    //
1532
                },
1533
            ]);
1534
        }
1535
 
1536
        return $preparedRelationships;
1537
    }
1538
 
1539
    /**
1540
     * Combine an array of constraints into a single constraint.
1541
     *
1542
     * @param  array  $constraints
1543
     * @return \Closure
1544
     */
1545
    protected function combineConstraints(array $constraints)
1546
    {
1547
        return function ($builder) use ($constraints) {
1548
            foreach ($constraints as $constraint) {
1549
                $builder = $constraint($builder) ?? $builder;
1550
            }
1551
 
1552
            return $builder;
1553
        };
1554
    }
1555
 
1556
    /**
1557
     * Parse the attribute select constraints from the name.
1558
     *
1559
     * @param  string  $name
1560
     * @return array
1561
     */
1562
    protected function parseNameAndAttributeSelectionConstraint($name)
1563
    {
1564
        return str_contains($name, ':')
1565
            ? $this->createSelectWithConstraint($name)
1566
            : [$name, static function () {
1567
                //
1568
            }];
1569
    }
1570
 
1571
    /**
1572
     * Create a constraint to select the given columns for the relation.
1573
     *
1574
     * @param  string  $name
1575
     * @return array
1576
     */
1577
    protected function createSelectWithConstraint($name)
1578
    {
1579
        return [explode(':', $name)[0], static function ($query) use ($name) {
1580
            $query->select(array_map(static function ($column) use ($query) {
1581
                if (str_contains($column, '.')) {
1582
                    return $column;
1583
                }
1584
 
1585
                return $query instanceof BelongsToMany
1586
                        ? $query->getRelated()->getTable().'.'.$column
1587
                        : $column;
1588
            }, explode(',', explode(':', $name)[1])));
1589
        }];
1590
    }
1591
 
1592
    /**
1593
     * Parse the nested relationships in a relation.
1594
     *
1595
     * @param  string  $name
1596
     * @param  array  $results
1597
     * @return array
1598
     */
1599
    protected function addNestedWiths($name, $results)
1600
    {
1601
        $progress = [];
1602
 
1603
        // If the relation has already been set on the result array, we will not set it
1604
        // again, since that would override any constraints that were already placed
1605
        // on the relationships. We will only set the ones that are not specified.
1606
        foreach (explode('.', $name) as $segment) {
1607
            $progress[] = $segment;
1608
 
1609
            if (! isset($results[$last = implode('.', $progress)])) {
1610
                $results[$last] = static function () {
1611
                    //
1612
                };
1613
            }
1614
        }
1615
 
1616
        return $results;
1617
    }
1618
 
1619
    /**
1620
     * Apply query-time casts to the model instance.
1621
     *
1622
     * @param  array  $casts
1623
     * @return $this
1624
     */
1625
    public function withCasts($casts)
1626
    {
1627
        $this->model->mergeCasts($casts);
1628
 
1629
        return $this;
1630
    }
1631
 
1632
    /**
1633
     * Get the underlying query builder instance.
1634
     *
1635
     * @return \Illuminate\Database\Query\Builder
1636
     */
1637
    public function getQuery()
1638
    {
1639
        return $this->query;
1640
    }
1641
 
1642
    /**
1643
     * Set the underlying query builder instance.
1644
     *
1645
     * @param  \Illuminate\Database\Query\Builder  $query
1646
     * @return $this
1647
     */
1648
    public function setQuery($query)
1649
    {
1650
        $this->query = $query;
1651
 
1652
        return $this;
1653
    }
1654
 
1655
    /**
1656
     * Get a base query builder instance.
1657
     *
1658
     * @return \Illuminate\Database\Query\Builder
1659
     */
1660
    public function toBase()
1661
    {
1662
        return $this->applyScopes()->getQuery();
1663
    }
1664
 
1665
    /**
1666
     * Get the relationships being eagerly loaded.
1667
     *
1668
     * @return array
1669
     */
1670
    public function getEagerLoads()
1671
    {
1672
        return $this->eagerLoad;
1673
    }
1674
 
1675
    /**
1676
     * Set the relationships being eagerly loaded.
1677
     *
1678
     * @param  array  $eagerLoad
1679
     * @return $this
1680
     */
1681
    public function setEagerLoads(array $eagerLoad)
1682
    {
1683
        $this->eagerLoad = $eagerLoad;
1684
 
1685
        return $this;
1686
    }
1687
 
1688
    /**
1689
     * Indicate that the given relationships should not be eagerly loaded.
1690
     *
1691
     * @param  array  $relations
1692
     * @return $this
1693
     */
1694
    public function withoutEagerLoad(array $relations)
1695
    {
1696
        $relations = array_diff(array_keys($this->model->getRelations()), $relations);
1697
 
1698
        return $this->with($relations);
1699
    }
1700
 
1701
    /**
1702
     * Flush the relationships being eagerly loaded.
1703
     *
1704
     * @return $this
1705
     */
1706
    public function withoutEagerLoads()
1707
    {
1708
        return $this->setEagerLoads([]);
1709
    }
1710
 
1711
    /**
1712
     * Get the default key name of the table.
1713
     *
1714
     * @return string
1715
     */
1716
    protected function defaultKeyName()
1717
    {
1718
        return $this->getModel()->getKeyName();
1719
    }
1720
 
1721
    /**
1722
     * Get the model instance being queried.
1723
     *
1724
     * @return \Illuminate\Database\Eloquent\Model|static
1725
     */
1726
    public function getModel()
1727
    {
1728
        return $this->model;
1729
    }
1730
 
1731
    /**
1732
     * Set a model instance for the model being queried.
1733
     *
1734
     * @param  \Illuminate\Database\Eloquent\Model  $model
1735
     * @return $this
1736
     */
1737
    public function setModel(Model $model)
1738
    {
1739
        $this->model = $model;
1740
 
1741
        $this->query->from($model->getTable());
1742
 
1743
        return $this;
1744
    }
1745
 
1746
    /**
1747
     * Qualify the given column name by the model's table.
1748
     *
1749
     * @param  string|\Illuminate\Database\Query\Expression  $column
1750
     * @return string
1751
     */
1752
    public function qualifyColumn($column)
1753
    {
1754
        return $this->model->qualifyColumn($column);
1755
    }
1756
 
1757
    /**
1758
     * Qualify the given columns with the model's table.
1759
     *
1760
     * @param  array|\Illuminate\Database\Query\Expression  $columns
1761
     * @return array
1762
     */
1763
    public function qualifyColumns($columns)
1764
    {
1765
        return $this->model->qualifyColumns($columns);
1766
    }
1767
 
1768
    /**
1769
     * Get the given macro by name.
1770
     *
1771
     * @param  string  $name
1772
     * @return \Closure
1773
     */
1774
    public function getMacro($name)
1775
    {
1776
        return Arr::get($this->localMacros, $name);
1777
    }
1778
 
1779
    /**
1780
     * Checks if a macro is registered.
1781
     *
1782
     * @param  string  $name
1783
     * @return bool
1784
     */
1785
    public function hasMacro($name)
1786
    {
1787
        return isset($this->localMacros[$name]);
1788
    }
1789
 
1790
    /**
1791
     * Get the given global macro by name.
1792
     *
1793
     * @param  string  $name
1794
     * @return \Closure
1795
     */
1796
    public static function getGlobalMacro($name)
1797
    {
1798
        return Arr::get(static::$macros, $name);
1799
    }
1800
 
1801
    /**
1802
     * Checks if a global macro is registered.
1803
     *
1804
     * @param  string  $name
1805
     * @return bool
1806
     */
1807
    public static function hasGlobalMacro($name)
1808
    {
1809
        return isset(static::$macros[$name]);
1810
    }
1811
 
1812
    /**
1813
     * Dynamically access builder proxies.
1814
     *
1815
     * @param  string  $key
1816
     * @return mixed
1817
     *
1818
     * @throws \Exception
1819
     */
1820
    public function __get($key)
1821
    {
1822
        if (in_array($key, ['orWhere', 'whereNot', 'orWhereNot'])) {
1823
            return new HigherOrderBuilderProxy($this, $key);
1824
        }
1825
 
1826
        if (in_array($key, $this->propertyPassthru)) {
1827
            return $this->toBase()->{$key};
1828
        }
1829
 
1830
        throw new Exception("Property [{$key}] does not exist on the Eloquent builder instance.");
1831
    }
1832
 
1833
    /**
1834
     * Dynamically handle calls into the query instance.
1835
     *
1836
     * @param  string  $method
1837
     * @param  array  $parameters
1838
     * @return mixed
1839
     */
1840
    public function __call($method, $parameters)
1841
    {
1842
        if ($method === 'macro') {
1843
            $this->localMacros[$parameters[0]] = $parameters[1];
1844
 
1845
            return;
1846
        }
1847
 
1848
        if ($this->hasMacro($method)) {
1849
            array_unshift($parameters, $this);
1850
 
1851
            return $this->localMacros[$method](...$parameters);
1852
        }
1853
 
1854
        if (static::hasGlobalMacro($method)) {
1855
            $callable = static::$macros[$method];
1856
 
1857
            if ($callable instanceof Closure) {
1858
                $callable = $callable->bindTo($this, static::class);
1859
            }
1860
 
1861
            return $callable(...$parameters);
1862
        }
1863
 
1864
        if ($this->hasNamedScope($method)) {
1865
            return $this->callNamedScope($method, $parameters);
1866
        }
1867
 
1868
        if (in_array($method, $this->passthru)) {
1869
            return $this->toBase()->{$method}(...$parameters);
1870
        }
1871
 
1872
        $this->forwardCallTo($this->query, $method, $parameters);
1873
 
1874
        return $this;
1875
    }
1876
 
1877
    /**
1878
     * Dynamically handle calls into the query instance.
1879
     *
1880
     * @param  string  $method
1881
     * @param  array  $parameters
1882
     * @return mixed
1883
     *
1884
     * @throws \BadMethodCallException
1885
     */
1886
    public static function __callStatic($method, $parameters)
1887
    {
1888
        if ($method === 'macro') {
1889
            static::$macros[$parameters[0]] = $parameters[1];
1890
 
1891
            return;
1892
        }
1893
 
1894
        if ($method === 'mixin') {
1895
            return static::registerMixin($parameters[0], $parameters[1] ?? true);
1896
        }
1897
 
1898
        if (! static::hasGlobalMacro($method)) {
1899
            static::throwBadMethodCallException($method);
1900
        }
1901
 
1902
        $callable = static::$macros[$method];
1903
 
1904
        if ($callable instanceof Closure) {
1905
            $callable = $callable->bindTo(null, static::class);
1906
        }
1907
 
1908
        return $callable(...$parameters);
1909
    }
1910
 
1911
    /**
1912
     * Register the given mixin with the builder.
1913
     *
1914
     * @param  string  $mixin
1915
     * @param  bool  $replace
1916
     * @return void
1917
     */
1918
    protected static function registerMixin($mixin, $replace)
1919
    {
1920
        $methods = (new ReflectionClass($mixin))->getMethods(
1921
            ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED
1922
        );
1923
 
1924
        foreach ($methods as $method) {
1925
            if ($replace || ! static::hasGlobalMacro($method->name)) {
1926
                $method->setAccessible(true);
1927
 
1928
                static::macro($method->name, $method->invoke($mixin));
1929
            }
1930
        }
1931
    }
1932
 
1933
    /**
1934
     * Clone the Eloquent query builder.
1935
     *
1936
     * @return static
1937
     */
1938
    public function clone()
1939
    {
1940
        return clone $this;
1941
    }
1942
 
1943
    /**
1944
     * Force a clone of the underlying query builder when cloning.
1945
     *
1946
     * @return void
1947
     */
1948
    public function __clone()
1949
    {
1950
        $this->query = clone $this->query;
1951
    }
1952
}