优雅的树形数据结构管理包,基于Closure Table
模式设计.php
github 欢迎不吝Starnode
优雅的树形数据设计模式laravel
数据和结构分表,操做数据不影响结构git
一个Eloquent Trait操做简单github
无需修改表,兼容旧数据数据库
完善的树操做方法设计模式
支持生成树形数据数组
支持多棵树并存(多个根)bash
支持节点/树修复数据结构
支持软删除
php > 5.6.0
laravel > 5.1.0
Closure Table
Closure table is a simple and elegant way of storing and querying hierarchical data in any RDBMS. By hierarchical data we mean a set of data that has some parent – child relationship among them. We use the word ‘tree’ instead of hierarchies commonly. As an example we may take the relationships between geographic locations like ‘Countries’, ‘States/ Province’, ‘Districts/ Cities’ etc.
Closure Table
将树中每一个节点与其后代节点的关系都存储了下来,
这将须要一个存储相互关系的表name_closure
.
部门表:
id | name |
---|---|
1 | 总经理 |
2 | 副总经理 |
3 | 行政主管 |
4 | 文秘 |
一个基本的closure
表包含ancestor
,descendant
,distance
3个字段,如:
ancestor | descendant | distance |
---|---|---|
1 | 1 | 0 |
1 | 2 | 1 |
1 | 3 | 2 |
1 | 4 | 3 |
2 | 2 | 0 |
2 | 3 | 1 |
2 | 4 | 2 |
3 | 3 | 0 |
3 | 4 | 1 |
4 | 4 | 0 |
这个表记录了每一个部门之间的关系,而且还记录了一条自身的关联.
ClosureTable
提供了大量方法操做树.
<?php $menu = Menu::find(10); // 将$menu做为根,return bool $menu->makeRoot(); // 建立一个子级节点,return new model $menu->createChild($attributes); // 建立一个新的菜单,此时该菜单无任何关联,return model $child = Menu::create($attributes); // 将一个已存在的菜单添加到子级,$child可为模型实例、模型实例集合或id、包含id的数组,return bool $menu->addChild($child); $menu->addChild(12); $menu->addChild('12'); $menu->addChild([3, 4, 5]); // 移动到$parent的下级,后代也将随之移动,$parent可为模型实例或id,return bool $menu->moveTo($parent); $menu->moveTo(2); $menu->moveTo('2'); // 同moveTo() $menu->addTo($parent); // 添加一个或多个同级节点,$siblings的后代也将随之移动,$siblings可为模型实例集合或id、包含id的数组,return bool $menu->addSibling($siblings); $menu->addSibling(2); $menu->addSibling('2'); $menu->addSibling([2,3,4]); // 新建一个同级节点,return new model $menu->createSibling($attributes); // 创建一个自身的关联,return bool $menu->attachSelf(); // 解除自身的全部关联,而且解除后代的全部关联(这个操做不保留子树,将使本身和全部后代都成孤立状态),return bool $menu->detachSelf();
<?php $menu = Menu::find(3); // 获取全部后代,return model collection $menu->getDescendants(); // 获取全部后代,包括本身,return model collection $menu->getDescendantsAndSelf(); // 获取全部祖先,return model collection $menu->getAncestors(); // 获取全部祖先,包括本身,return model collection $menu->getAncestorsAndSelf(); // 获取全部儿女(直接下级),return model collection $menu->getChildren(); // 获取父辈(直接上级),return model $menu->getParent(); // 获取祖先(根),return model $menu->getRoot(); // 获取全部兄弟姐妹,return model collection $menu->getSiblings(); //获取全部兄弟姐妹包括本身,return model collection $menu->getSiblingsAndSelf(); // 获取全部孤立节点 Menu::getIsolated(); Menu::isolated()->where('id', '>', 5)->get(); // 获取全部根 Menu::getRoots();
以上get...()
方法都包含一个query构造器,如getDescendants()
对应有一个queryDescendants
,这使得你能够在查询中加入条件查询或排序
你能够这样使用$menu->queryDescendants()->where('id', '>', 5)->orderBy('sort','desc')->get();
getRoot()
,getParent()
,getRoots()
,getIsolated()
4个方法没有query构造器
若是你想获取只包含单个或多个列的结果能够在get...()
方法里传入参数,如:$menu->getAncestors(['id','name']);
因为数据库不须要parent_id
列,若是你想在结果中显示包含此列的内容能够在构造器后加入withParent()
,
如:$menu->queryDescendantsAndSelf()->withParent()->get()
.
默认列名为parent
,若是你想自定义这个列名在model
里定义protected $parentColunm = 'parent_id'
提供多种方法生成树形数据,可从任意节点生成树
<?php $menu = Menu::find(3); // 从当前节点生成树,return tree $menu->getTree(); // 当前节点做为根生成树,以sort字段排序,return tree $menu->getTree(['sortColumn', 'desc']); // 从根节点生成树,return tree $menu->getRoot()->getTree(); //旁树,不包含本身和下级,return tree $menu->getBesideTree();
生成的树以下:
[ 'id' => 3, 'name' => 'node3', 'children' => [ [ 'id' => 4, 'name' => 'node4' ], [ 'id' => 5, 'name' => 'node5' 'children' => [ [ 'id' => 6, 'name' => 'node6' ] ] ] ] ]
生成的树的children
键默认为children
,若是你想自定义能够做为第2个参数传入,如:
$menu->getTree(['sortColumn', 'desc'], 'son');
若是你想获取只包含单个或多个列的结果能够做为第3个参数传入,如:$menu->getTree(['sortColumn', 'desc'], 'son', ['id', 'name']);
你的表里可能包含多棵树,若是你想一一获取他们能够这样作:
<?php $multiTree = []; $roots = Menu::getRoots(); foreach ($roots as $root) { $multiTree[] = $root->getTree(); } $data = $mutiTree;
<?php $menu = Menu::find(3); // 是否根 $menu->isRoot(); // 是否叶子节点 $menu->isLeaf(); // 是否孤立节点 $menu->isIsolated(); // 是否有上级 $menu->hasAncestors(); // 是否有下级 $menu->hasDescendants(); // 是否有孩子(直接下级) $menu->hasChildren(); // 是否有直接上级 $menu->hasParent(); // 是否$descendant的上级 $menu->isAncestorOf($descendant); // 是否$ancestor的下级 $menu->isDescendantOf($ancestor); // 是否$parent的直接下级 $menu->isChildOf($parent); // 是否$child的直接上级 $menu->isParentOf($child); // 是否$sibling的同级(同一个上级) $menu->isSiblingOf($sibling); // 若是$beside不是本身也不是本身的后代返回true $menu->isBesideOf($beside);
<?php // 清理冗余的关联信息 Menu::deleteRedundancies(); $menu = Menu::find(20); // 修复此节点的关联 $menu->perfectNode(); // 修复树关联,注意:这将循环整颗树调用perfectNode(),若是你的树很庞大将耗费大量资源,请慎用 $menu->perfectTree();
$ composer requrie jiaxincui/closure-table
创建树须要新建一个closure
表如:menu_closure
<?php Schema::create('menu_closure', function (Blueprint $table) { $table->unsignedInteger('ancestor'); $table->unsignedInteger('descendant'); $table->unsignedTinyInteger('distance'); $table->primary(['ancestor', 'descendant']); });
在model
里使用Jiaxincui\ClosureTable\Traits\ClosureTable
Trait.
若是你想自定义表名和字段,可在model
里定义如下属性:$closureTable
,$ancestorColumn
,$descendantColumn
,$distanceColumn
.
若是你想自定义生成的树形数据里parent
字段,在model
里定义属性$parentColumn
.
以下示例:
<?php namespace App; use Illuminate\Database\Eloquent\Model; use Jiaxincui\ClosureTable\Traits\ClosureTable; class Menu extends Model { use ClosureTable; // 关联表名,默认'Model类名+_closure',如'menu_closure' protected $closureTable = 'menu_closure'; // ancestor列名,默认'ancestor' protected $ancestorColumn = 'ancestor'; // descendant列名,默认'descendant' protected $descendantColumn = 'descendant'; // distance列名,默认'distance' protected $distanceColumn = 'distance'; // parent列名,默认'parent',此列是计算生成,不在数据库存储 protected $parentColumn = 'parent'; }
接下来,你就能够自由的使用ClosureTable
带来的全部功能了.