码迷,mamicode.com
首页 > 其他好文 > 详细

Laravel + Nestedset 扩展:嵌套集合模型实现无限级分类

时间:2020-12-23 12:43:50      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:group   boolean   ons   migrate   ctc   upd   路径   this   html   


一、两种分层数据模型

分层数据(Hierarchical Data),比如无限级分类菜单、省市区分级等,类似于树型数据结构,在MySQL 等关系型数据库中不能很自然的展示这种父-子关系,通常有两种方式实现,一种是邻接表模型 (The Adjacency List Model),另一种是嵌套集合模型 (Nested Set Model)。

技术图片
邻接表模型,至少有 id 和 parent_id 两个字段,通过父级ID(parent_id)字段值,递归,将存储的数据构建为树。

嵌套集合模型是一种新的分层数据模型,通过集合的包含关系表示分层结构,每一个分层可以用一个集合(图中的一个圈)来表示。在 MySQL 中定义两个字段 lft 和 rgt ,即集合的左值和右值来表示一个集合的范围。

技术图片
如图所示,处于层级结构顶端的 Clothing 分类包含所有的子类,因此它的左值和右值分别为 1 和 22,右值是其包含的所有节点总数的两倍。下一层级 Men‘s 和 Women‘s 两个子类的左值和右值分别为(2,9)和(10,21),每一层节点的左值和右值都根据它们包含的子层级来赋值。如果这时在 Suits 中再增加一个分类到 jackets 之后,那么新增加的分类左值和右值分别是(8,9),原来的 Suits、Men‘s、Clothing 的右值及 Women‘s 集合内的所有的集合的左值和右值都加 2 。

技术图片

如图所示,在 Laravel 框架的Nestedset 扩展模块中,默认定义了字段 _lft 和 _rgt 分别保存左值和右值,表示集合的范围(因为 left 和 right 在MySQL当中是保留字,所以不能用于字段名),还定义了字段 parent_id 用来表示父级节点。

增加节点时,新节点的所有右边节点的值都加 2。查询时按节点的边缘走,子节点的 _lft 值总是在其父节点的_lft值 和 _rgt值之间。

总之在实现无限级分类时,邻接表模型增加相对容易,但查询因为要用递归,虽然容易理解但严重影响效率。嵌套集合模型增加和修改比较复杂,但查询时比较简单。不过对于分层数据,一般在创建好之后就很少修改,而查询却要频繁的调用。一般情况下嵌套集合模型应该更适用一些吧。

在 Laravel 框架中,嵌套集合模型已经有了一个很好的“轮子”,就是 Nestedset 扩展模块,让我们可以不用去管实现的细节,简单好用,简直不要太爽啊。

二、Laravel 实现无限级分级实例
1、安装 nestedset 扩展

安装时要注意版本,查看laravel 版本命令:php artisan -v,Laravel 5.7+ 之后支持 nestedset v5 版本。

composer require kalnoy/nestedset

2、创建一个 district 模型及其数据库迁移文件。


php artisan make:model district -m

编辑生成的模型文件 district.php ,增加一行,use NodeTrait; 引入嵌套集合模型 Nestedset 扩展的 Trait ,然后 district 模型就可以直接使用 Nestedset 扩展定义的方法了,是的,就是这么简单。

编辑生成的数据库迁移文件,迁移位于database/migrations目录下,文件名形如 2020_05_20_094506_create_districts_table.php ,在 up 方法的 schema 构建器中增加一行:$table->nestedSet(); 是 nestedset 扩展用来生成_lft _rgt parent_id 三个字段,用嵌套集模型实现无限级分类。


public function up()
{
        Schema::table(‘districts‘, function (Blueprint $table) {
            $table->increments(‘id‘);
            $table->string(‘name‘,64)->comment(‘名称‘);
            // 嵌套集模型,无限级分类,nestedset扩展,增加了三个字段:_lft _rgt parent_id ,类型 int 长度10
            $table->nestedSet();
            $table->unsignedSmallInteger(‘type‘)->nullable()->comment(‘机构类别_省市县乡学区_12345_五类,允许值为NULL‘);
            $table->boolean(‘status‘)->default(1)->comment(‘启用或禁用状态,默认值为1(启用)‘);
        });
    }

运行迁移,将字段添加到 districts 表中


php artisan migrate

3、配置路由,编辑 routes/api.php 文件,创建 restfull 风格的路由。


Route::middleware(‘auth:api‘)->prefix(‘v1‘)->group(function() {
    // 区域
    Route::apiResource(‘districts‘, ‘DistrictController‘);
    // ......
});

4、生成Rest 风格的资源控制器文件。

php artisan make:controller DistrictController --resource

编辑控制器文件,为了简单,对代码做了简化,没有做数据校验及权限验证,并且已经对返回的 json 格式数据做了全局处理,测试客户端是 Postman,最后的前端展示是 Element。

5、查询操作。编辑 index 方法


public function index(Request $request)
{   
        // 从当前认证用户信息中获取用户所在区域、用户角色
        $user = $request->user(‘api‘);
        $userDistrictID = 55;
        $isSuperAdmin = true;

        if ($isSuperAdmin) {
            $result = District::get()->toTree(); // 获取全部节点的集合,将它转化为树
        } else {
            $result = District::descendantsAndSelf($userDistrictID)->toTree();  // 按节点 id 获取包含自身在内的所有子节点
        }

        return $this->success($result);
}

这里用了两个方法,District::get()->toTree(); // 获取全部节点的集合,将它转化为树。District::descendantsAndSelf($userDistrictID)->toTree(); // 按节点 id 获取包含自身在内的所有子节点,并转化为树。

get 请求路径 /api/v1/districts ,返回结果示例:


{
    "status": "success",
    "code": 200,
    "data": {
        "name": "北京市",
        "parent_id": null,
        "_lft": 11,
        "_rgt": 16,
        "id": 37,
        "children": [
            {
                "name": "东城区",
                "parent_id": 37,
                "_lft": 12,
                "_rgt": 13,
                "id": 38,
                "children": []
            },
            {
                "name": "西城区",
                "parent_id": 37,
                "_lft": 14,
                "_rgt": 15,
                "id": 39,
                "children": []
            }
        ]
    }
}

编辑 show 方法:


public function show($id)
{
        //
        $result = District::find($id);
        return $this->success($result);
    }

get 请求,路径 /api/v1/districts/55 (其中55是区域的 ID)

6、修改 update 方法,实现修改节点名称的功能。


public function update(Request $request, $id)
{
        // put 请求,修改名称
        $node = District::find($id); // 要修改的节点
        $node->name = $request->name;
        $node->save();

        return $this->success($node);
    }

put 请求,路径 /api/v1/districts/55 (其中55是区域的 ID)

7、编辑 store 方法,实现增加节点的功能。前面说过,增加节点相对复杂一些。先给出三个示例数组,用来展示增加节点时的数据结构。在一次添加操作中,只能有一个根节点,可以有多个后代节点。nestArrA 用来展示添加多个后代节点,其中第一个键 name 就是根节点的名称。nestArrC 用来展示添加子节点,只能一次添加一个子节点,其中 ‘parent_id‘ => 55,表示要添加的父节点的 ID。


// 数据示例,创建节点,第一个 name 是root节点
        $nestArrA = [
            ‘name‘ => ‘甘肃省‘,
            ‘children‘ => [
                [
                    ‘name‘ => ‘兰州市‘,
                    ‘children‘ => [
                        [
                            ‘name‘ => ‘城关区‘,
                        ],
                    ],
                ],
                [
                    ‘name‘ => ‘陇南市‘,
                    ‘children‘ => [
                        [
                            ‘name‘ => ‘武都区‘,
                        ],
                        [
                            ‘name‘ => ‘文县‘,
                        ],
                    ],
                ],
            ],
        ];

        // 数据示例,创建节点,多个子节点
        $nestArrB = [
            ‘name‘ => ‘北京市‘,
            ‘children‘ => [
                [
                    ‘name‘ => ‘东城区‘,
                ],
                [
                    ‘name‘ => ‘西城区‘,
                ],
            ],
        ];

        // 添加子节点示例数据,一次只能添加一条node
        $nestArrC = [
                ‘parent_id‘ => 55,
                ‘node‘ => [
                    ‘name‘ => ‘柏林学区‘,
                    ‘type‘ => ‘5‘
                ],
            ];

post 请求,路径 /api/v1/districts ,发送的 json 格式数据示例:

{
    "parent_id":"35",
  "node":
    {
      "name":"武都区"
    }
}

三、前端实现
前端使用 Element 框架实现,代码太多,只用图片展示一下完成的效果。

技术图片
技术图片
参考资料:

laravel-nestedset扩展:
https://github.com/lazychaser/laravel-nestedset

laravel-nestedset:多级无限分类正确姿势
https://segmentfault.com/a/1190000012986277
在 MySql 中管理分层数据(译文:Yimin):
https://www.cnblogs.com/phaibin/archive/2009/06/09/1499687.html
维基百科_嵌套集合模型
https://en.wikipedia.org/wiki/Nested_set_model
分层数据 Hierarchical Data 探索 (3.嵌套集合模型) 无限极分类
https://segmentfault.com/a/1190000021727382

Laravel + Nestedset 扩展:嵌套集合模型实现无限级分类

标签:group   boolean   ons   migrate   ctc   upd   路径   this   html   

原文地址:https://blog.51cto.com/15057852/2567068

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!