yiiext/nested-set-behavior

AR模型行为,允许与嵌套集树一起工作。

安装次数: 110 427

依赖项: 1

建议者: 0

安全: 0

星标: 157

关注者: 32

分支: 64

开放性问题: 5

类型:yii-extension

v1.0.7 2015-01-01 06:28 UTC

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',
        ),
    );
}

不需要对指定在leftAttributerightAttributerootAttributelevelAttribute选项中的字段进行验证。此外,如果存在这些字段的验证规则,可能会出现问题。请检查模型规则()方法中是否有这些字段的规则。

如果要在数据库中存储单个树,可以使用extensions/yiiext/behaviors/trees/schema.sql构建数据库结构。如果您打算存储多个树,则需要extensions/yiiext/behaviors/trees/schema_many_roots.sql

默认情况下,leftAttributerightAttributelevelAttribute的值与默认数据库模式中的字段名称相匹配,因此您可以跳过这些配置。

此行为有两种工作方式:每个表一个树和每个表多个树。根据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";
}