This article is from pilishen.com– link to the original; Welcome to our php&Laravel learning group: 109256050

This document is an extension of Nestedset – infinite classification of correct poses to read

Laravel-nestedset is a relational database traversal tree larvel4-5 plug-in package

Directory:

  • Nested Sets Model
  • Installation requirements
  • The installation
  • Begin to use
    • The migration file
    • Insert the node
    • Access to the node
    • Remove nodes
    • Consistency check and repair
    • scope

Nested Sets Model

Nested Set Model is a kind of implementation of ordered tree brillant method, it quickly and do not need a recursive query, no matter how many layer trees are, for example, you can only use a query to get with a node, all the offspring, the disadvantage is that it insert, move, delete, you need to perform complex SQL statements, but these are within the plug-in processing! See Wikipedia for more! Nested set Model and its Chinese translation! Nested collection model

Installation requirements

  • PHP > = 5.4
  • Laravel > = 4.1
  • Laravel-5.5 is supported after v4.3
  • Version V4 supports Laravel-5.2, 5.3, and 5.4
  • Laravel-5.1 is supported in v3
  • Laravel-4 support for version v2 is strongly recommended to use a data engine that supports transactional functionality (like MySql’s innoDb) to prevent possible data corruption.

The installation

Add the following code to the composer. Json file:

"kalnoy/nestedset": "^ 4.3".Copy the code

Run Composer Install to install it.

Or enter it directly from the command line

composer require kalnoy/nestedset
Copy the code

To install the historical version please click more version

Begin to use

The migration file

You can use the Columns method of the NestedSet class to add columns with default names:

. use Kalnoy\Nestedset\NestedSet; Schema::create('table'.function (Blueprint $table) {... NestedSet::columns($table);
});
Copy the code

Delete field:

. use Kalnoy\Nestedset\NestedSet; Schema::table('table'.function (Blueprint $table) {
    NestedSet::dropColumns($table);
});
Copy the code

The default fields are _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());
    }
Copy the code

model

Your model needs to use Kalnoy\Nestedset\NodeTraittrait to implement nested sets

use Kalnoy\Nestedset\NodeTrait;

class Foo extends Model {
    use NodeTrait;
}
Copy the code

Migrate data that already exists elsewhere

Migrate from other nested set model libraries

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);
}
Copy the code

Migrate from other model libraries that have parent-child relationships

If your database structure tree contains the parent_ID field information, you need to add the following two columns to your blueprint file:

$table->unsignedInteger('_lft');
$table->unsignedInteger('_rgt');
Copy the code

After setting up your model you just need to fix your structure tree to fill the _lft and _rGT fields:

MyModel::fixTree();
Copy the code

Relationship between

Node has the following features, which are fully functional and preloaded:

  • Node belongs to parent
  • Node has many children
  • Node has many ancestors
  • Node has many descendants

Suppose we have a Category model; The variable $node is an instance of this model and is the node that we operate on. It can be a newly created node or a node pulled from a database

Inserting a Node

Several database operations are performed each time a node is inserted or moved, and transaction is highly recommended.

Attention! Transaction is not automatically opened in V4.2.0, and node’s structured operations require manual save on the model, but some methods implicitly perform save and return Boolean results after the operation.

Creating a Node

When you simply create a node, it is added to the end of the tree.

Category::create($attributes); // Automatically save as a root node (root)Copy the code

or

$node = new Category($attributes);
$node->save(); // Save as a root node (root)Copy the code

Here node is set to root, meaning it has no parent node

Set an existing node to root

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

// # 2 dominant save
$node->makeRoot()->save();
Copy the code

Adds a child node to the end or front end of the specified parent node

If you want to add children, you can add either the first child or the last child of the parent node. * In the following example, $parent is an existing node

Methods added to the end of the parent node include:

// #1 Use deferred insertion
$node->appendToNode($parent)->save();

// #2 Use the parent node
$parent->appendNode($node);

// #3 Use the children relation of the parent node
$parent->children()->create($attributes);

// #5 Use the parent relationship of the child node
$node->parent()->associate($parent)->save();

// #6 Use the parent node attribute
$node->parent_id = $parent->id;
$node->save();

// #7 Use static methods
Category::create($attributes.$parent);
Copy the code

Method added to the front end of the parent node

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

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

Inserts a node before or after the specified node

You can add $node as a neighbor to the specified node $neighbor using the following method

$neighbor must exist. $node can be a newly created node or an existing node. If $node is an existing node, it will move to a new location to be next to $neighbor, and its parent will change if necessary.

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

# the recessive save
$node->insertAfterNode($neighbor);
$node->insertBeforeNode($neighbor);
Copy the code

Build an array as a tree

But with the create static method, it checks to see if the array contains the children key and, if so, recursively creates more nodes.

$node = Category::create([
    'name'= >'Foo'.'children' => [
        [
            'name'= >'Bar'.'children' => [
                [ 'name'= >'Baz'[,], [,]);Copy the code

Now $node->children contains a set of created nodes.

Rebuild the array as a tree

You can easily rebuild a tree, which is very useful for preserving a large number of modified tree structures. Category::rebuildTree($data, $delete);

$data represents the array of nodes

$data= [['id'= > 1,'name'= >'foo'.'children' => [ ... ] ],
    [ 'name'= >'bar']];Copy the code

There is a node named foo, which has the specified ID to indicate that the existing node will be populated, and if the node does not exist, a ModelNotFoundException will be thrown. In addition, this node has the children array, The array is added to the foo node in the same way. The bar node has no primary key, that is, does not exist, and will be created. $delete indicates whether to delete data that already exists in the database but does not exist in $data. The default value is no deletion.

Rebuild subtrees For versions 4.3.8 and later you can rebuild subtrees

Category::rebuildSubtree($root, $data);

This will restrict the rebuilding of only the $root subtree

Retrieve node

In some cases we need to use the variable $ID to represent the primary key ID of the target node

Ancestors and descendants

Ancestors creates a parent chain of a node, which is useful for displaying the current kind of breadcrumbs. Descendants are all the children of a parent node. Ancestors and Descendants can be preloaded.

// Accessing ancestors
$node->ancestors;

// Accessing descendants
$node->descendants;
Copy the code

Load ancestors and descendants with custom queries:

$result = Category::ancestorsOf($id);
$result = Category::ancestorsAndSelf($id);
$result = Category::descendantsOf($id);
$result = Category::descendantsAndSelf($id);
Copy the code

In most cases, you need to sort by hierarchy:

$result = Category::defaultOrder()->ancestorsOf($id);

Ancestor collections can be preloaded:

$categories = Category::with('ancestors')->paginate(30); // Breadcrumbs in view template: @foreach($categories as $i= >$category)
    <small> $category->ancestors->count() ? implode('>'.$category->ancestors->pluck('name')->toArray()) : 'Top Level' </small><br>
    $category->name
@endforeach
Copy the code

After all the names of the ancestors are taken out, they are converted to an array and then output as a string with >.

Brother nodes

Nodes with the same parent are called siblings

$result = $node->getSiblings();

$result = $node->siblings()->get();
Copy the code

Get the next sibling node:

// Get the next sibling of the neighboring node$result = $node->getNextSibling(); // Get all subsequent sibling nodes$result = $node->getNextSiblings(); // Use the query to get all sibling nodes$result = $node->nextSiblings()->get();
Copy the code

Get adjacent previous sibling nodes:

// Get the previous sibling of the adjacent node$result = $node->getPrevSibling(); // Get all previous sibling nodes$result = $node->getPrevSiblings(); // Use the query to get all sibling nodes$result = $node->prevSiblings()->get();
Copy the code

Get the relevant model for the table

Assuming that each category has many goods and the hasMany relationship has been established, how can we simply get $category and all its descendants’ goods?

// Get the id of the descendant$categories = $category->descendants()->pluck('id'); // Contains the id of the Category itself$categories[] = $category->getKey(); / / get the goods$goods = Goods::whereIn('category_id'.$categories)->get();
Copy the code

Contains node depth

If you need to know node entry and exit tiers:

$result = Category::withDepth()->find($id);

$depth = $result->depth;
Copy the code

The root node is level 0, root’s children are level 1, and so on. You can use the having constraint to get nodes at a particular level

$result = Category::withDepth()->having('depth', '=', 1)->get();

Note that this does not work in database strict mode

The default sort

All nodes are strictly organized internally, with no order by default, so nodes are presented randomly. This affects presentation, and you can sort nodes alphabetically and otherwise.

However, hierarchical presentation is necessary in some cases, and it is useful for obtaining ancestors and for menu ordering.

$result = Category::defaultOrder()->get();

$result = Category::reversed()->get();

Change the default order by moving the node up and down within the parent:

$bool = $node->down();
$bool = $node->up(); // Move down three sibling nodes$bool = $node->down(3);
Copy the code

The operation returns a Boolean value based on whether the position of the node being operated on has changed

The constraint

A number of constraints can be applied to these query constructors:

  • WhereIsRoot () takes only the root node;
  • WhereIsAfter ($ID) gets all nodes (not just siblings) after a node with a specific ID.
  • WhereIsBefore ($ID) gets all nodes (not just siblings) before a node with a specific ID.

Ancestors constraints

$result = Category::whereAncestorOf($node)->get();
$result = Category::whereAncestorOrSelf($id)->get();
Copy the code

$node can be the primary key of the model or an instance of the model

Offspring constraints

$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(); // The result set contains the target node itself$result = Category::whereDescendantOrSelf($node)->get();
Copy the code

Build a tree

$tree = Category::get()->toTree(); $tree = Category::get()->toTree()

This will add parent and children relationships to each node, and you can use recursive algorithms to render the tree:

$nodes = Category::get()->toTree();

$traverse = function ($categories.$prefix = The '-') use (&$traverse) {
    foreach ($categories as $category) {
        echo PHP_EOL.$prefix.' '.$category->name;

        $traverse($category->children, $prefix.The '-'); }};$traverse($nodes);
Copy the code

This will output something like the following:

- Root
-- Child 1
--- Sub child 1
-- Child 2
- Another root
Copy the code

Build a flat tree

You can also build a flat tree by placing children directly after their parents. Useful when you get custom sorted nodes and don’t want to use recursive loops on your nodes. $nodes = Category::get()->toFlatTree();

The previous example would output the following:

Root
Child 1
Sub child 1
Child 2
Another root
Copy the code

Build a subtree

Sometimes you don’t need to load the entire tree but only need some specific subtree: $root = Category: : descendantsAndSelf ($rootId) – > toTree () – > first (); With a simple query we can get the root node of the subtree and use the children relation to get all its descendants

If you don’t need totree = Category::descendantsOf(rootId); `

Remove nodes

Delete a node:

$node->delete();

* * attention! All descendants of the ** node will be deleted. Nodes need to be deleted like models. You cannot use the following statement to delete nodes:

Category::where('id', '=', $id)->delete();

This will destroy the tree structure supporting Softdelide Strait and in the model layer

Helper method

$bool = $node->isDescendantOf($parent);

$bool = $node->isRoot();

Other tests

  • other);
  • other);
  • other);
  • $node->isLeaf()

Checking consistency

$bool = Category::isBroken();

$data = Category::countErrors();

It returns an array containing the following keys

  • Oddness — Number of nodes with incorrect LFT and RGT values
  • Duplicates: Specifies the number of nodes whose LFT or RGT values duplicate
  • Number of nodes that cause invalid parent_id when wrong_parent — left and RGT do not correspond to parent_id
  • Missing_parent – Number of nodes with parent_id corresponding to a parent node that does not exist

Repair the tree

From v3.1 onwards, support for tree repair. By inherits the parent_id field information, set the appropriate LFT and RGT values for each node ::fixTree();

Scope

Suppose you have a Memu model and MenuItems. There is a one-to-many relationship between them. MenuItems has the menu_id attribute and implements the nested sets model. Obviously you want to process each tree individually based on the menu_id attribute. To do this, we need to specify the menu_id attribute as a scope attribute.

protected function getScopeAttributes()
{
    return [ 'menu_id' ];
}
Copy the code

Now in order to implement a custom query, we need to provide properties that need to be scoped.

MenuItem::scoped([ 'menu_id' => 5 ])->withDepth()->get(); // OK
MenuItem::descendantsOf($id)->get(); // WRONG: returns nodes from other scope
MenuItem::scoped([ 'menu_id' => 5 ])->fixTree();
Copy the code

However, when a Model instance is used to query nodes, Scope automatically deletes nodes based on the set limited scope property. Such as:

$node = MenuItem::findOrFail($id);

$node->siblings()->withDepth()->get(); // OK
Copy the code

$node->newScopedQuery();

Note that scope is not required when fetching the model through the primary key

$node = MenuItem::findOrFail($id); // OK
$node = MenuItem::scoped([ 'menu_id'=> 5 ])->findOrFail(); // OK, but redundantCopy the code