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.