在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
开源软件名称(OpenSource Name):lazychaser/laravel-nestedset开源软件地址(OpenSource Url):https://github.com/lazychaser/laravel-nestedset开源编程语言(OpenSource Language):PHP 100.0%开源软件介绍(OpenSource Introduction):This is a Laravel 4-8 package for working with trees in relational databases.
Although this project is completely free for use, I appreciate any support! Contents: What are nested sets?Nested sets or Nested Set Model is a way to effectively store hierarchical data in a relational table. From wikipedia:
ApplicationsNSM shows good performance when tree is updated rarely. It is tuned to be fast for getting related nodes. It'is ideally suited for building multi-depth menu or categories for shop. DocumentationSuppose that we have a model RelationshipsNode has following relationships that are fully functional and can be eagerly loaded:
Inserting nodesMoving and inserting nodes includes several database queries, so it is highly recommended to use transactions. IMPORTANT! As of v4.2.0 transaction is not automatically started Another important note is that structural manipulations are deferred until you
hit If model is successfully saved it doesn't mean that node was moved. If your application
depends on whether the node has actually changed its position, use if ($node->save()) {
$moved = $node->hasMoved();
} Creating nodesWhen you simply creating a node, it will be appended to the end of the tree: Category::create($attributes); // Saved as root $node = new Category($attributes);
$node->save(); // Saved as root In this case the node is considered a root which means that it doesn't have a parent. Making a root from existing node// #1 Implicit save
$node->saveAsRoot();
// #2 Explicit save
$node->makeRoot()->save(); The node will be appended to the end of the tree. Appending and prepending to the specified parentIf you want to make node a child of other node, you can make it last or first child. In following examples, There are few ways to append a node: // #1 Using deferred insert
$node->appendToNode($parent)->save();
// #2 Using parent node
$parent->appendNode($node);
// #3 Using parent's children relationship
$parent->children()->create($attributes);
// #5 Using node's parent relationship
$node->parent()->associate($parent)->save();
// #6 Using the parent attribute
$node->parent_id = $parent->id;
$node->save();
// #7 Using static method
Category::create($attributes, $parent); And only a couple ways to prepend: // #1
$node->prependToNode($parent)->save();
// #2
$parent->prependNode($node); Inserting before or after specified nodeYou can make
# Explicit save
$node->afterNode($neighbor)->save();
$node->beforeNode($neighbor)->save();
# Implicit save
$node->insertAfterNode($neighbor);
$node->insertBeforeNode($neighbor); Building a tree from arrayWhen using static method $node = Category::create([
'name' => 'Foo',
'children' => [
[
'name' => 'Bar',
'children' => [
[ 'name' => 'Baz' ],
],
],
],
]);
Rebuilding a tree from arrayYou can easily rebuild a tree. This is useful for mass-changing the structure of the tree. Category::rebuildTree($data, $delete);
$data = [
[ 'id' => 1, 'name' => 'foo', 'children' => [ ... ] ],
[ 'name' => 'bar' ],
]; There is an id specified for node with the name of Node
Rebuilding a subtreeAs of 4.2.8 you can rebuild a subtree: Category::rebuildSubtree($root, $data); This constraints tree rebuilding to descendants of Retrieving nodesIn some cases we will use an Ancestors and descendantsAncestors make a chain of parents to the node. Helpful for displaying breadcrumbs to the current category. Descendants are all nodes in a sub tree, i.e. children of node, children of children, etc. Both ancestors and descendants can be eagerly loaded. // Accessing ancestors
$node->ancestors;
// Accessing descendants
$node->descendants; It is possible to load ancestors and descendants using custom query: $result = Category::ancestorsOf($id);
$result = Category::ancestorsAndSelf($id);
$result = Category::descendantsOf($id);
$result = Category::descendantsAndSelf($id); In most cases, you need your ancestors to be ordered by the level: $result = Category::defaultOrder()->ancestorsOf($id); A collection of ancestors can be eagerly loaded: $categories = Category::with('ancestors')->paginate(30);
// in view for breadcrumbs:
@foreach($categories as $i => $category)
<small>{{ $category->ancestors->count() ? implode(' > ', $category->ancestors->pluck('name')->toArray()) : 'Top Level' }}</small><br>
{{ $category->name }}
@endforeach SiblingsSiblings are nodes that have same parent. $result = $node->getSiblings();
$result = $node->siblings()->get(); To get only next siblings: // Get a sibling that is immediately after the node
$result = $node->getNextSibling();
// Get all siblings that are after the node
$result = $node->getNextSiblings();
// Get all siblings using a query
$result = $node->nextSiblings()->get(); To get previous siblings: // Get a sibling that is immediately before the node
$result = $node->getPrevSibling();
// Get all siblings that are before the node
$result = $node->getPrevSiblings();
// Get all siblings using a query
$result = $node->prevSiblings()->get(); Getting related models from other tableImagine that each category // Get ids of descendants
$categories = $category->descendants()->pluck('id');
// Include the id of category itself
$categories[] = $category->getKey();
// Get goods
$goods = Goods::whereIn('category_id', $categories)->get(); Including node depthIf you need to know at which level the node is: $result = Category::withDepth()->find($id);
$depth = $result->depth; Root node will be at level 0. Children of root nodes will have a level of 1, etc. To get nodes of specified level, you can apply $result = Category::withDepth()->having('depth', '=', 1)->get(); IMPORTANT! This will not work in database strict mode Default orderAll nodes are strictly organized internally. By default, no order is applied, so nodes may appear in random order and this doesn't affect displaying a tree. You can order nodes by alphabet or other index. But in some cases hierarchical order is essential. It is required for retrieving ancestors and can be used to order menu items. To apply tree order $result = Category::defaultOrder()->get(); You can get nodes in reversed order: $result = Category::reversed()->get(); To shift node up or down inside parent to affect default order: $bool = $node->down();
$bool = $node->up();
// Shift node by 3 siblings
$bool = $node->down(3); The result of the operation is boolean value of whether the node has changed its position. ConstraintsVarious constraints that can be applied to the query builder:
Descendants 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();
// Include target node into result set
$result = Category::whereDescendantOrSelf($node)->get(); Ancestor constraints: $result = Category::whereAncestorOf($node)->get();
$result = Category::whereAncestorOrSelf($id)->get();
Building a treeAfter getting a set of nodes, you can convert it to tree. For example: $tree = Category::get()->toTree(); This will fill $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); This will output something like this:
Building flat treeAlso, you can build a flat tree: a list of nodes where child nodes are immediately after parent node. This is helpful when you get nodes with custom order (i.e. alphabetically) and don't want to use recursion to iterate over your nodes. $nodes = Category::get()->toFlatTree(); Previous example will output:
Getting a subtreeSometimes you don't need whole tree to be loaded and just some subtree of specific node. It is show in following example: $root = Category::descendantsAndSelf($rootId)->toTree()->first(); In a single query we are getting a root of a subtree and all of its
descendants that are accessible via If you don't need $tree = Category::descendantsOf($rootId)->toTree($rootId); Deleting nodesTo delete a node: $node->delete(); IMPORTANT! Any descendant that node has will also be deleted! IMPORTANT! Nodes are required to be deleted as models, don't try do delete them using a query like so: Category::where('id', '=', $id)->delete(); This will break the tree!
Helper methodsTo check if node is a descendant of other node: $bool = $node->isDescendantOf($parent); To check whether the node is a root: $bool = $node->isRoot(); Other checks:
Checking consistencyYou can check whether a tree is broken (i.e. has some structural errors): $bool = Category::isBroken(); It is possible to get error statistics: $data = Category::countErrors(); It will return an array with following keys:
Fixing treeSince v3.1 tree can now be fixed. Using inheritance info from Node::fixTree(); ScopingImagine you have protected function getScopeAttributes()
{
return [ 'menu_id' ];
} But now, in order to execute some custom query, you need to provide attributes that are used for scoping: MenuItem::scoped([ 'menu_id' => 5 ])->withDepth()->get(); // OK
MenuItem::descendantsOf($id)->get(); // WRONG: returns nodes from other scope
MenuItem::scoped([ 'menu_id' => 5 ])->fixTree(); // OK When requesting nodes using model instance, scopes applied automatically based on the attributes of that model: $node = MenuItem::findOrFail($id);
$node->siblings()->withDepth()->get(); // OK To get scoped query builder using instance: $node->newScopedQuery(); Scoping and eager loadingAlways use scoped query when eager loading: MenuItem::scoped([ 'menu_id' => 5])->with('descendants')->findOrFail($id); // OK
MenuItem::with('descendants')->findOrFail($id); // WRONG |