摘要:通過自定義的查詢加載和大多數情況下,你需要按層級排序祖先集合可以被預加載視圖模板中面包屑將祖先的全部取出后轉換為數組,在用拼接為字符串輸出。
原文鏈接:http://www.pilishen.com/posts...; 歡迎作客我們的php&Laravel學習群:109256050
laravel-nestedset是一個關系型數據庫遍歷樹的larvel4-5的插件包
目錄:
Nested Sets Model簡介
安裝要求
安裝
開始使用
遷移文件
插入節點
獲取節點
刪除節點
一致性檢查和修復
作用域
Nested Sets Model簡介Nested Set Model 是一種實現有序樹的高明的方法,它快速且不需要遞歸查詢,例如不管樹有多少層,你可以僅使用一條查詢來獲取某個節點下的所有的后代,缺點是它的插入、移動、刪除需要執行復雜的sql語句,但是這些都在這個插件內處理了!
更多關于詳見維基百科!Nested set model 及它中文翻譯!嵌套集合模型
PHP>=5.4
laravel>=4.1
v4.3版本以后支持Laravel-5.5
v4版本支持Laravel-5.2、5.3、5.4
v3版本支持Laravel-5.1
v2版本支持Laravel-4
強烈建議使用支持事物功能的數據引擎(像MySql的innoDb)來防止可能的數據損壞。
安裝在composer.json文件中加入下面代碼:
"kalnoy/nestedset": "^4.3",
運行composer install 來安裝它。
或者直接在命令行輸入
composer require kalnoy/nestedset
如需安裝歷史版本請點擊更多版本
開始使用 遷移文件你可以使用NestedSet類的columns方法來添加有默認名字的字段:
... use KalnoyNestedsetNestedSet; Schema::create("table", function (Blueprint $table) { ... NestedSet::columns($table); });
刪除字段:
... use KalnoyNestedsetNestedSet; Schema::table("table", function (Blueprint $table) { NestedSet::dropColumns($table); });
默認的字段名為:_lft、_rgt、parent_id,源碼如下:
public static function columns(Blueprint $table) { $table->unsignedInteger(self::LFT)->default(0); $table->unsignedInteger(self::RGT)->default(0); $table->unsignedInteger(self::PARENT_ID)->nullable(); $table->index(static::getDefaultColumns()); }模型
你的模型需要使用KalnoyNestedsetNodeTraittrait 來實現nested sets
use KalnoyNestedsetNodeTrait; class Foo extends Model { use NodeTrait; }遷移其他地方已有的數據 從其他的nested set 模型庫遷移
public function getLftName() { return "left"; } public function getRgtName() { return "right"; } public function getParentIdName() { return "parent"; } // Specify parent id attribute mutator public function setParentAttribute($value) { $this->setParentIdAttribute($value); }從其他的具有父子關系的模型庫遷移
如果你的數據庫結構樹包含 parent_id 字段信息,你需要添加下面兩欄字段到你的藍圖文件:
$table->unsignedInteger("_lft"); $table->unsignedInteger("_rgt");
設置好你的模型后你只需要修復你的結構樹來填充_lft和_rgt字段:
MyModel::fixTree();關系
Node具有以下功能,他們功能完全且被預加載:
Node belongs to parent
Node has many children
Node has many ancestors
Node has many descendants
假設我們有一個Category模型;變量$node是該模型的一個實例是我們操作的node(節點)。它可以為一個新創建的node或者是從數據庫中取出的node
插入節點(node)每次插入或者移動一個節點都要執行好幾條數據庫操作,所有強烈推薦使用transaction.
注意! 對于v4.2.0版本不是自動開啟transaction的,另外node的結構化操作需要在模型上手動執行save,但是有些方法會隱性執行save并返回操作后的布爾類型的結果。
創建節點(node)當你簡單的創建一個node,它會被添加到樹的末端。
Category::create($attributes); // 自動save為一個根節點(root)
或者
$node = new Category($attributes); $node->save(); // save為一個根節點(root)
在這里node被設置為root,意味著它沒有父節點
將一個已存在的node設置為root// #1 隱性 save $node->saveAsRoot(); // #2 顯性 save $node->makeRoot()->save();添加子節點到指定的父節點末端或前端
如果你想添加子節點,你可以添加為父節點的第一個子節點或者最后一個子節點。
*在下面的例子中, $parent 為已存在的節點
添加到父節點的末端的方法包括:
// #1 使用延遲插入 $node->appendToNode($parent)->save(); // #2 使用父節點 $parent->appendNode($node); // #3 借助父節點的children關系 $parent->children()->create($attributes); // #5 借助子節點的parent關系 $node->parent()->associate($parent)->save(); // #6 借助父節點屬性 $node->parent_id = $parent->id; $node->save(); // #7 使用靜態方法 Category::create($attributes, $parent);
添加到父節點的前端的方法
// #1 $node->prependToNode($parent)->save(); // #2 $parent->prependNode($node);插入節點到指定節點的前面或后面
你可以使用下面的方法來將$node添加為指定節點$neighbor的相鄰節點
$neighbor必須存在,$node可以為新創建的節點,也可以為已存在的,如果$node為已存在的節點,它將移動到新的位置與$neighbor相鄰,必要時它的父級將改變。
# 顯性save $node->afterNode($neighbor)->save(); $node->beforeNode($neighbor)->save(); # 隱性 save $node->insertAfterNode($neighbor); $node->insertBeforeNode($neighbor);將數組構建為樹
但使用create靜態方法時,它將檢查數組是否包含children鍵,如果有的話,將遞歸創建更多的節點。
$node = Category::create([ "name" => "Foo", "children" => [ [ "name" => "Bar", "children" => [ [ "name" => "Baz" ], ], ], ], ]);
現在$node->children包含一組已創建的節點。
將數組重建為樹你可以輕松的重建一個樹,這對于大量的修改的樹結構的保存非常有用。
Category::rebuildTree($data, $delete);
$data為代表節點的數組
$data = [ [ "id" => 1, "name" => "foo", "children" => [ ... ] ], [ "name" => "bar" ], ];
上面有一個name為foo的節點,它有指定的id,代表這個已存在的節點將被填充,如果這個節點不存在,就好拋出一個ModelNotFoundException ,另外,這個節點還有children數組,這個數組也會以相同的方式添加到foo節點內。
bar節點沒有主鍵,就是不存在,它將會被創建。
$delete 代表是否刪除數據庫中已存在的但是$data 中不存在的數據,默認為不刪除。
重建子樹
對于4.3.8版本以后你可以重建子樹
Category::rebuildSubtree($root, $data);
這將限制只重建$root子樹
檢索節點在某些情況下我們需要使用變量$id代表目標節點的主鍵id
祖先和后代Ancestors 創建一個節點的父級鏈,這對于展示當前種類的面包屑很有幫助。
Descendants 是一個父節點的所有子節點。
Ancestors和Descendants都可以預加載。
// Accessing ancestors $node->ancestors; // Accessing descendants $node->descendants;
通過自定義的查詢加載ancestors和descendants:
$result = Category::ancestorsOf($id); $result = Category::ancestorsAndSelf($id); $result = Category::descendantsOf($id); $result = Category::descendantsAndSelf($id);
大多數情況下,你需要按層級排序:
$result = Category::defaultOrder()->ancestorsOf($id);
祖先集合可以被預加載:
$categories = Category::with("ancestors")->paginate(30); // 視圖模板中面包屑: @foreach($categories as $i => $category) $category->ancestors->count() ? implode(" > ", $category->ancestors->pluck("name")->toArray()) : "Top Level"
$category->name @endforeach
將祖先的name全部取出后轉換為數組,在用>拼接為字符串輸出。
兄弟節點有相同父節點的節點互稱為兄弟節點
$result = $node->getSiblings(); $result = $node->siblings()->get();
獲取相鄰的后面兄弟節點:
// 獲取相鄰的下一個兄弟節點 $result = $node->getNextSibling(); // 獲取后面的所有兄弟節點 $result = $node->getNextSiblings(); // 使用查詢獲得所有兄弟節點 $result = $node->nextSiblings()->get();
獲取相鄰的前面兄弟節點:
// 獲取相鄰的前一個兄弟節點 $result = $node->getPrevSibling(); // 獲取前面的所有兄弟節點 $result = $node->getPrevSiblings(); // 使用查詢獲得所有兄弟節點 $result = $node->prevSiblings()->get();獲取表的相關model
假設每一個category has many goods, 并且 hasMany 關系已經建立,怎么樣簡單的獲取$category 和它所有后代下所有的goods?
// 獲取后代的id $categories = $category->descendants()->pluck("id"); // 包含Category本身的id $categories[] = $category->getKey(); // 獲得goods $goods = Goods::whereIn("category_id", $categories)->get();包含node深度(depth)
如果你需要知道node的出入那一層級:
$result = Category::withDepth()->find($id); $depth = $result->depth;
根節點(root)是第0層(level 0),root的子節點是第一層(level 1),以此類推
你可以使用having約束來獲得特定的層級的節點
$result = Category::withDepth()->having("depth", "=", 1)->get();
注意 這在數據庫嚴格模式下無效
默認排序所有的節點都是在內部嚴格組織的,默認情況下沒有順序,所以節點是隨機展現的,這部影響展現,你可以按字母和其他的順序對節點排序。
但是在一些情況下按層級展示是必要的,它對獲取祖先和用于菜單順序有用。
使用deaultOrder運用樹的排序:
$result = Category::defaultOrder()->get();
你也可以使用倒序排序:
$result = Category::reversed()->get();
讓節點在父級內部上下移動來改變默認排序:
$bool = $node->down(); $bool = $node->up(); // 向下移動3個兄弟節點 $bool = $node->down(3);
操作返回根據操作的節點的位置是否改變的布爾值
約束很多約束條件可以被用到這些查詢構造器上:
whereIsRoot() 僅獲取根節點;
whereIsAfter($id) 獲取特定id的節點后面的所有節點(不僅是兄弟節點)。
whereIsBefore($id) 獲取特定id的節點前面的所有節點(不僅是兄弟節點)。
祖先約束
$result = Category::whereAncestorOf($node)->get(); $result = Category::whereAncestorOrSelf($id)->get();
$node 可以為模型的主鍵或者模型實例
后代約束
$result = Category::whereDescendantOf($node)->get(); $result = Category::whereNotDescendantOf($node)->get(); $result = Category::orWhereDescendantOf($node)->get(); $result = Category::orWhereNotDescendantOf($node)->get(); $result = Category::whereDescendantAndSelf($id)->get(); //結果集合中包含目標node自身 $result = Category::whereDescendantOrSelf($node)->get();構建樹
在獲取了node的結果集合后,我們就可以將它轉化為樹,例如:
$tree = Category::get()->toTree();
這將在每個node上添加parent 和 children 關系,且你可以使用遞歸算法來渲染樹:
$nodes = Category::get()->toTree(); $traverse = function ($categories, $prefix = "-") use (&$traverse) { foreach ($categories as $category) { echo PHP_EOL.$prefix." ".$category->name; $traverse($category->children, $prefix."-"); } }; $traverse($nodes);
這將像下面類似的輸出:
- Root -- Child 1 --- Sub child 1 -- Child 2 - Another root構建一個扁平樹
你也可以構建一個扁平樹:將子節點直接放于父節點后面。當你獲取自定義排序的節點和不想使用遞歸來循環你的節點時很有用。
$nodes = Category::get()->toFlatTree();
之前的例子將向下面這樣輸出:
Root Child 1 Sub child 1 Child 2 Another root構建一個子樹
有時你并不需要加載整個樹而是只需要一些特定的子樹:
$root = Category::descendantsAndSelf($rootId)->toTree()->first();
通過一個簡單的查詢我們就可以獲得子樹的根節點和使用children關系獲取它所有的后代
如果你不需要$root節點本身,你可以這樣:
$tree = Category::descendantsOf($rootId)->toTree($rootId);
刪掉一個節點:
$node->delete();
注意!節點的所有后代將一并刪除
注意! 節點需要向模型一樣刪除,不能使用下面的語句來刪除節點:
Category::where("id", "=", $id)->delete();
這將破壞樹結構
支持SoftDeletestrait,且在模型層
檢查節點是否為其他節點的子節點
$bool = $node->isDescendantOf($parent);
檢查是否為根節點
$bool = $node->isRoot();
其他的檢查
$node->isChildOf($other);
$node->isAncestorOf($other);
$node->isSiblingOf($other);
$node->isLeaf()
檢查一致性你可以檢查樹是否被破環
$bool = Category::isBroken();
獲取錯誤統計:
$data = Category::countErrors();
它將返回含有一下鍵的數組
oddness -- lft 和 rgt 值錯誤的節點的數量
duplicates -- lft 或者 rgt 值重復的節點的數量
wrong_parent -- left 和 rgt 值 與parent_id 不對應的造成無效parent_id 的節點的數量
missing_parent -- 含有parent_id對應的父節點不存在的節點的數量
修復樹從v3.1往后支持修復樹,通過parent_id字段的繼承信息,給每個node設置合適的lft 和 rgt值
Node::fixTree();
假設你有個Memu模型和MenuItems.他們之間是one-to-many 關系。MenuItems有menu_id屬性并實現nested sets模型。顯然你想基于menu_id屬性來多帶帶處理每個樹,為了實現這樣的功能,我們需要指定這個menu_id屬性為scope屬性。
protected function getScopeAttributes() { return [ "menu_id" ]; }
現在我們為了實現自定義的查詢,我們需要提供需要限制作用域的屬性。
MenuItem::scoped([ "menu_id" => 5 ])->withDepth()->get(); // OK MenuItem::descendantsOf($id)->get(); // WRONG: returns nodes from other scope MenuItem::scoped([ "menu_id" => 5 ])->fixTree();
但使用model實例查詢node,scope自動基于設置的限制作用域屬性來刪選node。例如:
$node = MenuItem::findOrFail($id); $node->siblings()->withDepth()->get(); // OK
使用實例來獲取刪選的查詢:
$node->newScopedQuery();
注意,當通過主鍵獲取模型時不需要使用scope
$node = MenuItem::findOrFail($id); // OK $node = MenuItem::scoped([ "menu_id" => 5 ])->findOrFail(); // OK, 但是多余
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/28168.html
摘要:分層數據探索例如無限級分類多級菜單省份城市引言什么是分層數據類似于樹形結構,除了根節點和葉子節點外,所有節點都有一個父節點和一個或多個子節點。接下來我會先通過一般方法和遞歸方法來實現無限極分類,然后再通過兩種數據模型來談一談分層數據的處理。 分層數據Hierarchical Data探索(例如:無限級分類、多級菜單、省份城市) 引言 什么是分層數據? 類似于樹形結構,除了根節點和葉子節...
摘要:對我們來說最大的便利就是利用日志進行錯誤發現和排查的效率變高了。官方也提倡正確設置接收的日志的同時,用戶也能繼續舊的日志備份。 在各種系統和應用里,無論你的代碼再完美也還是會拋異常,出錯誤。今天的主角是當今比較流行的異常記錄框架 - Sentry,來了解一下。 關于日志管理 應用越做越復雜,輸出日志五花八門,有print的,有寫stdout的,有寫stderr的, 有寫logging的...
摘要:三大牛和在深度學習領域的地位無人不知。逐漸地,這些應用使用一種叫深度學習的技術。監督學習機器學習中,不論是否是深層,最常見的形式是監督學習。 三大牛Yann LeCun、Yoshua Bengio和Geoffrey Hinton在深度學習領域的地位無人不知。為紀念人工智能提出60周年,的《Nature》雜志專門開辟了一個人工智能 + 機器人專題 ,發表多篇相關論文,其中包括了Yann LeC...
摘要:作為前端不用算法也可以寫成項目但是如果明白會算法的話你寫起代碼來會更得心應手無限分類遞歸多數用在樹形結構數據有這樣一組數據張三的兒子張三的兒子李四的兒子張三的兒子的兒子張三李四王五想要得到的結果是這樣子的張三張三的兒子張三的兒子的兒子張三的 作為前端, 不用算法也可以寫成項目. 但是如果明白會算法的話, 你寫起代碼來會更得心應手.無限分類遞歸 多數用在樹形結構數據. 有這樣一組數據: ...
閱讀 2441·2019-08-30 15:52
閱讀 2241·2019-08-30 12:51
閱讀 2837·2019-08-29 18:41
閱讀 2822·2019-08-29 17:04
閱讀 817·2019-08-29 15:11
閱讀 1727·2019-08-28 18:02
閱讀 3606·2019-08-26 10:22
閱讀 2514·2019-08-26 10:12