前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >laravel生成无限级分类

laravel生成无限级分类

作者头像
章鱼喵
发布2019-05-06 17:38:30
2.7K0
发布2019-05-06 17:38:30
举报
文章被收录于专栏:codingcodingcoding

无限级分类是很常见的功能,算法的好坏对于获取分类树的性能起到决定性的作用。尤其当分类数据和层级多时,一个糟糕的算法将使服务器不堪重负

以下用laravel实现无限级分类功能,包括:

  • 数据表设计
  • 填充模拟数据
  • 生成分类树
  • 分类树的后台维护

数据表设计

字段名

描述

id

主键id

name

类目名称

parent_id

父类目 ID

is_directory

是否拥有子类目

level

当前类目层级

path

该类目所有父类目 id

为什么要用level与path

无限级分类中,我们经常需要获取一个分类的所有祖先类目或者后代类目,以及判断两个类目是否存在层级关系。倘若都使用递归查询,会产生极多的sql查询。level和path这两个冗余字段便应运而生

以下面的模拟数据为例:

[
    [
        "id" => 1,
        "name" => "手机配件",
        "parent_id" => null,
        "level" => 0,
        "path" => "-"
    ],
    [
        "id" => 2,
        "name" => "耳机",
        "parent_id" => 1,
        "level" => 1,
        "path" => "-1-"
    ],
    [
        "id" => 3,
        "name" => "蓝牙耳机",
        "parent_id" => 2,
        "level" => 2,
        "path" => "-1-2-"
    ],
    [
        "id" => 4,
        "name" => "移动电源",
        "parent_id" => 1,
        "level" => 1,
        "path" => "-1-"
    ],
];

目录树

  • 场景1:查询蓝牙耳机的所有祖先类目

根据path字段的值获取其祖先id为[1, 2],用 Category::whereIn('id', [1, 2])->orderBy('level')->get() 即可获取结果

  • 场景2:查询手机配件的所有后代类目

id字段追加到path字段,得到-1-, 用Category::where('path', 'like', '-1-%')->get() 即可获取结果

  • 场景3:判断移动电源蓝牙耳机是否有层级关系
$highLevelPath = '-1-2'; // 蓝牙耳机的path
$lowLevelPath = '-1-' . '4-'; // 移动电源的path值拼接id值
if (strpos($highLevelPath, $lowLevelPath) === 0) { // 判断蓝牙耳机的path值是否以移动电源的path值为开头
    echo '存在层级关系';
} else {
    echo '并无层级关系';
}

创建数据表

$ php artisan make:model Models/Category -m

编写迁移文件:database/migrations/2019_04_21_140243_create_categories_table.php

    public function up()
    {
        Schema::create('categories', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name')->comment('分类名称');
            $table->unsignedInteger('parent_id')->nullable()->comment('父类id');
            $table->foreign('parent_id')->references('id')->on('categories')->onDelete('cascade');
            $table->boolean('is_directory')->comment('是否有子类目');
            $table->unsignedInteger('level')->comment('当前类目层级');
            $table->string('path')->comment('该分类的所有父类id, 用 - 连接');
            $table->timestamps();
        });
    }

执行迁移创建数据表

$ php artisan migrate

填充模拟数据

调整模型类app/Models/Category.php代码:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Category extends Model
{
    protected $fillable = ['name', 'is_directory', 'level', 'path'];
    protected $casts = [
        'is_directory' => 'boolean',
    ];
    protected static function boot()
    {
        parent::boot();
        // 当创建Category时,自动初始化 path 和 level
        static::creating(function (Category $category) {
            if (is_null($category->parent_id)) { // 创建的是根目录
                $category->level = 0; // 将层级设为0
                $category->path = '-'; // 将 path 设为 -
            } else { // 创建的并非根目录
                $category->level = $category->parent->level + 1; // 将层级设为父类层级+1
                $category->path = $category->parent->path . $category->parent_id . '-'; // 将path值设为父类path+父类id
            }
        });
    }

    public function parent()
    {
        return $this->belongsTo(Category::class);
    }

    public function children()
    {
        return $this->hasMany(Category::class, 'parent_id');
    }

    /**
     * 获取所有祖先分类id
     * @date 2019-04-21
     */
    public function getPathIdsAttribute()
    {
        $path = trim($this->path, '-'); // 过滤两端的 -
        $path = explode('-', $path); // 以 - 为分隔符切割为数组
        $path = array_filter($path); // 过滤空值元素
        return $path;
    }

    /**
     * 获取所有祖先分类且按层级正序排列
     * @date 2019-04-21
     */
    public function getAncestorsAttribute()
    {
        return Category::query()
            ->whereIn('id', $this->path_ids) // 调用 getPathIdsAttribute 获取祖先类目id
            ->orderBy('level') // 按层级排列
            ->get();
    }

    /**
     * 获取所有祖先类目名称以及当前类目的名称
     * @date 2019-04-21
     */
    public function getFullNameAttribute()
    {
        return $this->ancestors // 调用 getAncestorsAttribute 获取祖先类目
            ->pluck('name') // 将所有祖先类目的 name 字段作为一个数组
            ->push($this->name) // 追加当前类目的name字段到数组末尾
            ->implode(' - '); // 用 - 符号将数组的值组装成一个字符串
    }

}

创建填充文件:

$ php artisan make:seeder CategoriesSeeder

调整database/seeds/CategoriesSeeder.php代码:

<?php

use Illuminate\Database\Seeder;
use App\Models\Category;

class CategoriesSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $categories = [
            [
                'name'     => '手机配件',
                'children' => [
                    ['name' => '手机壳'],
                    ['name' => '贴膜'],
                    ['name' => '存储卡'],
                    ['name' => '数据线'],
                    ['name' => '充电器'],
                    [
                        'name'     => '耳机',
                        'children' => [
                            ['name' => '有线耳机'],
                            ['name' => '蓝牙耳机'],
                        ],
                    ],
                ],
            ],
            [
                'name'     => '电脑配件',
                'children' => [
                    ['name' => '显示器'],
                    ['name' => '显卡'],
                    ['name' => '内存'],
                    ['name' => 'CPU'],
                    ['name' => '主板'],
                    ['name' => '硬盘'],
                ],
            ],
            [
                'name'     => '电脑整机',
                'children' => [
                    ['name' => '笔记本'],
                    ['name' => '台式机'],
                    ['name' => '平板电脑'],
                    ['name' => '一体机'],
                    ['name' => '服务器'],
                    ['name' => '工作站'],
                ],
            ],
            [
                'name'     => '手机通讯',
                'children' => [
                    ['name' => '智能机'],
                    ['name' => '老人机'],
                    ['name' => '对讲机'],
                ],
            ],
        ];
        foreach ($categories as $data) {
            $this->createCategory($data);
        }
    }

    protected function createCategory($data, $parent = null)
    {
        $category = new Category(['name' => $data['name']]);
        $category->is_directory = isset($data['children']);
        if (!is_null($parent)) { // 如果存在parent,代表有父类目
            $category->parent()->associate($parent);
        }
        $category->save(); // 数据入库
        if (isset($data['children']) && is_array($data['children'])) {
            foreach ($data['children'] as $child) {
                // 递归调用 createCategory 方法
                $this->createCategory($child, $category); // $category 为刚创建的类目,作为子类目的父级类目参数
            }
        }
    }

}

执行数据填充:

$ php artisan db:seed --class=CategoriesSeeder

查看结果:

数据填充结果

生成分类树

分类树是一个通用的功能,适合将其封装为一个服务,创建CategoryService

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019.04.21 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 数据表设计
    • 为什么要用level与path
      • 创建数据表
      • 填充模拟数据
      • 生成分类树
      相关产品与服务
      数据库
      云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档