djvibegga / yii2-nested-set-behavior
此扩展允许您获得嵌套集合树的实用功能。
Requires
- yiisoft/yii2: *
This package is not auto-updated.
Last update: 2024-09-18 06:38:30 UTC
README
此扩展允许您获得嵌套集合树的实用功能。
安装
安装此扩展的首选方式是通过composer。
运行以下命令之一
php composer.phar require djvibegga/yii2-nested-set-behavior "*"
或
"djvibegga/yii2-nested-set-behavior": "*"
将以下内容添加到您的composer.json
文件的require部分。
配置
首先,您需要按照以下方式配置模型
class Category extends ActiveRecord { public function behaviors() { return [ [ 'class' => NestedSet::className(), ], ]; } public static function createQuery() { return new CategoryQuery(['modelClass' => get_called_class()]); } }
然后,您需要按照以下方式配置查询模型
class CategoryQuery extends ActiveQuery { public function behaviors() { return [ [ 'class' => NestedSetQuery::className(), ], ]; } }
不需要对leftAttribute
、rightAttribute
、rootAttribute
和levelAttribute
选项中指定的字段进行验证。此外,如果存在这些字段的验证规则,可能会出现问题。请检查模型规则()方法中是否没有针对提到的字段进行规则。
如果要在数据库中存储单个树,可以使用schema/schema.sql
构建DB结构。如果您打算存储多个树,则需要使用schema/schema-many-roots.sql
。
默认情况下,leftAttribute
、rightAttribute
和levelAttribute
的值与默认DB模式中的字段名称匹配,因此您可以跳过这些配置。
此行为有两种工作方式:每个表一个树和每个表多个树。模式是根据hasManyRoots
选项的值选择的,默认值为false
,表示单树模式。在多树模式下,您可以将rootAttribute
选项设置为与存储树的表中的现有字段匹配。
从树中选择
以下我们将使用以下示例模型Category
,其DB中的内容如下
- 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"; }