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年前 自动加精
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《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年前

这样会多次查库吧

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

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

3年前 评论

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

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

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

3年前 评论
Code_Er

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

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

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

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

3年前 评论

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

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

3年前 评论
dongzhiyu 3年前
liuyong 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年前 评论

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

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

不需要额外定义一个 allchildren

3年前 评论

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

3年前 评论
Junwind

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

2年前 评论

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