919 lines
26 KiB
PHP
919 lines
26 KiB
PHP
<?php
|
|
|
|
namespace Illuminate\Database\Eloquent\Relations;
|
|
|
|
use Closure;
|
|
use Illuminate\Contracts\Support\Arrayable;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Database\Eloquent\Collection;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
|
use Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithDictionary;
|
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
|
use Illuminate\Database\Query\Grammars\MySqlGrammar;
|
|
use Illuminate\Database\UniqueConstraintViolationException;
|
|
|
|
class HasManyThrough extends Relation
|
|
{
|
|
use InteractsWithDictionary;
|
|
|
|
/**
|
|
* The "through" parent model instance.
|
|
*
|
|
* @var \Illuminate\Database\Eloquent\Model
|
|
*/
|
|
protected $throughParent;
|
|
|
|
/**
|
|
* The far parent model instance.
|
|
*
|
|
* @var \Illuminate\Database\Eloquent\Model
|
|
*/
|
|
protected $farParent;
|
|
|
|
/**
|
|
* The near key on the relationship.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $firstKey;
|
|
|
|
/**
|
|
* The far key on the relationship.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $secondKey;
|
|
|
|
/**
|
|
* The local key on the relationship.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $localKey;
|
|
|
|
/**
|
|
* The local key on the intermediary model.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $secondLocalKey;
|
|
|
|
/**
|
|
* Create a new has many through relationship instance.
|
|
*
|
|
* @param \Illuminate\Database\Eloquent\Builder $query
|
|
* @param \Illuminate\Database\Eloquent\Model $farParent
|
|
* @param \Illuminate\Database\Eloquent\Model $throughParent
|
|
* @param string $firstKey
|
|
* @param string $secondKey
|
|
* @param string $localKey
|
|
* @param string $secondLocalKey
|
|
* @return void
|
|
*/
|
|
public function __construct(Builder $query, Model $farParent, Model $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey)
|
|
{
|
|
$this->localKey = $localKey;
|
|
$this->firstKey = $firstKey;
|
|
$this->secondKey = $secondKey;
|
|
$this->farParent = $farParent;
|
|
$this->throughParent = $throughParent;
|
|
$this->secondLocalKey = $secondLocalKey;
|
|
|
|
parent::__construct($query, $throughParent);
|
|
}
|
|
|
|
/**
|
|
* Convert the relationship to a "has one through" relationship.
|
|
*
|
|
* @return \Illuminate\Database\Eloquent\Relations\HasOneThrough
|
|
*/
|
|
public function one()
|
|
{
|
|
return HasOneThrough::noConstraints(fn () => new HasOneThrough(
|
|
$this->getQuery(),
|
|
$this->farParent,
|
|
$this->throughParent,
|
|
$this->getFirstKeyName(),
|
|
$this->secondKey,
|
|
$this->getLocalKeyName(),
|
|
$this->getSecondLocalKeyName(),
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Set the base constraints on the relation query.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function addConstraints()
|
|
{
|
|
$localValue = $this->farParent[$this->localKey];
|
|
|
|
$this->performJoin();
|
|
|
|
if (static::$constraints) {
|
|
$this->query->where($this->getQualifiedFirstKeyName(), '=', $localValue);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the join clause on the query.
|
|
*
|
|
* @param \Illuminate\Database\Eloquent\Builder|null $query
|
|
* @return void
|
|
*/
|
|
protected function performJoin(?Builder $query = null)
|
|
{
|
|
$query = $query ?: $this->query;
|
|
|
|
$farKey = $this->getQualifiedFarKeyName();
|
|
|
|
$query->join($this->throughParent->getTable(), $this->getQualifiedParentKeyName(), '=', $farKey);
|
|
|
|
if ($this->throughParentSoftDeletes()) {
|
|
$query->withGlobalScope('SoftDeletableHasManyThrough', function ($query) {
|
|
$query->whereNull($this->throughParent->getQualifiedDeletedAtColumn());
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the fully qualified parent key name.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getQualifiedParentKeyName()
|
|
{
|
|
return $this->parent->qualifyColumn($this->secondLocalKey);
|
|
}
|
|
|
|
/**
|
|
* Determine whether "through" parent of the relation uses Soft Deletes.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function throughParentSoftDeletes()
|
|
{
|
|
return in_array(SoftDeletes::class, class_uses_recursive($this->throughParent));
|
|
}
|
|
|
|
/**
|
|
* Indicate that trashed "through" parents should be included in the query.
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function withTrashedParents()
|
|
{
|
|
$this->query->withoutGlobalScope('SoftDeletableHasManyThrough');
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set the constraints for an eager load of the relation.
|
|
*
|
|
* @param array $models
|
|
* @return void
|
|
*/
|
|
public function addEagerConstraints(array $models)
|
|
{
|
|
$whereIn = $this->whereInMethod($this->farParent, $this->localKey);
|
|
|
|
$this->whereInEager(
|
|
$whereIn,
|
|
$this->getQualifiedFirstKeyName(),
|
|
$this->getKeys($models, $this->localKey)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Initialize the relation on a set of models.
|
|
*
|
|
* @param array $models
|
|
* @param string $relation
|
|
* @return array
|
|
*/
|
|
public function initRelation(array $models, $relation)
|
|
{
|
|
foreach ($models as $model) {
|
|
$model->setRelation($relation, $this->related->newCollection());
|
|
}
|
|
|
|
return $models;
|
|
}
|
|
|
|
/**
|
|
* Match the eagerly loaded results to their parents.
|
|
*
|
|
* @param array $models
|
|
* @param \Illuminate\Database\Eloquent\Collection $results
|
|
* @param string $relation
|
|
* @return array
|
|
*/
|
|
public function match(array $models, Collection $results, $relation)
|
|
{
|
|
$dictionary = $this->buildDictionary($results);
|
|
|
|
// Once we have the dictionary we can simply spin through the parent models to
|
|
// link them up with their children using the keyed dictionary to make the
|
|
// matching very convenient and easy work. Then we'll just return them.
|
|
foreach ($models as $model) {
|
|
if (isset($dictionary[$key = $this->getDictionaryKey($model->getAttribute($this->localKey))])) {
|
|
$model->setRelation(
|
|
$relation, $this->related->newCollection($dictionary[$key])
|
|
);
|
|
}
|
|
}
|
|
|
|
return $models;
|
|
}
|
|
|
|
/**
|
|
* Build model dictionary keyed by the relation's foreign key.
|
|
*
|
|
* @param \Illuminate\Database\Eloquent\Collection $results
|
|
* @return array
|
|
*/
|
|
protected function buildDictionary(Collection $results)
|
|
{
|
|
$dictionary = [];
|
|
|
|
// First we will create a dictionary of models keyed by the foreign key of the
|
|
// relationship as this will allow us to quickly access all of the related
|
|
// models without having to do nested looping which will be quite slow.
|
|
foreach ($results as $result) {
|
|
$dictionary[$result->laravel_through_key][] = $result;
|
|
}
|
|
|
|
return $dictionary;
|
|
}
|
|
|
|
/**
|
|
* Get the first related model record matching the attributes or instantiate it.
|
|
*
|
|
* @param array $attributes
|
|
* @param array $values
|
|
* @return \Illuminate\Database\Eloquent\Model
|
|
*/
|
|
public function firstOrNew(array $attributes = [], array $values = [])
|
|
{
|
|
if (! is_null($instance = $this->where($attributes)->first())) {
|
|
return $instance;
|
|
}
|
|
|
|
return $this->related->newInstance(array_merge($attributes, $values));
|
|
}
|
|
|
|
/**
|
|
* Get the first record matching the attributes. If the record is not found, create it.
|
|
*
|
|
* @param array $attributes
|
|
* @param array $values
|
|
* @return \Illuminate\Database\Eloquent\Model
|
|
*/
|
|
public function firstOrCreate(array $attributes = [], array $values = [])
|
|
{
|
|
if (! is_null($instance = (clone $this)->where($attributes)->first())) {
|
|
return $instance;
|
|
}
|
|
|
|
return $this->createOrFirst(array_merge($attributes, $values));
|
|
}
|
|
|
|
/**
|
|
* Attempt to create the record. If a unique constraint violation occurs, attempt to find the matching record.
|
|
*
|
|
* @param array $attributes
|
|
* @param array $values
|
|
* @return \Illuminate\Database\Eloquent\Model
|
|
*/
|
|
public function createOrFirst(array $attributes = [], array $values = [])
|
|
{
|
|
try {
|
|
return $this->getQuery()->withSavepointIfNeeded(fn () => $this->create(array_merge($attributes, $values)));
|
|
} catch (UniqueConstraintViolationException $exception) {
|
|
return $this->where($attributes)->first() ?? throw $exception;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create or update a related record matching the attributes, and fill it with values.
|
|
*
|
|
* @param array $attributes
|
|
* @param array $values
|
|
* @return \Illuminate\Database\Eloquent\Model
|
|
*/
|
|
public function updateOrCreate(array $attributes, array $values = [])
|
|
{
|
|
return tap($this->firstOrCreate($attributes, $values), function ($instance) use ($values) {
|
|
if (! $instance->wasRecentlyCreated) {
|
|
$instance->fill($values)->save();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Add a basic where clause to the query, and return the first result.
|
|
*
|
|
* @param \Closure|string|array $column
|
|
* @param mixed $operator
|
|
* @param mixed $value
|
|
* @param string $boolean
|
|
* @return \Illuminate\Database\Eloquent\Model|static|null
|
|
*/
|
|
public function firstWhere($column, $operator = null, $value = null, $boolean = 'and')
|
|
{
|
|
return $this->where($column, $operator, $value, $boolean)->first();
|
|
}
|
|
|
|
/**
|
|
* Execute the query and get the first related model.
|
|
*
|
|
* @param array $columns
|
|
* @return \Illuminate\Database\Eloquent\Model|static|null
|
|
*/
|
|
public function first($columns = ['*'])
|
|
{
|
|
$results = $this->take(1)->get($columns);
|
|
|
|
return count($results) > 0 ? $results->first() : null;
|
|
}
|
|
|
|
/**
|
|
* Execute the query and get the first result or throw an exception.
|
|
*
|
|
* @param array $columns
|
|
* @return \Illuminate\Database\Eloquent\Model|static
|
|
*
|
|
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model>
|
|
*/
|
|
public function firstOrFail($columns = ['*'])
|
|
{
|
|
if (! is_null($model = $this->first($columns))) {
|
|
return $model;
|
|
}
|
|
|
|
throw (new ModelNotFoundException)->setModel(get_class($this->related));
|
|
}
|
|
|
|
/**
|
|
* Execute the query and get the first result or call a callback.
|
|
*
|
|
* @param \Closure|array $columns
|
|
* @param \Closure|null $callback
|
|
* @return \Illuminate\Database\Eloquent\Model|static|mixed
|
|
*/
|
|
public function firstOr($columns = ['*'], ?Closure $callback = null)
|
|
{
|
|
if ($columns instanceof Closure) {
|
|
$callback = $columns;
|
|
|
|
$columns = ['*'];
|
|
}
|
|
|
|
if (! is_null($model = $this->first($columns))) {
|
|
return $model;
|
|
}
|
|
|
|
return $callback();
|
|
}
|
|
|
|
/**
|
|
* Find a related model by its primary key.
|
|
*
|
|
* @param mixed $id
|
|
* @param array $columns
|
|
* @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|null
|
|
*/
|
|
public function find($id, $columns = ['*'])
|
|
{
|
|
if (is_array($id) || $id instanceof Arrayable) {
|
|
return $this->findMany($id, $columns);
|
|
}
|
|
|
|
return $this->where(
|
|
$this->getRelated()->getQualifiedKeyName(), '=', $id
|
|
)->first($columns);
|
|
}
|
|
|
|
/**
|
|
* Find multiple related models by their primary keys.
|
|
*
|
|
* @param \Illuminate\Contracts\Support\Arrayable|array $ids
|
|
* @param array $columns
|
|
* @return \Illuminate\Database\Eloquent\Collection
|
|
*/
|
|
public function findMany($ids, $columns = ['*'])
|
|
{
|
|
$ids = $ids instanceof Arrayable ? $ids->toArray() : $ids;
|
|
|
|
if (empty($ids)) {
|
|
return $this->getRelated()->newCollection();
|
|
}
|
|
|
|
return $this->whereIn(
|
|
$this->getRelated()->getQualifiedKeyName(), $ids
|
|
)->get($columns);
|
|
}
|
|
|
|
/**
|
|
* Find a related model by its primary key or throw an exception.
|
|
*
|
|
* @param mixed $id
|
|
* @param array $columns
|
|
* @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection
|
|
*
|
|
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model>
|
|
*/
|
|
public function findOrFail($id, $columns = ['*'])
|
|
{
|
|
$result = $this->find($id, $columns);
|
|
|
|
$id = $id instanceof Arrayable ? $id->toArray() : $id;
|
|
|
|
if (is_array($id)) {
|
|
if (count($result) === count(array_unique($id))) {
|
|
return $result;
|
|
}
|
|
} elseif (! is_null($result)) {
|
|
return $result;
|
|
}
|
|
|
|
throw (new ModelNotFoundException)->setModel(get_class($this->related), $id);
|
|
}
|
|
|
|
/**
|
|
* Find a related model by its primary key or call a callback.
|
|
*
|
|
* @param mixed $id
|
|
* @param \Closure|array $columns
|
|
* @param \Closure|null $callback
|
|
* @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|mixed
|
|
*/
|
|
public function findOr($id, $columns = ['*'], ?Closure $callback = null)
|
|
{
|
|
if ($columns instanceof Closure) {
|
|
$callback = $columns;
|
|
|
|
$columns = ['*'];
|
|
}
|
|
|
|
$result = $this->find($id, $columns);
|
|
|
|
$id = $id instanceof Arrayable ? $id->toArray() : $id;
|
|
|
|
if (is_array($id)) {
|
|
if (count($result) === count(array_unique($id))) {
|
|
return $result;
|
|
}
|
|
} elseif (! is_null($result)) {
|
|
return $result;
|
|
}
|
|
|
|
return $callback();
|
|
}
|
|
|
|
/**
|
|
* Get the results of the relationship.
|
|
*
|
|
* @return mixed
|
|
*/
|
|
public function getResults()
|
|
{
|
|
return ! is_null($this->farParent->{$this->localKey})
|
|
? $this->get()
|
|
: $this->related->newCollection();
|
|
}
|
|
|
|
/**
|
|
* Execute the query as a "select" statement.
|
|
*
|
|
* @param array $columns
|
|
* @return \Illuminate\Database\Eloquent\Collection
|
|
*/
|
|
public function get($columns = ['*'])
|
|
{
|
|
$builder = $this->prepareQueryBuilder($columns);
|
|
|
|
$models = $builder->getModels();
|
|
|
|
// If we actually found models we will also eager load any relationships that
|
|
// have been specified as needing to be eager loaded. This will solve the
|
|
// n + 1 query problem for the developer and also increase performance.
|
|
if (count($models) > 0) {
|
|
$models = $builder->eagerLoadRelations($models);
|
|
}
|
|
|
|
return $this->query->applyAfterQueryCallbacks(
|
|
$this->related->newCollection($models)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get a paginator for the "select" statement.
|
|
*
|
|
* @param int|null $perPage
|
|
* @param array $columns
|
|
* @param string $pageName
|
|
* @param int $page
|
|
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
|
|
*/
|
|
public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
|
|
{
|
|
$this->query->addSelect($this->shouldSelect($columns));
|
|
|
|
return $this->query->paginate($perPage, $columns, $pageName, $page);
|
|
}
|
|
|
|
/**
|
|
* Paginate the given query into a simple paginator.
|
|
*
|
|
* @param int|null $perPage
|
|
* @param array $columns
|
|
* @param string $pageName
|
|
* @param int|null $page
|
|
* @return \Illuminate\Contracts\Pagination\Paginator
|
|
*/
|
|
public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
|
|
{
|
|
$this->query->addSelect($this->shouldSelect($columns));
|
|
|
|
return $this->query->simplePaginate($perPage, $columns, $pageName, $page);
|
|
}
|
|
|
|
/**
|
|
* Paginate the given query into a cursor paginator.
|
|
*
|
|
* @param int|null $perPage
|
|
* @param array $columns
|
|
* @param string $cursorName
|
|
* @param string|null $cursor
|
|
* @return \Illuminate\Contracts\Pagination\CursorPaginator
|
|
*/
|
|
public function cursorPaginate($perPage = null, $columns = ['*'], $cursorName = 'cursor', $cursor = null)
|
|
{
|
|
$this->query->addSelect($this->shouldSelect($columns));
|
|
|
|
return $this->query->cursorPaginate($perPage, $columns, $cursorName, $cursor);
|
|
}
|
|
|
|
/**
|
|
* Set the select clause for the relation query.
|
|
*
|
|
* @param array $columns
|
|
* @return array
|
|
*/
|
|
protected function shouldSelect(array $columns = ['*'])
|
|
{
|
|
if ($columns == ['*']) {
|
|
$columns = [$this->related->getTable().'.*'];
|
|
}
|
|
|
|
return array_merge($columns, [$this->getQualifiedFirstKeyName().' as laravel_through_key']);
|
|
}
|
|
|
|
/**
|
|
* Chunk the results of the query.
|
|
*
|
|
* @param int $count
|
|
* @param callable $callback
|
|
* @return bool
|
|
*/
|
|
public function chunk($count, callable $callback)
|
|
{
|
|
return $this->prepareQueryBuilder()->chunk($count, $callback);
|
|
}
|
|
|
|
/**
|
|
* Chunk the results of a query by comparing numeric IDs.
|
|
*
|
|
* @param int $count
|
|
* @param callable $callback
|
|
* @param string|null $column
|
|
* @param string|null $alias
|
|
* @return bool
|
|
*/
|
|
public function chunkById($count, callable $callback, $column = null, $alias = null)
|
|
{
|
|
$column ??= $this->getRelated()->getQualifiedKeyName();
|
|
|
|
$alias ??= $this->getRelated()->getKeyName();
|
|
|
|
return $this->prepareQueryBuilder()->chunkById($count, $callback, $column, $alias);
|
|
}
|
|
|
|
/**
|
|
* Chunk the results of a query by comparing IDs in descending order.
|
|
*
|
|
* @param int $count
|
|
* @param callable $callback
|
|
* @param string|null $column
|
|
* @param string|null $alias
|
|
* @return bool
|
|
*/
|
|
public function chunkByIdDesc($count, callable $callback, $column = null, $alias = null)
|
|
{
|
|
$column ??= $this->getRelated()->getQualifiedKeyName();
|
|
|
|
$alias ??= $this->getRelated()->getKeyName();
|
|
|
|
return $this->prepareQueryBuilder()->chunkByIdDesc($count, $callback, $column, $alias);
|
|
}
|
|
|
|
/**
|
|
* Execute a callback over each item while chunking by ID.
|
|
*
|
|
* @param callable $callback
|
|
* @param int $count
|
|
* @param string|null $column
|
|
* @param string|null $alias
|
|
* @return bool
|
|
*/
|
|
public function eachById(callable $callback, $count = 1000, $column = null, $alias = null)
|
|
{
|
|
$column = $column ?? $this->getRelated()->getQualifiedKeyName();
|
|
|
|
$alias = $alias ?? $this->getRelated()->getKeyName();
|
|
|
|
return $this->prepareQueryBuilder()->eachById($callback, $count, $column, $alias);
|
|
}
|
|
|
|
/**
|
|
* Get a generator for the given query.
|
|
*
|
|
* @return \Illuminate\Support\LazyCollection
|
|
*/
|
|
public function cursor()
|
|
{
|
|
return $this->prepareQueryBuilder()->cursor();
|
|
}
|
|
|
|
/**
|
|
* Execute a callback over each item while chunking.
|
|
*
|
|
* @param callable $callback
|
|
* @param int $count
|
|
* @return bool
|
|
*/
|
|
public function each(callable $callback, $count = 1000)
|
|
{
|
|
return $this->chunk($count, function ($results) use ($callback) {
|
|
foreach ($results as $key => $value) {
|
|
if ($callback($value, $key) === false) {
|
|
return false;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Query lazily, by chunks of the given size.
|
|
*
|
|
* @param int $chunkSize
|
|
* @return \Illuminate\Support\LazyCollection
|
|
*/
|
|
public function lazy($chunkSize = 1000)
|
|
{
|
|
return $this->prepareQueryBuilder()->lazy($chunkSize);
|
|
}
|
|
|
|
/**
|
|
* Query lazily, by chunking the results of a query by comparing IDs.
|
|
*
|
|
* @param int $chunkSize
|
|
* @param string|null $column
|
|
* @param string|null $alias
|
|
* @return \Illuminate\Support\LazyCollection
|
|
*/
|
|
public function lazyById($chunkSize = 1000, $column = null, $alias = null)
|
|
{
|
|
$column ??= $this->getRelated()->getQualifiedKeyName();
|
|
|
|
$alias ??= $this->getRelated()->getKeyName();
|
|
|
|
return $this->prepareQueryBuilder()->lazyById($chunkSize, $column, $alias);
|
|
}
|
|
|
|
/**
|
|
* Query lazily, by chunking the results of a query by comparing IDs in descending order.
|
|
*
|
|
* @param int $chunkSize
|
|
* @param string|null $column
|
|
* @param string|null $alias
|
|
* @return \Illuminate\Support\LazyCollection
|
|
*/
|
|
public function lazyByIdDesc($chunkSize = 1000, $column = null, $alias = null)
|
|
{
|
|
$column ??= $this->getRelated()->getQualifiedKeyName();
|
|
|
|
$alias ??= $this->getRelated()->getKeyName();
|
|
|
|
return $this->prepareQueryBuilder()->lazyByIdDesc($chunkSize, $column, $alias);
|
|
}
|
|
|
|
/**
|
|
* Prepare the query builder for query execution.
|
|
*
|
|
* @param array $columns
|
|
* @return \Illuminate\Database\Eloquent\Builder
|
|
*/
|
|
protected function prepareQueryBuilder($columns = ['*'])
|
|
{
|
|
$builder = $this->query->applyScopes();
|
|
|
|
return $builder->addSelect(
|
|
$this->shouldSelect($builder->getQuery()->columns ? [] : $columns)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Add the constraints for a relationship query.
|
|
*
|
|
* @param \Illuminate\Database\Eloquent\Builder $query
|
|
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
|
|
* @param array|mixed $columns
|
|
* @return \Illuminate\Database\Eloquent\Builder
|
|
*/
|
|
public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
|
|
{
|
|
if ($parentQuery->getQuery()->from === $query->getQuery()->from) {
|
|
return $this->getRelationExistenceQueryForSelfRelation($query, $parentQuery, $columns);
|
|
}
|
|
|
|
if ($parentQuery->getQuery()->from === $this->throughParent->getTable()) {
|
|
return $this->getRelationExistenceQueryForThroughSelfRelation($query, $parentQuery, $columns);
|
|
}
|
|
|
|
$this->performJoin($query);
|
|
|
|
return $query->select($columns)->whereColumn(
|
|
$this->getQualifiedLocalKeyName(), '=', $this->getQualifiedFirstKeyName()
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Add the constraints for a relationship query on the same table.
|
|
*
|
|
* @param \Illuminate\Database\Eloquent\Builder $query
|
|
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
|
|
* @param array|mixed $columns
|
|
* @return \Illuminate\Database\Eloquent\Builder
|
|
*/
|
|
public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*'])
|
|
{
|
|
$query->from($query->getModel()->getTable().' as '.$hash = $this->getRelationCountHash());
|
|
|
|
$query->join($this->throughParent->getTable(), $this->getQualifiedParentKeyName(), '=', $hash.'.'.$this->secondKey);
|
|
|
|
if ($this->throughParentSoftDeletes()) {
|
|
$query->whereNull($this->throughParent->getQualifiedDeletedAtColumn());
|
|
}
|
|
|
|
$query->getModel()->setTable($hash);
|
|
|
|
return $query->select($columns)->whereColumn(
|
|
$parentQuery->getQuery()->from.'.'.$this->localKey, '=', $this->getQualifiedFirstKeyName()
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Add the constraints for a relationship query on the same table as the through parent.
|
|
*
|
|
* @param \Illuminate\Database\Eloquent\Builder $query
|
|
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
|
|
* @param array|mixed $columns
|
|
* @return \Illuminate\Database\Eloquent\Builder
|
|
*/
|
|
public function getRelationExistenceQueryForThroughSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*'])
|
|
{
|
|
$table = $this->throughParent->getTable().' as '.$hash = $this->getRelationCountHash();
|
|
|
|
$query->join($table, $hash.'.'.$this->secondLocalKey, '=', $this->getQualifiedFarKeyName());
|
|
|
|
if ($this->throughParentSoftDeletes()) {
|
|
$query->whereNull($hash.'.'.$this->throughParent->getDeletedAtColumn());
|
|
}
|
|
|
|
return $query->select($columns)->whereColumn(
|
|
$parentQuery->getQuery()->from.'.'.$this->localKey, '=', $hash.'.'.$this->firstKey
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Alias to set the "limit" value of the query.
|
|
*
|
|
* @param int $value
|
|
* @return $this
|
|
*/
|
|
public function take($value)
|
|
{
|
|
return $this->limit($value);
|
|
}
|
|
|
|
/**
|
|
* Set the "limit" value of the query.
|
|
*
|
|
* @param int $value
|
|
* @return $this
|
|
*/
|
|
public function limit($value)
|
|
{
|
|
if ($this->farParent->exists) {
|
|
$this->query->limit($value);
|
|
} else {
|
|
$column = $this->getQualifiedFirstKeyName();
|
|
|
|
$grammar = $this->query->getQuery()->getGrammar();
|
|
|
|
if ($grammar instanceof MySqlGrammar && $grammar->useLegacyGroupLimit($this->query->getQuery())) {
|
|
$column = 'laravel_through_key';
|
|
}
|
|
|
|
$this->query->groupLimit($value, $column);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Get the qualified foreign key on the related model.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getQualifiedFarKeyName()
|
|
{
|
|
return $this->getQualifiedForeignKeyName();
|
|
}
|
|
|
|
/**
|
|
* Get the foreign key on the "through" model.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getFirstKeyName()
|
|
{
|
|
return $this->firstKey;
|
|
}
|
|
|
|
/**
|
|
* Get the qualified foreign key on the "through" model.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getQualifiedFirstKeyName()
|
|
{
|
|
return $this->throughParent->qualifyColumn($this->firstKey);
|
|
}
|
|
|
|
/**
|
|
* Get the foreign key on the related model.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getForeignKeyName()
|
|
{
|
|
return $this->secondKey;
|
|
}
|
|
|
|
/**
|
|
* Get the qualified foreign key on the related model.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getQualifiedForeignKeyName()
|
|
{
|
|
return $this->related->qualifyColumn($this->secondKey);
|
|
}
|
|
|
|
/**
|
|
* Get the local key on the far parent model.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getLocalKeyName()
|
|
{
|
|
return $this->localKey;
|
|
}
|
|
|
|
/**
|
|
* Get the qualified local key on the far parent model.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getQualifiedLocalKeyName()
|
|
{
|
|
return $this->farParent->qualifyColumn($this->localKey);
|
|
}
|
|
|
|
/**
|
|
* Get the local key on the intermediary model.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getSecondLocalKeyName()
|
|
{
|
|
return $this->secondLocalKey;
|
|
}
|
|
}
|