无限极分类的原理非常简单,就是把每一个分类节点规定一个左值和右值来确定该节点在整个分类中的位置。一般的我们还会加上一个深度(层级)的值,表示他所处的深度。
这样的一个好处是对于层级很深或者节点非常多的,比较庞大的分类或其他树我们想查找或者遍历就显得非常快。例如我们要查找所有的电脑办公分类下的所有后代分类。如果你的数据只是id和parent_id约束的结构那么你先要以电脑办公的id作为parent_id查找出下一级分类然后再以下一级分类的id作为parent_id查找再下一级分类一直到找不到子分类为止。这对于深度比较大的分支来说查询就非常非常麻烦。
但是你使用树状结构来查询电脑办公分类下的所有的后代分类只要查找左值大于14右值小于23的节点即可,一次就可以查出来非常方便。
对于下边这个结构的分类树来说我们给每一个节点标出左值和右值。(其实在开发中我们不去整体的关心这些问题)
在数据表中的结构
id | parent_id | name | level | left_key | right_key | description | status |
---|---|---|---|---|---|---|---|
1 | 0 | 数码电器 | 1 | 1 | 24 | 数码电器大分类 | 1 |
20 | 1 | 家用电器 | 2 | 2 | 7 | 1 | |
21 | 20 | 大家电 | 3 | 3 | 4 | 1 | |
22 | 20 | 小家电 | 3 | 5 | 6 | 1 | |
23 | 1 | 电脑办公 | 2 | 14 | 23 | 电脑及办公电器 | 1 |
24 | 1 | 手机通讯 | 2 | 8 | 13 | 手机通讯类 | 1 |
25 | 24 | 游戏手机 | 3 | 9 | 10 | 1 | |
26 | 24 | 拍照手机 | 3 | 11 | 12 | 1 | |
27 | 23 | 台式机 | 3 | 15 | 16 | 1 | |
28 | 23 | 笔记本 | 3 | 17 | 22 | 1 | |
29 | 28 | 轻薄本 | 4 | 18 | 19 | 超极本等 | 1 |
30 | 28 | 游戏本 | 4 | 20 | 21 | 高性能游戏本 | 1 |
因为在维护数据的结构关系时会变的仅仅是level,parent_id,left_key,right_key而id和其他数据不会改变所以在使用中不会出现歧义。
主要的性能消耗是在结构的维护中,也就是说一般情况下我们会在后台来添加这些分类等数据。在我们再树的顶端插入节点或移动节点的时候会重新计算level,left_key,right_key,parent_id的时候会更新整个表或者表的一部分。但是这个操作一般在后台完成,不会对用户体验的性能造成太大的影响,在查询中我们同样可以创建一些缓存,是非常简单的。
在过去我发布过一个PHP下的实现包,但是是基于thinkphp5来实现的,为了消除其对于PHP框架的依赖性,我重构了这个包,并且在github上https://github.com/gmars/infinite-tree中重提交了该包,新的名称叫做gmars/infinite-tree 使用方法如下:
php的无限树工具包
之前我已经写过一个无限级分类的PHP包名称叫tp5-nestedsets在packagist上的安装量还是挺大的。但是tp5-nestedsets是基于tp5的很显然其灵活性不够高。而infinite-tree是一个不受框架限制的无限级分类的包。
之前发布的tp5-nestedsets有人给我留言说一般来说分类最多也只有三级而三级分类用id和parent_id来约束就可以了何必要这种无限级的包呢?
首先这个包中对于分类这种具有树关系的数据是以树关系来描述的,查找,操作都是按照树的操作方式,显然比我们根据id和parent_id来遍历要方便,而且高效。就拿简单的来说。你查一个三级分类的顶级分类的所有后代分类最多需要查询3次,递归也效率不高。但是使用本工具包一次就可以查完。其实现实中分类的层级还真的不止三级那么简单,我们所看到的三级一般都是创建了虚拟分类让用户感受到就是三级分类,事实上比这复杂得多,也可能不是这个包就能解决的问题,可能会用到特征值,搜索等。
composer require gmars/infinite-tree
如果还没有使用composer请查看composer安装的相关教程
因为要脱离具体框架所以数据库配置需要自己配置,建议大家使用时可以再稍作封装,当然不封装也可以
//这一部分是数据库配置数组
$dbConfig = [
'hostname' => '127.0.0.1',
'username' => 'root',
'password' => 'root',
'database' => 'test',
'hostport' => 3306
];
//这一部分是数据表中的键配置如果和默认一致可以不用配置
$keyConfig = [
'left_key' => 'left_key',
'right_key' => 'right_key',
'level_key' => 'level',
'primary_key' => 'id',
'parent_key' => 'parent_id'
];
$infiniteTree = new InfiniteTree('tree', $dbConfig, $keyConfig);
上边实例化时第一个参数是你的表名,第二个是数据库配置,第三个是键对应关系
如果你还没有创建数据表调用此方法会创建一个数据表。具体其他需要的字段你可以在创建完手动添加。
$infiniteTree->checkTable()
1.获取整个树结构
$infiniteTree->getTree()
2.获取$id的所有后代节点(不包含自己)
$infiniteTree->getBranch($id)
3.获取$id的所有子节点(注意只是子节点)
$infiniteTree->getChildren($id)
4.获取节点$id及所有的后代节点
$infiniteTree->getPath($id)
5.在节点 i d 下 插 入 子 节 点 包 含 扩 展 数 据 n a m e , 并 且 是 在 id下插入子节点包含扩展数据name,并且是在 id下插入子节点包含扩展数据name,并且是在id的所有子节点最后(bottom)插入如果第三个参数是top就是在所有子节点之前插入
$infiniteTree->insert($id, ['name' => '测试节点'], 'bottom')
6.把id为8的节点移动到id为1的节点下
$infiniteTree->moveUnder(8, 1)
7.把id为8的节点移动到id为2的节点前面(before)如果要移到后边是after
$infiniteTree->moveNear(8, 2, "before")