Api接口实战:无限极分类实现

最近开发商品功能,在尝试递归和引用方式后,蓦然回首,突然发现laravel框架有更简单高效的实现方式,无限极分类最佳实践,open code与大家共享!感兴趣的Mark一下,谢谢~

表结构如下:

CREATE TABLE `goods_category` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `name` varchar(500) DEFAULT '' COMMENT '分类名称',
  `pid` int(5) unsigned DEFAULT '0' COMMENT '父级id',
  `level` tinyint(3) unsigned DEFAULT '1' COMMENT '分类等级',
  `status` tinyint(3) unsigned DEFAULT '0' COMMENT '分类状态:0-禁用,1-正常',
  `created_at` timestamp NULL DEFAULT NULL COMMENT '创建时间',
  `updated_at` timestamp NULL DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `status` (`status`)
) ENGINE=InnoDB AUTO_INCREMENT=32 DEFAULT CHARSET=utf8mb4 COMMENT='商品分类表';

数据存储格式:

Laravel框架无限极分类简单实现

业务代码:

    // 模型文件
    public function children() {
        return $this->hasMany(get_class($this), 'pid' ,'id');
    }

    public function allChildren() {
        return $this->children()->with( 'allChildren' );
    }
// 控制器
$list = GoodsCategory::with('allChildren')->first();
dd($list);

处理后数据:

Laravel框架无限极分类简单实现

至此,laravel框架无限极分类实现完毕,相比递归和引用实现无限极分类的两种方式,是不是简单高效很多呢,关于更多laravel特性,欢迎评论区留言探讨。
参考文档:laravel5.4实现无限级分类

欢迎关注公众号

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 3年前 自动加精
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 35

试试这两个方法,不用多次查询数据库

/**
     * 一维数据数组生成数据树
     * @param array $list 数据列表
     * @param string $id 父ID Key
     * @param string $pid ID Key
     * @param string $son 定义子数据Key
     * @return Collection
     */
    public static function arr2tree($list, $id = 'id', $pid = 'pid', $son = 'sub')
    {
        list($tree, $map) = [[], []];
        foreach ($list as $item) {
            $map[$item[$id]] = $item;
        }

        foreach ($list as $item) {
            if (isset($item[$pid]) && isset($map[$item[$pid]])) {
                $map[$item[$pid]][$son][] = &$map[$item[$id]];
            } else {
                $tree[] = &$map[$item[$id]];
            }
        }
        unset($map);
        return $tree;
    }

    /**
     * 一维数据数组生成数据树
     * @param array $list 数据列表
     * @param string $id ID Key
     * @param string $pid 父ID Key
     * @param string $path
     * @param string $ppath
     * @return array
     */
    public static function arr2table(array $list, $id = 'id', $pid = 'pid', $path = 'path', $ppath = '')
    {
        $tree = [];
        foreach (self::arr2tree($list, $id, $pid) as $attr) {
            $attr[$path] = "{$ppath}-{$attr[$id]}";
            $attr['sub'] = isset($attr['sub']) ? $attr['sub'] : [];
            $attr['spt'] = substr_count($ppath, '-');
            $attr['spl'] = str_repeat(" ├ ", $attr['spt']);
            $sub         = $attr['sub'];
            unset($attr['sub']);
            $tree[] = $attr;
            if (!empty($sub)) {
                $tree = array_merge($tree, self::arr2table($sub, $id, $pid, $path, $attr[$path]));
            }
        }
        return $tree;
    }
3年前 评论
miaotiao 3年前
PHPer技术栈 (楼主) 3年前
miaotiao 3年前
NiZerin 3年前
leifuping 3年前
aa24615 3年前
wangu7 3年前
麻雀在森林 2年前

用sql做递归第二天就被开了,在实现业务的同时更要注重性能

3年前 评论

试试这两个方法,不用多次查询数据库

/**
     * 一维数据数组生成数据树
     * @param array $list 数据列表
     * @param string $id 父ID Key
     * @param string $pid ID Key
     * @param string $son 定义子数据Key
     * @return Collection
     */
    public static function arr2tree($list, $id = 'id', $pid = 'pid', $son = 'sub')
    {
        list($tree, $map) = [[], []];
        foreach ($list as $item) {
            $map[$item[$id]] = $item;
        }

        foreach ($list as $item) {
            if (isset($item[$pid]) && isset($map[$item[$pid]])) {
                $map[$item[$pid]][$son][] = &$map[$item[$id]];
            } else {
                $tree[] = &$map[$item[$id]];
            }
        }
        unset($map);
        return $tree;
    }

    /**
     * 一维数据数组生成数据树
     * @param array $list 数据列表
     * @param string $id ID Key
     * @param string $pid 父ID Key
     * @param string $path
     * @param string $ppath
     * @return array
     */
    public static function arr2table(array $list, $id = 'id', $pid = 'pid', $path = 'path', $ppath = '')
    {
        $tree = [];
        foreach (self::arr2tree($list, $id, $pid) as $attr) {
            $attr[$path] = "{$ppath}-{$attr[$id]}";
            $attr['sub'] = isset($attr['sub']) ? $attr['sub'] : [];
            $attr['spt'] = substr_count($ppath, '-');
            $attr['spl'] = str_repeat(" ├ ", $attr['spt']);
            $sub         = $attr['sub'];
            unset($attr['sub']);
            $tree[] = $attr;
            if (!empty($sub)) {
                $tree = array_merge($tree, self::arr2table($sub, $id, $pid, $path, $attr[$path]));
            }
        }
        return $tree;
    }
3年前 评论
miaotiao 3年前
PHPer技术栈 (楼主) 3年前
miaotiao 3年前
NiZerin 3年前
leifuping 3年前
aa24615 3年前
wangu7 3年前
麻雀在森林 2年前

用sql做递归第二天就被开了,在实现业务的同时更要注重性能

3年前 评论

zhuanlan.zhihu.com/p/33279392 laravel-nestedset:多级无限分类正确姿势

用过这个也挺好用的,也是基于左右两个节点,效率高速度快,提供丰富的API查询和插入。

3年前 评论
dongzhiyu 3年前
liuyong 3年前

试过这个方法,在有 3000 条数据的情况下,处理时间太长,不可取。

3年前 评论
PHPer技术栈 (楼主) 3年前
WmKong (作者) 3年前

这样设计无限分类都是唠嗑有问题的,使用二叉树来设计,效果高,查询快,左右节点 my.oschina.net/corwien/blog/687664

3年前 评论

通过保存path去完成高性能树的建立,具体可参考:lumina.xbhub.com/docs/zh/guide/trai...

<?php

namespace Modules\Core\Traits;

use Exception;
use Illuminate\Support\Facades\DB;
use Modules\Core\Utils\Tree;

trait HasPathTree
{
    /**
     * Create the event listeners for the saving and saved events
     * This lets us save revisions whenever a save is made, no matter the
     * http method.
     *
     */
    public static function bootHasPathTree()
    {
        static::created(function($model){
            $tablename = $model->getTable();
            $model->level = 1;
            $model->path = '/0/'.$model->id.'/';
            if($model->parentid > 0){
                $parent = DB::table($tablename)->find($model->parentid);
                if($parent){
                    $model->path = $parent->path.$model->id.'/';
                    $model->level = $parent->level + 1;
                }
            }
            $model->save();
        });

        static::updated(function($model) {
            $tablename = $model->getTable();
            $original = $model->getOriginal();

            if(isset($original['parentid']) && $model->parentid != $original['parentid']) {
                // 树结构发生变更
                $parent = DB::table($tablename)->find($model->parentid);
                $old_parent = DB::table($tablename)->find($original['parentid']);

                if($model->parentid==0 || !$old_parent) {
                    $model->path = '/0/';
                    $model->level = 1;
                }else{
                    $level_delta = $old_parent->level - $parent->level;
                    $categories = DB::table($tablename)->where('path', 'like', '%/'.$model->id.'/%')->get();
                    foreach ($categories as $_category){
                        DB::table($tablename)->where('id', $_category->id)->update([
                            'path' =>  str_replace($old_parent->path, $parent->path, $_category->path),
                            'level' => $_category->level - $level_delta
                        ]);
                    }
                }
            }
        });

        static::deleted(function ($model) {
            $tablename = $model->getTable();
            DB::table($tablename)->where('path', 'like', '%/'.$model->id.'/%')->delete();
        });
    }

3年前 评论

用 ClosureTable 可以缩减到两次查询获取到所有子集/父级数据

www.yuque.com/docs/share/9852f2aa-...

3年前 评论
Junwind

直接先把mysql中的所有数据先查出来,利用php处理,结果可以加缓存

2年前 评论

www.phpbloger.com/article/50.html#...
这篇文章博主分析了四种方法优缺点,根据自己业务需求选择。

3年前 评论
public function children() {
    return $this->hasMany(get_class($this), 'pid' ,'id')->with('children');
}

不需要额外定义一个 allchildren

3年前 评论
Code_Er

我感觉性能上用文件静态化来解决

3年前 评论
PHPer技术栈 (楼主) 3年前
Code_Er (作者) 3年前

这样的话子查询了,建议还是用php处理比较好

3年前 评论

这样会多次查库吧

3年前 评论
PHPer技术栈 (楼主) 3年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!