341 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			PHP
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			341 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			PHP
		
	
	
		
			Executable File
		
	
	
	
	
| <?php namespace Illuminate\Database\Eloquent\Relations;
 | |
| 
 | |
| use Illuminate\Database\Eloquent\Model;
 | |
| use Illuminate\Database\Eloquent\Builder;
 | |
| use Illuminate\Database\Query\Expression;
 | |
| use Illuminate\Database\Eloquent\Collection;
 | |
| 
 | |
| class HasManyThrough extends Relation {
 | |
| 
 | |
| 	/**
 | |
| 	 * The distance 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;
 | |
| 
 | |
| 	/**
 | |
| 	 * 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  $parent
 | |
| 	 * @param  string  $firstKey
 | |
| 	 * @param  string  $secondKey
 | |
| 	 * @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()
 | |
| 	{
 | |
| 		$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();
 | |
| 	}
 | |
| 
 | |
| }
 |