Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
with
225 additions
and 1 deletion.
@@ -0,0 +1,204 @@ | ||
<?php namespace Illuminate\Database\Eloquent\Relations; | ||
|
||
use Illuminate\Database\Eloquent\Model; | ||
use Illuminate\Database\Eloquent\Builder; | ||
use Illuminate\Database\Eloquent\Collection; | ||
|
||
class HasManyThrough extends Relation { | ||
|
||
protected $farParent; | ||
|
||
protected $firstKey; | ||
|
||
protected $secondKey; | ||
|
||
/** | ||
* Create a new has many relationship instance. | ||
* | ||
* @param \Illuminate\Database\Eloquent\Builder $query | ||
* @param \Illuminate\Database\Eloquent\Model $parent | ||
* @param string $foreignKey | ||
* @return void | ||
*/ | ||
public function __construct(Builder $query, Model $farParent, Model $parent, $firstKey, $secondKey) | ||
{ | ||
$this->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() | ||
{ | ||
$this->setJoin($parentTable = $this->parent->getTable()); | ||
|
||
if (static::$constraints) | ||
{ | ||
$this->query->where($parentTable.'.'.$this->firstKey, '=', $this->farParent->getKey()); | ||
} | ||
} | ||
|
||
/** | ||
* Set the join clause on the query. | ||
* | ||
* @param string $parentTable | ||
* @return void | ||
*/ | ||
protected function setJoin($parentTable) | ||
{ | ||
$foreignKey = $this->related->getTable().'.'.$this->secondKey; | ||
|
||
$this->query->join($parentTable, $this->getQualifiedParentKey(), '=', $foreignKey); | ||
} | ||
|
||
/** | ||
* 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 void | ||
*/ | ||
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 = array(); | ||
|
||
$foreign = $this->farParent->getForeignKey(); | ||
|
||
// 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 as a "select" statement. | ||
* | ||
* @param array $columns | ||
* @return \Illuminate\Database\Eloquent\Collection | ||
*/ | ||
public function get($columns = array('*')) | ||
{ | ||
// 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. | ||
$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. | ||
* | ||
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany | ||
*/ | ||
protected function getSelectColumns(array $columns = array('*')) | ||
{ | ||
if ($columns == array('*')) | ||
{ | ||
$columns = array($this->related->getTable().'.*'); | ||
} | ||
|
||
return array_merge($columns, array($this->parent->getTable().'.'.$this->firstKey)); | ||
} | ||
|
||
/** | ||
* Get the key name of the parent model. | ||
* | ||
* @return string | ||
*/ | ||
protected function getQualifiedParentKey() | ||
{ | ||
return $this->parent->getQualifiedKeyName(); | ||
} | ||
|
||
} |