在Laravel的ORM体系中,父子关联属于自关联的一种特殊形式,指的是同一个模型对应的数据表内部存在层级引用关系,通常通过表中的父级ID字段来实现关联绑定。

一、数据表结构设计
要实现父子关联,首先需要在对应数据表中添加存储父级ID的字段,以常见的分类表为例,表结构可以设计如下:
CREATE TABLE categories (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
parent_id INT DEFAULT NULL,
INDEX parent_id_index (parent_id),
FOREIGN KEY (parent_id) REFERENCES categories(id) ON DELETE SET NULL
);
其中parent_id字段用来存储当前分类的父级分类ID,如果是一级分类则该字段为NULL。
二、模型中定义父子关联
在Laravel的模型文件中,需要分别定义子关联和父关联两个方法,以Category模型为例:
<?php
namespace AppModels;
use IlluminateDatabaseEloquentModel;
class Category extends Model
{
// 定义当前分类的子分类关联
public function children()
{
// 第一个参数是关联的模型,第二个参数是子表的父级ID字段,第三个参数是当前模型的主键
return $this->hasMany(Category::class, 'parent_id', 'id');
}
// 定义当前分类的父分类关联
public function parent()
{
// 第一个参数是关联的模型,第二个参数是当前表的父级ID字段,第三个参数是关联模型的主键
return $this->belongsTo(Category::class, 'parent_id', 'id');
}
}
三、关联数据查询操作
1. 查询指定分类的所有子分类
可以通过定义的children关联方法直接获取子分类数据:
<?php
use AppModelsCategory;
// 获取ID为1的分类的所有子分类
$category = Category::find(1);
$children = $category->children;
// 如果需要预加载子分类避免N+1查询问题,可以使用with方法
$categories = Category::with('children')->where('parent_id', null)->get();
2. 查询指定分类的父分类
通过parent关联方法可以获取父分类信息:
<?php use AppModelsCategory; // 获取ID为3的分类的父分类 $category = Category::find(3); $parent = $category->parent;
3. 递归查询所有后代分类
如果需要获取某个分类的所有层级后代,可以在模型中定义递归方法:
<?php
namespace AppModels;
use IlluminateDatabaseEloquentModel;
class Category extends Model
{
// 之前的children和parent方法省略
// 递归获取所有子分类
public function allChildren()
{
return $this->children()->with('allChildren');
}
}
// 使用时获取ID为1的分类的所有后代
$category = Category::find(1);
$allChildren = $category->allChildren->get();
四、关联数据新增与修改
1. 新增子分类
可以通过关联方法直接创建子分类数据:
<?php
use AppModelsCategory;
// 给ID为1的分类新增一个子分类
$parentCategory = Category::find(1);
$parentCategory->children()->create([
'name' => '子分类测试'
]);
2. 修改分类的父级
直接修改parent_id字段即可调整分类的父子关系:
<?php use AppModelsCategory; // 把ID为3的分类的父级改为ID为2的分类 $category = Category::find(3); $category->parent_id = 2; $category->save();
五、注意事项
- 定义自关联时,
hasMany和belongsTo的参数要对应正确,尤其是外键和主键的位置不要写反 - 查询层级数据时如果没有预加载关联,会触发N+1查询问题,影响性能,建议根据场景使用
with预加载 - 如果删除父分类时希望同时删除所有子分类,可以把外键的删除规则改为
ON DELETE CASCADE,或者在模型中定义删除事件处理 - 递归查询层级数据时,如果层级过深可能会导致查询性能下降,建议根据实际业务场景限制递归深度