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:
Nested sets or Nested Set Model isa way to effectively store hierarchical data in a relational table. From wikipedia:
The nested set model is to number the nodes according to a tree traversal,which visits each node twice, assigning numbers in the order of visiting, andat both visits. This leaves two numbers for each node, which are stored as twoattributes. Querying becomes inexpensive: hierarchy membership can be tested bycomparing these numbers. Updating requires renumbering and is therefore expensive.
NSM shows good performance when tree is updated rarely. It is tuned to be fast forgetting related nodes. It'is ideally suited for building multi-depth menu orcategories for shop.
Suppose that we have a model Category
; a $node
variable is an instance of that modeland the node that we are manipulating. It can be a fresh model or one from database.
Node has following relationships that are fully functional and can be eagerly loaded:
parent
children
ancestors
descendants
Moving and inserting nodes includes several database queries, so it ishighly 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 youhit save
on model (some methods implicitly call save
and return boolean resultof the operation).
If model is successfully saved it doesn't mean that node was moved. If your applicationdepends on whether the node has actually changed its position, use hasMoved
method:
if ($node->save()) {
$moved = $node->hasMoved();
}
When 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.
// #1 Implicit save
$node->saveAsRoot();
// #2 Explicit save
$node->makeRoot()->save();
The node will be appended to the end of the tree.
If you want to make node a child of other node, you can make it last or first child.
In following examples, $parent
is some existing node.
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);
You can make $node
to be a neighbor of the $neighbor
node using following methods:
$neighbor
must exists, target node can be fresh. If target node exists,it will be moved to the new position and parent will be changed if it's required.
# Explicit save
$node->afterNode($neighbor)->save();
$node->beforeNode($neighbor)->save();
# Implicit save
$node->insertAfterNode($neighbor);
$node->insertBeforeNode($neighbor);
When using static method create
on node, it checks whether attributes containschildren
key. If it does, it creates more nodes recursively.
$node = Category::create([
'name' => 'Foo',
'children' => [
[
'name' => 'Bar',
'children' => [
[ 'name' => 'Baz' ],
],
],
],
]);
$node->children
now contains a list of created child nodes.
You can easily rebuild a tree. This is useful for mass-changing the structure ofthe tree.
Category::rebuildTree($data, $delete);
$data
is an array of nodes:
$data = [
[ 'id' => 1, 'name' => 'foo', 'children' => [ ... ] ],
[ 'name' => 'bar' ],
];
There is an id specified for node with the name of foo
which means that existingnode will be filled and saved. If node is not exists ModelNotFoundException
isthrown. Also, this node has children
specified which is also an array of nodes;they will be processed in the same manner and saved as children of node foo
.
Node bar
has no primary key specified, so it will be created.
$delete
shows whether to delete nodes that are already exists but not presentin $data
. By default, nodes aren't deleted.
As of 4.2.8 you can rebuild a subtree:
Category::rebuildSubtree($root, $data);
This constraints tree rebuilding to descendants of $root
node.
In some cases we will use an $id
variable which is an id of the target node.
Ancestors make a chain of parents to the node. Helpful for displaying breadcrumbsto the current category.
Descendants are all nodes in a sub tree, i.e. children of node, children ofchildren, 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
Siblings 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();
Imagine that each category has many
goods. I.e. HasMany
relationship is established.How can you get all goods of $category
and every its descendant? Easy!
// 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();
If 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 having
constraint:
$result = Category::withDepth()->having('depth', '=', 1)->get();
IMPORTANT! This will not work in database strict mode
All nodes are strictly organized internally. By default, no order isapplied, so nodes may appear in random order and this doesn't affectdisplaying a tree. You can order nodes by alphabet or other index.
But in some cases hierarchical order is essential. It is required forretrieving ancestors and can be used to order menu items.
To apply tree order defaultOrder
method is used:
$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 itsposition.
Various 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();
$node
can be either a primary key of the model or model instance.
After getting a set of nodes, you can convert it to tree. For example:
$tree = Category::get()->toTree();
This will fill parent
and children
relationships on every node in the set andyou can render a tree using recursive algorithm:
$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:
- Root
-- Child 1
--- Sub child 1
-- Child 2
- Another root
Also, you can build a flat tree: a list of nodes where child nodes are immediatelyafter 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:
Root
Child 1
Sub child 1
Child 2
Another root
Sometimes 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 itsdescendants that are accessible via children
relation.
If you don't need $root
node itself, do following instead:
$tree = Category::descendantsOf($rootId)->toTree($rootId);
To 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!
SoftDeletes
trait is supported, also on model level.
To 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:
$node->isChildOf($other);
$node->isAncestorOf($other);
$node->isSiblingOf($other);
$node->isLeaf()
You 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:
oddness
-- the number of nodes that have wrong set of lft
and rgt
valuesduplicates
-- the number of nodes that have same lft
or rgt
valueswrong_parent
-- the number of nodes that have invalid parent_id
value thatdoesn't correspond to lft
and rgt
valuesmissing_parent
-- the number of nodes that have parent_id
pointing tonode that doesn't existsSince v3.1 tree can now be fixed. Using inheritance info from parent_id
column,proper _lft
and _rgt
values are set for every node.
Node::fixTree();
Imagine you have Menu
model and MenuItems
. There is a one-to-many relationshipset up between these models. MenuItem
has menu_id
attribute for joining modelstogether. MenuItem
incorporates nested sets. It is obvious that you would want toprocess each tree separately based on menu_id
attribute. In order to do so, youneed to specify this attribute as scope attribute:
protected function getScopeAttributes()
{
return [ 'menu_id' ];
}
But now, in order to execute some custom query, you need to provide attributesthat 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 basedon the attributes of that model:
$node = MenuItem::findOrFail($id);
$node->siblings()->withDepth()->get(); // OK
To get scoped query builder using instance:
$node->newScopedQuery();
Always use scoped query when eager loading:
MenuItem::scoped([ 'menu_id' => 5])->with('descendants')->findOrFail($id); // OK
MenuItem::with('descendants')->findOrFail($id); // WRONG
It is highly suggested to use database that supports transactions (like MySql's InnoDb)to secure a tree from possible corruption.
To install the package, in terminal:
composer require kalnoy/nestedset
For Laravel 5.5 and above users:
Schema::create('table', function (Blueprint $table) {
...
$table->nestedSet();
});
// To drop columns
Schema::table('table', function (Blueprint $table) {
$table->dropNestedSet();
});
For prior Laravel versions:
...
use Kalnoy\Nestedset\NestedSet;
Schema::create('table', function (Blueprint $table) {
...
NestedSet::columns($table);
});
To drop columns:
...
use Kalnoy\Nestedset\NestedSet;
Schema::table('table', function (Blueprint $table) {
NestedSet::dropColumns($table);
});
Your model should use Kalnoy\Nestedset\NodeTrait
trait to enable nested sets:
use Kalnoy\Nestedset\NodeTrait;
class Foo extends Model {
use NodeTrait;
}
If your previous extension used different set of columns, you just need to overridefollowing methods on your model class:
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);
}
If your tree contains parent_id
info, you need to add two columns to your schema:
$table->unsignedInteger('_lft');
$table->unsignedInteger('_rgt');
After setting up your model you only need to fix the tree to fill_lft
and _rgt
columns:
MyModel::fixTree();
Copyright (c) 2017 Alexander Kalnoy
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Laravel-nestedset是Laravel框架中的一个无限级分类的扩展包,它的实现有别于传统的邻接表模型,采用的是一种新的分层数据模型叫嵌套集合模型,这种模型能够实现快速查询,运用在少修改、频查询的业务场景中能够提升较大的查询效率。下面我就带着大家一步一步来熟悉这个扩展。 一、首先安装扩展包 1.进入到Laravel项目的根目录中,用composer安装kalnoy/nestedset。
介绍 Laravel 的 Eloquent ORM 提供了漂亮、简洁的 ActiveRecord 实现来和数据库的互动。 每个数据库表会和一个对应的「模型」互动。 在开始之前,记得把 config/database.php 里的数据库连接配置好。 基本用法 我们先从建立一个 Eloquent 模型开始。模型通常放在 app 目录下,但是您可以将它们放在任何地方,只要能通过 composer.jso
Eloquent 是 Laravel 的 'ORM',即 'Object Relational Mapping',对象关系映射。ORM 的出现是为了帮我们把对数据库的操作变得更加地方便。 Eloquent 让一个 'Model类' 对应一张数据库表,并且在底层封装了很多 'function',可以让 Model 类非常方便地调用。 在实际的使用过程中,我们在model里面定义一个class来继承E
快速入门 定义模型 php artisan make:model User --migration # 创建一个模型,并且创建他的迁移文件 // 等同于 php artisan make:model User -m <?php namespace App; use Illuminate\Database\Eloquent\Model; class Flight extends Model {
This is a Laravel 4-5 package for working with trees in relational databases. Laravel 5.5, 5.6, 5.7, 5.8 is supported since v4.3 Laravel 5.2, 5.3, 5.4 is supported since v4 Laravel 5.1 is supported in
原文链接:http://www.pilishen.com/posts...; 欢迎作客我们的php&Laravel学习群:109256050 laravel-nestedset是一个关系型数据库遍历树的larvel4-5的插件包 目录: Nested Sets Model简介 安装要求 安装 开始使用 迁移文件 插入节点 获取节点 删除节点 一致性检查和修复 作用域 Nested Sets Mod
今天再用laravel5.5使用lists方法 时候报错 $this->platform_list=DB::table('xxx')->lists('k','v');查了下资料: 而5.3版本则弃用了这个方法,改用方法pluck方法: $roles = DB::table('roles')->pluck('title', 'name'); foreach ($roles as $name =>
laravel Eloquent:Collection 怎么说呢,自己的记录远远没有文档说的好,也没有文档给的详细,全当自己理解的一个记录吧。 详细文档地址 链接 下面是我关于对Eloquent:collection的理解: 在上一篇中提到过一个对象builder,Illuminate\Database\Eloquent\Builder 它是一个对象构造器,或者是说它是一个中间操作流,他产生了一个
查询不存在的关联 doesntHave 在访问模型记录时,可能希望基于关联不存在来限制查询结果。 假设想要获取不存在任何评论的文章,可以通过向 doesntHave 和 orDoesntHave 方法传递关联名称来实现: $posts = App\Post::doesntHave('comments')->get(
写项目用到的 laravel5.4版本 用路由写数据 怎么写都报错 。解决方法如下 找到根目录下App\Http\Providers\RouteServiceProvider.php /* The controller namespace for the application. 应用程序的控制器命名空间。 When present, controller route declarations w
$query->whereNotExists(function ($query) use ($where){ $query->select(DB::raw(1)) ->from('saas_commission_config_staff as cs') ->whe
在系统中,所有的图片都被单独存储到一个表里面了,但这时候读取就会用with或join等去读取,但有时候存在关联读取再关联读取的时候就很麻烦了,所以可以添加一个额外字段,把图片的链接添加到Eloquent中 public $appends = ['attachmentUrl']; public function getAttachmentUrlAttribute() { $attachme
//只能查出闭包外的表有的字段数据 $users = DB::table('users') ->whereExists(function ($query) { //查询出与这表相对应的字段相等的数据 $query->select(DB::raw(1)) ->from('order
我正在将我的Laravel5.3网站升级到5.5,无法让Dusk在我的本地主机上正常工作。我有其他单元测试可以在本地主机上正常工作,但是由于某些原因,Dusk对任何本地页面返回。本地页面似乎工作很好,在我的桌面上浏览它。 我的DuskTestCase null
我在另一台pc上创建了一个项目,它运行良好,但当我将其复制到另一台pc中并尝试打开它时,它显示: file_put_contents(E:\xampp\htdocs\lweb2\storage\framework/sessions/tqp2cprddgwqpdlbx52ncxsrn6vrlcphczjzxdji):无法打开流:没有这样的文件或目录 它使用创建它的旧路径,
我已经安装了Laravel5.1并尝试连接mongoDB。我已经加载了Jenssers/mongodb,我已经按照说明进行了操作,但是当我转到我的应用程序“localhost/myapp/public”时,它是一个空页面,没有任何内容。 我已添加服务提供商: 别名: 我的配置/数据库是: 有人知道这是错的吗( 谢谢你!
Laravel 系列入门教程 如果你想下载我的代码到本地运行,下载完成后记得运行 composer update 命令呦~ 环境要求 PHP 7.0.0 以上 此为代码示例,教程地址: 2017 版 Laravel 系列入门教程(一)【最适合中国人的 Laravel 教程】 2017 版 Laravel 系列入门教程(二)【最适合中国人的 Laravel 教程】 2017 版 Laravel 系列
Laravel 是一套简洁、优雅的PHP Web开发框架(PHP Web Framework)。它可以让你从面条一样杂乱的代码中解脱出来;它可以帮你构建一个完美的网络APP,而且每行代码都可以简洁、富于表达力。 功能特点 1、语法更富有表现力 你知道下面这行代码里 “true” 代表什么意思么? $uri = Uri::create(‘some/uri’, array(), array(), tr
我使用任何类型的路由,然后在这个方法中,我在请求类型的基础上生成树。 路线 从控制器在检查类型之前我这样检查它
我需要空间/Laravel权限的帮助。当我试图分配它给我错误哎呀,看起来像出了问题。 错误 Connection.php第761行中的QueryExcema:SQLSTATE[23000]:完整性约束冲突:1048列role_id不能为空(SQL:插入到(,)值(9,))
我在laravel 5.4项目上成功安装了npm安装包。这是我的package.json: {“private”:true,“scripts”:{“dev”:“node node_modules/cross env/dist/bin/cross-env.js node_env=development node_modules/webpack/bin/webpack.js--progress--hi