Permalink
Browse files

Work on hasManyThrough relation.

  • Loading branch information...
taylorotwell committed Oct 30, 2013
1 parent 27793fb commit 0925d868b900372a39c0f9985308004d24bad46d
@@ -16,6 +16,7 @@
use Illuminate\Database\Query\Builder as QueryBuilder;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\ConnectionResolverInterface as Resolver;
abstract class Model implements ArrayAccess, ArrayableInterface, JsonableInterface {
@@ -656,6 +657,24 @@ public function hasMany($related, $foreignKey = null)
return new HasMany($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey);
}
/**
* Define a has-many-through relationship.
*
* @param string $related
* @param string $through
* @param string|null $firstKey
* @param string|null $secondKey
* @return \Illuminate\Database\Eloquent\Relations\HasManyThrough
*/
public function hasManyThrough($related, $through, $firstKey = null, $secondKey = null)
{
$firstKey = $firstKey ?: $this->getForeignKey();
$secondKey = $secondKey ?: with($through = new $through)->getForeignKey();
return new HasManyThrough(with(new $related)->newQuery(), $this, $through, $firstKey, $secondKey);
}
/**
* Define a polymorphic one-to-many relationship.
*
@@ -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();
}
}
@@ -32,7 +32,8 @@
{"message": "Deprecate 'close' application hooks, Stack middlewares should be used instead.", "backport": null},
{"message": "A new packages directory within `lang` can now override package language files.", "backport": null},
{"message": "Added new 'Auth::viaRemember method to determine if user was authed via 'remember me' cookie.", "backport": null},
{"message": "Allow passing a view name to paginator's 'links' method.", "backport": null}
{"message": "Allow passing a view name to paginator's 'links' method.", "backport": null},
{"message": "Added new hasManyThrough relationship type.", "backport": null}
],
"4.0.x": [
{"message": "Added implode method to query builder and Collection class.", "backport": null},

0 comments on commit 0925d86

Please sign in to comment.