paulzi/yii2-nested-intervals

为Yii2实现的嵌套区间行为

安装次数: 43,671

依赖: 7

建议者: 0

安全: 0

星标: 29

关注者: 9

分支: 6

开放问题: 0

类型:yii2-extension

v1.1.1 2018-08-06 08:37 UTC

This package is auto-updated.

Last update: 2024-09-12 22:07:17 UTC


README

实现嵌套区间算法,用于在数据库表中存储树。

Packagist Version Code Coverage Build Status Total Downloads

安装

通过Composer安装

composer require paulzi/yii2-nested-intervals

或添加

"paulzi/yii2-nested-intervals" : "^1.1"

到您的composer.json文件的require部分。

迁移示例

单个树迁移

class m150722_150000_single_tree extends Migration
{
    public function up()
    {
        $tableOptions = null;
        if ($this->db->driverName === 'mysql') {
            // http://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci
            $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
        }
        $this->createTable('{{%single_tree}}', [
            'id'    => Schema::TYPE_PK,
            'lft'   => Schema::TYPE_INTEGER . ' NOT NULL',
            'rgt'   => Schema::TYPE_INTEGER . ' NOT NULL',
            'depth' => Schema::TYPE_INTEGER . ' NOT NULL',
            'name'  => Schema::TYPE_STRING . ' NOT NULL', // example field
        ], $tableOptions);
        $this->createIndex('lft', '{{%single_tree}}', ['lft', 'rgt']);
        $this->createIndex('rgt', '{{%single_tree}}', ['rgt']);
    }
}

多个树迁移

class m150722_150100_multiple_tree extends Migration
{
    public function up()
    {
        $tableOptions = null;
        if ($this->db->driverName === 'mysql') {
            // http://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci
            $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
        }
        $this->createTable('{{%multiple_tree}}', [
            'id'    => Schema::TYPE_PK,
            'tree'  => Schema::TYPE_INTEGER . ' NULL',
            'lft'   => Schema::TYPE_INTEGER . ' NOT NULL',
            'rgt'   => Schema::TYPE_INTEGER . ' NOT NULL',
            'depth' => Schema::TYPE_INTEGER . ' NOT NULL',
            'name'  => Schema::TYPE_STRING . ' NOT NULL', // example field
        ], $tableOptions);
        $this->createIndex('lft', '{{%multiple_tree}}', ['tree', 'lft', 'rgt']);
        $this->createIndex('rgt', '{{%multiple_tree}}', ['tree', 'rgt']);
    }
}

配置

use paulzi\nestedintervals\NestedIntervalsBehavior;

class Sample extends \yii\db\ActiveRecord
{
    public function behaviors() {
        return [
            [
                'class' => NestedIntervalsBehavior::className(),
                // 'treeAttribute' => 'tree',
            ],
        ];
    }

    public function transactions()
    {
        return [
            self::SCENARIO_DEFAULT => self::OP_ALL,
        ];
    }
}

您可以设置查询以查找根节点(可选)

class Sample extends \yii\db\ActiveRecord
{
    public static function find()
    {
        return new SampleQuery(get_called_class());
    }
}

查询类

use paulzi\nestedintervals\NestedIntervalsQueryTrait;

class SampleQuery extends \yii\db\ActiveQuery
{
    use NestedIntervalsQueryTrait;
}

选项

  • $treeAttribute = null - 在表架构中设置多树树属性。
  • $leftAttribute = 'lft' - 表架构中的左属性。
  • $rightAttribute = 'rgt' - 表架构中的右属性。
  • $depthAttribute = 'depth' - 表架构中的深度属性(注意:它必须是带符号的整数)。
  • $range = [0, 2147483647] - 区间大小。默认值是32位PHP和标准带符号整数字段的最大值。如果您有BIGINT左和右列,支持64位表达式数据库和64位PHP版本,您可以使用[0, 9223372036854775807](SQLite不支持此)。
  • $amountOptimize = 10 - 插入优化 - 每层的平均子节点数。该值可以是整数或表示每级值的数组。如果级别比属性中指定的深,则从数组中的最后一个级别取值。
  • $reserveFactor = 1 - 决定节点之间间隙大小的系数。默认为1,表示区间大小等于元素本身的大小。如果您经常使用insertBefore()insertAfter()方法,您可以尝试增加此系数以提高效率。
  • $noPrepend = false - 如果为真,则当向空节点插入时将使用间隙的初始位置。
  • $noAppend = false - 如果为真,则当向空节点插入时将使用间隙的最终位置。
  • $noInsert = false - 如果为真,则相邻节点之间不会有间隙。

用法

选择

获取根节点

如果您连接NestedIntervalsQueryTrait,您可以获取所有根节点

$roots = Sample::find()->roots()->all();

获取节点的祖先

获取节点的祖先

$node11 = Sample::findOne(['name' => 'node 1.1']);
$parents = $node11->parents; // via relation
$parents = $node11->getParents()->all(); // via query
$parents = $node11->getParents(2)->all(); // get 2 levels of ancestors

获取节点的父节点

$node11 = Sample::findOne(['name' => 'node 1.1']);
$parent = $node11->parent; // via relation
$parent = $node11->getParent()->one(); // via query

获取节点的根节点

$node11 = Sample::findOne(['name' => 'node 1.1']);
$root = $node11->root; // via relation
$root = $node11->getRoot()->one(); // via query

获取节点的后代

获取节点的所有后代

$node11 = Sample::findOne(['name' => 'node 1.1']);
$descendants = $node11->descendants; // via relation
$descendants = $node11->getDescendants()->all(); // via query
$descendants = $node11->getDescendants(2, true)->all(); // get 2 levels of descendants and self node
$descendants = $node11->getDescendants(3, false, true)->all(); // get 3 levels of descendants in back order

为节点及其后代填充children关系

$node11 = Sample::findOne(['name' => 'node 1.1']);
$tree = $node11->populateTree(); // populate all levels
$tree = $node11->populateTree(2); // populate 2 levels of descendants

获取节点的子节点

$node11 = Sample::findOne(['name' => 'node 1.1']);
$children = $node11->children; // via relation
$children = $node11->getChildren()->all(); // via query

获取叶节点

获取节点的所有叶节点

$node11 = Sample::findOne(['name' => 'node 1.1']);
$leaves = $node11->leaves; // via relation
$leaves = $node11->getLeaves(2)->all(); // get 2 levels of leaves via query

获取相邻节点

获取下一个节点

$node11 = Sample::findOne(['name' => 'node 1.1']);
$next = $node11->next; // via relation
$next = $node11->getNext()->one(); // via query

获取上一个节点

$node11 = Sample::findOne(['name' => 'node 1.1']);
$prev = $node11->prev; // via relation
$prev = $node11->getPrev()->one(); // via query

一些检查

$node1 = Sample::findOne(['name' => 'node 1']);
$node11 = Sample::findOne(['name' => 'node 1.1']);
$node11->isRoot() - return true, if node is root
$node11->isLeaf() - return true, if node is leaf
$node11->isChildOf($node1) - return true, if node11 is child of $node1

修改

创建根节点

$node11 = new Sample();
$node11->name = 'node 1.1';
$node11->makeRoot()->save();

注意:如果您允许多树且未设置属性tree,它将自动采用主键值。

将节点作为另一个节点的第一个子节点插入

$node1 = Sample::findOne(['name' => 'node 1']);
$node11 = new Sample();
$node11->name = 'node 1.1';
$node11->prependTo($node1)->save(); // inserting new node

将节点作为另一个节点的最后一个子节点插入

$node11 = Sample::findOne(['name' => 'node 1.1']);
$node12 = Sample::findOne(['name' => 'node 1.2']);
$node12->appendTo($node11)->save(); // move existing node

在另一个节点之前插入节点

$node13 = Sample::findOne(['name' => 'node 1.3']);
$node12 = new Sample();
$node12->name = 'node 1.2';
$node12->insertBefore($node13)->save(); // inserting new node

在另一个节点之后插入节点

$node13 = Sample::findOne(['name' => 'node 1.3']);
$node14 = Sample::findOne(['name' => 'node 1.4']);
$node14->insertAfter($node13)->save(); // move existing node

删除有后代的节点

$node11 = Sample::findOne(['name' => 'node 1.1']);
$node11->delete(); // delete node, children come up to the parent
$node11->deleteWithChildren(); // delete node and all descendants 

优化

对于节点在区间上的均匀分布(慢!)

$node11 = Sample::findOne(['name' => 'node 1.1']);
$node11->optimize();