Laravel中withOnly方法未限制关联查询的问题排查
问题分析与解决
问题描述
我定义了User模型,通过$with设置了默认预加载subscription和photo关联:
class User extends Authenticatable { /* ........ */ protected $with = ['subscription', 'photo']; public function subscription(): HasOne { return $this->hasOne(Subscriptions::class, 'id', 'subscription_id'); } public function photo(): HasOne { return $this->hasOne(Files::class, 'id', 'photo_file_id'); } }
在查询Notifications模型时,我想只加载fromuser关联,不加载User的默认关联,于是用了withOnly方法:
$notifications = \App\Models\Notifications::withOnly('fromuser') ->where('to_user_id', auth()->user()->id) ->limit(15) ->orderBy('id', 'DESC') ->get();
但Debug Bar显示User的subscription和photo还是被加载了,Notifications模型代码如下:
class Notifications extends Model { /* ..... */ public function fromuser(): HasOne { return $this->hasOne(User::class, 'id', 'from_user_id'); } public function touser(): HasOne { return $this->hasOne(User::class, 'id', 'to_user_id'); } }
我不想每次用User关联都写闭包排除默认关联,请问问题出在哪?
原因与解决方法
问题根源
withOnly('fromuser')的作用是覆盖Notifications模型自身的默认预加载配置(如果Notifications有$with属性的话),但它管不到关联的User模型自带的$with预加载规则。只要User模型被加载,它就会自动触发自己的默认关联预加载,和Notifications的查询配置无关。
解决方法
下面两种方法可以不用每次写重复闭包,还能适配后续新增的默认关联:
方法1:在User模型定义全局作用域
在User模型里加一个作用域方法,专门用来排除所有默认预加载:
class User extends Authenticatable { // ...原有代码 public function scopeWithoutDefaultRelations($query) { // 直接用模型的$with属性来排除,后续新增默认关联会自动生效 return $query->without($this->with); } }
之后查询时,只需要在关联里调用这个作用域:
$notifications = Notifications::with(['fromuser' => function($query) { $query->withoutDefaultRelations(); }]) ->where('to_user_id', auth()->id()) ->limit(15) ->orderByDesc('id') ->get();
方法2:使用Laravel自带的withoutGlobalEagerLoads方法
Laravel提供了withoutGlobalEagerLoads方法,可以直接禁用模型的所有默认预加载,不用自己写作用域:
$notifications = Notifications::with(['fromuser' => function($query) { $query->withoutGlobalEagerLoads(); }]) ->where('to_user_id', auth()->id()) ->limit(15) ->orderByDesc('id') ->get();
这个方法会完全取消User模型的$with预加载,不管里面有什么关联,适合需要彻底禁用默认预加载的场景。
内容的提问来源于stack exchange,提问作者Linesofcode
相关产品推荐
相关产品推荐

