laravel-nestedset: unlimited multi-level classification of the correct posture
laravel-nestedset is a relational database tree traversal of the plug-in package larvel4-5
table of Contents:
- Nested Sets Model Introduction
- Installation Requirements
- installation
- start using
- File migration
- Insert Node
- Gets node
- Delete Node
- Consistency check and repair
- Scope
Nested Sets Model Introduction
Nested Set Model is a clever method for implementing ordered tree, which is fast and does not require recursive queries, such as a tree no matter how many layers, you can use only one query to get all the offspring of a node, the disadvantage is that it insert, move, delete, you need to perform complex sql statement, but these were dealt with within this plugin! See more on Wikipedia! Nested set model and its Chinese translation! Nested Set Model
Installation Requirements
- PHP>=5.4
- laravel>=4.1
- v4.3 support future versions Laravel-5.5
- v4 version supports Laravel-5.2,5.3,5.4
- v3 release supports Laravel-5.1
- v2 release supports Laravel-4 strongly recommend the use of data to support things function of engine (like the MySql innoDb) to prevent potential data corruption.
installation
In composer.json
adding the following code file:
"kalnoy/nestedset": "^4.3",
Run composer install
to install it.
Or directly from the command line input
composer require kalnoy/nestedset
To install the version history Click more versions
start using
File migration
You can use the NestedSet
class columns
to add a field with a default name of the method:
...
use Kalnoy\Nestedset\NestedSet;
Schema::create('table', function (Blueprint $table) {
...
NestedSet::columns($table);
});
Delete field:
...
use Kalnoy\Nestedset\NestedSet;
Schema::table('table', function (Blueprint $table) {
NestedSet::dropColumns($table);
});
The default field _lft
named: _rgt
, , parent_id
, source code as follows:
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());
}
model
You need to use the model Kalnoy\Nestedset\NodeTrait
trait to implement nested sets
use Kalnoy\Nestedset\NodeTrait;
class Foo extends Model {
use NodeTrait;
}
Migrate other existing local data
Migration from other nested set model library
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);
}
Migrating from another model library has a parent-child relationship
If your database contains tree parent_id
field information, you need to add the following two columns field to your blueprint document:
$table->unsignedInteger('_lft');
$table->unsignedInteger('_rgt');
After setting up your model you only need to fix your tree to populate _lft
and _rgt
field:
MyModel::fixTree();
relationship
Node has the following functions, they are fully functional and are pre-loaded:
- Node belongs to parent
- Node has many children
- Node has many ancestors
- Node has many descendants
Suppose we have a Category model; $ node is an instance variable of the model is the node (node) of our operations. It may be a new node is created or removed from the database node
Insertion node (node)
Each time you insert or move a node to be executed are several database operations, all strongly recommended transaction.
note! For v4.2.0 version is not automatically open transaction, and the other node of structured operations need be performed manually save on the model, but some will be hidden method to perform Boolean result of the save and return to operation.
Creating nodes (node)
When you create a simple node, it will be added to the end of the tree.
Category::create($attributes); // 自动save为一个根节点(root)
or
$node = new Category($attributes);
$node->save(); // save为一个根节点(root)
Here node is set to root, meaning that it has no parent
An existing node to root
// #1 隐性 save
$node->saveAsRoot();
// #2 显性 save
$node->makeRoot()->save();
Add a child node to the parent node or the end of the specified front end
If you want to add a child node, you can add the first child node of the parent node or the last child. * In the following examples, as existing nodes$parent
Added to the end of the parent node comprising:
// #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);
Added to a front end of the parent node
// #1
$node->prependToNode($parent)->save();
// #2
$parent->prependNode($node);
Node is inserted into the front or rear of the specified node
You can use the following methods to $node
add the specified node $neighbor
adjacent node
$neighbor
Must be present, $node
you may be newly created node, or may be existing, if $node
a node that already exists, it will move to a new location and $neighbor
the adjacent, if necessary, its parent will change.
# 显性save
$node->afterNode($neighbor)->save();
$node->beforeNode($neighbor)->save();
# 隐性 save
$node->insertAfterNode($neighbor);
$node->insertBeforeNode($neighbor);
The array is constructed tree
But using create
a static method, it checks whether the array contains the children
key, if any, will recursively create more nodes.
$node = Category::create([
'name' => 'Foo',
'children' => [
[
'name' => 'Bar',
'children' => [
[ 'name' => 'Baz' ],
],
],
],
]);
Now $node->children
contains a set of nodes that have been created.
The array rebuild a tree
You can easily reconstruct a tree, which is very useful to save a lot of modifications of the tree structure. Category::rebuildTree($data, $delete);
$data
An array representative node
$data = [
[ 'id' => 1, 'name' => 'foo', 'children' => [ ... ] ],
[ 'name' => 'bar' ],
];
Has a top name
for the foo
node, which has a specified id
, existing behalf of the node will be filled, if the node does not exist, like a throw ModelNotFoundException
addition, this node as well as children
an array, the array will be in the same manner adding to the foo
internal node. bar
Node has no primary key, just do not exist, it will be created. $delete
Representatives whether to delete the database already exists but $data
does not exist in the data, default is not deleted.
For subsequent reconstruction sub-tree version 4.3.8 you can rebuild the subtree
Category::rebuildSubtree($root, $data);
This will limit the reconstruction of only $ root subtree
Retrieval node
In some cases we need to use the variable $ id represents the primary key target node id
Ancestors and descendants
Ancestors create a chain of the parent node, which for the display of the current types of bread crumbs helpful. Descendants are all child nodes of a parent node. Ancestors and Descendants are preloaded.
// Accessing ancestors
$node->ancestors;
// Accessing descendants
$node->descendants;
Load ancestors and descendants through customized queries:
$result = Category::ancestorsOf($id);
$result = Category::ancestorsAndSelf($id);
$result = Category::descendantsOf($id);
$result = Category::descendantsAndSelf($id);
In most cases, you need to sort by hierarchy:
$result = Category::defaultOrder()->ancestorsOf($id);
Ancestors set can be pre-loaded:
$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
The ancestors name
all taken out into an array, with> string from the splice.
Sibling
We have the same parent is referred to as sibling node mutual
$result = $node->getSiblings();
$result = $node->siblings()->get();
Get behind the adjacent sibling:
// 获取相邻的下一个兄弟节点
$result = $node->getNextSibling();
// 获取后面的所有兄弟节点
$result = $node->getNextSiblings();
// 使用查询获得所有兄弟节点
$result = $node->nextSiblings()->get();
Get in front of the adjacent sibling:
// 获取相邻的前一个兄弟节点
$result = $node->getPrevSibling();
// 获取前面的所有兄弟节点
$result = $node->getPrevSiblings();
// 使用查询获得所有兄弟节点
$result = $node->prevSiblings()->get();
Access to relevant model table
Assume that each category has many goods, and hasMany relationship has been established, how simple it all and get $ category of goods in all future generations?
// 获取后代的id
$categories = $category->descendants()->pluck('id');
// 包含Category本身的id
$categories[] = $category->getKey();
// 获得goods
$goods = Goods::whereIn('category_id', $categories)->get();
Node contains depth (depth)
If you need to know the access node of that level:
$result = Category::withDepth()->find($id);
$depth = $result->depth;
The root node (root) is the layer 0 (level 0), the sub-root node is a first layer (level 1), so you can use having
constraints to obtain a particular node hierarchy
$result = Category::withDepth()->having('depth', '=', 1)->get();
Note that this is not available in the database strict mode
The default sort
All nodes are within the strict organization, no order by default, the node is random show, to show the impact of this, you can in alphabetical order and the other nodes sorting.
But in some cases it is necessary to show a hierarchy, it is useful for obtaining ancestors and menu order.
Use deaultOrder use the sort of tree: $result = Category::defaultOrder()->get();
You can also use the reverse order: $result = Category::reversed()->get();
Let the parent node moves up and down inside to change the default sort:
$bool = $node->down();
$bool = $node->up();
// 向下移动3个兄弟节点
$bool = $node->down(3);
The operation returns to whether to change the operating position of the node of the Boolean value
constraint
Many constraints can be used on these query builder:
- whereIsRoot () Gets the root node only;
- whereIsAfter ($ id) Gets all nodes in the back of the id of a particular node (not just siblings).
- whereIsBefore ($ id) Gets all nodes in the front of the id of a particular node (not just siblings).
Ancestor constraints
$result = Category::whereAncestorOf($node)->get();
$result = Category::whereAncestorOrSelf($id)->get();
$node
Examples which may be a primary key or the model 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();
//结果集合中包含目标node自身
$result = Category::whereDescendantOrSelf($node)->get();
Building a Tree
After acquiring a node of the result set, we can convert it to a tree, for example: $tree = Category::get()->toTree();
This will add the parent and children relationship on each node, and you can use a recursive algorithm to render the tree:
$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);
It will be like the following output similar to:
- Root
-- Child 1
--- Sub child 1
-- Child 2
- Another root
Construction of a flat tree
You can construct a flat tree: the sub-node is placed directly behind the parent node. When you get custom sorting nodes and do not want to use recursion to loop your nodes is useful. $nodes = Category::get()->toFlatTree();
Examples of this will be before the following output:
Root
Child 1
Sub child 1
Child 2
Another root
Construction of a subtree
Sometimes you do not need to load the entire tree, but only certain sub-tree: $root = Category::descendantsAndSelf($rootId)->toTree()->first();
a simple query that we can get the relationship between the root and the use of children subtree get it all descendants
If you do not need $ root node itself, you can: $tree = Category::descendantsOf($rootId)->toTree($rootId);
Delete Node
Delete a node:
$node->delete();
**note! ** All descendant nodes will be deleted Note! Nodes need to be removed to the same model, you can not use the following statement to delete a node:
Category::where('id', '=', $id)->delete();
This will destroy the tree structure supports SoftDeletes
trait, and in the model layer
helper method
Check the node is a child node to other nodes $bool = $node->isDescendantOf($parent);
Check whether the root $bool = $node->isRoot();
Other tests
- $node->isChildOf($other);
- $node->isAncestorOf($other);
- $node->isSiblingOf($other);
- $node->isLeaf()
Check the consistency
You can check whether the tree is broken ring $bool = Category::isBroken();
Get the error statistics: $data = Category::countErrors();
It returns an array containing about key
- Lft and rgt value of the error number of nodes - oddness
- Lft rgt value or number of repeating node - duplicates
- wrong_parent - left and rgt values parent_id parent_id invalid number does not correspond to nodes causing
- Number corresponding to the parent node does not exist parent_id containing nodes - missing_parent
Repair tree
From v3.1 support future restoration tree, through inheritance parent_id field of information, each node to set the appropriate value lft and rgt Node::fixTree();
Scope (scope)
Suppose you have a Memu model and MenuItems. Is a one-to-many relationship between them. MenuItems have menu_id property and realize nested sets model. Obviously you want to deal with property-based menu_id each tree individually, in order to achieve such a function, we need to specify the scope menu_id property to property.
protected function getScopeAttributes()
{
return [ 'menu_id' ];
}
Now we have to implement a custom query, we need to provide the need to limit the scope of the property.
MenuItem::scoped([ 'menu_id' => 5 ])->withDepth()->get(); // OK
MenuItem::descendantsOf($id)->get(); // WRONG: returns nodes from other scope
MenuItem::scoped([ 'menu_id' => 5 ])->fixTree();
But using model instance query node, scope based automatically limit the scope attribute set to delete the selected node. E.g:
$node = MenuItem::findOrFail($id);
$node->siblings()->withDepth()->get(); // OK
Example query to get the deletion of selected: $node->newScopedQuery();
Note that, when the keys do not require acquisition model by using the scope
$node = MenuItem::findOrFail($id); // OK
$node = MenuItem::scoped([ 'menu_id' => 5 ])->findOrFail(); // OK, 但是多余