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