| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891 |
- <?php
- namespace Illuminate\Database\Eloquent\Concerns;
- use BadMethodCallException;
- use Closure;
- use Illuminate\Database\Eloquent\Builder;
- use Illuminate\Database\Eloquent\Collection;
- use Illuminate\Database\Eloquent\RelationNotFoundException;
- use Illuminate\Database\Eloquent\Relations\BelongsTo;
- use Illuminate\Database\Eloquent\Relations\MorphTo;
- use Illuminate\Database\Eloquent\Relations\Relation;
- use Illuminate\Database\Query\Builder as QueryBuilder;
- use Illuminate\Database\Query\Expression;
- use Illuminate\Support\Str;
- use InvalidArgumentException;
- trait QueriesRelationships
- {
- /**
- * Add a relationship count / exists condition to the query.
- *
- * @param \Illuminate\Database\Eloquent\Relations\Relation|string $relation
- * @param string $operator
- * @param int $count
- * @param string $boolean
- * @param \Closure|null $callback
- * @return \Illuminate\Database\Eloquent\Builder|static
- *
- * @throws \RuntimeException
- */
- public function has($relation, $operator = '>=', $count = 1, $boolean = 'and', ?Closure $callback = null)
- {
- if (is_string($relation)) {
- if (str_contains($relation, '.')) {
- return $this->hasNested($relation, $operator, $count, $boolean, $callback);
- }
- $relation = $this->getRelationWithoutConstraints($relation);
- }
- if ($relation instanceof MorphTo) {
- return $this->hasMorph($relation, ['*'], $operator, $count, $boolean, $callback);
- }
- // If we only need to check for the existence of the relation, then we can optimize
- // the subquery to only run a "where exists" clause instead of this full "count"
- // clause. This will make these queries run much faster compared with a count.
- $method = $this->canUseExistsForExistenceCheck($operator, $count)
- ? 'getRelationExistenceQuery'
- : 'getRelationExistenceCountQuery';
- $hasQuery = $relation->{$method}(
- $relation->getRelated()->newQueryWithoutRelationships(), $this
- );
- // Next we will call any given callback as an "anonymous" scope so they can get the
- // proper logical grouping of the where clauses if needed by this Eloquent query
- // builder. Then, we will be ready to finalize and return this query instance.
- if ($callback) {
- $hasQuery->callScope($callback);
- }
- return $this->addHasWhere(
- $hasQuery, $relation, $operator, $count, $boolean
- );
- }
- /**
- * Add nested relationship count / exists conditions to the query.
- *
- * Sets up recursive call to whereHas until we finish the nested relation.
- *
- * @param string $relations
- * @param string $operator
- * @param int $count
- * @param string $boolean
- * @param \Closure|null $callback
- * @return \Illuminate\Database\Eloquent\Builder|static
- */
- protected function hasNested($relations, $operator = '>=', $count = 1, $boolean = 'and', $callback = null)
- {
- $relations = explode('.', $relations);
- $doesntHave = $operator === '<' && $count === 1;
- if ($doesntHave) {
- $operator = '>=';
- $count = 1;
- }
- $closure = function ($q) use (&$closure, &$relations, $operator, $count, $callback) {
- // In order to nest "has", we need to add count relation constraints on the
- // callback Closure. We'll do this by simply passing the Closure its own
- // reference to itself so it calls itself recursively on each segment.
- count($relations) > 1
- ? $q->whereHas(array_shift($relations), $closure)
- : $q->has(array_shift($relations), $operator, $count, 'and', $callback);
- };
- return $this->has(array_shift($relations), $doesntHave ? '<' : '>=', 1, $boolean, $closure);
- }
- /**
- * Add a relationship count / exists condition to the query with an "or".
- *
- * @param string $relation
- * @param string $operator
- * @param int $count
- * @return \Illuminate\Database\Eloquent\Builder|static
- */
- public function orHas($relation, $operator = '>=', $count = 1)
- {
- return $this->has($relation, $operator, $count, 'or');
- }
- /**
- * Add a relationship count / exists condition to the query.
- *
- * @param string $relation
- * @param string $boolean
- * @param \Closure|null $callback
- * @return \Illuminate\Database\Eloquent\Builder|static
- */
- public function doesntHave($relation, $boolean = 'and', ?Closure $callback = null)
- {
- return $this->has($relation, '<', 1, $boolean, $callback);
- }
- /**
- * Add a relationship count / exists condition to the query with an "or".
- *
- * @param string $relation
- * @return \Illuminate\Database\Eloquent\Builder|static
- */
- public function orDoesntHave($relation)
- {
- return $this->doesntHave($relation, 'or');
- }
- /**
- * Add a relationship count / exists condition to the query with where clauses.
- *
- * @param string $relation
- * @param \Closure|null $callback
- * @param string $operator
- * @param int $count
- * @return \Illuminate\Database\Eloquent\Builder|static
- */
- public function whereHas($relation, ?Closure $callback = null, $operator = '>=', $count = 1)
- {
- return $this->has($relation, $operator, $count, 'and', $callback);
- }
- /**
- * Add a relationship count / exists condition to the query with where clauses.
- *
- * Also load the relationship with same condition.
- *
- * @param string $relation
- * @param \Closure|null $callback
- * @param string $operator
- * @param int $count
- * @return \Illuminate\Database\Eloquent\Builder|static
- */
- public function withWhereHas($relation, ?Closure $callback = null, $operator = '>=', $count = 1)
- {
- return $this->whereHas(Str::before($relation, ':'), $callback, $operator, $count)
- ->with($callback ? [$relation => fn ($query) => $callback($query)] : $relation);
- }
- /**
- * Add a relationship count / exists condition to the query with where clauses and an "or".
- *
- * @param string $relation
- * @param \Closure|null $callback
- * @param string $operator
- * @param int $count
- * @return \Illuminate\Database\Eloquent\Builder|static
- */
- public function orWhereHas($relation, ?Closure $callback = null, $operator = '>=', $count = 1)
- {
- return $this->has($relation, $operator, $count, 'or', $callback);
- }
- /**
- * Add a relationship count / exists condition to the query with where clauses.
- *
- * @param string $relation
- * @param \Closure|null $callback
- * @return \Illuminate\Database\Eloquent\Builder|static
- */
- public function whereDoesntHave($relation, ?Closure $callback = null)
- {
- return $this->doesntHave($relation, 'and', $callback);
- }
- /**
- * Add a relationship count / exists condition to the query with where clauses and an "or".
- *
- * @param string $relation
- * @param \Closure|null $callback
- * @return \Illuminate\Database\Eloquent\Builder|static
- */
- public function orWhereDoesntHave($relation, ?Closure $callback = null)
- {
- return $this->doesntHave($relation, 'or', $callback);
- }
- /**
- * Add a polymorphic relationship count / exists condition to the query.
- *
- * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
- * @param string|array $types
- * @param string $operator
- * @param int $count
- * @param string $boolean
- * @param \Closure|null $callback
- * @return \Illuminate\Database\Eloquent\Builder|static
- */
- public function hasMorph($relation, $types, $operator = '>=', $count = 1, $boolean = 'and', ?Closure $callback = null)
- {
- if (is_string($relation)) {
- $relation = $this->getRelationWithoutConstraints($relation);
- }
- $types = (array) $types;
- if ($types === ['*']) {
- $types = $this->model->newModelQuery()->distinct()->pluck($relation->getMorphType())->filter()->all();
- }
- if (empty($types)) {
- return $this->where(new Expression('0'), $operator, $count, $boolean);
- }
- foreach ($types as &$type) {
- $type = Relation::getMorphedModel($type) ?? $type;
- }
- return $this->where(function ($query) use ($relation, $callback, $operator, $count, $types) {
- foreach ($types as $type) {
- $query->orWhere(function ($query) use ($relation, $callback, $operator, $count, $type) {
- $belongsTo = $this->getBelongsToRelation($relation, $type);
- if ($callback) {
- $callback = function ($query) use ($callback, $type) {
- return $callback($query, $type);
- };
- }
- $query->where($this->qualifyColumn($relation->getMorphType()), '=', (new $type)->getMorphClass())
- ->whereHas($belongsTo, $callback, $operator, $count);
- });
- }
- }, null, null, $boolean);
- }
- /**
- * Get the BelongsTo relationship for a single polymorphic type.
- *
- * @param \Illuminate\Database\Eloquent\Relations\MorphTo $relation
- * @param string $type
- * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
- */
- protected function getBelongsToRelation(MorphTo $relation, $type)
- {
- $belongsTo = Relation::noConstraints(function () use ($relation, $type) {
- return $this->model->belongsTo(
- $type,
- $relation->getForeignKeyName(),
- $relation->getOwnerKeyName()
- );
- });
- $belongsTo->getQuery()->mergeConstraintsFrom($relation->getQuery());
- return $belongsTo;
- }
- /**
- * Add a polymorphic relationship count / exists condition to the query with an "or".
- *
- * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
- * @param string|array $types
- * @param string $operator
- * @param int $count
- * @return \Illuminate\Database\Eloquent\Builder|static
- */
- public function orHasMorph($relation, $types, $operator = '>=', $count = 1)
- {
- return $this->hasMorph($relation, $types, $operator, $count, 'or');
- }
- /**
- * Add a polymorphic relationship count / exists condition to the query.
- *
- * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
- * @param string|array $types
- * @param string $boolean
- * @param \Closure|null $callback
- * @return \Illuminate\Database\Eloquent\Builder|static
- */
- public function doesntHaveMorph($relation, $types, $boolean = 'and', ?Closure $callback = null)
- {
- return $this->hasMorph($relation, $types, '<', 1, $boolean, $callback);
- }
- /**
- * Add a polymorphic relationship count / exists condition to the query with an "or".
- *
- * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
- * @param string|array $types
- * @return \Illuminate\Database\Eloquent\Builder|static
- */
- public function orDoesntHaveMorph($relation, $types)
- {
- return $this->doesntHaveMorph($relation, $types, 'or');
- }
- /**
- * Add a polymorphic relationship count / exists condition to the query with where clauses.
- *
- * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
- * @param string|array $types
- * @param \Closure|null $callback
- * @param string $operator
- * @param int $count
- * @return \Illuminate\Database\Eloquent\Builder|static
- */
- public function whereHasMorph($relation, $types, ?Closure $callback = null, $operator = '>=', $count = 1)
- {
- return $this->hasMorph($relation, $types, $operator, $count, 'and', $callback);
- }
- /**
- * Add a polymorphic relationship count / exists condition to the query with where clauses and an "or".
- *
- * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
- * @param string|array $types
- * @param \Closure|null $callback
- * @param string $operator
- * @param int $count
- * @return \Illuminate\Database\Eloquent\Builder|static
- */
- public function orWhereHasMorph($relation, $types, ?Closure $callback = null, $operator = '>=', $count = 1)
- {
- return $this->hasMorph($relation, $types, $operator, $count, 'or', $callback);
- }
- /**
- * Add a polymorphic relationship count / exists condition to the query with where clauses.
- *
- * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
- * @param string|array $types
- * @param \Closure|null $callback
- * @return \Illuminate\Database\Eloquent\Builder|static
- */
- public function whereDoesntHaveMorph($relation, $types, ?Closure $callback = null)
- {
- return $this->doesntHaveMorph($relation, $types, 'and', $callback);
- }
- /**
- * Add a polymorphic relationship count / exists condition to the query with where clauses and an "or".
- *
- * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
- * @param string|array $types
- * @param \Closure|null $callback
- * @return \Illuminate\Database\Eloquent\Builder|static
- */
- public function orWhereDoesntHaveMorph($relation, $types, ?Closure $callback = null)
- {
- return $this->doesntHaveMorph($relation, $types, 'or', $callback);
- }
- /**
- * Add a basic where clause to a relationship query.
- *
- * @param string $relation
- * @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column
- * @param mixed $operator
- * @param mixed $value
- * @return \Illuminate\Database\Eloquent\Builder|static
- */
- public function whereRelation($relation, $column, $operator = null, $value = null)
- {
- return $this->whereHas($relation, function ($query) use ($column, $operator, $value) {
- if ($column instanceof Closure) {
- $column($query);
- } else {
- $query->where($column, $operator, $value);
- }
- });
- }
- /**
- * Add an "or where" clause to a relationship query.
- *
- * @param string $relation
- * @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column
- * @param mixed $operator
- * @param mixed $value
- * @return \Illuminate\Database\Eloquent\Builder|static
- */
- public function orWhereRelation($relation, $column, $operator = null, $value = null)
- {
- return $this->orWhereHas($relation, function ($query) use ($column, $operator, $value) {
- if ($column instanceof Closure) {
- $column($query);
- } else {
- $query->where($column, $operator, $value);
- }
- });
- }
- /**
- * Add a polymorphic relationship condition to the query with a where clause.
- *
- * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
- * @param string|array $types
- * @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column
- * @param mixed $operator
- * @param mixed $value
- * @return \Illuminate\Database\Eloquent\Builder|static
- */
- public function whereMorphRelation($relation, $types, $column, $operator = null, $value = null)
- {
- return $this->whereHasMorph($relation, $types, function ($query) use ($column, $operator, $value) {
- $query->where($column, $operator, $value);
- });
- }
- /**
- * Add a polymorphic relationship condition to the query with an "or where" clause.
- *
- * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
- * @param string|array $types
- * @param \Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column
- * @param mixed $operator
- * @param mixed $value
- * @return \Illuminate\Database\Eloquent\Builder|static
- */
- public function orWhereMorphRelation($relation, $types, $column, $operator = null, $value = null)
- {
- return $this->orWhereHasMorph($relation, $types, function ($query) use ($column, $operator, $value) {
- $query->where($column, $operator, $value);
- });
- }
- /**
- * Add a morph-to relationship condition to the query.
- *
- * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
- * @param \Illuminate\Database\Eloquent\Model|string|null $model
- * @return \Illuminate\Database\Eloquent\Builder|static
- */
- public function whereMorphedTo($relation, $model, $boolean = 'and')
- {
- if (is_string($relation)) {
- $relation = $this->getRelationWithoutConstraints($relation);
- }
- if (is_null($model)) {
- return $this->whereNull($relation->getMorphType(), $boolean);
- }
- if (is_string($model)) {
- $morphMap = Relation::morphMap();
- if (! empty($morphMap) && in_array($model, $morphMap)) {
- $model = array_search($model, $morphMap, true);
- }
- return $this->where($relation->getMorphType(), $model, null, $boolean);
- }
- return $this->where(function ($query) use ($relation, $model) {
- $query->where($relation->getMorphType(), $model->getMorphClass())
- ->where($relation->getForeignKeyName(), $model->getKey());
- }, null, null, $boolean);
- }
- /**
- * Add a not morph-to relationship condition to the query.
- *
- * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
- * @param \Illuminate\Database\Eloquent\Model|string $model
- * @return \Illuminate\Database\Eloquent\Builder|static
- */
- public function whereNotMorphedTo($relation, $model, $boolean = 'and')
- {
- if (is_string($relation)) {
- $relation = $this->getRelationWithoutConstraints($relation);
- }
- if (is_string($model)) {
- $morphMap = Relation::morphMap();
- if (! empty($morphMap) && in_array($model, $morphMap)) {
- $model = array_search($model, $morphMap, true);
- }
- return $this->whereNot($relation->getMorphType(), '<=>', $model, $boolean);
- }
- return $this->whereNot(function ($query) use ($relation, $model) {
- $query->where($relation->getMorphType(), '<=>', $model->getMorphClass())
- ->where($relation->getForeignKeyName(), '<=>', $model->getKey());
- }, null, null, $boolean);
- }
- /**
- * Add a morph-to relationship condition to the query with an "or where" clause.
- *
- * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
- * @param \Illuminate\Database\Eloquent\Model|string|null $model
- * @return \Illuminate\Database\Eloquent\Builder|static
- */
- public function orWhereMorphedTo($relation, $model)
- {
- return $this->whereMorphedTo($relation, $model, 'or');
- }
- /**
- * Add a not morph-to relationship condition to the query with an "or where" clause.
- *
- * @param \Illuminate\Database\Eloquent\Relations\MorphTo|string $relation
- * @param \Illuminate\Database\Eloquent\Model|string $model
- * @return \Illuminate\Database\Eloquent\Builder|static
- */
- public function orWhereNotMorphedTo($relation, $model)
- {
- return $this->whereNotMorphedTo($relation, $model, 'or');
- }
- /**
- * Add a "belongs to" relationship where clause to the query.
- *
- * @param \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection<\Illuminate\Database\Eloquent\Model> $related
- * @param string|null $relationshipName
- * @param string $boolean
- * @return $this
- *
- * @throws \Illuminate\Database\Eloquent\RelationNotFoundException
- */
- public function whereBelongsTo($related, $relationshipName = null, $boolean = 'and')
- {
- if (! $related instanceof Collection) {
- $relatedCollection = $related->newCollection([$related]);
- } else {
- $relatedCollection = $related;
- $related = $relatedCollection->first();
- }
- if ($relatedCollection->isEmpty()) {
- throw new InvalidArgumentException('Collection given to whereBelongsTo method may not be empty.');
- }
- if ($relationshipName === null) {
- $relationshipName = Str::camel(class_basename($related));
- }
- try {
- $relationship = $this->model->{$relationshipName}();
- } catch (BadMethodCallException) {
- throw RelationNotFoundException::make($this->model, $relationshipName);
- }
- if (! $relationship instanceof BelongsTo) {
- throw RelationNotFoundException::make($this->model, $relationshipName, BelongsTo::class);
- }
- $this->whereIn(
- $relationship->getQualifiedForeignKeyName(),
- $relatedCollection->pluck($relationship->getOwnerKeyName())->toArray(),
- $boolean,
- );
- return $this;
- }
- /**
- * Add an "BelongsTo" relationship with an "or where" clause to the query.
- *
- * @param \Illuminate\Database\Eloquent\Model $related
- * @param string|null $relationshipName
- * @return $this
- *
- * @throws \RuntimeException
- */
- public function orWhereBelongsTo($related, $relationshipName = null)
- {
- return $this->whereBelongsTo($related, $relationshipName, 'or');
- }
- /**
- * Add subselect queries to include an aggregate value for a relationship.
- *
- * @param mixed $relations
- * @param \Illuminate\Contracts\Database\Query\Expression|string $column
- * @param string $function
- * @return $this
- */
- public function withAggregate($relations, $column, $function = null)
- {
- if (empty($relations)) {
- return $this;
- }
- if (is_null($this->query->columns)) {
- $this->query->select([$this->query->from.'.*']);
- }
- $relations = is_array($relations) ? $relations : [$relations];
- foreach ($this->parseWithRelations($relations) as $name => $constraints) {
- // First we will determine if the name has been aliased using an "as" clause on the name
- // and if it has we will extract the actual relationship name and the desired name of
- // the resulting column. This allows multiple aggregates on the same relationships.
- $segments = explode(' ', $name);
- unset($alias);
- if (count($segments) === 3 && Str::lower($segments[1]) === 'as') {
- [$name, $alias] = [$segments[0], $segments[2]];
- }
- $relation = $this->getRelationWithoutConstraints($name);
- if ($function) {
- if ($this->getQuery()->getGrammar()->isExpression($column)) {
- $aggregateColumn = $this->getQuery()->getGrammar()->getValue($column);
- } else {
- $hashedColumn = $this->getRelationHashedColumn($column, $relation);
- $aggregateColumn = $this->getQuery()->getGrammar()->wrap(
- $column === '*' ? $column : $relation->getRelated()->qualifyColumn($hashedColumn)
- );
- }
- $expression = $function === 'exists' ? $aggregateColumn : sprintf('%s(%s)', $function, $aggregateColumn);
- } else {
- $expression = $this->getQuery()->getGrammar()->getValue($column);
- }
- // Here, we will grab the relationship sub-query and prepare to add it to the main query
- // as a sub-select. First, we'll get the "has" query and use that to get the relation
- // sub-query. We'll format this relationship name and append this column if needed.
- $query = $relation->getRelationExistenceQuery(
- $relation->getRelated()->newQuery(), $this, new Expression($expression)
- )->setBindings([], 'select');
- $query->callScope($constraints);
- $query = $query->mergeConstraintsFrom($relation->getQuery())->toBase();
- // If the query contains certain elements like orderings / more than one column selected
- // then we will remove those elements from the query so that it will execute properly
- // when given to the database. Otherwise, we may receive SQL errors or poor syntax.
- $query->orders = null;
- $query->setBindings([], 'order');
- if (count($query->columns) > 1) {
- $query->columns = [$query->columns[0]];
- $query->bindings['select'] = [];
- }
- // Finally, we will make the proper column alias to the query and run this sub-select on
- // the query builder. Then, we will return the builder instance back to the developer
- // for further constraint chaining that needs to take place on the query as needed.
- $alias ??= Str::snake(
- preg_replace('/[^[:alnum:][:space:]_]/u', '', "$name $function {$this->getQuery()->getGrammar()->getValue($column)}")
- );
- if ($function === 'exists') {
- $this->selectRaw(
- sprintf('exists(%s) as %s', $query->toSql(), $this->getQuery()->grammar->wrap($alias)),
- $query->getBindings()
- )->withCasts([$alias => 'bool']);
- } else {
- $this->selectSub(
- $function ? $query : $query->limit(1),
- $alias
- );
- }
- }
- return $this;
- }
- /**
- * Get the relation hashed column name for the given column and relation.
- *
- * @param string $column
- * @param \Illuminate\Database\Eloquent\Relations\Relation $relation
- * @return string
- */
- protected function getRelationHashedColumn($column, $relation)
- {
- if (str_contains($column, '.')) {
- return $column;
- }
- return $this->getQuery()->from === $relation->getQuery()->getQuery()->from
- ? "{$relation->getRelationCountHash(false)}.$column"
- : $column;
- }
- /**
- * Add subselect queries to count the relations.
- *
- * @param mixed $relations
- * @return $this
- */
- public function withCount($relations)
- {
- return $this->withAggregate(is_array($relations) ? $relations : func_get_args(), '*', 'count');
- }
- /**
- * Add subselect queries to include the max of the relation's column.
- *
- * @param string|array $relation
- * @param \Illuminate\Contracts\Database\Query\Expression|string $column
- * @return $this
- */
- public function withMax($relation, $column)
- {
- return $this->withAggregate($relation, $column, 'max');
- }
- /**
- * Add subselect queries to include the min of the relation's column.
- *
- * @param string|array $relation
- * @param \Illuminate\Contracts\Database\Query\Expression|string $column
- * @return $this
- */
- public function withMin($relation, $column)
- {
- return $this->withAggregate($relation, $column, 'min');
- }
- /**
- * Add subselect queries to include the sum of the relation's column.
- *
- * @param string|array $relation
- * @param \Illuminate\Contracts\Database\Query\Expression|string $column
- * @return $this
- */
- public function withSum($relation, $column)
- {
- return $this->withAggregate($relation, $column, 'sum');
- }
- /**
- * Add subselect queries to include the average of the relation's column.
- *
- * @param string|array $relation
- * @param \Illuminate\Contracts\Database\Query\Expression|string $column
- * @return $this
- */
- public function withAvg($relation, $column)
- {
- return $this->withAggregate($relation, $column, 'avg');
- }
- /**
- * Add subselect queries to include the existence of related models.
- *
- * @param string|array $relation
- * @return $this
- */
- public function withExists($relation)
- {
- return $this->withAggregate($relation, '*', 'exists');
- }
- /**
- * Add the "has" condition where clause to the query.
- *
- * @param \Illuminate\Database\Eloquent\Builder $hasQuery
- * @param \Illuminate\Database\Eloquent\Relations\Relation $relation
- * @param string $operator
- * @param int $count
- * @param string $boolean
- * @return \Illuminate\Database\Eloquent\Builder|static
- */
- protected function addHasWhere(Builder $hasQuery, Relation $relation, $operator, $count, $boolean)
- {
- $hasQuery->mergeConstraintsFrom($relation->getQuery());
- return $this->canUseExistsForExistenceCheck($operator, $count)
- ? $this->addWhereExistsQuery($hasQuery->toBase(), $boolean, $operator === '<' && $count === 1)
- : $this->addWhereCountQuery($hasQuery->toBase(), $operator, $count, $boolean);
- }
- /**
- * Merge the where constraints from another query to the current query.
- *
- * @param \Illuminate\Database\Eloquent\Builder $from
- * @return \Illuminate\Database\Eloquent\Builder|static
- */
- public function mergeConstraintsFrom(Builder $from)
- {
- $whereBindings = $from->getQuery()->getRawBindings()['where'] ?? [];
- $wheres = $from->getQuery()->from !== $this->getQuery()->from
- ? $this->requalifyWhereTables(
- $from->getQuery()->wheres,
- $from->getQuery()->grammar->getValue($from->getQuery()->from),
- $this->getModel()->getTable()
- ) : $from->getQuery()->wheres;
- // Here we have some other query that we want to merge the where constraints from. We will
- // copy over any where constraints on the query as well as remove any global scopes the
- // query might have removed. Then we will return ourselves with the finished merging.
- return $this->withoutGlobalScopes(
- $from->removedScopes()
- )->mergeWheres(
- $wheres, $whereBindings
- );
- }
- /**
- * Updates the table name for any columns with a new qualified name.
- *
- * @param array $wheres
- * @param string $from
- * @param string $to
- * @return array
- */
- protected function requalifyWhereTables(array $wheres, string $from, string $to): array
- {
- return collect($wheres)->map(function ($where) use ($from, $to) {
- return collect($where)->map(function ($value) use ($from, $to) {
- return is_string($value) && str_starts_with($value, $from.'.')
- ? $to.'.'.Str::afterLast($value, '.')
- : $value;
- });
- })->toArray();
- }
- /**
- * Add a sub-query count clause to this query.
- *
- * @param \Illuminate\Database\Query\Builder $query
- * @param string $operator
- * @param int $count
- * @param string $boolean
- * @return $this
- */
- protected function addWhereCountQuery(QueryBuilder $query, $operator = '>=', $count = 1, $boolean = 'and')
- {
- $this->query->addBinding($query->getBindings(), 'where');
- return $this->where(
- new Expression('('.$query->toSql().')'),
- $operator,
- is_numeric($count) ? new Expression($count) : $count,
- $boolean
- );
- }
- /**
- * Get the "has relation" base query instance.
- *
- * @param string $relation
- * @return \Illuminate\Database\Eloquent\Relations\Relation
- */
- protected function getRelationWithoutConstraints($relation)
- {
- return Relation::noConstraints(function () use ($relation) {
- return $this->getModel()->{$relation}();
- });
- }
- /**
- * Check if we can run an "exists" query to optimize performance.
- *
- * @param string $operator
- * @param int $count
- * @return bool
- */
- protected function canUseExistsForExistenceCheck($operator, $count)
- {
- return ($operator === '>=' || $operator === '<') && $count === 1;
- }
- }
|