Laravel Eloquent with()与find()配合失效,where()却正常?
问题背景
我最近在用Laravel Eloquent的with()方法做三张表的关联查询,模型之间的关联已经配置好了,但遇到了一个让人困惑的问题:
我最初写了这段代码:
$request_form = RequestForm::find(7)->with(['RequestFormsCheck' => function ($q) { $q->orderBy("created_at", "DESC")->with('RequestFormsCheckOptions'); }])->get(); dd($request_form);
结果返回的是整个request_forms表的所有数据,完全不是我要的id=7的那条记录;后来我把get()换成first(),结果返回的记录id居然是1,完全不符合预期。
但换成下面这段代码就正常了,能正确获取到id=7的记录及其关联数据:
$request_form = RequestForm::where('id', 7)->with(['RequestFormsCheck' => function ($q) { $q->orderBy("created_at", "DESC")->with('RequestFormsCheckOptions'); }])->first(); dd($request_form->toArray());
附上我的数据表迁移代码:
request_forms 表迁移
Schema::create('request_forms', function (Blueprint $table) { $table->increments('id'); $table->string('name', 100)->nullable(); $table->string('email', 100)->nullable(); $table->string('telephone', 20)->nullable(); $table->text('description')->nullable(); $table->string('site', 100); $table->integer('item_id'); $table->string('lang'); $table->timestamps(); });
request_forms_check_options 表迁移
Schema::create('request_forms_check_options', function (Blueprint $table) { $table->increments('id'); $table->string('title', 500); $table->timestamps(); });
request_forms_checks 表迁移
Schema::create('request_forms_checks', function (Blueprint $table) { $table->increments('id'); $table->integer('request_form_id')->unsigned(); $table->foreign('request_form_id')->references('id')->on('request_forms')->onDelete('cascade'); $table->tinyInteger('type')->comment("1: option, 2:description"); $table->integer('request_forms_check_options_id')->unsigned()->nullable(); $table->foreign('request_forms_check_options_id')->references('id')->on('request_forms_check_options')->onDelete('cascade'); $table->text('description')->nullable(); $table->timestamps(); });
为什么会出现这种差异?
核心问题就是方法调用顺序错了!
当你执行RequestForm::find(7)时,Laravel已经立刻执行了数据库查询,返回的是一个RequestForm模型实例(不是查询构造器)。这时候你再调用with()方法,它的作用是给这个模型实例预加载关联,但随后你调用get()方法,会直接创建一个全新的查询构造器,执行SELECT * FROM request_forms,所以返回了全表数据。
同理,把get()换成first()时,也是发起了全新的查询,默认取表中第一条记录(也就是id=1的那条),自然和你要的id=7无关。
而where('id',7)->with()->first()的写法是正确的:先通过where()给查询构造器添加条件,再用with()指定要预加载的关联,最后first()执行查询——整个过程都是基于同一个查询构造器,所以能精准获取到id=7的记录及其关联数据。
修复方案
你有两种靠谱的写法可以选择:
方案一:先指定预加载,再查询(推荐)
把with()放在find()前面,这样查询构造器会先记住要预加载的关联,再执行查询获取指定id的记录:
$request_form = RequestForm::with(['RequestFormsCheck' => function ($q) { $q->orderBy("created_at", "DESC")->with('RequestFormsCheckOptions'); }])->find(7); dd($request_form->toArray());
或者用你已经验证过的where()写法,效果是一样的。
方案二:延迟加载关联(适合已获取模型实例的场景)
如果已经拿到了模型实例(比如之前已经查询过这条记录),可以用load()方法来延迟加载关联,而不是with():
// 先获取id=7的模型实例 $request_form = RequestForm::find(7); // 给这个实例加载关联数据 $request_form->load(['RequestFormsCheck' => function ($q) { $q->orderBy("created_at", "DESC")->with('RequestFormsCheckOptions'); }]); dd($request_form->toArray());
load()是针对已存在的模型实例加载关联,不会重新发起全表查询,完美解决你的问题。
内容的提问来源于stack exchange,提问作者SoheilYou

