美文网首页简书付费文章Laravel
laravel生成无限级分类

laravel生成无限级分类

作者: 闲睡猫 | 来源:发表于2019-04-21 16:38 被阅读0次

完整源码在文末,并附带详细的安装说明,源码均亲测可用

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

以下用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

查看结果:

相关文章

  • laravel生成无限级分类

    完整源码在文末,并附带详细的安装说明,源码均亲测可用 无限级分类是很常见的功能,算法的好坏对于获取分类树的性能起到...

  • laravel无限级分类

    用laravel 做无限极分类;找了很多资料;假大空;有很多都是残缺的;而且达不到预期;无法做到无限极;今天分享下...

  • laravel 无限级树状分类

    1、数据库数据结构和数据 控制器代码: 结果展示:

  • 无限级分类之Laravel-nestedset扩展包的使用

    Laravel-nestedset是Laravel框架中的一个无限级分类的扩展包,它的实现有别于传统的邻接表模型,...

  • 无限级分类

    1.有两种实现方式:a.递归方式,b.迭代方式; a.递归方式:(实现家谱树和子孙树) 家谱树: /** ...

  • 2018-12-10

    复习无限级分类

  • 预排序遍历树

    什么是左右值无限级分类左右值无限级分类,也称为预排序树无限级分类,是一种有序的树状结构,位于这些树状结构中的每一个...

  • 2018 10 16

    学习商城的无限级分类

  • PHP无限级分类

    layout: posttitle: "PHP无限级分类"date: 2016-04-29 09:36:19 +0...

  • golang 无限级分类

    分类结构 分类列表如下 递归实现

网友评论

    本文标题:laravel生成无限级分类

    本文链接:https://www.haomeiwen.com/subject/gbjjgqtx.html