yiiext / nested-set-behavior
AR模型行为,允许与嵌套集树一起工作。
Requires
- php: >=5.1.0
This package is auto-updated.
Last update: 2024-09-17 11:11:59 UTC
README
为Yii 2提供嵌套集行为:https://github.com/creocoder/yii2-nested-sets
此扩展允许管理以嵌套集形式存储在数据库中的树。它作为Active Record行为实现。
安装和配置
首先您需要按照以下方式配置模型
public function behaviors() { return array( 'nestedSetBehavior'=>array( 'class'=>'ext.yiiext.behaviors.model.trees.NestedSetBehavior', 'leftAttribute'=>'lft', 'rightAttribute'=>'rgt', 'levelAttribute'=>'level', ), ); }
不需要对指定在leftAttribute
、rightAttribute
、rootAttribute
和levelAttribute
选项中的字段进行验证。此外,如果存在这些字段的验证规则,可能会出现问题。请检查模型规则()方法中是否有这些字段的规则。
如果要在数据库中存储单个树,可以使用extensions/yiiext/behaviors/trees/schema.sql
构建数据库结构。如果您打算存储多个树,则需要extensions/yiiext/behaviors/trees/schema_many_roots.sql
。
默认情况下,leftAttribute
、rightAttribute
和levelAttribute
的值与默认数据库模式中的字段名称相匹配,因此您可以跳过这些配置。
此行为有两种工作方式:每个表一个树和每个表多个树。根据hasManyRoots
选项的值选择模式,默认值为false
,表示单树模式。在多树模式下,您可以设置rootAttribute
选项以匹配存储树的表中的现有字段。
从树中选择
以下我们将使用一个示例模型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的记录。
获取所有根节点
使用NestedSetBehavior::roots()
$roots=Category::model()->roots()->findAll();
结果
对应于手机和汽车节点的Active Record对象数组。
获取节点的所有后代
使用NestedSetBehavior::descendants()
$category=Category::model()->findByPk(1); $descendants=$category->descendants()->findAll();
结果
对应于iPhone、Samsung、X100、C200和Motorola的Active Record对象数组。
获取节点的所有子节点
使用NestedSetBehavior::children()
$category=Category::model()->findByPk(1); $descendants=$category->children()->findAll();
结果
对应于iPhone、Samsung和Motorola的Active Record对象数组。
获取节点的所有祖先
使用NestedSetBehavior::ancestors()
$category=Category::model()->findByPk(5); $ancestors=$category->ancestors()->findAll();
结果
对应于Samsung和手机的Active Record对象数组。
获取节点的父节点
使用NestedSetBehavior::parent()
$category=Category::model()->findByPk(9); $parent=$category->parent()->find();
结果
对应于汽车的Active Record对象数组。
获取节点的兄弟节点
使用NestedSetBehavior::prev()
或NestedSetBehavior::next()
$category=Category::model()->findByPk(9); $nextSibling=$category->next()->find();
结果
对应于Mercedes的Active Record对象数组。
获取整个树
您可以使用以下标准AR方法获取整个树。
对于每个表一个树
Category::model()->findAll(array('order'=>'lft'));
对于每个表多个树
Category::model()->findAll(array('condition'=>'root=?','order'=>'lft'),array($root_id));
修改树
在本节中,我们将构建与上一节中使用相同的树。
创建根节点
您可以使用NestedSetBehavior::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::model()->findByPk(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::model()->findByPk(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::model()->findByPk(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::model()->findByPk(10); $c200=Category::model()->findByPk(9); $samsung=Category::model()->findByPk(7); $x100->moveAsFirst($samsung); $c200->moveBefore($x100); // now move all Samsung phones branch $mobile_phones=Category::model()->findByPk(1); $samsung->moveAsFirst($mobile_phones); // move the rest of phone models $iphone=Category::model()->findByPk(6); $iphone->moveAsFirst($mobile_phones); $motorola=Category::model()->findByPk(8); $motorola->moveAfter($samsung); // move car models to appropriate place $cars=Category::model()->findByPk(2); $audi=Category::model()->findByPk(3); $ford=Category::model()->findByPk(4); $mercedes=Category::model()->findByPk(5); foreach(array($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::model()->findByPk(10); $node->moveAsRoot();
识别节点类型
有三种方法可以获取节点类型:isRoot()
、isLeaf()
、isDescendantOf()
。
示例
$root=Category::model()->findByPk(1); CVarDumper::dump($root->isRoot()); //true; CVarDumper::dump($root->isLeaf()); //false; $node=Category::model()->findByPk(9); CVarDumper::dump($node->isDescendantOf($root)); //true; CVarDumper::dump($node->isRoot()); //false; CVarDumper::dump($node->isLeaf()); //true; $samsung=Category::model()->findByPk(7); CVarDumper::dump($node->isDescendantOf($samsung)); //true;
有用代码
非递归树遍历
$criteria=new CDbCriteria; $criteria->order='t.lft'; // or 't.root, t.lft' for multiple trees $categories=Category::model()->findAll($criteria); $level=0; foreach($categories as $n=>$category) { if($category->level==$level) echo CHtml::closeTag('li')."\n"; else if($category->level>$level) echo CHtml::openTag('ul')."\n"; else { echo CHtml::closeTag('li')."\n"; for($i=$level-$category->level;$i;$i--) { echo CHtml::closeTag('ul')."\n"; echo CHtml::closeTag('li')."\n"; } } echo CHtml::openTag('li'); echo CHtml::encode($category->title); $level=$category->level; } for($i=$level;$i;$i--) { echo CHtml::closeTag('li')."\n"; echo CHtml::closeTag('ul')."\n"; }