firstKey = $firstKey; $this->secondKey = $secondKey; $this->farParent = $farParent; parent::__construct($query, $parent); } /** * Set the base constraints on the relation query. * * @return void */ public function addConstraints() { $parentTable = $this->parent->getTable(); $this->setJoin(); if (static::$constraints) { $this->query->where($parentTable.'.'.$this->firstKey, '=', $this->farParent->getKey()); } } /** * Add the constraints for a relationship count query. * * @param \Illuminate\Database\Eloquent\Builder $query * @param \Illuminate\Database\Eloquent\Builder $parent * @return \Illuminate\Database\Eloquent\Builder */ public function getRelationCountQuery(Builder $query, Builder $parent) { $parentTable = $this->parent->getTable(); $this->setJoin($query); $query->select(new Expression('count(*)')); $key = $this->wrap($parentTable.'.'.$this->firstKey); return $query->where($this->getHasCompareKey(), '=', new Expression($key)); } /** * Set the join clause on the query. * * @param \Illuminate\Database\Eloquent\Builder|null $query * @return void */ protected function setJoin(Builder $query = null) { $query = $query ?: $this->query; $foreignKey = $this->related->getTable().'.'.$this->secondKey; $query->join($this->parent->getTable(), $this->getQualifiedParentKeyName(), '=', $foreignKey); if ($this->parentSoftDeletes()) { $query->whereNull($this->parent->getQualifiedDeletedAtColumn()); } } /** * Determine whether close parent of the relation uses Soft Deletes. * * @return bool */ public function parentSoftDeletes() { return in_array('Illuminate\Database\Eloquent\SoftDeletes', class_uses_recursive(get_class($this->parent))); } /** * Set the constraints for an eager load of the relation. * * @param array $models * @return void */ public function addEagerConstraints(array $models) { $table = $this->parent->getTable(); $this->query->whereIn($table.'.'.$this->firstKey, $this->getKeys($models)); } /** * 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) { $key = $model->getKey(); if (isset($dictionary[$key])) { $value = $this->related->newCollection($dictionary[$key]); $model->setRelation($relation, $value); } } 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 = []; $foreign = $this->firstKey; // 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->{$foreign}][] = $result; } return $dictionary; } /** * Get the results of the relationship. * * @return mixed */ public function getResults() { return $this->get(); } /** * 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; } /** * 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); } $this->where($this->getRelated()->getQualifiedKeyName(), '=', $id); return $this->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(); $this->whereIn($this->getRelated()->getQualifiedKeyName(), $ids); return $this->get($columns); } /** * 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; $select = $this->getSelectColumns($columns); $models = $this->query->addSelect($select)->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 = $this->query->eagerLoadRelations($models); } return $this->related->newCollection($models); } /** * Set the select clause for the relation query. * * @param array $columns * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ protected function getSelectColumns(array $columns = ['*']) { if ($columns == ['*']) { $columns = [$this->related->getTable().'.*']; } return array_merge($columns, [$this->parent->getTable().'.'.$this->firstKey]); } /** * Get a paginator for the "select" statement. * * @param int $perPage * @param array $columns * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator */ public function paginate($perPage = null, $columns = ['*']) { $this->query->addSelect($this->getSelectColumns($columns)); return $this->query->paginate($perPage, $columns); } /** * Paginate the given query into a simple paginator. * * @param int $perPage * @param array $columns * @return \Illuminate\Contracts\Pagination\Paginator */ public function simplePaginate($perPage = null, $columns = ['*']) { $this->query->addSelect($this->getSelectColumns($columns)); return $this->query->simplePaginate($perPage, $columns); } /** * Get the key for comparing against the parent key in "has" query. * * @return string */ public function getHasCompareKey() { return $this->farParent->getQualifiedKeyName(); } }