Heim  >  Artikel  >  Backend-Entwicklung  >  Detaillierte Erläuterung der mehrstufigen unendlichen Laravel-Nestedset-Klassifizierung

Detaillierte Erläuterung der mehrstufigen unendlichen Laravel-Nestedset-Klassifizierung

小云云
小云云Original
2018-01-25 13:28:033153Durchsuche
Laravel-Nestedset ist ein Larvel4-5-Plug-In-Paket für den relationalen Datenbank-Traversal-Baum. In diesem Artikel wird hauptsächlich die mehrstufige unbegrenzte Klassifizierung von Laravel-Nestedset mit Ihnen geteilt, in der Hoffnung, allen zu helfen.

Verzeichnis:

  • Einführung in das Modell verschachtelter Mengen

  • Installationsanforderungen

  • Installieren

  • Erste Schritte

    • Dateien migrieren

    • Knoten einfügen

    • Knoten abrufen

    • Knoten löschen

    • Konsistenzprüfung und -korrektur

    • Geltungsbereich

Einführung in das Nested-Sets-Modell

Das Nested-Set-Modell ist eine clevere und schnelle Möglichkeit, geordnete Bäume zu implementieren und erfordert keine rekursiven Abfragen. Unabhängig davon, wie viele Ebenen der Baum hat, können Sie nur eine Abfrage verwenden, um alle Nachkommen unter einem bestimmten Knoten abzurufen komplexe SQL-Anweisungen, aber dieses Plugin kümmert sich um alles!
Weitere Informationen finden Sie auf Wikipedia! Verschachteltes Mengenmodell und seine chinesische Übersetzung! Verschachteltes Sammlungsmodell

Installationsanforderungen

  • PHP>=5.4

  • laravel>=4.1

  • v4.3-Version und höher unterstützt Laravel-5.5

  • v4-Version unterstützt Laravel-5.2, 5.3, 5.4

  • v3 Version unterstützt Laravel-5.1

  • v2-Version unterstützt Laravel-4

Es wird dringend empfohlen, eine Daten-Engine zu verwenden, die Transaktionsfunktionen unterstützt (wie MySqls innoDb), um mögliche Datenbeschädigungen zu verhindern.

Installation

Fügen Sie den folgenden Code zur composer.json-Datei hinzu:

"kalnoy/nestedset": "^4.3",

Führen Sie composer install aus, um ihn zu installieren.

Oder geben Sie

composer require kalnoy/nestedset

direkt in die Befehlszeile ein. Wenn Sie die historische Version installieren möchten, klicken Sie bitte auf Weitere Versionen

Mit der Verwendung beginnen

Dateien migrieren

Sie können die Methode NestedSet der Klasse columns verwenden, um ein Feld mit einem Standardnamen hinzuzufügen:

...
use Kalnoy\Nestedset\NestedSet;

Schema::create('table', function (Blueprint $table) {
    ...
    NestedSet::columns($table);
});

Ein Feld löschen:

...
use Kalnoy\Nestedset\NestedSet;

Schema::table('table', function (Blueprint $table) {
    NestedSet::dropColumns($table);
});

Der Standardfeldname lautet: _lft, _rgt, parent_id, der Quellcode lautet wie folgt:

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());
    }

Modell

Ihr Modell muss das verwenden KalnoyNestedsetNodeTraitMerkmal zum Implementieren verschachtelter Mengen

use Kalnoy\Nestedset\NodeTrait;

class Foo extends Model {
    use NodeTrait;
}

Vorhandene Daten woanders migrieren

Aus anderen verschachtelten Mengenmodellbibliotheken migrieren

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);
}

Aus anderen Modellbibliotheken mit übergeordnetem Element migrieren -Untergeordnete Beziehungen

Wenn Ihr Datenbankstrukturbaum parent_id Feldinformationen enthält, müssen Sie die folgenden zwei Feldspalten zu Ihrer Blueprint-Datei hinzufügen:

$table->unsignedInteger('_lft');
$table->unsignedInteger('_rgt');

Nach dem Einrichten Ihres Modells Sie müssen nur Ihren Strukturbaum reparieren, um ihn mit den Feldern _lft und _rgt zu füllen:

MyModel::fixTree();

Beziehungen

Knoten verfügt über die folgenden Funktionen, sie sind voll funktionsfähig und vorinstalliert :

  • Knoten gehört zum Elternteil

  • Knoten hat viele Kinder

  • Knoten hat viele Vorfahren

  • Knoten hat viele Nachkommen

Angenommen, wir haben ein Kategoriemodell; die Variable $node ist eine Instanz des Modells und der Knoten, den wir betreiben An. Es kann einen Knoten (Knoten) für einen neu erstellten Knoten oder einen aus der Datenbank entnommenen Knoten einfügen

Jedes Mal, wenn Sie einen Knoten einfügen oder verschieben, müssen Sie mehrere Datenbankoperationen ausführen, Alle davon sind intensiv. Es wird empfohlen, Transaktion zu verwenden.

Achtung! Für Version v4.2.0 sind Transaktionen nicht automatisch aktiviert. Darüber hinaus erfordert die strukturierte Operation des Knotens eine manuelle Speicherung des Modells, aber einige Methoden führen implizit die Speicherung aus und geben das boolesche Ergebnis nach der Operation zurück.

Erstellen eines Knotens

Wenn Sie einfach einen Knoten erstellen, wird dieser am Ende des Baums hinzugefügt.

Category::create($attributes); // 自动save为一个根节点(root)

oder

$node = new Category($attributes);
$node->save(); // save为一个根节点(root)

Hier ist der Knoten auf Root gesetzt, was bedeutet, dass er keinen übergeordneten Knoten hat

Einen vorhandenen Knoten auf Root setzen

// #1 隐性 save
$node->saveAsRoot();

// #2 显性 save
$node->makeRoot()->save();

Untergeordnete Knoten am Ende oder vor dem angegebenen übergeordneten Knoten hinzufügen

Wenn Sie einen untergeordneten Knoten hinzufügen möchten, können Sie ihn als ersten untergeordneten Knoten oder als letzten untergeordneten Knoten des übergeordneten Knotens hinzufügen .
* Im folgenden Beispiel umfassen die Methoden zum Hinzufügen von $parent am Ende des übergeordneten Knotens für einen vorhandenen Knoten

:

// #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);

Methode vor dem übergeordneten Knoten hinzufügen

// #1
$node->prependToNode($parent)->save();

// #2
$parent->prependNode($node);

Knoten vor oder nach dem angegebenen Knoten einfügen

Sie können die folgende Methode verwenden, um $node als angegebenen Knoten $neighbor hinzuzufügen Der benachbarte Knoten

$neighbor muss vorhanden sein. $node kann ein neu erstellter oder vorhandener Knoten sein. Wenn $node ein vorhandener Knoten ist, wird er an die neue Position verschoben und $neighbor Angrenzend , sein übergeordnetes Element ändert sich bei Bedarf.

# 显性save
$node->afterNode($neighbor)->save();
$node->beforeNode($neighbor)->save();

# 隐性 save
$node->insertAfterNode($neighbor);
$node->insertBeforeNode($neighbor);

erstellt das Array als Baum

, aber bei Verwendung der statischen Methode create prüft es, ob das Array den Schlüssel children enthält, und erstellt in diesem Fall rekursiv weitere Knoten.

$node = Category::create([
    'name' => 'Foo',

    'children' => [
        [
            'name' => 'Bar',

            'children' => [
                [ 'name' => 'Baz' ],
            ],
        ],
    ],
]);

enthält jetzt $node->children eine Reihe erstellter Knoten.

Rekonstruieren Sie ein Array in einen Baum

Sie können einen Baum leicht rekonstruieren, was sehr nützlich ist, um eine große Anzahl geänderter Baumstrukturen zu speichern.
Category::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技巧

Das obige ist der detaillierte Inhalt vonDetaillierte Erläuterung der mehrstufigen unendlichen Laravel-Nestedset-Klassifizierung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn