在Laravel的Eloquent关联模型中,父子关联是非常常见的数据关系,比如分类模型Category和子分类模型之间的一对多关联,或者订单模型Order和订单项OrderItem的关联。实际开发中我们常常需要过滤关联数据,比如只获取父分类下状态为启用的子分类,或者只查询订单中价格大于100的订单项,下面介绍几种常用的实现方式。

基础关联定义
首先我们需要先定义好父子模型的关联关系,以分类和子分类为例,父分类模型Category的定义如下:
<?php
namespace AppModels;
use IlluminateDatabaseEloquentModel;
class Category extends Model
{
// 定义一对多关联,获取当前分类的所有子分类
public function children()
{
return $this->hasMany(Category::class, 'parent_id');
}
// 定义反向关联,获取当前分类的父分类
public function parent()
{
return $this->belongsTo(Category::class, 'parent_id');
}
}
子分类使用的是同一个Category模型,通过parent_id字段区分父子关系,子分类的关联定义已经在上面的children方法中完成。
过滤父模型下的关联子数据
使用with方法配合闭包过滤
如果我们需要在查询父模型的同时,过滤加载的关联子数据,可以使用with方法的闭包参数,示例如下:
<?php
use AppModelsCategory;
// 查询所有父分类,并且只加载状态为1(启用)的子分类
$categories = Category::whereNull('parent_id')
->with(['children' => function ($query) {
// 过滤子分类的条件,只获取状态为启用的子分类
$query->where('status', 1);
}])
->get();
foreach ($categories as $category) {
// 这里的children只包含状态为1的子分类
echo "父分类: {$category->name}, 启用的子分类数量: " . $category->children->count() . PHP_EOL;
}
这种方式会在查询父模型的同时,通过子查询过滤关联的子数据,不会把不符合条件的子分类加载到父模型的关联属性中。
使用whereHas过滤存在符合条件子数据的父模型
如果我们不仅需要过滤子数据,还需要只获取那些存在符合条件子数据的父模型,可以使用whereHas方法:
<?php
use AppModelsCategory;
// 查询所有存在状态为启用的子分类的父分类,并且加载这些启用的子分类
$categories = Category::whereNull('parent_id')
->whereHas('children', function ($query) {
$query->where('status', 1);
})
->with(['children' => function ($query) {
$query->where('status', 1);
}])
->get();
whereHas方法会筛选出至少存在一个符合条件的子分类的父模型,避免查询出没有符合条件的子分类的父模型数据。
过滤子模型对应的父模型
如果我们需要根据子模型的条件过滤对应的父模型,比如查询所有包含价格大于100的订单项的订单,可以使用反向关联的whereHas:
<?php
use AppModelsOrder;
use AppModelsOrderItem;
// 订单模型定义和订单项的关联
class Order extends Model
{
public function items()
{
return $this->hasMany(OrderItem::class);
}
}
// 查询所有包含价格大于100的订单项的订单
$orders = Order::whereHas('items', function ($query) {
$query->where('price', '>', 100);
})->get();
多层关联的条件过滤
如果存在三层及以上的关联,比如分类、子分类、子分类下的商品,我们同样可以在with的闭包中嵌套关联查询:
<?php
use AppModelsCategory;
// 商品模型关联子分类
class Goods extends Model
{
public function category()
{
return $this->belongsTo(Category::class);
}
}
// 查询父分类,加载启用的子分类,并且子分类下加载库存大于0的商品
$categories = Category::whereNull('parent_id')
->with(['children' => function ($query) {
$query->where('status', 1)
->with(['goods' => function ($q) {
$q->where('stock', '>', 0);
}]);
}])
->get();
注意事项
- 使用with的闭包过滤时,只会影响关联数据的加载,不会影响父模型的查询结果,如果需要过滤父模型需要配合whereHas使用。
- 如果关联数据量较大,建议合理添加索引,比如children关联中的parent_id和status字段可以建立联合索引,提升查询效率。
- 避免在闭包中编写过于复杂的查询逻辑,复杂的过滤条件可以先封装成模型的作用域,提升代码可读性。
通过上述几种方式,我们可以灵活实现Laravel中父子关联数据的条件过滤,根据实际需求选择合适的查询方法即可。