如何在Eloquent嵌套预加载子查询中传递上层Product模型的id
解决方案
你不能直接在with预加载的闭包里拿到单个Product的id,因为Eloquent的预加载是批量查询逻辑:先查所有Product数据,再批量查关联的Local数据,再批量查关联的Presentation数据,全程只有3次查询,不是逐个Product查询,所以没有单独的$product_id变量可以直接调用。
你可以通过调整关联定义的方式完全避免手动传参的问题,也是性能最优的方案:
步骤1:给中间表模型LocalProduct添加关联
你的Presentation是关联中间表LocalProduct的,直接在LocalProduct模型中定义到Presentation的一对多关联即可:
// App/Models/LocalProduct.php public function presentations() { return $this->hasMany(Presentation::class); }
步骤2:调整查询代码直接预加载中间表关联
因为中间表local_product本身就存储了对应的product_id和local_id,所以通过中间表加载的presentations天然就符合你要的过滤条件,不需要额外加where约束:
$products = Product::select('id', 'nombre') ->with(['locals' => function ($localsQuery) { $localsQuery->select('locals.id', 'descripcion') ->withPivot('id', 'is_active') // 直接预加载中间表的presentations关联 ->with(['pivot.presentations' => function ($presentationsQuery) { $presentationsQuery->select( 'local_product_id', 'product_id', 'id', 'presentation', 'price' ); }]); }]) ->get(); return $products;
最终数据结构说明
查询完成后,你可以直接按下面的方式取到对应的数据,和你原来循环拼接的结果完全一致:
- 每个Product实例下有
locals属性,存储关联的门店列表 - 每个Local实例下有
pivot属性,对应中间表LocalProduct的实例 - 每个
pivot实例下有presentations属性,存储当前Product+当前Local对应的所有规格数据
替代方案:不想改关联定义的情况
如果不想新增关联,也可以用集合过滤的方式,预加载所有关联数据后再过滤,适合数据量不大的场景:
$products = Product::select('id', 'nombre') ->with(['locals' => function ($localsQuery) { $localsQuery->select('locals.id', 'descripcion') ->with(['presentations' => function ($presentationsQuery) { $presentationsQuery->select( 'presentations.local_id', 'presentations.product_id', 'presentations.id', 'presentation', 'price' ); }]); }]) ->get() // 集合层面过滤每个local下的presentations ->each(function ($product) { $product->locals->each(function ($local) use ($product) { $local->presentations = $local->presentations->where('product_id', $product->id)->values(); }); }); return $products;
内容的提问来源于stack exchange,提问作者Miguel Miller
相关产品推荐
相关产品推荐

