laravel-nestedset는 관계형 데이터베이스 순회 트리를 위한 larvel4-5 플러그인 패키지입니다. 이 기사는 모든 사람에게 도움이 되기를 바라며 주로 laravel-nestedset 다중 레벨 무제한 분류를 공유합니다.
디렉토리:
중첩 세트 모델 소개
설치 요구 사항
설치
시작하기
마이그레이션 파일
노드 삽입
노드 가져오기
노드 삭제
일관성 검사 및 복구
Scope
중첩 세트 모델은 정렬된 트리를 구현하는 영리한 방법으로, 빠르며 오류가 없습니다. 예를 들어 트리의 수준 수에 관계없이 하나의 쿼리만 사용하면 특정 노드 아래의 모든 하위 항목을 가져올 수 있습니다. 단점은 삽입, 이동 및 삭제에 복잡한 SQL 실행이 필요하다는 것입니다. 명령문이 있지만 모두 여기에 있습니다. 플러그인에서 처리됩니다!
자세한 내용은 위키피디아를 참조하세요! 중첩된 세트 모델과 중국어 번역! 중첩 컬렉션 모델
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
파일에 다음 코드를 추가하세요. composer.json
文件中加入下面代码:
"kalnoy/nestedset": "^4.3",
运行composer install
来安装它。
或者直接在命令行输入
composer require kalnoy/nestedset
如需安装历史版本请点击更多版本
你可以使用NestedSet
类的columns
方法来添加有默认名字的字段:
... use Kalnoy\Nestedset\NestedSet; Schema::create('table', function (Blueprint $table) { ... NestedSet::columns($table); });
删除字段:
... use Kalnoy\Nestedset\NestedSet; 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()); }
你的模型需要使用KalnoyNestedsetNodeTrait
trait 来实现nested sets
use Kalnoy\Nestedset\NodeTrait; class Foo extends Model { use NodeTrait; }
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
每次插入或者移动一个节点都要执行好几条数据库操作,所有强烈推荐使用transaction.
注意! 对于v4.2.0版本不是自动开启transaction的,另外node的结构化操作需要在模型上手动执行save,但是有些方法会隐性执行save并返回操作后的布尔类型的结果。
当你简单的创建一个node,它会被添加到树的末端。
Category::create($attributes); // 自动save为一个根节点(root)
或者
$node = new Category($attributes); $node->save(); // save为一个根节点(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 = [ [ 'id' => 1, 'name' => 'foo', 'children' => [ ... ] ], [ 'name' => 'bar' ], ];
composer install
을 실행하여 설치하세요. 🎜🎜또는 명령줄에 직접 입력하세요🎜// Accessing ancestors $node->ancestors; // Accessing descendants $node->descendants;
NestedSet을 사용할 수 있습니다
클래스columns 메소드를 사용하여 기본 이름이 있는 필드를 추가합니다: 🎜$result = Category::ancestorsOf($id); $result = Category::ancestorsAndSelf($id); $result = Category::descendantsOf($id); $result = Category::descendantsAndSelf($id);
$categories = Category::with('ancestors')->paginate(30); // 视图模板中面包屑: @foreach($categories as $i => $category) <small> $category->ancestors->count() ? implode(' > ', $category->ancestors->pluck('name')->toArray()) : 'Top Level' </small><br> $category->name @endforeach
_lft
, _rgt
, parent_id, 소스 코드는 다음과 같습니다: 🎜$result = $node->getSiblings(); $result = $node->siblings()->get();
KalnoyNestedsetNodeTrait
특성을 사용해야 합니다🎜// 获取相邻的下一个兄弟节点 $result = $node->getNextSibling(); // 获取后面的所有兄弟节点 $result = $node->getNextSiblings(); // 使用查询获得所有兄弟节点 $result = $node->nextSiblings()->get();
// 获取相邻的前一个兄弟节点 $result = $node->getPrevSibling(); // 获取前面的所有兄弟节点 $result = $node->getPrevSiblings(); // 使用查询获得所有兄弟节点 $result = $node->prevSiblings()->get();
parent_id 필드 정보를 보려면 청사진 파일에 다음 두 필드 열을 추가해야 합니다. 🎜<div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">// 获取后代的id
$categories = $category->descendants()->pluck('id');
// 包含Category本身的id
$categories[] = $category->getKey();
// 获得goods
$goods = Goods::whereIn('category_id', $categories)->get();</pre><div class="contentsignin">로그인 후 복사</div></div><div class="contentsignin">로그인 후 복사</div></div>🎜모델을 설정한 후 구조 트리를 복구하여 <code>_lft
를 채우기만 하면 됩니다. 및 _rgt code> 필드: 🎜<div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">$result = Category::withDepth()->find($id);
$depth = $result->depth;</pre><div class="contentsignin">로그인 후 복사</div></div><div class="contentsignin">로그인 후 복사</div></div>🎜Relationship🎜🎜Node에는 다음과 같은 기능이 있으며 완벽하게 작동하고 사전 로드되어 있습니다.🎜🎜🎜🎜Node는 parent에 속합니다🎜🎜🎜🎜Node에는 많은 하위 항목이 있습니다🎜🎜🎜🎜 Node에는 많은 조상이 있습니다🎜🎜🎜 🎜Node에는 많은 자손이 있습니다🎜🎜🎜🎜 Category 모델이 있다고 가정합니다. 변수 $node는 모델의 인스턴스이고 우리가 작업하는 노드(노드)입니다. 새로 생성된 노드나 데이터베이스에서 꺼낸 노드에 노드를 삽입할 수 있습니다🎜🎜🎜🎜노드를 삽입하거나 이동할 때마다 여러 데이터베이스 작업을 수행해야 하므로 트랜잭션을 사용하는 것이 좋습니다.🎜🎜 <strong>주목하세요! </strong> 버전 v4.2.0의 경우 트랜잭션이 자동으로 활성화되지 않습니다. 또한 노드의 구조화된 작업에는 모델에 대한 수동 저장이 필요하지만 일부 메서드는 작업 후 암시적으로 저장을 실행하고 부울 결과를 반환합니다. 🎜<h4>노드 생성</h4>🎜단순히 노드를 생성하면 트리 끝에 추가됩니다. 🎜<div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">$bool = $node->down();
$bool = $node->up();
// 向下移动3个兄弟节点
$bool = $node->down(3);</pre><div class="contentsignin">로그인 후 복사</div></div><div class="contentsignin">로그인 후 복사</div></div>🎜 또는 🎜<div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">$result = Category::whereAncestorOf($node)->get();
$result = Category::whereAncestorOrSelf($id)->get();</pre><div class="contentsignin">로그인 후 복사</div></div><div class="contentsignin">로그인 후 복사</div></div>🎜여기 노드는 루트로 설정됩니다. 이는 상위 노드가 없음을 의미합니다.🎜<h4>기존 노드를 루트로 설정</h4>
<div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">$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();</pre><div class="contentsignin">로그인 후 복사</div></div><div class="contentsignin">로그인 후 복사</div></div>
<h4>지정된 상위 노드에 하위 노드 추가 끝 또는 앞 end</h4>🎜자식 노드를 추가하려면 상위 노드의 첫 번째 하위 노드 또는 마지막 하위 노드로 추가하면 됩니다. 🎜<em>*다음 예에서 <code>$parent
는 기존 노드입니다.🎜🎜상위 노드 끝에 추가된 메소드는 다음과 같습니다. 🎜$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
$node
를 지정된 노드 $neighbor로 추가할 수 있습니다. code >인접 노드 🎜🎜<code>$neighbor
는 반드시 존재해야 합니다. $node
는 새로 생성된 노드이거나 $node
코드인 경우에는 기존 노드일 수 있습니다. >이(가) 기존 노드인 경우 $neighbor
에 인접한 새 위치로 이동되고 필요한 경우 상위 노드가 변경됩니다. 🎜Root Child 1 Sub child 1 Child 2 Another root
create
정적 메서드를 사용하면 배열에 children
키가 포함되어 있는지 확인하고 따라서 더 많은 노드가 재귀적으로 생성됩니다. 🎜protected function getScopeAttributes() { return [ 'menu_id' ]; }
$node->children
에는 생성된 노드 세트가 포함됩니다. 🎜카테고리::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) <small> $category->ancestors->count() ? implode(' > ', $category->ancestors->pluck('name')->toArray()) : 'Top Level' </small><br> $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();
假设每一个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的出入那一层级:
$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();
这将破坏树结构
支持SoftDeletes
trait,且在模型层
检查节点是否为其他节点的子节点$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, 但是多余
相关推荐:
위 내용은 laravel-nestedset 다중 레벨 무한 분류에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!