Viewing file: HasManyThrough.php (14.49 KB) -rw-rw-r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
<?php
namespace Illuminate\Database\Eloquent\Relations;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\ModelNotFoundException;
class HasManyThrough extends Relation { /** * 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;
/** * The count of self joins. * * @var int */ protected static $selfJoinCount = 0;
/** * 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); }
/** * 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->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( get_class($this->throughParent) )); }
/** * Set the constraints for an eager load of the relation. * * @param array $models * @return void */ public function addEagerConstraints(array $models) { $this->query->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 = $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->{$this->firstKey}][] = $result; }
return $dictionary; }
/** * Get the first related model record matching the attributes or instantiate it. * * @param array $attributes * @return \Illuminate\Database\Eloquent\Model */ public function firstOrNew(array $attributes) { if (is_null($instance = $this->where($attributes)->first())) { $instance = $this->related->newInstance($attributes); }
return $instance; }
/** * 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 = []) { $instance = $this->firstOrNew($attributes);
$instance->fill($values)->save();
return $instance; }
/** * Execute the query and get the first related model. * * @param array $columns * @return mixed */ 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 */ public function firstOrFail($columns = ['*']) { if (! is_null($model = $this->first($columns))) { return $model; }
throw (new ModelNotFoundException)->setModel(get_class($this->related)); }
/** * 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)) { return $this->findMany($id, $columns); }
return $this->where( $this->getRelated()->getQualifiedKeyName(), '=', $id )->first($columns); }
/** * Find multiple related models by their primary keys. * * @param mixed $ids * @param array $columns * @return \Illuminate\Database\Eloquent\Collection */ public function findMany($ids, $columns = ['*']) { 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 */ public function findOrFail($id, $columns = ['*']) { $result = $this->find($id, $columns);
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)); }
/** * Get the results of the relationship. * * @return mixed */ public function getResults() { return $this->get(); }
/** * Execute the query as a "select" statement. * * @param array $columns * @return \Illuminate\Database\Eloquent\Collection */ public function get($columns = ['*']) { // First we'll add the proper select columns onto the query so it is run with // the proper columns. Then, we will get the results and hydrate out pivot // models with the result of those columns as a separate model relation. $columns = $this->query->getQuery()->columns ? [] : $columns;
$builder = $this->query->applyScopes();
$models = $builder->addSelect( $this->shouldSelect($columns) )->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->related->newCollection($models); }
/** * Get a paginator for the "select" statement. * * @param int $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 $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); }
/** * 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()]); }
/** * 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); }
$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->secondLocalKey);
if ($this->throughParentSoftDeletes()) { $query->whereNull($this->throughParent->getQualifiedDeletedAtColumn()); }
$query->getModel()->setTable($hash);
return $query->select($columns)->whereColumn( $parentQuery->getQuery()->from.'.'.$query->getModel()->getKeyName(), '=', $this->getQualifiedFirstKeyName() ); }
/** * Get a relationship join table hash. * * @return string */ public function getRelationCountHash() { return 'laravel_reserved_'.static::$selfJoinCount++; }
/** * Get the qualified foreign key on the related model. * * @return string */ public function getQualifiedFarKeyName() { return $this->getQualifiedForeignKeyName(); }
/** * Get the qualified foreign key on the "through" model. * * @return string */ public function getQualifiedFirstKeyName() { return $this->throughParent->qualifyColumn($this->firstKey); }
/** * Get the qualified foreign key on the related model. * * @return string */ public function getQualifiedForeignKeyName() { return $this->related->qualifyColumn($this->secondKey); }
/** * Get the qualified local key on the far parent model. * * @return string */ public function getQualifiedLocalKeyName() { return $this->farParent->qualifyColumn($this->localKey); } }
|