mgrechanik / yii2-materialized-path
为Yii2框架的Materialized Path行为
Requires
- yiisoft/yii2: ~2.0
Requires (Dev)
- phpunit/phpunit: 7.*
This package is auto-updated.
Last update: 2024-09-15 23:13:59 UTC
README
此扩展允许根据Materialized Path算法将Active Record模型组织成树结构
目录
功能
- 允许将Active Record模型组织成树
- 每个树只有一个根节点
- 可以在一个数据库表中保留多个独立的树,例如保留多个菜单的菜单项
- 多种方法在树的节点之间导航以及查询节点的属性
- 树修改操作:插入新节点,移动现有节点。如果请求,它们在
transaction中运行 - 删除节点的两种模式:当删除节点时与其子节点一起删除或将其移动到其父节点
- 管理树的允许服务
- 通过一个数据库查询查询树(或子树)
- 树以两种格式形成:嵌套结构,适用于显示为
<ul>-<li>列表,或“平面”表示的树 - 简单的php数组,适用于显示在<select>列表中或用作数据提供者 - 克隆子树
- 获取节点的子节点的
id范围,这对于验证规则很有用
安装
安装此扩展的最佳方式是通过composer。
运行以下命令:
composer require --prefer-dist mgrechanik/yii2-materialized-path
或者添加以下内容到你的composer.json文件中的require部分:
"mgrechanik/yii2-materialized-path" : "^1.0"
迁移
此扩展期望数据库表中具有保留树所必需的额外列。
多树表的迁移示例请参考这里
单树表的迁移示例请参考这里
use yii\db\Migration; /** * Handles the creation of table `animal`. */ class m170208_094404_create_animal_table extends Migration { /** * @inheritdoc */ public function up() { $this->createTable('animal', [ 'id' => $this->primaryKey(), 'path' => $this->string(255)->notNull()->defaultValue('')->comment('Path to parent node'), 'level' => $this->integer(4)->notNull()->defaultValue(1)->comment('Level of the node in the tree'), 'weight' => $this->integer(11)->notNull()->defaultValue(1)->comment('Weight among siblings'), 'name' => $this->string()->notNull()->comment('Name'), ]); $this->createIndex('animal_path_index', 'animal', 'path'); $this->createIndex('animal_level_index', 'animal', 'level'); $this->createIndex('animal_weight_index', 'animal', 'weight'); } /** * @inheritdoc */ public function down() { $this->dropTable('animal'); } }
上述代码中的要点
- 字段角色的说明将在数据结构说明中给出
- 对于
path,我们将字段长度设置为255个符号,这对于mysql是最佳的,允许保留具有巨大嵌套的树,但您可以设置任何想要的值 path、weight和level字段的defaultValue已设置,以防万一,即使不是通过此扩展的api而是手动(例如使用phpmyadmin)添加的行也能在树中占据其起始位置- 对于
SQLite数据库,请从上述迁移中移除->comment()
设置
要将Active Record模型转换为树节点,您需要为其设置以下行为:
use mgrechanik\yiimaterializedpath\MaterializedPathBehavior; class Animal extends \yii\db\ActiveRecord { public static function tableName() { return 'animal'; } public function behaviors() { return [ 'materializedpath' => [ 'class' => MaterializedPathBehavior::class, // behavior settings ], ]; } // ... }
此MaterializedPathBehavior行为有以下设置:
treeIdentityFields- 包含树唯一标识符的字段名的数组(当存在多个时)。mpFieldNames- 用于此扩展的字段名与模型类中的字段名的映射。当您的名称与id、path、level、weight不同时使用。modelScenarioForAffectedModelsForSavingProcess的解释在这里- 关于
moveChildrenWnenDeletingParent和modelScenarioForChildrenNodesWhenTheyDeletedAfterParent,请参阅删除节点。 maxPathLength可以任意设置为path字段的限制值,当它变长时将抛出异常。
数据结构说明
数据库中的数据如下所示
以下示例将使用上面的树。
对于第一个表,有Animal Active Record模型。
对于第二个表,有Menuitem Active Record模型。
常见问题
- 如果表包含多个树,如
Menuitem表,我们将使用额外的列(例如treeid)作为唯一树标识符。并且所有未来的树操作都将隔离,它们只涉及这个具体的树,其他树则不在考虑范围内。 - 每个树只有一个根节点,它不包含在表中,它是一个虚拟节点,但您可以通过与任何其他节点相同的方式获取它并与之交互。更多详细信息
- 节点
Animal(1) - cat是此根节点的第一个子节点 id列 - 它持有节点的唯一数值标识符。此扩展要求使用正整数作为节点的id。path列 - 它持有此节点的父节点路径。以节点的id的形式,由/分隔。根节点的子节点为空字符串。level列 - 它持有此节点的级别,根节点的级别为0。weight列 - 它持有节点在兄弟节点中的“权重”。相邻节点按此列排序。
因此,这种架构保证了将树保存到数据库表中的有效和足够结构。
- 如果需要,我们保留一个标记,指明节点属于哪个树。
- 每个节点都通过
path知道其父节点。 - 在兄弟节点之间,节点根据
weight排序。
与其他流行算法实现的区别
- 在其他扩展中,如
Animal(1) - cat这样的节点通常被视为根节点。这看起来像是一个树有多个根节点。
在我们的扩展中,每个树只有一个根节点(它不保存到数据库中)。
这样,所有树节点都组织在一个树中。- 此外,在
path列中通常保存的是完整路径- 以末尾节点id结尾的路径。
在这个扩展中,我们只保存节点父级的路径。对于兄弟节点,这个值将是相同的
扩展结构
此扩展提供了两个主要功能
mgrechanik\yiimaterializedpath\MaterializedPathBehavior- 此行为连接到ActiveRecord模型,通过向其中添加必要的功能,将其转换为树节点mgrechanik\yiimaterializedpath\Service- 此服务旨在提供管理树形结构的额外操作:构建和输出树(子树)、获取根节点、克隆等。
处理树形结构
树的根节点
常见
每个树只有一个根节点(以后简称“根”) - 位于树顶部的节点,没有父节点。
我们不会为这个根节点在数据库中保存一行,因为我们已经知道每个树都有它。因此,我们不需要在表中额外创建它以开始填充树节点。
我们认为根节点已经存在。
在数据库中,我们只保存真正添加到树中的数据。从第一级节点开始 - cat、dog、snake、bear。
然而,我们可以像处理任何其他树节点一样处理这个根节点
- 我们可以要求它的后代,这样我们就能得到整个树
- 我们可以向它添加节点,它们将成为第一级节点
- 但在根节点的情况下,只有逻辑上的事情起作用。我们可以向根节点添加节点,但不能在根节点之前或之后插入
技术上,根节点由
mgrechanik\yiimaterializedpath\tools\RootNode类的对象表示 - 这是一个一些虚拟 AR模型,你不应该尝试将其保存到数据库中(将引发异常),但它也被配置了MaterializedPathBehavior,这允许像处理任何其他节点一样处理它。
根节点的ID
正如我们之前所说的,在path字段中,我们不保存根的id(因为它在表中不存在),但根节点仍然有它的id - 它在RootNode::getId()中受到控制。我们主要需要它用于编辑html表单时,我们可能需要在表单中选择根节点,并且我们需要一种方法通过标识符来区分它和其他节点。
此根节点的id是根据一个简单的算法形成的
- 它将是一个负整数
- 如果表中只有一个树,它的值将是
-100 - 如果有许多树在表中,它的值是通过以下公式计算的 -
-100 * (treeField1 + treeField1 + treeFieldi),因此对于['treeid' => 2],id将是-200
处理根节点
要处理根节点,我们首先需要获取它的对象。
这可以通过几种方式完成
- 通过AR模型名称(和任意树条件)
use mgrechanik\yiimaterializedpath\ServiceInterface; // AR class use mgrechanik\yiimaterializedpath\tests\models\Animal; // service of managing the trees $service = \Yii::createObject(ServiceInterface::class); // getting // the root of the tree $root = $service->getRoot(Animal::class);
如果你的表包含多个树,为了获取所需树的根,你需要提供该树的树条件
use mgrechanik\yiimaterializedpath\tests\models\Menuitem; // ... // the root of the first tree $root1 = $service->getRoot(Menuitem::class, ['treeid' => 1]); // the root of the second tree $root2 = $service->getRoot(Menuitem::class, ['treeid' => 2]);
- 通过任何AR模型(不是新记录)我们可以获取此节点所属树根节点
$model7 = Animal::findOne(7); // 'stag' // the root of the tree to which $model7 belongs to $root = $model7->getRoot();
对于树中的所有节点,缓存将返回相同的根对象(你可以使用===运算符进行比较)。
- 通过它的负
id
$root = $service->getModelById(Animal::class, -100);
当你在html表单中获取id值时,其中根节点和普通节点都可以选择,请使用此方法获取节点。
查询
选择任何节点后,包括根节点,您都可以请求下一级信息
- 获取节点的子节点
获取节点子节点的查询对象
$node->getDescendantsQuery($exceptIds = [], $depth = null)
- 通过
$exceptIds分别告知您想从查询中排除哪些子树,其格式如下:[1, 2, 3 => false],表示排除节点1和2的子树,但保留节点3及其所有子节点 $depth显示要查询的子节点层级数
null表示查询所有子节点
1表示仅查询1级深度,换句话说,仅查询子节点
依此类推。
示例
$model = Animal::findOne(1); $query = $model->getDescendantsQuery(); $result = $query->asArray()->all();
我们得到的结果将按level ASC, weight ASC排序,因此它不适合任何树形结构的输出。
有关构建php树的更多信息,请参阅此处。
- 获取查询对象
当您需要构建自己的查询以获取树节点而不是使用AR模型(如ClassName::find())时,您应该从以下查询对象开始
$node->getQuery()
- 此对象持有属于此
$node的树的tree condition - 所有此扩展的查询都从该查询对象开始
- 您还可以从根节点获取它
- 从技术上讲,您可以在
RootNode::getQuery()中查看其实现 - 您现在需要开始使用
andWhere()添加新条件
导航
- 获取树的根节点
$node->getRoot()
它仅适用于存在于数据库模型中的节点,因为新模型尚未属于任何树。
它为每个树节点返回相同的对象,因为树只有一个根节点。
- 处理节点的子节点
获取所有节点的子节点
$node->children()
我们将获得AR模型的数组 - 节点的直接子节点。
或者您可以使用更常见的查询对象
$node->getChildrenQuery($sortAsc = true)
示例
$query = $model->getChildrenQuery(); $result = $query->asArray()->all();
获取节点的第一个子节点
$node->firstChild()
获取节点的最后一个子节点
$node->lastChild()
- 处理节点的父节点
获取父节点
$node->parent()
获取父节点
$node->parents($orderFromRootToCurrent = true, $includeRootNode = false, $indexResultBy = false)
在这里
$orderFromRootToCurrent- 从根节点到当前节点或相反排序父节点$includeRootNode- 是否在结果中包含根节点$indexResultBy- 结果是否应按模型的id进行索引
获取父节点的id
$node->getParentIds($includeRootNode = false)
在这里
- 结果将按从根节点到当前节点父节点的顺序显示节点的
id $includeRootNode- 是否在结果中包含根节点的id
- 处理节点的兄弟节点
获取
所有兄弟节点
$node->siblings($withCurrent = false, $indexResultBy = false)
在这里
$withCurrent- 是否在结果中包含当前节点$indexResultBy- 结果是否应按模型的id进行索引
下一个
$node->next()
上一个
$node->prev()
所有下一个
$node->nextAll()
所有上一个
$node->prevAll()
获取当前节点在兄弟节点中的位置(从0开始)
$node->index()
节点属性
您可以在节点上执行以下检查
检查节点是否为根节点
$node->isRoot()
检查节点是否为叶子节点,即它没有任何子节点
$node->isLeaf()
检查节点是否是另一个节点的子节点
$node->isDescendantOf($node)
,这里的$node参数可以是ActiveRecord对象或数字 - 节点的主键。
检查节点是否是另一个节点的子节点
$node->isChildOf($node)
,参数$node如上所述
检查节点是否是另一个节点的兄弟节点
$node->isSiblingOf($node)
,参数$node只能是ActiveRecord模型
关于节点的更多信息
节点的完整路径
$node->getFullPath()
, 此路径包括我们持有的 path 字段与 id 的连接
的当前节点。例如,对于 Animal(5) 节点,此值将是 '1/5'
节点 ID
$node->getId()
, 此包装器很有用,因为它也适用于根节点(给出负 id)
节点级别
$node->getLevel()
此节点所属的树的条件
$node->getTreeCondition()
, 它将为 Menuitem 表返回一个类似 ['treeid' => 2] 的数组
获取此扩展使用的字段名称
$node->getIdFieldName() $node->getPathFieldName() $node->getLevelFieldName() $node->getWeightFieldName()
插入新节点和移动现有节点
常见信息
关于插入/移动节点您需要了解的常见信息
- 这些方法对插入新节点和移动现有节点都有效
- 每个修改方法的签名如下
$node->appendTo($node, $runValidation = true, $runTransaction = true)- 这些方法物理保存模型,因此它们作为
ActiveRecord::save()工作,这意味着可以在保存之前使用$runValidation进行验证检查 - 它们返回
true/false以表示操作是否成功 - 通常,插入/移动操作会导致某些其他节点需要更改并保存,因此使用
$runTransaction在一个事务中运行所有这些操作很方便。 - 此外,还有设置
MaterializedPathBehavior::$modelScenarioForAffectedModelsForSavingProcess,允许在保存之前为这些额外更改的模型设置一些场景
- 在这些操作中,根节点 只能在添加/移动节点到它的操作中使用
- 所有如
prependTo/insertBefore(After)的操作都需要重建所有新兄弟的weight字段。为了解决这个任务,此扩展不执行搜索weight的空闲间隔或类似的工作,它重建所有新兄弟的“权重”并将它们全部保存 - 看到表中有很多树的情况
- 当创建新节点时,它们的 树条件 将与我们要添加新节点的相关节点相同
- 不允许将现有节点移动到另一棵树
因此,以下操作
将节点添加/移动到新父节点最后子节点的位置
$model->appendTo($node, $runValidation = true, $runTransaction = true)
$model将被添加/移动到新父节点$node,位于最后子节点的位置- 在移动时,所有
$model的后代将自然地与之移动
有一个“镜像”方法用于上述方法,当我们可以向左节点添加子节点时
$model->add($child, $runValidation = true, $runTransaction = true)
将节点添加/移动到新父节点第一个子节点的位置
$model->prependTo($node, $runValidation = true, $runTransaction = true)
$model将被添加/移动到新父节点$node,位于第一个子节点的位置
将节点添加/移动到另一个节点之前的位置
$model->insertBefore($node, $runValidation = true, $runTransaction = true)
$model将被添加/移动到$node之前的位置。它们的父节点将是相同的
示例.
将节点添加/移动到另一个节点之后的位置
$model->insertAfter($node, $runValidation = true, $runTransaction = true)
- $model
将被添加/移动到$node` 之后的位置
示例.
将节点添加/移动到新父节点指定为数字的位置
$model->insertAsChildAtPosition($node, $position, $runValidation = true, $runTransaction = true)
$model将被添加/移动到新父节点$node,并在其子节点中占据$position位置(从零开始计数)- 技术上,这是上述方法的包装器
删除节点
通过在 Yii 中存在的 ActiveRecord::delete() 方法来删除节点
$model->delete()
在删除节点时,下一个 MaterializedPathBehavior::$moveChildrenWnenDeletingParent 行为设置提供了两种选择,用于节点后代
true- 被删除节点的后代将被移动到其父节点(通过appendTo方式)。这是默认设置。false- 后代将与节点一起被删除
因为delete()方法是框架的内部方法,如果您想它在事务中运行,您需要自己设置以下内容,请参考文档。请查看目录模型中的此设置示例。
在目录模型示例中,删除操作被包含在事务中,但如果需要删除后代,我们不希望在嵌套事务中运行所有这些额外的删除操作,因此通过使用设置MaterializedPathBehavior::$modelScenarioForChildrenNodesWhenTheyDeletedAfterParent,我们可以在删除这些后代节点之前为它们设置一些场景(不同于在transactions()中设置的场景)。
服务用于管理树
常见
此服务在需要操作多个节点时,为管理树提供了额外的功能。
我们可以通过以下方式获取此服务
use mgrechanik\yiimaterializedpath\ServiceInterface; // trees managing service $service = \Yii::createObject(ServiceInterface::class);
或使用DI注入。
从技术上讲,此服务的单例在扩展的引导程序中定义:mgrechanik\yiimaterializedpath\tools\Bootstrap。
构建树及其输出
常见信息
如上所述,$node->getDescendantsQuery()查询(适用于root node,如果需要整个树)通过一个数据库查询获取所需数量的节点后代。但是,我们需要将此查询结果数组转换为便于工作(以及显示)的树形结构。
因此,在此服务中,我们将此结构转换为两种类型的php树
buildTree和buildDescendantsTree构建以节点形式连接在一起的特殊类型的层次结构。这种结构便于以嵌套的<ul>-<li>列表的形式递归输出树。buildFlatTree根据上述信息构建“平面”树 - 简单的一维节点数组,根据它们的顺序排列。它便于在管理员页面使用单个foreach输出树,用于<select>列表或数据提供者。
层次树
$service->buildTree($parent, $isArray = true, $exceptIds = [], $depth = null) $service->buildDescendantsTree($parent, $isArray = true, $exceptIds = [], $depth = null)
这些方法将构建以下树:
- 常见算法如下
- 选择我们将从其中开始我们的后代树的节点
- 构建树
- 打印它
- 结果将是
mgrechanik\yiimaterializedpath\tools\TreeNode类型的对象数组,这些对象在children属性中引用其子节点 buildTree从$parent节点开始构建树,而buildDescendantsTree则从$parent节点的子节点开始构建树isArray- 选择我们将数据放入TreeNode::$node的格式(数组或AR对象)$exceptIds请参阅以上$depth请参阅以上- 可以使用简单的小部件,如
mgrechanik\yiimaterializedpath\widgets\TreeToListWidget,以嵌套的<ul>-<li>列表的形式打印此树。此小部件功能非常基本,主要作为示例,但仍有机会创建您可能需要的任何树项标签
示例
use mgrechanik\yiimaterializedpath\ServiceInterface; use mgrechanik\yiimaterializedpath\tests\models\Animal; use mgrechanik\yiimaterializedpath\widgets\TreeToListWidget; $service = \Yii::createObject(ServiceInterface::class); // 1) choose the model $model1 = Animal::findOne(1);
// 2) build the tree $tree = $service->buildTree($model1);
我们将得到如下结构
Array
(
[0] => TreeNode Object
(
[node] => Array ([id] => 1, [name] => cat, ...)) // <---- The very node with id=1
[parent] =>
[children] => Array // <---- This is it's children (ARRAY-Z)
(
[0] => TreeNode Object
(
[node] => Array ([id] => 5, [name] => mouse, ...)
[parent] => ...
[children] => Array
(
[0] => TreeNode Object
(
[node] => Array ([id] => 7, [name] => stag, ...)
[parent] => ...
[children] => Array ()
)
)
)
[1] => TreeNode Object
(
[node] => Array ( [id] => 6, [name] => fox, ... )
[parent] => ...
[children] => Array ()
)
)
)
)
如果树以这种方式构建 -
$tree = $service->buildDescendantsTree($model1);,则结果将是上面的ARRAY-Z
// 3) print the tree: print TreeToListWidget::widget(['tree' => $tree]);
我们将得到
- 猫
- 老鼠
- 麋鹿
- 狐狸
- 老鼠
上述代码的HTML如下
<ul> <li>cat <ul> <li>mouse <ul> <li>stag</li> </ul> </li> <li>fox</li> </ul> </li> </ul>
全部树的输出示例
代码
$root = $service->getRoot(Animal::class); $tree = $service->buildDescendantsTree($root); print TreeToListWidget::widget(['tree' => $tree]);
我们将得到
- 猫
- 老鼠
- 麋鹿
- 狐狸
- 老鼠
- 狗
- 蛇
- 狮子
- 刺猬
- 熊
树的前两级输出的示例(使用$depth参数)
代码
$root = $service->getRoot(Animal::class); $tree = $service->buildDescendantsTree($root, true, [], 2); print TreeToListWidget::widget(['tree' => $tree]);
将打印
- 猫
- 老鼠
- 狐狸
- 狗
- 蛇
- 狮子
- 刺猬
- 熊
扁平树
术语“扁平”意味着一种简单数组的树形式,可以通过一个foreach循环以如下形式输出
- root
- (1) cat
-- (5) mouse
--- (7) stag
-- (6) fox
- (2) dog
- (3) snake
-- (8) lion
-- (9) hedgehog
- (4) bear
此树是这样创建的
$service->buildFlatTree($parent, $isArray = true, $includeItself = false, $indexBy = false, $exceptIds = [], $depth = null)
此方法将构建以下树:
- 常见算法如下
- 选择我们将从其中开始我们的后代树的节点
- 构建树
- 打印它
- 结果将是表示为关联数组的节点数组(
$isArray = true)或AR对象 $includeItself设置从哪里开始我们的树 - 当$includeItself = true时从$parent开始,或从它的子节点开始$indexBy- 是否使用模型的id来索引结果数组。与数据提供程序一起使用时非常有用$exceptIds见上面。$depth见上面。
示例
// 1) choose the node $root = $service->getRoot(Animal::class);
// 2) build the tree $tree = $service->buildFlatTree($root);
我们将得到以下数组
Array
(
[0] => Array // To make the keys equal to ids look at $indexBy above
(
[id] => 1
[path] =>
[level] => 1
[weight] => 1
[name] => cat
)
[1] => Array
(
[id] => 5
[path] => 1/
[level] => 2
[weight] => 1
[name] => mouse
)
[2] => Array
(
[id] => 7
[path] => 1/5/
[level] => 3
[weight] => 1
[name] => stag
)
// .....
// .....
[8] => Array
(
[id] => 4
[path] =>
[level] => 1
[weight] => 4
[name] => bear
)
)
此数组(当与$indexBy一起使用时)准备好提供给数据提供程序,例如,查看目录视图页面 - actionIndex - 在目录控制器中。
要将此数组转换为$items以供Yii的内置listBox使用,我们需要以下助手
$service->buildSelectItems($flatTreeArray, callable $createLabel, $indexKey = 'id', $isArray = true);
$flatTreeArray- 通过buildFlatTree构建的数组$createLabel- 创建项目标签的匿名函数。此函数接收处理过的节点并返回字符串 - 我们在<select>列表中看到的项标签$indexKey- 使用哪个字段来索引选择项- 结果将是选项数组
[id1 => label1, id2 => label2, ...]
// 3) Build select list $items = $service->buildSelectItems($tree, function($node) { return ($node['id'] < 0) ? '- root' : str_repeat(' ', $node['level']) . str_repeat('-', $node['level']) . ' (' . $node['id'] . ') ' . $node['name']; } );
这将生成以下列表
- (1) cat
-- (5) mouse
--- (7) stag
-- (6) fox
- (2) dog
- (3) snake
-- (8) lion
-- (9) hedgehog
- (4) bear
全部树的输出示例,包括根
$root = $service->getRoot(Animal::class); $tree = $service->buildFlatTree($root, true, true); $items = $service->buildSelectItems($tree, function($node) { return ($node['id'] < 0) ? '- root' : str_repeat(' ', $node['level']) . str_repeat('-', $node['level']) . ' (' . $node['id'] . ') ' . $node['name']; } );
我们将有
- root
- (1) cat
-- (5) mouse
--- (7) stag
-- (6) fox
- (2) dog
- (3) snake
-- (8) lion
-- (9) hedgehog
- (4) bear
您可以在目录元素编辑表单中看到此代码的工作示例。
克隆
$service->cloneSubtree($sourceNode, $destNode, $withSourceNode = true, $scenario = null)
- 常见
- 此操作将在
transaction中执行 - 克隆只能在同一类型的模型之间进行
- 您可以将克隆到另一个树中
$sourceNode,$destNode- Active Record模型或根节点
- 此操作将在
$sourceNode- 我们正在克隆的子树的根$destNode- 我们正在克隆到的节点$withSourceNode- 是否从$sourceNode本身开始克隆或从它的子节点开始
例如,如果$sourceNode是根,则需要将其设置为false。整个树将被克隆$scenario- 可选地,我们可以在保存之前将scenario设置到克隆的节点上
其他机会
获取树的根
$service->getRoot($className, $treeCondition = [])
$className- ActiveRecord模型名称$treeCondition- 当表包含多个树时,树条件。它是一个如['treeid' => 1]的数组
通过其id获取任何节点
$service->getModelById($className, $id, $treeCondition = [])
- 这是一个包装器,用于
$className::findOne($id),它能够通过其负$id找到根节点。它用于当我们有一个html表单,并且根节点可以与其它节点一起选择时 $className- ActiveRecord模型名称$id- 模型的唯一标识符或作为根的id的负数$treeCondition- 当表包含多个树时的树条件。只有当树条件由多个字段组成时,才需要提供它。对于只有一个字段的条件,如 -['treeid' => 2],省略此参数,因为它可以从$id中推断出来。
获取某些子树的节点id
$service->buildSubtreeIdRange($parent, $includeItself = false, $exceptIds = [], $depth = null)
- 允许获取
$parent节点特定后代的节点id数组 $includeItself- 是否包含$parent的id$exceptIds见上面。$depth见上面。- 此功能与
yii\validators\RangeValidator一起使用很有趣
节点的树条件
$service->getTreeCondition($model)
$model- 我们正在检查的节点- 它将返回一个数组,如
['treeid' => 1]- 通过该数组,$model节点属于其树
从路径中获取父级的id
$service->getParentidFromPath($path)
$path- 路径- 它将返回路径中的最后一个
id或如果路径为空则返回null
附录A:构建目录示例
有关如何在admin页面创建/编辑树节点并显示树的示例,请参阅在Yii2中创建目录一文,您可以在其中看到所有这些架构的工作情况。
附录B:API操作示例
常见
所有示例都将与以下起始状态的Animal表一起工作
- root
- (1) cat
-- (5) mouse
--- (7) stag
-- (6) fox
- (2) dog
- (3) snake
-- (8) lion
-- (9) hedgehog
- (4) bear
所有代码示例的下一个起始状态也是隐式的
use mgrechanik\yiimaterializedpath\ServiceInterface; use mgrechanik\yiimaterializedpath\tests\models\Animal; // tree managing service $service = \Yii::createObject(ServiceInterface::class);
处理根节点
使用add()或appendTo()向根节点添加新节点
是否这种方式
$root = $service->getRoot(Animal::class); $root->add(new Animal(['name' => 'new']));
或另一种方式
$root = $service->getRoot(Animal::class); $newModel = new Animal(['name' => 'new']); $newModel->appendTo($root);
以下更改将发生
- root - root
- (1) cat - (1) cat
-- (5) mouse -- (5) mouse
--- (7) stag --- (7) stag
-- (6) fox -- (6) fox
- (2) dog ==> - (2) dog
- (3) snake - (3) snake
-- (8) lion -- (8) lion
-- (9) hedgehog -- (9) hedgehog
- (4) bear - (4) bear
- (10) new
使用appendTo()将现有节点移动到根位置
$model7 = Animal::findOne(7); $root = $model7->getRoot(); $model7->appendTo($root);
以下更改将发生
- root - root
- (1) cat - (1) cat
-- (5) mouse -- (5) mouse
--- (7) stag -- (6) fox
-- (6) fox - (2) dog
- (2) dog ==> - (3) snake
- (3) snake -- (8) lion
-- (8) lion -- (9) hedgehog
-- (9) hedgehog - (4) bear
- (4) bear - (7) stag
appendTo()
将子树移动到新位置
$model1 = Animal::findOne(1); $model3 = Animal::findOne(3); $model1->appendTo($model3);
以下更改将发生
- root - root
- (1) cat - (2) dog
-- (5) mouse - (3) snake
--- (7) stag -- (8) lion
-- (6) fox -- (9) hedgehog
- (2) dog ==> -- (1) cat
- (3) snake --- (5) mouse
-- (8) lion ---- (7) stag
-- (9) hedgehog --- (6) fox
- (4) bear - (4) bear
prependTo()
将新节点作为另一个节点的第一个子节点添加
$model1 = Animal::findOne(1); $newModel = new Animal(['name' => 'new']); $newModel->prependTo($model1);
以下更改将发生
- root - root
- (1) cat - (1) cat
-- (5) mouse -- (12) new
--- (7) stag -- (5) mouse
-- (6) fox --- (7) stag
- (2) dog ==> -- (6) fox
- (3) snake - (2) dog
-- (8) lion - (3) snake
-- (9) hedgehog -- (8) lion
- (4) bear -- (9) hedgehog
- (4) bear
insertBefore()
将新节点添加到另一个节点之前
$model3 = Animal::findOne(3); $newModel = new Animal(['name' => 'new']); $newModel->insertBefore($model3);
以下更改将发生
- root - root
- (1) cat - (1) cat
-- (5) mouse -- (5) mouse
--- (7) stag --- (7) stag
-- (6) fox -- (6) fox
- (2) dog ==> - (2) dog
- (3) snake - (13) new
-- (8) lion - (3) snake
-- (9) hedgehog -- (8) lion
- (4) bear -- (9) hedgehog
- (4) bear
insertAfter()
将现有节点移动到另一个节点之后
$model7 = Animal::findOne(7); $model8 = Animal::findOne(8); $model7->insertAfter($model8);
以下更改将发生
- root - root
- (1) cat - (1) cat
-- (5) mouse -- (5) mouse
--- (7) stag -- (6) fox
-- (6) fox - (2) dog
- (2) dog ==> - (3) snake
- (3) snake -- (8) lion
-- (8) lion -- (7) stag
-- (9) hedgehog -- (9) hedgehog
- (4) bear - (4) bear
insertAsChildAtPosition()
将新模型作为根的第三个子节点插入(位置2)
$root = $service->getRoot(Animal::class); $newModel = new Animal(['name' => 'new']); $newModel->insertAsChildAtPosition($root, 2);
以下更改将发生
- root - root
- (1) cat - (1) cat
-- (5) mouse -- (5) mouse
--- (7) stag --- (7) stag
-- (6) fox -- (6) fox
- (2) dog ==> - (2) dog
- (3) snake - (14) new
-- (8) lion - (3) snake
-- (9) hedgehog -- (8) lion
- (4) bear -- (9) hedgehog
- (4) bear
delete()
删除现有节点及其后代移动到其父节点
$model3 = Animal::findOne(3); $model3->delete()
以下更改将发生
- root - root
- (1) cat - (1) cat
-- (5) mouse -- (5) mouse
--- (7) stag --- (7) stag
-- (6) fox -- (6) fox
- (2) dog ==> - (2) dog
- (3) snake - (4) bear
-- (8) lion - (8) lion
-- (9) hedgehog - (9) hedgehog
- (4) bear
克隆
克隆一个节点
$model7 = Animal::findOne(7); $model8 = Animal::findOne(8); $service->cloneSubtree($model7, $model8);
以下更改将发生
- root - root
- (1) cat - (1) cat
-- (5) mouse -- (5) mouse
--- (7) stag --- (7) stag
-- (6) fox -- (6) fox
- (2) dog ==> - (2) dog
- (3) snake - (3) snake
-- (8) lion -- (8) lion
-- (9) hedgehog --- (197) stag
- (4) bear -- (9) hedgehog
- (4) bear
克隆整个子树
克隆所有子树(默认模式)
$model1 = Animal::findOne(1); $model8 = Animal::findOne(8); $service->cloneSubtree($model1, $model8);
以下更改将发生
- root - root
- (1) cat - (1) cat
-- (5) mouse -- (5) mouse
--- (7) stag --- (7) stag
-- (6) fox -- (6) fox
- (2) dog ==> - (2) dog
- (3) snake - (3) snake
-- (8) lion -- (8) lion
-- (9) hedgehog --- (198) cat
- (4) bear ---- (199) mouse
----- (200) stag
---- (201) fox
-- (9) hedgehog
- (4) bear
克隆不包含根的子树
从源节点的子节点开始克隆子树
$model1 = Animal::findOne(1); $model8 = Animal::findOne(8); $service->cloneSubtree($model1, $model8, false);
以下更改将发生
- root - root
- (1) cat - (1) cat
-- (5) mouse -- (5) mouse
--- (7) stag --- (7) stag
-- (6) fox -- (6) fox
- (2) dog ==> - (2) dog
- (3) snake - (3) snake
-- (8) lion -- (8) lion
-- (9) hedgehog --- (202) mouse
- (4) bear ---- (203) stag
--- (204) fox
-- (9) hedgehog
- (4) bear
克隆节点的后代
$model1 = Animal::findOne(1); $service->cloneSubtree($model1, $model1, false);
以下更改将发生
- root - root
- (1) cat - (1) cat
-- (5) mouse -- (5) mouse
--- (7) stag --- (7) stag
-- (6) fox -- (6) fox
- (2) dog ==> -- (205) mouse
- (3) snake --- (206) stag
-- (8) lion -- (207) fox
-- (9) hedgehog - (2) dog
- (4) bear - (3) snake
-- (8) lion
-- (9) hedgehog
- (4) bear
将整个树克隆到新树中
显示Menuitem表的原状态此处。
将所有树节点克隆到新树中
// root of not empty tree $root1 = $service->getRoot(Menuitem::class, ['treeid' => 1]); // root of new and empty tree $root5 = $service->getRoot(Menuitem::class, ['treeid' => 5]); // cloning by starting from root1's children $service->cloneSubtree($root1, $root5, false);
以下更改将发生
- root1
- (1) red
-- (4) black
-- (5) yellow ==> ...
- (2) green
-- (6) blue
- (3) brown
- root5 - root5
- (32) red
-- (33) black
==> -- (34) yellow
- (35) green
-- (36) blue
- (37) brown

