優雅的樹形數據結構管理包,基於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
帶來的全部功能了.