Laravel belongsTo with condition and eager load Laravel belongsTo with condition and eager load laravel laravel

Laravel belongsTo with condition and eager load


You can achieve it by defining custom relationship.

BelongsToWith.php

<?phpdeclare(strict_types=1);namespace App\Database\Eloquent\Relations;use Illuminate\Database\Eloquent\Builder;use Illuminate\Database\Eloquent\Collection;use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\BelongsTo;class BelongsToWith extends BelongsTo{    /**     * @var array [$foreignColumn => $ownerColumn, ...] assoc or [$column, ...] array     */    protected $conditions = [];    public function __construct(array $conditions, Builder $query, Model $child, string $foreignKey, string $ownerKey, string $relation)    {        $this->conditions = $conditions;        parent::__construct($query, $child, $foreignKey, $ownerKey, $relation);    }    public function addConstraints()    {        if (static::$constraints) {            // Add base constraints            parent::addConstraints();            // Add extra constraints            foreach ($this->conditions as $key => $value) {                if (is_int($key)) {                    $key = $value;                }                $this->getQuery()->where($this->related->getTable() . '.' . $value, '=', $this->child->{$key});            }        }    }    public function addEagerConstraints(array $models)    {        // Ignore empty models        if ([null] === $this->getEagerModelKeys($models)) {            parent::addEagerConstraints($models);            return;        }        $this->getQuery()->where(function (Builder $query) use ($models) {            foreach ($models as $model) {                $query->orWhere(function (Builder $query) use ($model) {                    // Add base constraints                    $query->where($this->related->getTable() . '.' . $this->ownerKey, $model->getAttribute($this->foreignKey));                    // Add extra constraints                    foreach ($this->conditions as $key => $value) {                        if (is_int($key)) {                            $key = $value;                        }                        $query->where($this->related->getTable() . '.' . $value, $model->getAttribute($key));                    }                });            }        });    }    public function match(array $models, Collection $results, $relation)    {        $dictionary = [];        foreach ($results as $result) {            // Base constraints            $keys = [$result->getAttribute($this->ownerKey)];            // Extra constraints            foreach ($this->conditions as $key => $value) {                $keys[] = $result->getAttribute($value);            }            // Build nested dictionary            $current = &$dictionary;            foreach ($keys as $key) {                $current = &$current[$key];            }            $current = $result;            unset($current);        }        foreach ($models as $model) {            $current = $dictionary;            // Base constraints            if (!isset($current[$model->{$this->foreignKey}])) {                continue;            }            $current = $current[$model->{$this->foreignKey}];            // Extra constraints            foreach ($this->conditions as $key => $value) {                if (is_int($key)) {                    $key = $value;                }                if (!isset($current[$model->{$key}])) {                    continue 2;                }                $current = $current[$model->{$key}];            }            // Set passed result            $model->setRelation($relation, $current);        }        return $models;    }}

HasExtendedRelationships.php

<?phpdeclare(strict_types=1);namespace App\Database\Eloquent\Concerns;use App\Database\Eloquent\Relations\BelongsToWith;use Illuminate\Support\Str;trait HasExtendedRelationships{    public function belongsToWith(array $conditions, $related, $foreignKey = null, $ownerKey = null, $relation = null): BelongsToWith    {        if ($relation === null) {            $relation = $this->guessBelongsToRelation();        }        $instance = $this->newRelatedInstance($related);        if ($foreignKey === null) {            $foreignKey = Str::snake($relation) . '_' . $instance->getKeyName();        }        $ownerKey = $ownerKey ?: $instance->getKeyName();        return new BelongsToWith($conditions, $instance->newQuery(), $this, $foreignKey, $ownerKey, $relation);    }}

Then:

class Post extends Base{    use HasExtendedRelationships;    public function section(): BelongsToWith    {        return $this->belongsToWith(['website'], App\Models\Section::class, 'id_cat');    }}

 

$posts = Post::with('section')->find([1, 2]);

Your Eager Loading query will be like:

select * from `sections`where (    (        `sections`.`id` = {$posts[0]->id_cat}        and `sections`.`website` = {$posts[0]->website}    )    or    (        `sections`.`id` = {$posts[1]->id_cat}        and `sections`.`website` = {$posts[1]->website}    ))


As mentioned in my comment, where shouldn't be used in the relationship definition. Hence, your relation definition is good with just

public function section(){    return $this->belongsTo('App\Models\Section', 'id_cat');}

and you can eager load in this way (not giving out the exact query with chunk etc)

Post::with(['section' => function ($query) use ($request) {    $query->where('website', $request['website'])}])->get()->first();

i.e. when you pass the variable website in request or else use any other variable in a similar way.

I hope that explains. Please add comments if anything is unclear.