
本教程详细介绍了如何在 laravel 递归关系中,高效地查询并排除指定节点及其所有子孙节点的数据。通过定义 eloquent 模型中的递归关系,并结合自定义的 scope 方法和辅助函数,我们能够从复杂的层次结构数据中,精确地过滤掉特定分支,实现灵活的数据检索。文章涵盖了模型设置、核心逻辑实现、代码示例及性能优化考量。
在处理具有父子关系的层级数据时,Laravel Eloquent 提供了强大的递归关系定义能力。假设我们有一个 hobbies 表,其结构如下:
- id - name - parent_id
其中 parent_id 字段指向其父级爱好。为了在 Eloquent 模型中表示这种递归关系,我们需要在 Hobbies 模型中定义相应的关联方法:
// app/Models/Hobbies.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Hobbies extends Model
{
use HasFactory;
protected $fillable = ['name', 'parent_id'];
/**
* 获取当前爱好的所有子爱好。
*/
public function sub_hobbies()
{
return $this->hasMany(Hobbies::class, 'parent_id');
}
/**
* 获取当前爱好的父爱好。
*/
public function parent_hobbies()
{
return $this->belongsTo(Hobbies::class, 'parent_id');
}
/**
* 递归获取当前爱好的所有子孙爱好。
* 使用 with('allsub') 实现无限层级预加载。
*/
public function allsub()
{
return $this->sub_hobbies()->with('allsub');
}
/**
* 递归获取当前爱好的所有祖先爱好。
* 使用 with('allparent') 实现无限层级预加载。
*/
public function allparent()
{
return $this->parent_hobbies()->with('allparent');
}
// ... 其他方法或 Scope
}上述模型定义中,sub_hobbies 和 parent_hobbies 定义了直接的父子关系。allsub 和 allparent 方法通过 with 语句递归地加载所有子孙或祖先,这对于处理深度不确定的层级结构至关重要。
我们的目标是:给定一个爱好ID,查询所有爱好,但排除该ID对应的爱好及其所有子孙爱好。
例如,有以下爱好层级结构:
- 爱好 1
- 爱好 11
- 爱好 12
- 爱好 121
- 爱好 122
- 爱好 13
- 爱好 2
- 爱好 21
- 爱好 22
- 爱好 221
- 爱好 222
- 爱好 23
- 爱好 3
- 爱好 31
- 爱好 32
- 爱好 321
- 爱好 322
- 爱好 33如果给定“爱好 1”的ID,我们希望查询结果中不包含“爱好 1”、“爱好 11”、“爱好 12”、“爱好 121”、“爱好 122”和“爱好 13”。
为了实现上述目标,我们可以在 Hobbies 模型中添加一个局部作用域(Scope)方法 scopeIsNotLine 和一个私有辅助函数 flatten。
在 app/Models/Hobbies.php 模型中添加以下方法:
// app/Models/Hobbies.php
class Hobbies extends Model
{
// ... 其他已定义的方法
/**
* 局部作用域:查询不属于指定爱好及其子孙链的所有爱好。
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param int $id 要排除的根爱好ID
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeIsNotLine($query, $id)
{
// 1. 获取要排除的根爱好及其所有子孙爱好
// toArray() 将 Eloquent 集合转换为 PHP 数组,便于后续处理
$hobbiesToExclude = Hobbies::with('allsub')->where('id', $id)->get()->toArray();
// 2. 将嵌套的爱好数据扁平化,提取所有爱好节点的ID
// 使用 collect 辅助函数和 map 闭包来提取ID
$excludeIds = collect($this->flattenRecursiveData($hobbiesToExclude))
->map(function ($item) {
// 确保 item 是数组且包含 'id' 键
return is_array($item) && isset($item['id']) ? $item['id'] : null;
})
->filter() // 过滤掉 null 值
->flatten() // 确保结果是扁平数组
->unique() // 确保ID唯一
->all();
// 3. 执行查询:排除在 $excludeIds 列表中的所有爱好
// 示例中还包含一个 whereDoesntHave('is_archive') 条件,
// 这表示排除那些没有关联 'is_archive' 关系的爱好,
// 这是一个额外的业务逻辑,可根据实际需求移除或修改。
return $query->whereNotIn('id', $excludeIds)->whereDoesntHave('is_archive');
}
/**
* 辅助函数:将嵌套的递归结果扁平化为包含所有节点(非嵌套)的数组。
*
* 该函数会遍历输入的数组,提取每个数组元素(代表一个爱好节点)的非数组属性,
* 并递归处理其内部的嵌套数组(如 'sub_hobbies')。
*
* @param array $array 嵌套的爱好数据数组
* @return array 扁平化的爱好节点数组
*/
private function flattenRecursiveData(array $array): array
{
$result = [];
foreach ($array as $item) {
if (is_array($item)) {
// 提取当前项的非数组属性(即当前节点自身的属性,不包含嵌套关系)
$result[] = array_filter($item, function ($value) {
return !is_array($value) && !is_object($value);
});
// 递归处理当前项中的所有嵌套数组(例如 'sub_hobbies')
foreach ($item as $key => $value) {
if (is_array($value)) {
$result = array_merge($result, $this->flattenRecursiveData($value));
}
}
}
}
// 过滤掉可能产生的空数组
return array_filter($result);
}
}在控制器或任何需要查询的地方,你可以像这样使用 isNotLine 局部作用域:
use App\Models\Hobbies; // 假设要排除的爱好ID是 1 $hobbies = Hobbies::isNotLine(1)->get(); // $hobbies 集合中将包含除了 ID 为 1 及其所有子孙爱好之外的所有爱好。
通过在 Laravel Eloquent 模型中定义递归关系,并结合自定义的局部作用域和辅助函数,我们可以有效地处理复杂的层级数据查询需求,例如排除特定分支及其所有子孙节点。这种方法保持了代码的清晰性和 Eloquent 的优雅
以上就是Laravel 递归模型:实现排除特定祖先及其所有后代记录的查询的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号