Tag Archives: eloquent

Eloquent HasManyThrough 5.5 functionality with version 5.1

The 5.1 version of HasManyThrough is limited; you cannot specify the key on the final table that connects it to the intermediate table.

If you’re unfortunately stuck on 5.1 but want this functionality, the following HasManyThrough should help. Tested with normally loading the relationship (ie modelInstance->relationship) and using with (ie model::with('relationship')->where()), not yet with has or whereHas.

<?php
use Illuminate\Database\Eloquent\Model as Eloquent;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;


/**
 * A custom version of HasManyThrough upgrading Eloquent 5.1 HasManyThrough to the 5.5 version.
 * This allows us to specify the key on the intermediate table connecting to the final table.
 */
class CustomHasManyThrough extends HasManyThrough {

    /**
     * The "through" parent model instance.
     *
     * @var \Illuminate\Database\Eloquent\Model
     */
    protected $throughParent;

    /**
     * The local key on the intermediary model.
     *
     * @var string
     */
    protected $secondLocalKey;

    /**
     * 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($query, $farParent, $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey){
        $this->localKey = $localKey;
        $this->firstKey = $firstKey;
        $this->secondKey = $secondKey;
        $this->secondLocalKey = $secondLocalKey;

        $this->farParent = $farParent;        
        $this->throughParent = $throughParent;

        Relation::__construct($query, $throughParent);
    }

    /**
     * Get the fully qualified parent key name.
     *
     * @return string
     */
    public function getQualifiedParentKeyName(){
        return $this->parent->getTable().'.'.$this->secondLocalKey;
    }

    /**
     * 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, $this->localKey));
    }

    /**
     * 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, \Illuminate\Database\Eloquent\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->getAttribute($this->localKey);
            if (isset($dictionary[$key])) {
                $value = $this->related->newCollection($dictionary[$key]);
                $model->setRelation($relation, $value);
            }
        }
        return $models;
    }
}


class CustomEloquent extends Eloquent {

    /**
     * Replace the Eloquent has-many-through relationship with our custom one.
     *
     * @param  string  $related
     * @param  string  $through
     * @param  string|null  $firstKey
     * @param  string|null  $secondKey
     * @param  string|null  $localKey
     * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough
     */
    public function hasManyThrough($related, $through, $firstKey = null, $secondKey = null, $localKey = null, $secondLocalKey = null){
        $through = new $through;
        $firstKey = $firstKey ?: $this->getForeignKey();
        $secondKey = $secondKey ?: $through->getForeignKey();
        $localKey = $localKey ?: $this->getKeyName();
        $secondLocalKey ?: $through->getKeyName();

        return new CustomHasManyThrough((new $related)->newQuery(), $this, $through, $firstKey, $secondKey, $localKey, $secondLocalKey);
    }
}