yujin1st / yii2-nested-set-behavior
此扩展允许您获得嵌套集合树的实用功能。
Requires
- yiisoft/yii2: *
This package is auto-updated.
Last update: 2024-09-13 14:43:47 UTC
README
此扩展允许您获得嵌套集合树的实用功能。
此扩展支持 parentId 属性更新 - 这是与原 creocoder 版本的主要区别
安装
安装此扩展的首选方式是通过 composer。
运行以下命令
php composer.phar require yujin1st/yii2-nested-set-behavior "*"
或
"yujin1st/yii2-nested-set-behavior": "*"
将以下内容添加到您的 composer.json
文件的 require 部分。
配置
首先,您需要按以下方式配置模型
class Category extends ActiveRecord { public function behaviors() { return [ [ 'class' => NestedSet::className(), ], ]; } /** * @return CategoryQuery */ public static function find() { return Yii::createObject(CategoryQuery::className(), [get_called_class()]); } }
其次,您需要按以下方式配置查询模型
class CategoryQuery extends ActiveQuery { public function behaviors() { return [ [ 'class' => NestedSetQuery::className(), ], ]; } }
不需要验证在 leftAttribute
、rightAttribute
、rootAttribute
和 levelAttribute
选项中指定的字段。此外,如果存在这些字段的验证规则,可能会出现问题。请检查模型 rules() 方法中是否没有提及的字段规则。
如果要在数据库中存储单个树,可以使用 schema/schema.sql
构建数据库结构。如果您打算存储多个树,则需要 schema/schema-many-roots.sql
。
默认情况下,leftAttribute
、rightAttribute
和 levelAttribute
的值与默认数据库模式中的字段名称匹配,因此您可以跳过这些配置。
此行为可以通过两种方式工作:每个表一个树和每个表多个树。根据 hasManyRoots
选项的值选择模式,默认值为 false
,表示单树模式。在多树模式下,您可以将 rootAttribute
选项设置为匹配存储树的表中的现有字段。
您可以指定 parentId
属性名称 - 并在插入或移动记录时更新此属性。此功能不会影响行为本身(默认情况下已关闭),
但似乎是一个方便的方式来获取父级的主键而不需要额外的查询。
从树中选择
以下我们将使用一个示例模型 Category
,其在数据库中的结构如下
- 1. Mobile phones
- 2. iPhone
- 3. Samsung
- 4. X100
- 5. C200
- 6. Motorola
- 7. Cars
- 8. Audi
- 9. Ford
- 10. Mercedes
在此示例中,我们有两个树。树根是 ID=1 和 ID=7 的记录。
获取所有根节点
使用 NestedSet::roots()
$roots = Category::find()->roots()->all();
结果
与手机和汽车节点对应的 Active Record 对象数组。
获取节点的所有后代
使用 NestedSet::descendants()
$category = Category::find(1); $descendants = $category->descendants()->all();
结果
与 iPhone、Samsung、X100、C200 和 Motorola 对应的 Active Record 对象数组。
获取节点的所有子节点
使用 NestedSet::children()
$category = Category::find(1); $descendants = $category->children()->all();
结果
与 iPhone、Samsung 和 Motorola 对应的 Active Record 对象数组。
获取节点的所有祖先
使用 NestedSet::ancestors()
$category = Category::find(5); $ancestors = $category->ancestors()->all();
结果
与 Samsung 和 手机对应 的 Active Record 对象数组。
获取节点的父节点
使用 NestedSet::parent()
$category = Category::find(9); $parent = $category->parent()->one();
结果
与 汽车 对应的 Active Record 对象数组。
获取节点同级节点
使用 NestedSet::prev()
或 NestedSet::next()
$category = Category::find(9); $nextSibling = $category->next()->one();
结果
与 Mercedes 对应的 Active Record 对象数组。
获取整个树
您可以使用以下标准 AR 方法获取整个树。
对于每个表一个树
Category::find()->addOrderBy('lft')->all();
对于每个表多个树
Category::find()->where('root = ?', [$root_id])->addOrderBy('lft')->all();
修改树
在本节中,我们将构建与上一节中使用的相同的树。
创建根节点
您可以使用 NestedSet::saveNode()
创建根节点。在单个表一个树模式下,您只能创建一个根节点。如果您尝试创建更多,则会抛出 CException。
$root = new Category; $root->title = 'Mobile Phones'; $root->saveNode(); $root = new Category; $root->title = 'Cars'; $root->saveNode();
结果
- 1. Mobile Phones
- 2. Cars
添加子节点
有多种方法可以添加子节点。有关这些方法的更多信息,请参阅API。让我们使用这些方法为我们已有的树添加节点。
$category1 = new Category; $category1->title = 'Ford'; $category2 = new Category; $category2->title = 'Mercedes'; $category3 = new Category; $category3->title = 'Audi'; $root = Category::find(1); $category1->appendTo($root); $category2->insertAfter($category1); $category3->insertBefore($category1);
结果
- 1. Mobile phones
- 3. Audi
- 4. Ford
- 5. Mercedes
- 2. Cars
从逻辑上看,上面的树看起来不正确。我们稍后将其修复。
$category1 = new Category; $category1->title = 'Samsung'; $category2 = new Category; $category2->title = 'Motorola'; $category3 = new Category; $category3->title = 'iPhone'; $root = Category::find(2); $category1->appendTo($root); $category2->insertAfter($category1); $category3->prependTo($root);
结果
- 1. Mobile phones
- 3. Audi
- 4. Ford
- 5. Mercedes
- 2. Cars
- 6. iPhone
- 7. Samsung
- 8. Motorola
$category1 = new Category; $category1->title = 'X100'; $category2 = new Category; $category2->title = 'C200'; $node = Category::find(3); $category1->appendTo($node); $category2->prependTo($node);
结果
- 1. Mobile phones
- 3. Audi
- 9. С200
- 10. X100
- 4. Ford
- 5. Mercedes
- 2. Cars
- 6. iPhone
- 7. Samsung
- 8. Motorola
修改树
在本节中,我们最终将使我们的树变得逻辑化。
树修改方法
有几个方法允许您修改树。有关这些方法的更多信息,请参阅API。
让我们开始吧
// move phones to the proper place $x100 = Category::find(10); $c200 = Category::find(9); $samsung = Category::find(7); $x100->moveAsFirst($samsung); $c200->moveBefore($x100); // now move all Samsung phones branch $mobile_phones = Category::find(1); $samsung->moveAsFirst($mobile_phones); // move the rest of phone models $iphone = Category::find(6); $iphone->moveAsFirst($mobile_phones); $motorola = Category::find(8); $motorola->moveAfter($samsung); // move car models to appropriate place $cars = Category::find(2); $audi = Category::find(3); $ford = Category::find(4); $mercedes = Category::find(5); foreach([$audi, $ford, $mercedes] as $category) { $category->moveAsLast($cars); }
结果
- 1. Mobile phones
- 6. iPhone
- 7. Samsung
- 10. X100
- 9. С200
- 8. Motorola
- 2. Cars
- 3. Audi
- 4. Ford
- 5. Mercedes
移动节点使其成为新的根节点
存在一个特殊的moveAsRoot()
方法,允许移动节点并将其作为新的根节点。在这种情况下,所有后代也会一起移动。
示例
$node = Category::find(10); $node->moveAsRoot();
识别节点类型
有三种方法可以获取节点类型:isRoot()
、isLeaf()
、isDescendantOf()
。
示例
$root = Category::find(1); VarDumper::dump($root->isRoot()); //true; VarDumper::dump($root->isLeaf()); //false; $node = Category::find(9); VarDumper::dump($node->isDescendantOf($root)); //true; VarDumper::dump($node->isRoot()); //false; VarDumper::dump($node->isLeaf()); //true; $samsung = Category::find(7); VarDumper::dump($node->isDescendantOf($samsung)); //true;
有用代码
非递归树遍历
$categories = Category::find()->addOrderBy('lft')->all(); $level = 0; foreach ($categories as $n => $category) { if ($category->level == $level) { echo Html::endTag('li') . "\n"; } elseif ($category->level > $level) { echo Html::beginTag('ul') . "\n"; } else { echo Html::endTag('li') . "\n"; for ($i = $level - $category->level; $i; $i--) { echo Html::endTag('ul') . "\n"; echo Html::endTag('li') . "\n"; } } echo Html::beginTag('li'); echo Html::encode($category->title); $level = $category->level; } for ($i = $level; $i; $i--) { echo Html::endTag('li') . "\n"; echo Html::endTag('ul') . "\n"; }
使用parentId属性构建两级树
$categories = Category::find()->andWhere('level<3')->all(); $list = []; foreach ($categories as $category) { // if record is root it may have the **null** value (for correct foreign keys) // but you can specify this in **emptyParentAttributeValue** property $list[$category->parentId ? $category->parentId : 0][] = $category; } foreach ($list[0] as $category) { echo $category->title; foreach ($list[$category->id] as $subCategory){ echo ' - ' . $subCategory->title; } }