djvibegga/yii2-nested-set-behavior

此扩展允许您获得嵌套集合树的实用功能。

安装: 47

依赖: 0

建议者: 0

安全: 0

星级: 0

关注者: 3

分支: 2

开放问题: 0

类型:yii2-extension

1.0 2015-02-06 14:14 UTC

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

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

如果要在数据库中存储单个树,可以使用schema/schema.sql构建DB结构。如果您打算存储多个树,则需要使用schema/schema-many-roots.sql

默认情况下,leftAttributerightAttributelevelAttribute的值与默认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";
}