>  기사  >  백엔드 개발  >  laravel-nestedset 다중 레벨 무한 분류에 대한 자세한 설명

laravel-nestedset 다중 레벨 무한 분류에 대한 자세한 설명

小云云
小云云원래의
2018-01-25 13:28:033154검색
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)을 사용하는 것이 좋습니다.

Installation

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_rgtparent_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 Kalnoy\Nestedset\NodeTrait;

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 = [
    [ '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();

Model

🎜모델은 중첩 세트를 구현하려면 KalnoyNestedsetNodeTrait 특성을 사용해야 합니다🎜
// 获取相邻的下一个兄弟节点
$result = $node->getNextSibling();

// 获取后面的所有兄弟节点
$result = $node->getNextSiblings();

// 使用查询获得所有兄弟节点
$result = $node->nextSiblings()->get();
🎜기존 데이터를 다른 곳으로 마이그레이션 🎜

다른 중첩 세트 모델 라이브러리에서 마이그레이션

// 获取相邻的前一个兄弟节点
$result = $node->getPrevSibling();

// 获取前面的所有兄弟节点
$result = $node->getPrevSiblings();

// 使用查询获得所有兄弟节点
$result = $node->prevSiblings()->get();

상위-하위 관계가 있는 다른 모델 라이브러리에서 마이그레이션

🎜데이터베이스 구조 트리에 parent_id 필드 정보를 보려면 청사진 파일에 다음 두 필드 열을 추가해야 합니다. 🎜<pre class="brush:php;toolbar:false">// 获取后代的id $categories = $category-&gt;descendants()-&gt;pluck('id'); // 包含Category本身的id $categories[] = $category-&gt;getKey(); // 获得goods $goods = Goods::whereIn('category_id', $categories)-&gt;get();</pre>🎜모델을 설정한 후 구조 트리를 복구하여 <code>_lft를 채우기만 하면 됩니다. 및 _rgt code> 필드: 🎜<pre class="brush:php;toolbar:false">$result = Category::withDepth()-&gt;find($id); $depth = $result-&gt;depth;</pre>🎜Relationship🎜🎜Node에는 다음과 같은 기능이 있으며 완벽하게 작동하고 사전 로드되어 있습니다.🎜🎜🎜🎜Node는 parent에 속합니다🎜🎜🎜🎜Node에는 많은 하위 항목이 있습니다🎜🎜🎜🎜 Node에는 많은 조상이 있습니다🎜🎜🎜 🎜Node에는 많은 자손이 있습니다🎜🎜🎜🎜 Category 모델이 있다고 가정합니다. 변수 $node는 모델의 인스턴스이고 우리가 작업하는 노드(노드)입니다. 새로 생성된 노드나 데이터베이스에서 꺼낸 노드에 노드를 삽입할 수 있습니다🎜🎜🎜🎜노드를 삽입하거나 이동할 때마다 여러 데이터베이스 작업을 수행해야 하므로 트랜잭션을 사용하는 것이 좋습니다.🎜🎜 <strong>주목하세요! </strong> 버전 v4.2.0의 경우 트랜잭션이 자동으로 활성화되지 않습니다. 또한 노드의 구조화된 작업에는 모델에 대한 수동 저장이 필요하지만 일부 메서드는 작업 후 암시적으로 저장을 실행하고 부울 결과를 반환합니다. 🎜<h4>노드 생성</h4>🎜단순히 노드를 생성하면 트리 끝에 추가됩니다. 🎜<pre class="brush:php;toolbar:false">$bool = $node-&gt;down(); $bool = $node-&gt;up(); // 向下移动3个兄弟节点 $bool = $node-&gt;down(3);</pre>🎜 또는 🎜<pre class="brush:php;toolbar:false">$result = Category::whereAncestorOf($node)-&gt;get(); $result = Category::whereAncestorOrSelf($id)-&gt;get();</pre>🎜여기 노드는 루트로 설정됩니다. 이는 상위 노드가 없음을 의미합니다.🎜<h4>기존 노드를 루트로 설정</h4> <pre class="brush:php;toolbar:false">$result = Category::whereDescendantOf($node)-&gt;get(); $result = Category::whereNotDescendantOf($node)-&gt;get(); $result = Category::orWhereDescendantOf($node)-&gt;get(); $result = Category::orWhereNotDescendantOf($node)-&gt;get(); $result = Category::whereDescendantAndSelf($id)-&gt;get(); //结果集合中包含目标node自身 $result = Category::whereDescendantOrSelf($node)-&gt;get();</pre> <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);
🎜Add to the front end of 상위 노드 방법🎜
- 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' ],
];

上面有一个namefoo的节点,它有指定的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();

获取表的相关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,且在模型层

helper 方法

检查节点是否为其他节点的子节点
$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();

作用域(scope)

假设你有个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, 但是多余

相关推荐:

php实现多级分类筛选程序代码

PHP 查询多级分类的实例程序代码

smarty实现多级分类的方法_php技巧

위 내용은 laravel-nestedset 다중 레벨 무한 분류에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.