Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Illuminate\Database\Eloquent\Concerns;
4
 
5
use BadMethodCallException;
6
use Closure;
7
use Illuminate\Database\Eloquent\Builder;
8
use Illuminate\Database\Eloquent\Collection;
9
use Illuminate\Database\Eloquent\RelationNotFoundException;
10
use Illuminate\Database\Eloquent\Relations\BelongsTo;
11
use Illuminate\Database\Eloquent\Relations\MorphTo;
12
use Illuminate\Database\Eloquent\Relations\Relation;
13
use Illuminate\Database\Query\Builder as QueryBuilder;
14
use Illuminate\Database\Query\Expression;
15
use Illuminate\Support\Str;
16
use InvalidArgumentException;
17
 
18
trait QueriesRelationships
19
{
20
    /**
21
     * Add a relationship count / exists condition to the query.
22
     *
23
     * @param  \Illuminate\Database\Eloquent\Relations\Relation|string  $relation
24
     * @param  string  $operator
25
     * @param  int  $count
26
     * @param  string  $boolean
27
     * @param  \Closure|null  $callback
28
     * @return \Illuminate\Database\Eloquent\Builder|static
29
     *
30
     * @throws \RuntimeException
31
     */
32
    public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', Closure $callback = null)
33
    {
34
        if (is_string($relation)) {
35
            if (str_contains($relation, '.')) {
36
                return $this->hasNested($relation, $operator, $count, $boolean, $callback);
37
            }
38
 
39
            $relation = $this->getRelationWithoutConstraints($relation);
40
        }
41
 
42
        if ($relation instanceof MorphTo) {
43
            return $this->hasMorph($relation, ['*'], $operator, $count, $boolean, $callback);
44
        }
45
 
46
        // If we only need to check for the existence of the relation, then we can optimize
47
        // the subquery to only run a "where exists" clause instead of this full "count"
48
        // clause. This will make these queries run much faster compared with a count.
49
        $method = $this->canUseExistsForExistenceCheck($operator, $count)
50
                        ? 'getRelationExistenceQuery'
51
                        : 'getRelationExistenceCountQuery';
52
 
53
        $hasQuery = $relation->{$method}(
54
            $relation->getRelated()->newQueryWithoutRelationships(), $this
55
        );
56
 
57
        // Next we will call any given callback as an "anonymous" scope so they can get the
58
        // proper logical grouping of the where clauses if needed by this Eloquent query
59
        // builder. Then, we will be ready to finalize and return this query instance.
60
        if ($callback) {
61
            $hasQuery->callScope($callback);
62
        }
63
 
64
        return $this->addHasWhere(
65
            $hasQuery, $relation, $operator, $count, $boolean
66
        );
67
    }
68
 
69
    /**
70
     * Add nested relationship count / exists conditions to the query.
71
     *
72
     * Sets up recursive call to whereHas until we finish the nested relation.
73
     *
74
     * @param  string  $relations
75
     * @param  string  $operator
76
     * @param  int  $count
77
     * @param  string  $boolean
78
     * @param  \Closure|null  $callback
79
     * @return \Illuminate\Database\Eloquent\Builder|static
80
     */
81
    protected function hasNested($relations, $operator = '>=', $count = 1, $boolean = 'and', $callback = null)
82
    {
83
        $relations = explode('.', $relations);
84
 
85
        $doesntHave = $operator === '<' && $count === 1;
86
 
87
        if ($doesntHave) {
88
            $operator = '>=';
89
            $count = 1;
90
        }
91
 
92
        $closure = function ($q) use (&$closure, &$relations, $operator, $count, $callback) {
93
            // In order to nest "has", we need to add count relation constraints on the
94
            // callback Closure. We'll do this by simply passing the Closure its own
95
            // reference to itself so it calls itself recursively on each segment.
96
            count($relations) > 1
97
                ? $q->whereHas(array_shift($relations), $closure)
98
                : $q->has(array_shift($relations), $operator, $count, 'and', $callback);
99
        };
100
 
101
        return $this->has(array_shift($relations), $doesntHave ? '<' : '>=', 1, $boolean, $closure);
102
    }
103
 
104
    /**
105
     * Add a relationship count / exists condition to the query with an "or".
106
     *
107
     * @param  string  $relation
108
     * @param  string  $operator
109
     * @param  int  $count
110
     * @return \Illuminate\Database\Eloquent\Builder|static
111
     */
112
    public function orHas($relation, $operator = '>=', $count = 1)
113
    {
114
        return $this->has($relation, $operator, $count, 'or');
115
    }
116
 
117
    /**
118
     * Add a relationship count / exists condition to the query.
119
     *
120
     * @param  string  $relation
121
     * @param  string  $boolean
122
     * @param  \Closure|null  $callback
123
     * @return \Illuminate\Database\Eloquent\Builder|static
124
     */
125
    public function doesntHave($relation, $boolean = 'and', Closure $callback = null)
126
    {
127
        return $this->has($relation, '<', 1, $boolean, $callback);
128
    }
129
 
130
    /**
131
     * Add a relationship count / exists condition to the query with an "or".
132
     *
133
     * @param  string  $relation
134
     * @return \Illuminate\Database\Eloquent\Builder|static
135
     */
136
    public function orDoesntHave($relation)
137
    {
138
        return $this->doesntHave($relation, 'or');
139
    }
140
 
141
    /**
142
     * Add a relationship count / exists condition to the query with where clauses.
143
     *
144
     * @param  string  $relation
145
     * @param  \Closure|null  $callback
146
     * @param  string  $operator
147
     * @param  int  $count
148
     * @return \Illuminate\Database\Eloquent\Builder|static
149
     */
150
    public function whereHas($relation, Closure $callback = null, $operator = '>=', $count = 1)
151
    {
152
        return $this->has($relation, $operator, $count, 'and', $callback);
153
    }
154
 
155
    /**
156
     * Add a relationship count / exists condition to the query with where clauses.
157
     *
158
     * Also load the relationship with same condition.
159
     *
160
     * @param  string  $relation
161
     * @param  \Closure|null  $callback
162
     * @param  string  $operator
163
     * @param  int  $count
164
     * @return \Illuminate\Database\Eloquent\Builder|static
165
     */
166
    public function withWhereHas($relation, Closure $callback = null, $operator = '>=', $count = 1)
167
    {
168
        return $this->whereHas(Str::before($relation, ':'), $callback, $operator, $count)
169
            ->with($callback ? [$relation => fn ($query) => $callback($query)] : $relation);
170
    }
171
 
172
    /**
173
     * Add a relationship count / exists condition to the query with where clauses and an "or".
174
     *
175
     * @param  string  $relation
176
     * @param  \Closure|null  $callback
177
     * @param  string  $operator
178
     * @param  int  $count
179
     * @return \Illuminate\Database\Eloquent\Builder|static
180
     */
181
    public function orWhereHas($relation, Closure $callback = null, $operator = '>=', $count = 1)
182
    {
183
        return $this->has($relation, $operator, $count, 'or', $callback);
184
    }
185
 
186
    /**
187
     * Add a relationship count / exists condition to the query with where clauses.
188
     *
189
     * @param  string  $relation
190
     * @param  \Closure|null  $callback
191
     * @return \Illuminate\Database\Eloquent\Builder|static
192
     */
193
    public function whereDoesntHave($relation, Closure $callback = null)
194
    {
195
        return $this->doesntHave($relation, 'and', $callback);
196
    }
197
 
198
    /**
199
     * Add a relationship count / exists condition to the query with where clauses and an "or".
200
     *
201
     * @param  string  $relation
202
     * @param  \Closure|null  $callback
203
     * @return \Illuminate\Database\Eloquent\Builder|static
204
     */
205
    public function orWhereDoesntHave($relation, Closure $callback = null)
206
    {
207
        return $this->doesntHave($relation, 'or', $callback);
208
    }
209
 
210
    /**
211
     * Add a polymorphic relationship count / exists condition to the query.
212
     *
213
     * @param  \Illuminate\Database\Eloquent\Relations\MorphTo|string  $relation
214
     * @param  string|array  $types
215
     * @param  string  $operator
216
     * @param  int  $count
217
     * @param  string  $boolean
218
     * @param  \Closure|null  $callback
219
     * @return \Illuminate\Database\Eloquent\Builder|static
220
     */
221
    public function hasMorph($relation, $types, $operator = '>=', $count = 1, $boolean = 'and', Closure $callback = null)
222
    {
223
        if (is_string($relation)) {
224
            $relation = $this->getRelationWithoutConstraints($relation);
225
        }
226
 
227
        $types = (array) $types;
228
 
229
        if ($types === ['*']) {
230
            $types = $this->model->newModelQuery()->distinct()->pluck($relation->getMorphType())->filter()->all();
231
        }
232
 
233
        foreach ($types as &$type) {
234
            $type = Relation::getMorphedModel($type) ?? $type;
235
        }
236
 
237
        return $this->where(function ($query) use ($relation, $callback, $operator, $count, $types) {
238
            foreach ($types as $type) {
239
                $query->orWhere(function ($query) use ($relation, $callback, $operator, $count, $type) {
240
                    $belongsTo = $this->getBelongsToRelation($relation, $type);
241
 
242
                    if ($callback) {
243
                        $callback = function ($query) use ($callback, $type) {
244
                            return $callback($query, $type);
245
                        };
246
                    }
247
 
248
                    $query->where($this->qualifyColumn($relation->getMorphType()), '=', (new $type)->getMorphClass())
249
                                ->whereHas($belongsTo, $callback, $operator, $count);
250
                });
251
            }
252
        }, null, null, $boolean);
253
    }
254
 
255
    /**
256
     * Get the BelongsTo relationship for a single polymorphic type.
257
     *
258
     * @param  \Illuminate\Database\Eloquent\Relations\MorphTo  $relation
259
     * @param  string  $type
260
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
261
     */
262
    protected function getBelongsToRelation(MorphTo $relation, $type)
263
    {
264
        $belongsTo = Relation::noConstraints(function () use ($relation, $type) {
265
            return $this->model->belongsTo(
266
                $type,
267
                $relation->getForeignKeyName(),
268
                $relation->getOwnerKeyName()
269
            );
270
        });
271
 
272
        $belongsTo->getQuery()->mergeConstraintsFrom($relation->getQuery());
273
 
274
        return $belongsTo;
275
    }
276
 
277
    /**
278
     * Add a polymorphic relationship count / exists condition to the query with an "or".
279
     *
280
     * @param  \Illuminate\Database\Eloquent\Relations\MorphTo|string  $relation
281
     * @param  string|array  $types
282
     * @param  string  $operator
283
     * @param  int  $count
284
     * @return \Illuminate\Database\Eloquent\Builder|static
285
     */
286
    public function orHasMorph($relation, $types, $operator = '>=', $count = 1)
287
    {
288
        return $this->hasMorph($relation, $types, $operator, $count, 'or');
289
    }
290
 
291
    /**
292
     * Add a polymorphic relationship count / exists condition to the query.
293
     *
294
     * @param  \Illuminate\Database\Eloquent\Relations\MorphTo|string  $relation
295
     * @param  string|array  $types
296
     * @param  string  $boolean
297
     * @param  \Closure|null  $callback
298
     * @return \Illuminate\Database\Eloquent\Builder|static
299
     */
300
    public function doesntHaveMorph($relation, $types, $boolean = 'and', Closure $callback = null)
301
    {
302
        return $this->hasMorph($relation, $types, '<', 1, $boolean, $callback);
303
    }
304
 
305
    /**
306
     * Add a polymorphic relationship count / exists condition to the query with an "or".
307
     *
308
     * @param  \Illuminate\Database\Eloquent\Relations\MorphTo|string  $relation
309
     * @param  string|array  $types
310
     * @return \Illuminate\Database\Eloquent\Builder|static
311
     */
312
    public function orDoesntHaveMorph($relation, $types)
313
    {
314
        return $this->doesntHaveMorph($relation, $types, 'or');
315
    }
316
 
317
    /**
318
     * Add a polymorphic relationship count / exists condition to the query with where clauses.
319
     *
320
     * @param  \Illuminate\Database\Eloquent\Relations\MorphTo|string  $relation
321
     * @param  string|array  $types
322
     * @param  \Closure|null  $callback
323
     * @param  string  $operator
324
     * @param  int  $count
325
     * @return \Illuminate\Database\Eloquent\Builder|static
326
     */
327
    public function whereHasMorph($relation, $types, Closure $callback = null, $operator = '>=', $count = 1)
328
    {
329
        return $this->hasMorph($relation, $types, $operator, $count, 'and', $callback);
330
    }
331
 
332
    /**
333
     * Add a polymorphic relationship count / exists condition to the query with where clauses and an "or".
334
     *
335
     * @param  \Illuminate\Database\Eloquent\Relations\MorphTo|string  $relation
336
     * @param  string|array  $types
337
     * @param  \Closure|null  $callback
338
     * @param  string  $operator
339
     * @param  int  $count
340
     * @return \Illuminate\Database\Eloquent\Builder|static
341
     */
342
    public function orWhereHasMorph($relation, $types, Closure $callback = null, $operator = '>=', $count = 1)
343
    {
344
        return $this->hasMorph($relation, $types, $operator, $count, 'or', $callback);
345
    }
346
 
347
    /**
348
     * Add a polymorphic relationship count / exists condition to the query with where clauses.
349
     *
350
     * @param  \Illuminate\Database\Eloquent\Relations\MorphTo|string  $relation
351
     * @param  string|array  $types
352
     * @param  \Closure|null  $callback
353
     * @return \Illuminate\Database\Eloquent\Builder|static
354
     */
355
    public function whereDoesntHaveMorph($relation, $types, Closure $callback = null)
356
    {
357
        return $this->doesntHaveMorph($relation, $types, 'and', $callback);
358
    }
359
 
360
    /**
361
     * Add a polymorphic relationship count / exists condition to the query with where clauses and an "or".
362
     *
363
     * @param  \Illuminate\Database\Eloquent\Relations\MorphTo|string  $relation
364
     * @param  string|array  $types
365
     * @param  \Closure|null  $callback
366
     * @return \Illuminate\Database\Eloquent\Builder|static
367
     */
368
    public function orWhereDoesntHaveMorph($relation, $types, Closure $callback = null)
369
    {
370
        return $this->doesntHaveMorph($relation, $types, 'or', $callback);
371
    }
372
 
373
    /**
374
     * Add a basic where clause to a relationship query.
375
     *
376
     * @param  string  $relation
377
     * @param  \Closure|string|array|\Illuminate\Database\Query\Expression  $column
378
     * @param  mixed  $operator
379
     * @param  mixed  $value
380
     * @return \Illuminate\Database\Eloquent\Builder|static
381
     */
382
    public function whereRelation($relation, $column, $operator = null, $value = null)
383
    {
384
        return $this->whereHas($relation, function ($query) use ($column, $operator, $value) {
385
            if ($column instanceof Closure) {
386
                $column($query);
387
            } else {
388
                $query->where($column, $operator, $value);
389
            }
390
        });
391
    }
392
 
393
    /**
394
     * Add an "or where" clause to a relationship query.
395
     *
396
     * @param  string  $relation
397
     * @param  \Closure|string|array|\Illuminate\Database\Query\Expression  $column
398
     * @param  mixed  $operator
399
     * @param  mixed  $value
400
     * @return \Illuminate\Database\Eloquent\Builder|static
401
     */
402
    public function orWhereRelation($relation, $column, $operator = null, $value = null)
403
    {
404
        return $this->orWhereHas($relation, function ($query) use ($column, $operator, $value) {
405
            if ($column instanceof Closure) {
406
                $column($query);
407
            } else {
408
                $query->where($column, $operator, $value);
409
            }
410
        });
411
    }
412
 
413
    /**
414
     * Add a polymorphic relationship condition to the query with a where clause.
415
     *
416
     * @param  \Illuminate\Database\Eloquent\Relations\MorphTo|string  $relation
417
     * @param  string|array  $types
418
     * @param  \Closure|string|array|\Illuminate\Database\Query\Expression  $column
419
     * @param  mixed  $operator
420
     * @param  mixed  $value
421
     * @return \Illuminate\Database\Eloquent\Builder|static
422
     */
423
    public function whereMorphRelation($relation, $types, $column, $operator = null, $value = null)
424
    {
425
        return $this->whereHasMorph($relation, $types, function ($query) use ($column, $operator, $value) {
426
            $query->where($column, $operator, $value);
427
        });
428
    }
429
 
430
    /**
431
     * Add a polymorphic relationship condition to the query with an "or where" clause.
432
     *
433
     * @param  \Illuminate\Database\Eloquent\Relations\MorphTo|string  $relation
434
     * @param  string|array  $types
435
     * @param  \Closure|string|array|\Illuminate\Database\Query\Expression  $column
436
     * @param  mixed  $operator
437
     * @param  mixed  $value
438
     * @return \Illuminate\Database\Eloquent\Builder|static
439
     */
440
    public function orWhereMorphRelation($relation, $types, $column, $operator = null, $value = null)
441
    {
442
        return $this->orWhereHasMorph($relation, $types, function ($query) use ($column, $operator, $value) {
443
            $query->where($column, $operator, $value);
444
        });
445
    }
446
 
447
    /**
448
     * Add a morph-to relationship condition to the query.
449
     *
450
     * @param  \Illuminate\Database\Eloquent\Relations\MorphTo|string  $relation
451
     * @param  \Illuminate\Database\Eloquent\Model|string  $model
452
     * @return \Illuminate\Database\Eloquent\Builder|static
453
     */
454
    public function whereMorphedTo($relation, $model, $boolean = 'and')
455
    {
456
        if (is_string($relation)) {
457
            $relation = $this->getRelationWithoutConstraints($relation);
458
        }
459
 
460
        if (is_string($model)) {
461
            $morphMap = Relation::morphMap();
462
 
463
            if (! empty($morphMap) && in_array($model, $morphMap)) {
464
                $model = array_search($model, $morphMap, true);
465
            }
466
 
467
            return $this->where($relation->getMorphType(), $model, null, $boolean);
468
        }
469
 
470
        return $this->where(function ($query) use ($relation, $model) {
471
            $query->where($relation->getMorphType(), $model->getMorphClass())
472
                ->where($relation->getForeignKeyName(), $model->getKey());
473
        }, null, null, $boolean);
474
    }
475
 
476
    /**
477
     * Add a not morph-to relationship condition to the query.
478
     *
479
     * @param  \Illuminate\Database\Eloquent\Relations\MorphTo|string  $relation
480
     * @param  \Illuminate\Database\Eloquent\Model|string  $model
481
     * @return \Illuminate\Database\Eloquent\Builder|static
482
     */
483
    public function whereNotMorphedTo($relation, $model, $boolean = 'and')
484
    {
485
        if (is_string($relation)) {
486
            $relation = $this->getRelationWithoutConstraints($relation);
487
        }
488
 
489
        if (is_string($model)) {
490
            $morphMap = Relation::morphMap();
491
 
492
            if (! empty($morphMap) && in_array($model, $morphMap)) {
493
                $model = array_search($model, $morphMap, true);
494
            }
495
 
496
            return $this->whereNot($relation->getMorphType(), '<=>', $model, $boolean);
497
        }
498
 
499
        return $this->whereNot(function ($query) use ($relation, $model) {
500
            $query->where($relation->getMorphType(), '<=>', $model->getMorphClass())
501
                ->where($relation->getForeignKeyName(), '<=>', $model->getKey());
502
        }, null, null, $boolean);
503
    }
504
 
505
    /**
506
     * Add a morph-to relationship condition to the query with an "or where" clause.
507
     *
508
     * @param  \Illuminate\Database\Eloquent\Relations\MorphTo|string  $relation
509
     * @param  \Illuminate\Database\Eloquent\Model|string  $model
510
     * @return \Illuminate\Database\Eloquent\Builder|static
511
     */
512
    public function orWhereMorphedTo($relation, $model)
513
    {
514
        return $this->whereMorphedTo($relation, $model, 'or');
515
    }
516
 
517
    /**
518
     * Add a not morph-to relationship condition to the query with an "or where" clause.
519
     *
520
     * @param  \Illuminate\Database\Eloquent\Relations\MorphTo|string  $relation
521
     * @param  \Illuminate\Database\Eloquent\Model|string  $model
522
     * @return \Illuminate\Database\Eloquent\Builder|static
523
     */
524
    public function orWhereNotMorphedTo($relation, $model)
525
    {
526
        return $this->whereNotMorphedTo($relation, $model, 'or');
527
    }
528
 
529
    /**
530
     * Add a "belongs to" relationship where clause to the query.
531
     *
532
     * @param  \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection<\Illuminate\Database\Eloquent\Model>  $related
533
     * @param  string|null  $relationshipName
534
     * @param  string  $boolean
535
     * @return $this
536
     *
537
     * @throws \Illuminate\Database\Eloquent\RelationNotFoundException
538
     */
539
    public function whereBelongsTo($related, $relationshipName = null, $boolean = 'and')
540
    {
541
        if (! $related instanceof Collection) {
542
            $relatedCollection = $related->newCollection([$related]);
543
        } else {
544
            $relatedCollection = $related;
545
 
546
            $related = $relatedCollection->first();
547
        }
548
 
549
        if ($relatedCollection->isEmpty()) {
550
            throw new InvalidArgumentException('Collection given to whereBelongsTo method may not be empty.');
551
        }
552
 
553
        if ($relationshipName === null) {
554
            $relationshipName = Str::camel(class_basename($related));
555
        }
556
 
557
        try {
558
            $relationship = $this->model->{$relationshipName}();
559
        } catch (BadMethodCallException $exception) {
560
            throw RelationNotFoundException::make($this->model, $relationshipName);
561
        }
562
 
563
        if (! $relationship instanceof BelongsTo) {
564
            throw RelationNotFoundException::make($this->model, $relationshipName, BelongsTo::class);
565
        }
566
 
567
        $this->whereIn(
568
            $relationship->getQualifiedForeignKeyName(),
569
            $relatedCollection->pluck($relationship->getOwnerKeyName())->toArray(),
570
            $boolean,
571
        );
572
 
573
        return $this;
574
    }
575
 
576
    /**
577
     * Add an "BelongsTo" relationship with an "or where" clause to the query.
578
     *
579
     * @param  \Illuminate\Database\Eloquent\Model  $related
580
     * @param  string|null  $relationshipName
581
     * @return $this
582
     *
583
     * @throws \RuntimeException
584
     */
585
    public function orWhereBelongsTo($related, $relationshipName = null)
586
    {
587
        return $this->whereBelongsTo($related, $relationshipName, 'or');
588
    }
589
 
590
    /**
591
     * Add subselect queries to include an aggregate value for a relationship.
592
     *
593
     * @param  mixed  $relations
594
     * @param  string  $column
595
     * @param  string  $function
596
     * @return $this
597
     */
598
    public function withAggregate($relations, $column, $function = null)
599
    {
600
        if (empty($relations)) {
601
            return $this;
602
        }
603
 
604
        if (is_null($this->query->columns)) {
605
            $this->query->select([$this->query->from.'.*']);
606
        }
607
 
608
        $relations = is_array($relations) ? $relations : [$relations];
609
 
610
        foreach ($this->parseWithRelations($relations) as $name => $constraints) {
611
            // First we will determine if the name has been aliased using an "as" clause on the name
612
            // and if it has we will extract the actual relationship name and the desired name of
613
            // the resulting column. This allows multiple aggregates on the same relationships.
614
            $segments = explode(' ', $name);
615
 
616
            unset($alias);
617
 
618
            if (count($segments) === 3 && Str::lower($segments[1]) === 'as') {
619
                [$name, $alias] = [$segments[0], $segments[2]];
620
            }
621
 
622
            $relation = $this->getRelationWithoutConstraints($name);
623
 
624
            if ($function) {
625
                $hashedColumn = $this->getRelationHashedColumn($column, $relation);
626
 
627
                $wrappedColumn = $this->getQuery()->getGrammar()->wrap(
628
                    $column === '*' ? $column : $relation->getRelated()->qualifyColumn($hashedColumn)
629
                );
630
 
631
                $expression = $function === 'exists' ? $wrappedColumn : sprintf('%s(%s)', $function, $wrappedColumn);
632
            } else {
633
                $expression = $column;
634
            }
635
 
636
            // Here, we will grab the relationship sub-query and prepare to add it to the main query
637
            // as a sub-select. First, we'll get the "has" query and use that to get the relation
638
            // sub-query. We'll format this relationship name and append this column if needed.
639
            $query = $relation->getRelationExistenceQuery(
640
                $relation->getRelated()->newQuery(), $this, new Expression($expression)
641
            )->setBindings([], 'select');
642
 
643
            $query->callScope($constraints);
644
 
645
            $query = $query->mergeConstraintsFrom($relation->getQuery())->toBase();
646
 
647
            // If the query contains certain elements like orderings / more than one column selected
648
            // then we will remove those elements from the query so that it will execute properly
649
            // when given to the database. Otherwise, we may receive SQL errors or poor syntax.
650
            $query->orders = null;
651
            $query->setBindings([], 'order');
652
 
653
            if (count($query->columns) > 1) {
654
                $query->columns = [$query->columns[0]];
655
                $query->bindings['select'] = [];
656
            }
657
 
658
            // Finally, we will make the proper column alias to the query and run this sub-select on
659
            // the query builder. Then, we will return the builder instance back to the developer
660
            // for further constraint chaining that needs to take place on the query as needed.
661
            $alias ??= Str::snake(
662
                preg_replace('/[^[:alnum:][:space:]_]/u', '', "$name $function $column")
663
            );
664
 
665
            if ($function === 'exists') {
666
                $this->selectRaw(
667
                    sprintf('exists(%s) as %s', $query->toSql(), $this->getQuery()->grammar->wrap($alias)),
668
                    $query->getBindings()
669
                )->withCasts([$alias => 'bool']);
670
            } else {
671
                $this->selectSub(
672
                    $function ? $query : $query->limit(1),
673
                    $alias
674
                );
675
            }
676
        }
677
 
678
        return $this;
679
    }
680
 
681
    /**
682
     * Get the relation hashed column name for the given column and relation.
683
     *
684
     * @param  string  $column
685
     * @param  \Illuminate\Database\Eloquent\Relations\Relationship  $relation
686
     * @return string
687
     */
688
    protected function getRelationHashedColumn($column, $relation)
689
    {
690
        if (str_contains($column, '.')) {
691
            return $column;
692
        }
693
 
694
        return $this->getQuery()->from === $relation->getQuery()->getQuery()->from
695
            ? "{$relation->getRelationCountHash(false)}.$column"
696
            : $column;
697
    }
698
 
699
    /**
700
     * Add subselect queries to count the relations.
701
     *
702
     * @param  mixed  $relations
703
     * @return $this
704
     */
705
    public function withCount($relations)
706
    {
707
        return $this->withAggregate(is_array($relations) ? $relations : func_get_args(), '*', 'count');
708
    }
709
 
710
    /**
711
     * Add subselect queries to include the max of the relation's column.
712
     *
713
     * @param  string|array  $relation
714
     * @param  string  $column
715
     * @return $this
716
     */
717
    public function withMax($relation, $column)
718
    {
719
        return $this->withAggregate($relation, $column, 'max');
720
    }
721
 
722
    /**
723
     * Add subselect queries to include the min of the relation's column.
724
     *
725
     * @param  string|array  $relation
726
     * @param  string  $column
727
     * @return $this
728
     */
729
    public function withMin($relation, $column)
730
    {
731
        return $this->withAggregate($relation, $column, 'min');
732
    }
733
 
734
    /**
735
     * Add subselect queries to include the sum of the relation's column.
736
     *
737
     * @param  string|array  $relation
738
     * @param  string  $column
739
     * @return $this
740
     */
741
    public function withSum($relation, $column)
742
    {
743
        return $this->withAggregate($relation, $column, 'sum');
744
    }
745
 
746
    /**
747
     * Add subselect queries to include the average of the relation's column.
748
     *
749
     * @param  string|array  $relation
750
     * @param  string  $column
751
     * @return $this
752
     */
753
    public function withAvg($relation, $column)
754
    {
755
        return $this->withAggregate($relation, $column, 'avg');
756
    }
757
 
758
    /**
759
     * Add subselect queries to include the existence of related models.
760
     *
761
     * @param  string|array  $relation
762
     * @return $this
763
     */
764
    public function withExists($relation)
765
    {
766
        return $this->withAggregate($relation, '*', 'exists');
767
    }
768
 
769
    /**
770
     * Add the "has" condition where clause to the query.
771
     *
772
     * @param  \Illuminate\Database\Eloquent\Builder  $hasQuery
773
     * @param  \Illuminate\Database\Eloquent\Relations\Relation  $relation
774
     * @param  string  $operator
775
     * @param  int  $count
776
     * @param  string  $boolean
777
     * @return \Illuminate\Database\Eloquent\Builder|static
778
     */
779
    protected function addHasWhere(Builder $hasQuery, Relation $relation, $operator, $count, $boolean)
780
    {
781
        $hasQuery->mergeConstraintsFrom($relation->getQuery());
782
 
783
        return $this->canUseExistsForExistenceCheck($operator, $count)
784
                ? $this->addWhereExistsQuery($hasQuery->toBase(), $boolean, $operator === '<' && $count === 1)
785
                : $this->addWhereCountQuery($hasQuery->toBase(), $operator, $count, $boolean);
786
    }
787
 
788
    /**
789
     * Merge the where constraints from another query to the current query.
790
     *
791
     * @param  \Illuminate\Database\Eloquent\Builder  $from
792
     * @return \Illuminate\Database\Eloquent\Builder|static
793
     */
794
    public function mergeConstraintsFrom(Builder $from)
795
    {
796
        $whereBindings = $from->getQuery()->getRawBindings()['where'] ?? [];
797
 
798
        $wheres = $from->getQuery()->from !== $this->getQuery()->from
799
            ? $this->requalifyWhereTables(
800
                $from->getQuery()->wheres,
801
                $from->getQuery()->from,
802
                $this->getModel()->getTable()
803
            ) : $from->getQuery()->wheres;
804
 
805
        // Here we have some other query that we want to merge the where constraints from. We will
806
        // copy over any where constraints on the query as well as remove any global scopes the
807
        // query might have removed. Then we will return ourselves with the finished merging.
808
        return $this->withoutGlobalScopes(
809
            $from->removedScopes()
810
        )->mergeWheres(
811
            $wheres, $whereBindings
812
        );
813
    }
814
 
815
    /**
816
     * Updates the table name for any columns with a new qualified name.
817
     *
818
     * @param  array  $wheres
819
     * @param  string  $from
820
     * @param  string  $to
821
     * @return array
822
     */
823
    protected function requalifyWhereTables(array $wheres, string $from, string $to): array
824
    {
825
        return collect($wheres)->map(function ($where) use ($from, $to) {
826
            return collect($where)->map(function ($value) use ($from, $to) {
827
                return is_string($value) && str_starts_with($value, $from.'.')
828
                    ? $to.'.'.Str::afterLast($value, '.')
829
                    : $value;
830
            });
831
        })->toArray();
832
    }
833
 
834
    /**
835
     * Add a sub-query count clause to this query.
836
     *
837
     * @param  \Illuminate\Database\Query\Builder  $query
838
     * @param  string  $operator
839
     * @param  int  $count
840
     * @param  string  $boolean
841
     * @return $this
842
     */
843
    protected function addWhereCountQuery(QueryBuilder $query, $operator = '>=', $count = 1, $boolean = 'and')
844
    {
845
        $this->query->addBinding($query->getBindings(), 'where');
846
 
847
        return $this->where(
848
            new Expression('('.$query->toSql().')'),
849
            $operator,
850
            is_numeric($count) ? new Expression($count) : $count,
851
            $boolean
852
        );
853
    }
854
 
855
    /**
856
     * Get the "has relation" base query instance.
857
     *
858
     * @param  string  $relation
859
     * @return \Illuminate\Database\Eloquent\Relations\Relation
860
     */
861
    protected function getRelationWithoutConstraints($relation)
862
    {
863
        return Relation::noConstraints(function () use ($relation) {
864
            return $this->getModel()->{$relation}();
865
        });
866
    }
867
 
868
    /**
869
     * Check if we can run an "exists" query to optimize performance.
870
     *
871
     * @param  string  $operator
872
     * @param  int  $count
873
     * @return bool
874
     */
875
    protected function canUseExistsForExistenceCheck($operator, $count)
876
    {
877
        return ($operator === '>=' || $operator === '<') && $count === 1;
878
    }
879
}