serj/sortable-tree

一组类,用于为Yii2创建和维护树结构。

v1.0.1 2019-04-20 11:50 UTC

This package is auto-updated.

Last update: 2024-09-28 14:48:04 UTC


README

一组类,用于为Yii2创建和维护类似树的结构。

安装

要将组件导入您的项目,将以下行添加到您的composer.json文件的require部分

"serj/sortable-tree": "~1.0.0"

或者运行以下命令

$ composer require serj/sortable-tree "~1.0.0"

要创建数据库表,应用迁移。

./yii migrate --migrationPath=@app/vendor/serj/sortable-tree/migrations

用法

添加根节点

Tree::addItem();

添加嵌套项

假设根项的id = 1。要在根节点下添加项

Tree::addItem(1);

让我们再添加一个项,但插入到上一个项之前。我们假设最后一个插入的项的id = 2。

$parent = 1;
$target = 2;
$position = 'before';
Tree::addItem($parent, $target, $position);

目前,我们的树看起来是这样的

├── 1
│   ├── 3
│   └── 2

移动项

让我们交换项2和3

$parent = 1; // we move items under the same parent
$id = 3 // move this item
$target = 2; // we want to locate the item after this one
$position = 'after';
Tree::moveTo($id, $parent, $target, $position)

现在它应该像这样

├── 1
│   ├── 2
│   └── 3

让我们将项3嵌套到项2中

$parent = 2;
$id = 3
$position = 'after';
Tree::moveTo($id, $parent)

结果是

├── 1
│   ├── 2
│   	├── 3

删除

Tree::deleteRecursive(2);

我们删除了项2及其子项3。只剩下根项。

├── 1

获取树

Tree::getDescendingTree();

还有许多其他方法可以操作树。有关更多信息,您可以探索Tree类的公共方法和单元测试。

我想存储更多信息

假设您想存储titlecreated_atupdated_at字段。并且您不再想从树中删除项,而是将其标记为deleted。为了实现这一点,我们可以扩展Tree类。但是首先,让我们修改迁移。

class m171217_033811_sortable_tree_tables extends Migration
{
    /**
     * @inheritdoc
     */
    public function safeUp()
    {
        $this->createTable('{{%tree_data}}', [
            'id' => $this->primaryKey(),
            'parent_id' => $this->integer(),
            'level' => $this->integer(),
            'sort' => $this->integer(),
            'title' => $this->string(),
            'deleted' => $this->boolean()->defaultValue(false),
            'created_at' => $this->timestamp()->defaultValue('NOW()'),
            'updated_at' => $this->timestamp()->defaultValue('NOW()'),
            'deleted_at' => $this->timestamp(),
        ]);

        $this->createTable('{{%tree_structure}}', [
            'id' => $this->primaryKey(),
            'parent' => $this->integer(),
            'child' => $this->integer()
        ]);
    }

    /**
     * @inheritdoc
     */
    public function safeDown()
    {
        $this->dropTable('{{%tree_data}}');
        $this->dropTable('{{%tree_structure}}');
    }
}

不要忘记应用新的迁移。

扩展树类。添加和覆盖一些方法。

<?php

use yii\db\Expression;

class TreeExtended extends \serj\sortableTree\Tree
{
    /**
     * @inheritdoc
     */
    public static function instantiate($row)
    {
        $model = new self();
        $model->setFilter(new Filter());

        return $model;
    }

    /**
     * @inheritdoc
     */
    public function rules()
    {
        return array_merge(
            parent::rules(),
            [
                ['title', 'string'],
                ['deleted', 'boolean'],
                [['created_at', 'updated_at', 'deleted_at'], 'safe']
            ]
        );
    }

    /**
     * @param int $id
     * @return null|RecordTreeData|\yii\db\ActiveRecord
     * @throws NotFoundHttpException
     */
    public function editTitle(int $id, $title)
    {
        $model = self::getRecord($id);
        $model->setAttributes([
            'title' => $title,
            'updated_at' => new Expression('NOW()')
        ]);

        if ($model->save()) {
            $model->refresh();

            return $model;
        }

        return $model;
    }

    /**
     * @param array $ids
     * @return int
     * @throws \yii\db\Exception
     */
    protected static function deleteItems(array $ids) {
        return \Yii::$app->db->createCommand()
            ->update(
                self::tableName(),
                ['deleted' => true, 'deleted_at' => new Expression('NOW()')],
                ['id' => $ids]
            )
            ->execute();
    }
}

为了跳过已删除的项,我们在类构造函数中添加了一个过滤器。让我们实现它。

<?php

use yii\db\Query;

class Filter implements \serj\sortableTree\FilterInterface
{
    /**
     * @inheritdoc
     */
    public function applyFilter(Query $query)
    {
        $query->andWhere([
            'deleted' => false
        ]);

        return $query;
    }
}

现在要添加一个项,传递一个包含额外属性的数组,这些属性已添加到迁移中。在我们的例子中是title。

TreeExtended::addItem(0, null, null , ['title' => 'Root']);

要编辑现有项的标题

TreeExtended::editTitle(1, 'Root modified');

使用与上面相同的方法进行删除。

Tree::deleteRecursive(1);

树类触发一系列事件,这可能很有用。

    const EVENT_AFTER_ADD = 'tree.after_add';
    const EVENT_BEFORE_MOVE = 'tree.before_move';
    const EVENT_AFTER_MOVE = 'tree.after_move';
    const EVENT_BEFORE_DELETE = 'tree.before_delete';
    const EVENT_AFTER_DELETE = 'tree.after_delete';
    const EVENT_BEFORE_TREE_QUERY = 'tree.before_tree_query';

例如,在删除项之前获取项的ID

\Yii::$app->on(Tree::EVENT_BEFORE_DELETE, function (\serj\sortableTree\EventTree $event) {
    print_r($event->senderData['ids']);
});

使用MySQL数据库(默认为PostgreSQL)

Tree::setSortManager(
    new Sortable([
        'targetTable' => Tree::tableName(),
        'pkColumn' => 'id',
        'srtColumn' => 'sort',
        'grpColumn' => 'parent_id',
        'databaseDriver' => Sortable::DB_DRIVER_MYSQL
    ])
);