Laravel-nestedset是Laravel框架中的一個無限級分類的擴充套件包,它的實現有別於傳統的鄰接表模型,採用的是一種新的分層資料模型叫巢狀集合模型,這種模型能夠實現快速查詢,運用在少修改、頻查詢的業務場景中能夠提升較大的查詢效率。下面我就帶著大家一步一步來熟悉這個擴充套件。
u=3352133920,2187839175&fm=26&gp=0.jpg
1.進入到Laravel專案的根目錄中,用composer安裝kalnoy/nestedset。
composer require kalnoy/nestedset
1.建立好資料遷移檔案。
php artisan make:migration create_category_table
2.進入database/migrations目錄下,開啟生成的遷移檔案,並新增需要的欄位。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
//引入NestedSet類
use Kalnoy\Nestedset\NestedSet;
class CreateCategoryTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('category', function (Blueprint $table) {
//主鍵ID
$table->id();
//新增分類的名稱欄位
$table->string('name')->default('');
//在此新增此方法,nestedset將會自動生成:_lft、_rgt、parent_id三個欄位
NestedSet::columns($table);
//根據實際場景需要決定需不需要軟刪除
$table->softDeletes();
//新增建立時間、更新時間欄位
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('category');
}
}
3.遷移檔案調整好之後,我們就可以執行遷移操作,建立出分類表,執行之後如下所示。
php artisan migrate
image.png
image.png
4.建立模型檔案
php artisan make:model CategoryModel
5.開啟該模型檔案,引入NodeTrait。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
//引入NodeTrait
use Kalnoy\Nestedset\NodeTrait;
class CategoryModel extends Model
{
//使用NodeTrait
use HasFactory,NodeTrait;
protected $table = 'category';
protected $fillable = ['name'];
}
6.若想要替換欄位_lft、_rgt包括parent_id的名稱可以做如下操作
image.png
image.png
1.新增路由
//顯示節點
Route::get('node/show',[CategoryController::class,'showNodes']);
//建立節點
Route::get('node/create',[CategoryController::class,'createNode']);
//移動節點
Route::get('node/move',[CategoryController::class,'moveNode']);
//刪除節點
Route::get('node/delete',[CategoryController::class,'deleteNode']);
2.新增控制器
php artisan make:controller CategoryController
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\CategoryModel;
class CategoryController extends Controller
{
//顯示節點
public function showNodes()
{
}
//建立節點
public function createNode()
{
}
//移動節點
public function moveNode()
{
}
//刪除節點
public function deleteNode()
{
}
}
(1)建立節點並新增到樹節點的末端
1.通過模型的構造方法傳參的形式建立單節點
$model = new CategoryModel(['name' => '節點A']);
$model->save();
2.直接針對模型的屬性賦值來建立單節點
$model = new CategoryModel();
$model->name = '節點B';
$model->save();
3.呼叫模型的create方法建立單節點或者多節點(可包含子節點)
//建立一個單節點,預設建立的節點在根節點
CategoryModel::create(['name' => '節點C']);
//一次性建立多個節點
CategoryModel::create([
'name' => '節點D',
//使用children來定義子節點
'children' => [
[
'name' => '節點E',
'children' => [
['name' => '節點F']
]
],
[
'name' => '節點G'
]
]
]);
4.建立之後資料庫資料以及樹節點顯示如下
image.png
[
{
"id": 1,
"name": "節點A",
"_lft": 1,
"_rgt": 2,
"parent_id": null,
"deleted_at": null,
"created_at": "2020-12-18T03:12:43.000000Z",
"updated_at": "2020-12-18T03:12:43.000000Z",
"children": []
},
{
"id": 2,
"name": "節點B",
"_lft": 3,
"_rgt": 4,
"parent_id": null,
"deleted_at": null,
"created_at": "2020-12-18T03:13:11.000000Z",
"updated_at": "2020-12-18T03:13:11.000000Z",
"children": []
},
{
"id": 3,
"name": "節點C",
"_lft": 5,
"_rgt": 6,
"parent_id": null,
"deleted_at": null,
"created_at": "2020-12-18T03:14:22.000000Z",
"updated_at": "2020-12-18T03:14:22.000000Z",
"children": []
},
{
"id": 4,
"name": "節點D",
"_lft": 7,
"_rgt": 14,
"parent_id": null,
"deleted_at": null,
"created_at": "2020-12-18T03:14:46.000000Z",
"updated_at": "2020-12-18T03:14:46.000000Z",
"children": [
{
"id": 5,
"name": "節點E",
"_lft": 8,
"_rgt": 11,
"parent_id": 4,
"deleted_at": null,
"created_at": "2020-12-18T03:14:46.000000Z",
"updated_at": "2020-12-18T03:14:46.000000Z",
"children": [
{
"id": 6,
"name": "節點F",
"_lft": 9,
"_rgt": 10,
"parent_id": 5,
"deleted_at": null,
"created_at": "2020-12-18T03:14:46.000000Z",
"updated_at": "2020-12-18T03:14:46.000000Z",
"children": []
}
]
},
{
"id": 7,
"name": "節點G",
"_lft": 12,
"_rgt": 13,
"parent_id": 4,
"deleted_at": null,
"created_at": "2020-12-18T03:14:46.000000Z",
"updated_at": "2020-12-18T03:14:46.000000Z",
"children": []
}
]
}
]
(2)為指定的父節點新增子節點,並且新增在子節點列表的尾部(若子節點已經存在,則會將該子節點移動到父節點的子節點列表中)
1.使用子節點的appendToNode方法
//首先建立一個子節點
$node = new CategoryModel(['name' => '節點H']);
//然後找一個父節點(假如以節點E作為父節點,id是5)
$parentNode = CategoryModel::find(5);
//將子節點新增到父節點中
$node->appendToNode($parentNode)->save();
2.使用父節點的appendNode方法
//首先建立一個子節點
$node = new CategoryModel(['name' => '節點I']);
//然後找一個父節點(假如以節點E作為父節點,id是5)
$parentNode = CategoryModel::find(5);
//使用父節點的appendNode方法新增子節點
$parentNode->appendNode($node);
3.使用父節點的children()關係
//首先找一個父節點(假如以節點E作為父節點,id是5)
$parentNode = CategoryModel::find(5);
//使用父節點的children()方法新增子節點
$parentNode->children()->create(['name' => '節點J']);
4.使用子節點的parent()關係的
//首先建立一個子節點
$node = new CategoryModel(['name' => '節點K']);
//然後找一個父節點(假如以節點E作為父節點,id是5)
$parentNode = CategoryModel::find(5);
//使用子節點的parent()關係
$node->parent()->associate($parentNode)->save();
5.使用子節點的parent_id屬性設定將子節點新增到指定的父節點
//首先建立一個子節點
$node = new CategoryModel(['name' => '節點L']);
//然後找一個父節點(假如以節點E作為父節點,id是5)
$parentNode = CategoryModel::find(5);
//設定子節點的parent_id為父節點
$node->parent_id = $parentNode->id;
//設定完成之後儲存
$node->save();
6.使用模型的靜態方法將子節點新增到父節點中
//首先找一個父節點(假如以節點E作為父節點,id是5)
$parentNode = CategoryModel::find(5);
//呼叫模型的create靜態方法
CategoryModel::create(['name' => '節點M'],$parentNode);
7.從1~6方法執行之後,最終的樹結構圖如下所示
//執行上面的程式碼之後,我們來檢視一下樹節點
//新增加的節點都依次被新增到了節點E的children列表的尾部
{
"id": 4,
"name": "節點D",
"_lft": 7,
"_rgt": 28,
"parent_id": null,
"deleted_at": null,
"created_at": "2020-12-18T03:14:46.000000Z",
"updated_at": "2020-12-18T03:14:46.000000Z",
"children": [
{
"id": 5,
"name": "節點E",
"_lft": 8,
"_rgt": 25,
"parent_id": 4,
"deleted_at": null,
"created_at": "2020-12-18T03:14:46.000000Z",
"updated_at": "2020-12-18T03:14:46.000000Z",
"children": [
{
"id": 6,
"name": "節點F",
"_lft": 9,
"_rgt": 10,
"parent_id": 5,
"deleted_at": null,
"created_at": "2020-12-18T03:14:46.000000Z",
"updated_at": "2020-12-18T03:14:46.000000Z",
"children": []
},
{
"id": 9,
"name": "節點H",
"_lft": 11,
"_rgt": 12,
"parent_id": 5,
"deleted_at": null,
"created_at": "2020-12-18T05:27:21.000000Z",
"updated_at": "2020-12-18T05:27:21.000000Z",
"children": []
},
{
"id": 11,
"name": "節點I",
"_lft": 15,
"_rgt": 16,
"parent_id": 5,
"deleted_at": null,
"created_at": "2020-12-18T05:40:55.000000Z",
"updated_at": "2020-12-18T05:40:55.000000Z",
"children": []
},
{
"id": 12,
"name": "節點J",
"_lft": 17,
"_rgt": 18,
"parent_id": 5,
"deleted_at": null,
"created_at": "2020-12-18T05:45:48.000000Z",
"updated_at": "2020-12-18T05:45:48.000000Z",
"children": []
},
{
"id": 13,
"name": "節點K",
"_lft": 19,
"_rgt": 20,
"parent_id": 5,
"deleted_at": null,
"created_at": "2020-12-18T05:53:22.000000Z",
"updated_at": "2020-12-18T05:53:22.000000Z",
"children": []
},
{
"id": 14,
"name": "節點L",
"_lft": 21,
"_rgt": 22,
"parent_id": 5,
"deleted_at": null,
"created_at": "2020-12-18T05:57:52.000000Z",
"updated_at": "2020-12-18T05:57:52.000000Z",
"children": []
},
{
"id": 15,
"name": "節點M",
"_lft": 23,
"_rgt": 24,
"parent_id": 5,
"deleted_at": null,
"created_at": "2020-12-18T06:02:43.000000Z",
"updated_at": "2020-12-18T06:02:43.000000Z",
"children": []
}
]
},
{
"id": 7,
"name": "節點G",
"_lft": 26,
"_rgt": 27,
"parent_id": 4,
"deleted_at": null,
"created_at": "2020-12-18T03:14:46.000000Z",
"updated_at": "2020-12-18T03:14:46.000000Z",
"children": []
}
]
}
(3)為指定的父節點新增子節點,並且新增在子節點列表的頭部(若子節點已經存在,則會將該子節點移動到父節點的子節點列表中)
1.使用子節點的prependToNode方法
//首先建立一個子節點
$node = new CategoryModel(['name' => '節點N']);
//然後找一個父節點(假如以節點E作為父節點,id是5)
$parentNode = CategoryModel::find(5);
//通過子節點的prependToNode方法
$node->prependToNode($parentNode)->save();
2.使用父節點的prependNode方法
//首先建立一個子節點
$node = new CategoryModel(['name' => '節點O']);
//然後找一個父節點(假如以節點E作為父節點,id是5)
$parentNode = CategoryModel::find(5);
//通過父節點的prependNode方法
$parentNode->prependNode($node);
3.執行以上兩個操作之後的資料結構如下
{
"id": 4,
"name": "節點D",
"_lft": 7,
"_rgt": 10,
"parent_id": null,
"deleted_at": null,
"created_at": "2020-12-18T03:14:46.000000Z",
"updated_at": "2020-12-18T03:14:46.000000Z",
"children": [
{
"id": 7,
"name": "節點G",
"_lft": 8,
"_rgt": 9,
"parent_id": 4,
"deleted_at": null,
"created_at": "2020-12-18T03:14:46.000000Z",
"updated_at": "2020-12-18T03:14:46.000000Z",
"children": []
},
{
"id": 5,
"name": "節點E",
"_lft": 12,
"_rgt": 33,
"parent_id": 4,
"deleted_at": null,
"created_at": "2020-12-18T03:14:46.000000Z",
"updated_at": "2020-12-18T06:51:59.000000Z",
"children": [
{
"id": 18,
"name": "節點O",
"_lft": 13,
"_rgt": 14,
"parent_id": 5,
"deleted_at": null,
"created_at": "2020-12-18T07:01:04.000000Z",
"updated_at": "2020-12-18T07:01:04.000000Z",
"children": []
},
{
"id": 17,
"name": "節點N",
"_lft": 15,
"_rgt": 16,
"parent_id": 5,
"deleted_at": null,
"created_at": "2020-12-18T06:53:11.000000Z",
"updated_at": "2020-12-18T06:53:11.000000Z",
"children": []
},
{
"id": 6,
"name": "節點F",
"_lft": 17,
"_rgt": 18,
"parent_id": 5,
"deleted_at": null,
"created_at": "2020-12-18T03:14:46.000000Z",
"updated_at": "2020-12-18T03:14:46.000000Z",
"children": []
},
{
"id": 9,
"name": "節點H",
"_lft": 19,
"_rgt": 20,
"parent_id": 5,
"deleted_at": null,
"created_at": "2020-12-18T05:27:21.000000Z",
"updated_at": "2020-12-18T05:27:21.000000Z",
"children": []
},
{
"id": 11,
"name": "節點I",
"_lft": 23,
"_rgt": 24,
"parent_id": 5,
"deleted_at": null,
"created_at": "2020-12-18T05:40:55.000000Z",
"updated_at": "2020-12-18T05:40:55.000000Z",
"children": []
},
{
"id": 12,
"name": "節點J",
"_lft": 25,
"_rgt": 26,
"parent_id": 5,
"deleted_at": null,
"created_at": "2020-12-18T05:45:48.000000Z",
"updated_at": "2020-12-18T05:45:48.000000Z",
"children": []
},
{
"id": 13,
"name": "節點K",
"_lft": 27,
"_rgt": 28,
"parent_id": 5,
"deleted_at": null,
"created_at": "2020-12-18T05:53:22.000000Z",
"updated_at": "2020-12-18T05:53:22.000000Z",
"children": []
},
{
"id": 14,
"name": "節點L",
"_lft": 29,
"_rgt": 30,
"parent_id": 5,
"deleted_at": null,
"created_at": "2020-12-18T05:57:52.000000Z",
"updated_at": "2020-12-18T05:57:52.000000Z",
"children": []
},
{
"id": 15,
"name": "節點M",
"_lft": 31,
"_rgt": 32,
"parent_id": 5,
"deleted_at": null,
"created_at": "2020-12-18T06:02:43.000000Z",
"updated_at": "2020-12-18T06:02:43.000000Z",
"children": []
}
]
}
]
}
(4)新增節點並插入到指定節點的前面或後面
1.新增節點插入到指定節點的前面
/*************************顯性 save****************************/
# 首先建立一個子節點
$node = new CategoryModel(['name' => '節點Q']);
# 然後找一個指定的節點(假如指定節點B,id是2)
$neighbor = CategoryModel::find(2);
# 將該節點插入到節點B的前面
$node->beforeNode($neighbor)->save();
/*************************顯性 save****************************/
//或者
/*************************隱性 save****************************/
# 首先建立一個子節點
$node = new CategoryModel(['name' => '節點Q']);
# 然後找一個指定的節點(假如指定節點B,id是2)
$neighbor = CategoryModel::find(2);
# 將該節點插入到節點B的前面
$node->insertBeforeNode($neighbor);
/*************************隱性 save****************************/
// 執行之後樹結構如下
[
{
"id": 1,
"name": "節點A",
"_lft": 1,
"_rgt": 2,
"parent_id": null,
"deleted_at": null,
"created_at": "2020-12-18T03:12:43.000000Z",
"updated_at": "2020-12-18T03:12:43.000000Z",
"children": []
},
{
"id": 20,
"name": "節點Q",
"_lft": 3,
"_rgt": 4,
"parent_id": null,
"deleted_at": null,
"created_at": "2020-12-18T09:41:48.000000Z",
"updated_at": "2020-12-18T09:41:48.000000Z",
"children": []
},
{
"id": 2,
"name": "節點B",
"_lft": 5,
"_rgt": 6,
"parent_id": null,
"deleted_at": null,
"created_at": "2020-12-18T03:13:11.000000Z",
"updated_at": "2020-12-18T03:13:11.000000Z",
"children": []
},
...
]
2.新增節點插入到指定節點的後面
/*************************顯性 save****************************/
# 首先建立一個子節點
$node = new CategoryModel(['name' => '節點S']);
# 然後找一個指定的節點(假如指定節點B,id是2)
$neighbor = CategoryModel::find(2);
# 將該節點插入到節點B的後面
$node->afterNode($neighbor)->save();
/*************************顯性 save****************************/
//或者
/*************************隱性 save****************************/
# 首先建立一個子節點
$node = new CategoryModel(['name' => '節點S']);
# 然後找一個指定的節點(假如指定節點B,id是2)
$neighbor = CategoryModel::find(2);
# 將該節點插入到節點B的後面
$node->insertAfterNode($neighbor);
/*************************隱性 save****************************/
//執行之後樹結構如下
[
{
"id": 1,
"name": "節點A",
"_lft": 1,
"_rgt": 2,
"parent_id": null,
"deleted_at": null,
"created_at": "2020-12-18T03:12:43.000000Z",
"updated_at": "2020-12-18T03:12:43.000000Z",
"children": []
},
{
"id": 20,
"name": "節點Q",
"_lft": 3,
"_rgt": 4,
"parent_id": null,
"deleted_at": null,
"created_at": "2020-12-18T09:41:48.000000Z",
"updated_at": "2020-12-18T09:41:48.000000Z",
"children": []
},
{
"id": 2,
"name": "節點B",
"_lft": 7,
"_rgt": 8,
"parent_id": null,
"deleted_at": null,
"created_at": "2020-12-18T03:13:11.000000Z",
"updated_at": "2020-12-18T03:13:11.000000Z",
"children": []
},
{
"id": 22,
"name": "節點S",
"_lft": 9,
"_rgt": 10,
"parent_id": null,
"deleted_at": null,
"created_at": "2020-12-18T09:57:56.000000Z",
"updated_at": "2020-12-18T09:57:56.000000Z",
"children": []
},
{
"id": 3,
"name": "節點C",
"_lft": 11,
"_rgt": 12,
"parent_id": null,
"deleted_at": null,
"created_at": "2020-12-18T03:14:22.000000Z",
"updated_at": "2020-12-18T07:29:22.000000Z",
"children": []
},
...
]
1.將集合資料樹形展現
//樹形結構展現(預設情況下,節點未進行排序展示,也就是說未根據depth欄位進行排序)
$tree = CategoryModel::get()->toTree();
//採用預設排序再轉換成樹形(預設排序是根據depth欄位從小到大的順序)
$tree = CategoryModel::defaultOrder()->get()->toTree();
//採用倒序排序再轉換成樹形(倒序排序是根據depth欄位從大到小的順序)
$tree = CategoryModel::reversed()->get()->toTree();
2.獲取節點資料的同時附帶每個節點的深度
//展現節點的時候,使用withDepth()方法,輸出的資料會帶一個深度欄位depth
$result = CategoryModel::withDepth()->get();
//按照深度值來篩選出節點
$result = CategoryModel::withDepth()->having('depth', '=', 1)->get();
image.png
3.獲取兄弟節點
//獲取某個節點的兄弟節點
$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();
4.獲取祖先和後代節點
// 獲取該節點的所有祖先節點
$node->ancestors;
// 獲取該節點的所有後代節點
$node->descendants;
//獲取$id這個節點的所有祖先節點
$result = CategoryModel::ancestorsOf($id);
//獲取$id這個節點的所有祖先節點包括本節點
$result = CategoryModel::ancestorsAndSelf($id);
//獲取$id這個節點的所有子節點
$result = CategoryModel::descendantsOf($id);
//獲取$id這個節點的所有子節點包括本節點
$result = CategoryModel::descendantsAndSelf($id);
5.查詢資料的條件約束
//僅獲取根節點
$result = CategoryModel::whereIsRoot();
//獲取特定$id的節點後面的所有節點(不僅是兄弟節點)。
$result = CategoryModel::whereIsAfter($id);
//獲取特定$id的節點前面的所有節點(不僅是兄弟節點)。
$result = CategoryModel::whereIsBefore($id);
//查詢祖先的條件約束
$result = CategoryModel::whereAncestorOf($node)->get();
$result = CategoryModel::whereAncestorOrSelf($id)->get();
//查詢後代的條件約束
$result = CategoryModel::whereDescendantOf($node)->get();
$result = CategoryModel::whereNotDescendantOf($node)->get();
$result = CategoryModel::orWhereDescendantOf($node)->get();
$result = CategoryModel::orWhereNotDescendantOf($node)->get();
$result = CategoryModel::whereDescendantAndSelf($id)->get();
//結果集合中包含目標node自身
$result = Category::whereDescendantOrSelf($node)->get();
1.向上移動節點
//獲取需要移動的節點
$node = CategoryModel::find(1);
//將該節點向上移動1個位置
$node->up();
//將該節點向上移動3個位置(如果節點向上移動的位置超過了範圍,則移動無效)
$node->up(3);
2.向下移動節點
//獲取需要移動的節點
$node = CategoryModel::find(1);
//將該節點向下移動1個位置
$node->down();
//將該節點向下移動3個位置(如果節點向下移動的位置超過了範圍,則移動無效)
$node->down(3);
3.將一個已存在的節點設定為根節點
//將節點E設定為根節點
$node = CategoryModel::find(5);
// 隱性 save
$node->saveAsRoot();
// 或者
// 顯性 save
$node->makeRoot()->save();
4.將一個已經存在的節點移動到指定節點的前面或後面(該方法與前述插入的方法一致,區別在於如果節點不存在則會新建立節點再移動,而如果節點已經存在則直接移動)
$node = CategoryModel::find(5);
$neighbor = CategoryModel::find(3);
//顯性save
//將ID是5的節點移動到ID是3的節點的後面
$node->afterNode($neighbor)->save();
//將ID是5的節點移動到ID是3的節點的前面
$node->beforeNode($neighbor)->save();
//或者
// 隱性 save
//將ID是5的節點移動到ID是3的節點的後面
$node->insertAfterNode($neighbor);
//將ID是5的節點移動到ID是3的節點的前面
$node->insertBeforeNode($neighbor);
5.將一個節點移動到某個節點的子節點列表中(可以是列表的頭部,也可以是列表的尾部),該部分參考「三」-(2)以及「三」-(3)。
1.使用模型的delete方法刪除,節點的所有後代元素將一併刪除
//刪除
$node = CategoryModel::find(19);
$node->delete();
2.不可以使用以下的語句刪除,否則會破會樹結構
//不可以這樣刪除,請謹慎操作,否則破壞樹結構
? CategoryModel::where('id', '=', $id)->delete();
3.模型也支援軟刪除,在模型裡面新增SoftDeletes trait
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Kalnoy\Nestedset\NodeTrait;
class CategoryModel extends Model
{
use HasFactory,NodeTrait,SoftDeletes;
protected $table = 'category';
protected $fillable = ['name'];
}
4.軟刪除之後我們可以檢視錶資料
image.png
1.檢查節點是否為其他節點的子節點
$bool = $node->isDescendantOf($parent);
2.檢查是否為根節點
$bool = $node->isRoot();
3.檢查樹節點是否被破環
$bool = CategoryModel::isBroken();
4.檢查當前節點是否是另外一個節點的子節點
$bool = $node−>isChildOf($otherNode);
5.檢查當前節點是否是另外一個節點的
$bool = $node−>isAncestorOf($otherNode);
6.檢查當前節點是否是另外一個節點的兄弟節點
$bool = $node−>isSiblingOf($otherNode);
7.檢查當前節點是否是葉子節點
$bool = $node->isLeaf();
本作品採用《CC 協議》,轉載必須註明作者和本文連結