Вход Регистрация
Файл: vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasOneOrManyThrough.php
Строк: 936
<?php

namespace IlluminateDatabaseEloquentRelations;

use 
Closure;
use 
IlluminateContractsSupportArrayable;
use 
IlluminateDatabaseEloquentBuilder;
use 
IlluminateDatabaseEloquentCollection as EloquentCollection;
use 
IlluminateDatabaseEloquentModel;
use 
IlluminateDatabaseEloquentModelNotFoundException;
use 
IlluminateDatabaseEloquentRelationsConcernsInteractsWithDictionary;
use 
IlluminateDatabaseQueryGrammarsMySqlGrammar;
use 
IlluminateDatabaseUniqueConstraintViolationException;
use 
IlluminateSupportArr;

/**
 * @template TRelatedModel of IlluminateDatabaseEloquentModel
 * @template TIntermediateModel of IlluminateDatabaseEloquentModel
 * @template TDeclaringModel of IlluminateDatabaseEloquentModel
 * @template TResult
 *
 * @extends IlluminateDatabaseEloquentRelationsRelation<TRelatedModel, TIntermediateModel, TResult>
 */
abstract class HasOneOrManyThrough extends Relation
{
    use 
InteractsWithDictionary;

    
/**
     * The "through" parent model instance.
     *
     * @var TIntermediateModel
     */
    
protected $throughParent;

    
/**
     * The far parent model instance.
     *
     * @var TDeclaringModel
     */
    
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  IlluminateDatabaseEloquentBuilder<TRelatedModel>  $query
     * @param  TDeclaringModel  $farParent
     * @param  TIntermediateModel  $throughParent
     * @param  string  $firstKey
     * @param  string  $secondKey
     * @param  string  $localKey
     * @param  string  $secondLocalKey
     */
    
public function __construct(Builder $queryModel $farParentModel $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);
    }

    
/**
     * Set the base constraints on the relation query.
     *
     * @return void
     */
    
public function addConstraints()
    {
        
$query $this->getRelationQuery();

        
$this->performJoin($query);

        if (static::
$constraints) {
            
$localValue $this->farParent[$this->localKey];

            
$query->where($this->getQualifiedFirstKeyName(), '='$localValue);
        }
    }

    
/**
     * Set the join clause on the query.
     *
     * @param  IlluminateDatabaseEloquentBuilder<TRelatedModel>|null  $query
     * @return void
     */
    
protected function performJoin(?Builder $query null)
    {
        
$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 
$this->throughParent::isSoftDeletable();
    }

    
/**
     * Indicate that trashed "through" parents should be included in the query.
     *
     * @return $this
     */
    
public function withTrashedParents()
    {
        
$this->query->withoutGlobalScope('SoftDeletableHasManyThrough');

        return 
$this;
    }

    
/** @inheritDoc */
    
public function addEagerConstraints(array $models)
    {
        
$whereIn $this->whereInMethod($this->farParent$this->localKey);

        
$this->whereInEager(
            
$whereIn,
            
$this->getQualifiedFirstKeyName(),
            
$this->getKeys($models$this->localKey),
            
$this->getRelationQuery(),
        );
    }

    
/**
     * Build model dictionary keyed by the relation's foreign key.
     *
     * @param  IlluminateDatabaseEloquentCollection<int, TRelatedModel>  $results
     * @return array<array<array-key, TRelatedModel>>
     */
    
protected function buildDictionary(EloquentCollection $results)
    {
        
$dictionary = [];

        
$isAssociative Arr::isAssoc($results->all());

        
// 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 $key => $result) {
            if (
$isAssociative) {
                
$dictionary[$result->laravel_through_key][$key] = $result;
            } else {
                
$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 TRelatedModel
     */
    
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  (Closure(): array)|array  $values
     * @return TRelatedModel
     */
    
public function firstOrCreate(array $attributes = [], Closure|array $values = [])
    {
        if (! 
is_null($instance = (clone $this)->where($attributes)->first())) {
            return 
$instance;
        }

        return 
$this->createOrFirst(array_merge($attributesvalue($values)));
    }

    
/**
     * Attempt to create the record. If a unique constraint violation occurs, attempt to find the matching record.
     *
     * @param  array  $attributes
     * @param  (Closure(): array)|array  $values
     * @return TRelatedModel
     *
     * @throws IlluminateDatabaseUniqueConstraintViolationException
     */
    
public function createOrFirst(array $attributes = [], Closure|array $values = [])
    {
        try {
            return 
$this->getQuery()->withSavepointIfNeeded(fn () => $this->create(array_merge($attributesvalue($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 TRelatedModel
     */
    
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 TRelatedModel|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 TRelatedModel|null
     */
    
public function first($columns = ['*'])
    {
        
$results $this->limit(1)->get($columns);

        return 
count($results) > $results->first() : null;
    }

    
/**
     * Execute the query and get the first result or throw an exception.
     *
     * @param  array  $columns
     * @return TRelatedModel
     *
     * @throws IlluminateDatabaseEloquentModelNotFoundException<TRelatedModel>
     */
    
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.
     *
     * @template TValue
     *
     * @param  (Closure(): TValue)|list<string>  $columns
     * @param  (Closure(): TValue)|null  $callback
     * @return TRelatedModel|TValue
     */
    
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 ($id is (IlluminateContractsSupportArrayable<array-key, mixed>|array<mixed>) ? IlluminateDatabaseEloquentCollection<int, TRelatedModel> : TRelatedModel|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 a sole related model by its primary key.
     *
     * @param  mixed  $id
     * @param  array  $columns
     * @return TRelatedModel
     *
     * @throws IlluminateDatabaseEloquentModelNotFoundException<TRelatedModel>
     * @throws IlluminateDatabaseMultipleRecordsFoundException
     */
    
public function findSole($id$columns = ['*'])
    {
        return 
$this->where(
            
$this->getRelated()->getQualifiedKeyName(), '='$id
        
)->sole($columns);
    }

    
/**
     * Find multiple related models by their primary keys.
     *
     * @param  IlluminateContractsSupportArrayable|array  $ids
     * @param  array  $columns
     * @return IlluminateDatabaseEloquentCollection<int, TRelatedModel>
     */
    
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 ($id is (IlluminateContractsSupportArrayable<array-key, mixed>|array<mixed>) ? IlluminateDatabaseEloquentCollection<int, TRelatedModel> : TRelatedModel)
     *
     * @throws IlluminateDatabaseEloquentModelNotFoundException<TRelatedModel>
     */
    
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.
     *
     * @template TValue
     *
     * @param  mixed  $id
     * @param  (Closure(): TValue)|list<string>|string  $columns
     * @param  (Closure(): TValue)|null  $callback
     * @return (
     *     $id is (IlluminateContractsSupportArrayable<array-key, mixed>|array<mixed>)
     *     ? IlluminateDatabaseEloquentCollection<int, TRelatedModel>|TValue
     *     : TRelatedModel|TValue
     * )
     */
    
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();
    }

    
/** @inheritDoc */
    
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|null  $page
     * @return IlluminatePaginationLengthAwarePaginator
     */
    
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 IlluminateContractsPaginationPaginator
     */
    
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 IlluminateContractsPaginationCursorPaginator
     */
    
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->qualifyColumn('*')];
        }

        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 ??= $this->getRelated()->getQualifiedKeyName();

        
$alias ??= $this->getRelated()->getKeyName();

        return 
$this->prepareQueryBuilder()->eachById($callback$count$column$alias);
    }

    
/**
     * Get a generator for the given query.
     *
     * @return IlluminateSupportLazyCollection<int, TRelatedModel>
     */
    
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 IlluminateSupportLazyCollection<int, TRelatedModel>
     */
    
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 IlluminateSupportLazyCollection<int, TRelatedModel>
     */
    
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 IlluminateSupportLazyCollection<int, TRelatedModel>
     */
    
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 IlluminateDatabaseEloquentBuilder<TRelatedModel>
     */
    
protected function prepareQueryBuilder($columns = ['*'])
    {
        
$builder $this->query->applyScopes();

        return 
$builder->addSelect(
            
$this->shouldSelect($builder->getQuery()->columns ? [] : $columns)
        );
    }

    
/** @inheritDoc */
    
public function getRelationExistenceQuery(Builder $queryBuilder $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  IlluminateDatabaseEloquentBuilder<TRelatedModel>  $query
     * @param  IlluminateDatabaseEloquentBuilder<TDeclaringModel>  $parentQuery
     * @param  mixed  $columns
     * @return IlluminateDatabaseEloquentBuilder<TRelatedModel>
     */
    
public function getRelationExistenceQueryForSelfRelation(Builder $queryBuilder $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  IlluminateDatabaseEloquentBuilder<TRelatedModel>  $query
     * @param  IlluminateDatabaseEloquentBuilder<TDeclaringModel>  $parentQuery
     * @param  mixed  $columns
     * @return IlluminateDatabaseEloquentBuilder<TRelatedModel>
     */
    
public function getRelationExistenceQueryForThroughSelfRelation(Builder $queryBuilder $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;
    }
}
Онлайн: 2
Реклама