0

0

Laravel 递归关系中排除指定分支的教程

霞舞

霞舞

发布时间:2025-12-01 12:27:01

|

852人浏览过

|

来源于php中文网

原创

Laravel 递归关系中排除指定分支的教程

本教程旨在解决laravel中处理递归关系时,如何有效地排除特定节点及其所有子孙节点的问题。通过利用eloquent的递归关系加载能力、自定义的数组扁平化辅助函数以及`wherenotin`查询,我们将展示一种在数据库层面高效过滤出非指定分支数据的方法,从而实现对复杂层级数据的精准控制。

在构建具有层级结构的数据模型时,例如分类、评论回复或如本例中的兴趣爱好(hobbies),我们经常会遇到需要对这些层级数据进行特定过滤的需求。一个常见的场景是,给定一个父节点,我们需要获取所有不属于该父节点及其任何子孙节点的数据。本教程将详细介绍如何在Laravel中实现这一功能。

数据模型与递归关系定义

首先,我们假设有一个hobbies表,其结构包含id、name和parent_id字段,其中parent_id用于建立自引用关系,表示层级结构。

CREATE TABLE hobbies (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(255) NOT NULL,
    parent_id INT NULL,
    FOREIGN KEY (parent_id) REFERENCES hobbies(id) ON DELETE CASCADE
);

在Laravel的Hobbies模型中,我们需要定义递归关系来方便地访问父级和子级:

hasMany(Hobbies::class, 'parent_id');
    }

    /**
     * 定义父兴趣爱好关系。
     */
    public function parent_hobbies()
    {
        return $this->belongsTo(Hobbies::class, 'parent_id');
    }

    /**
     * 定义所有子孙兴趣爱好(递归子级)关系。
     */
    public function allsub()
    {
        return $this->sub_hobbies()->with('allsub');
    }

    /**
     * 定义所有祖先兴趣爱好(递归父级)关系。
     */
    public function allparent()
    {
        return $this->parent_hobbies()->with('allparent');
    }

    // ... 其他方法将在下面定义
}

allsub()方法是实现递归加载所有子孙节点的关键,它通过with('allsub')实现了自递归加载。

排除指定分支的实现策略

我们的目标是,给定一个兴趣爱好ID,获取所有不属于该ID及其所有子孙节点的兴趣爱好。实现这一目标的策略分为以下几步:

  1. 获取目标分支: 首先,查询指定ID的兴趣爱好,并利用allsub关系加载其所有子孙节点。
  2. 扁平化结果: 将包含多层嵌套关系的查询结果扁平化,转换为一个包含所有相关节点(包括目标节点及其所有子孙)的单一数组。
  3. 提取ID列表: 从扁平化后的数组中提取所有节点的id。
  4. 执行排除查询: 使用whereNotIn条件,从数据库中查询所有id不在上述列表中的兴趣爱好。

为了封装这一逻辑,我们将在Hobbies模型中添加一个局部作用域(Local Scope)和私有辅助方法。

1. 辅助方法:扁平化嵌套数组

由于Eloquent的with递归加载会返回嵌套的数组结构,我们需要一个方法来将其转换为一个扁平的列表,以便提取所有节点的ID。

Mistral AI
Mistral AI

Mistral AI被称为“欧洲版的OpenAI”,也是目前欧洲最强的 LLM 大模型平台

下载
    /**
     * 辅助方法:将嵌套的Eloquent集合或数组扁平化为单一数组。
     * 仅保留非数组字段,并合并所有层级的数据。
     *
     * @param array|\Illuminate\Support\Collection $items
     * @return array
     */
    private function flattenRecursiveData($items)
    {
        $result = [];
        foreach ($items as $item) {
            // 如果是Eloquent模型实例,先转换为数组
            $itemArray = $item instanceof Model ? $item->toArray() : (array)$item;

            // 提取当前层级的非关系字段
            $currentLevelData = array_filter($itemArray, function ($value) {
                return !is_array($value) && !($value instanceof \Illuminate\Support\Collection);
            });
            if (!empty($currentLevelData)) {
                $result[] = $currentLevelData;
            }

            // 递归处理嵌套关系数据
            foreach ($itemArray as $key => $value) {
                if (is_array($value) || $value instanceof \Illuminate\Support\Collection) {
                    $result = array_merge($result, $this->flattenRecursiveData($value));
                }
            }
        }
        return $result;
    }

方法说明:flattenRecursiveData方法迭代输入的数组或集合。对于每个元素,它首先提取非数组(非关系)的字段,将其作为一个独立的项添加到结果中。然后,它递归地处理所有嵌套的数组或集合(即关系数据),将它们的结果也合并到最终的扁平化数组中。

2. 局部作用域:scopeIsNotLine

现在,我们可以使用上述辅助方法来创建我们的局部作用域scopeIsNotLine。

    /**
     * 局部作用域:查询所有不属于指定ID及其子孙节点的兴趣爱好。
     *
     * @param \Illuminate\Database\Eloquent\Builder $query
     * @param int $id 要排除的根节点ID
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeIsNotLine(Builder $query, int $id): Builder
    {
        // 1. 获取要排除的根节点及其所有子孙节点
        $hobbiesToExclude = Hobbies::with('allsub')->where('id', $id)->get();

        // 2. 扁平化结果,获取所有相关节点的数组表示
        $flattenedHobbies = $this->flattenRecursiveData($hobbiesToExclude);

        // 3. 提取所有要排除的节点的ID
        $excludeIds = collect($flattenedHobbies)->map(function ($item) {
            return $item['id'] ?? null; // 确保ID存在
        })->filter()->unique()->values()->all(); // 过滤空值,去重并重置索引

        // 4. 执行排除查询
        return $query->whereNotIn('id', $excludeIds);
    }

局部作用域说明:

  1. Hobbies::with('allsub')->where('id', $id)->get():这行代码查询了指定$id的兴趣爱好,并通过allsub递归加载了其所有的子孙节点。
  2. $this->flattenRecursiveData($hobbiesToExclude):调用我们之前定义的辅助方法,将嵌套的Eloquent集合转换为一个扁平的数组。
  3. collect($flattenedHobbies)->map(...):使用Laravel的集合操作,从扁平化后的数组中提取所有id,并进行过滤、去重,确保得到一个干净的ID列表。
  4. return $query->whereNotIn('id', $excludeIds):这是核心的过滤操作。它会返回所有id不在$excludeIds列表中的兴趣爱好。

使用示例

现在,你可以在任何地方方便地调用这个局部作用域来获取所需的数据:

get();

// $filteredHobbies 将包含所有不属于ID为1的兴趣爱好及其子孙的数据
foreach ($filteredHobbies as $hobby) {
    echo "ID: {$hobby->id}, Name: {$hobby->name}\n";
}

注意事项与性能考量

  • 数据量与深度: 如果你的层级结构非常深,或者每个节点有大量的子节点,with('allsub')的递归加载可能会导致生成非常大的查询结果集,占用较多内存。对于极端情况,可以考虑使用数据库的递归CTE(Common Table Expressions)功能,但这会使代码与数据库方言耦合。
  • 缓存: 对于不经常变化的层级数据,可以考虑将排除列表或最终结果进行缓存,以提高性能。
  • flattenRecursiveData的优化: flattenRecursiveData方法在处理大量数据时可能会有性能开销,因为它涉及多次数组合并和迭代。对于非常大的数据集,可能需要更优化的扁平化策略。
  • 额外条件: 在原始问题中提到了whereDoesntHave('is_archive')。你可以在isNotLine作用域之后链式调用其他查询条件,例如:
    $filteredHobbies = Hobbies::isNotLine($idToExclude)->whereDoesntHave('is_archive')->get();

总结

本教程提供了一种在Laravel中处理递归关系并排除特定分支的实用方法。通过结合Eloquent的递归关系加载、自定义的数组扁平化辅助函数以及whereNotIn查询,我们能够以清晰和相对高效的方式实现对复杂层级数据的精准过滤。这种方法在大多数应用场景下都非常有效,并且保持了Laravel Eloquent的优雅性。在面对极大数据量或深度时,可以进一步考虑数据库层面的优化方案。

相关专题

更多
laravel组件介绍
laravel组件介绍

laravel 提供了丰富的组件,包括身份验证、模板引擎、缓存、命令行工具、数据库交互、对象关系映射器、事件处理、文件操作、电子邮件发送、队列管理和数据验证。想了解更多laravel的相关内容,可以阅读本专题下面的文章。

316

2024.04.09

laravel中间件介绍
laravel中间件介绍

laravel 中间件分为五种类型:全局、路由、组、终止和自定。想了解更多laravel中间件的相关内容,可以阅读本专题下面的文章。

274

2024.04.09

laravel使用的设计模式有哪些
laravel使用的设计模式有哪些

laravel使用的设计模式有:1、单例模式;2、工厂方法模式;3、建造者模式;4、适配器模式;5、装饰器模式;6、策略模式;7、观察者模式。想了解更多laravel的相关内容,可以阅读本专题下面的文章。

369

2024.04.09

thinkphp和laravel哪个简单
thinkphp和laravel哪个简单

对于初学者来说,laravel 的入门门槛较低,更易上手,原因包括:1. 更简单的安装和配置;2. 丰富的文档和社区支持;3. 简洁易懂的语法和 api;4. 平缓的学习曲线。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

370

2024.04.10

laravel入门教程
laravel入门教程

本专题整合了laravel入门教程,想了解更多详细内容,请阅读专题下面的文章。

81

2025.08.05

laravel实战教程
laravel实战教程

本专题整合了laravel实战教程,阅读专题下面的文章了解更多详细内容。

64

2025.08.05

laravel面试题
laravel面试题

本专题整合了laravel面试题相关内容,阅读专题下面的文章了解更多详细内容。

67

2025.08.05

golang map内存释放
golang map内存释放

本专题整合了golang map内存相关教程,阅读专题下面的文章了解更多相关内容。

75

2025.09.05

高德地图升级方法汇总
高德地图升级方法汇总

本专题整合了高德地图升级相关教程,阅读专题下面的文章了解更多详细内容。

42

2026.01.16

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PHP课程
PHP课程

共137课时 | 8.8万人学习

JavaScript ES5基础线上课程教学
JavaScript ES5基础线上课程教学

共6课时 | 7.5万人学习

PHP新手语法线上课程教学
PHP新手语法线上课程教学

共13课时 | 0.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号