About
優雅的樹形資料結構管理包,基於Closure Table
模式設計.
github 歡迎不吝Star
Features
- 優雅的樹形資料設計模式
- 資料和結構分表,運算元據不影響結構
- 一個Eloquent Trait操作簡單
- 無需修改表,相容舊資料
- 完善的樹操作方法
- 支援生成樹形資料
- 支援多棵樹並存(多個根)
- 支援節點/樹修復
- 支援軟刪除
依賴
- 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
帶來的所有功能了.
本作品採用《CC 協議》,轉載必須註明作者和本文連結