efureev/laravel-trees

Laravel的多树结构


README

PHP Laravel Package Total Downloads License composer.lock available

Latest Stable Version

Maintainability Test Coverage

内容

信息

本包是多树结构(许多根节点)。

table image

嵌套集是什么?

嵌套集或嵌套集模型是一种有效存储关系型表中的层次数据的方法。来自维基百科

嵌套集模型是按照树遍历对节点进行编号,每个节点访问两次,按访问顺序分配数字,并在两次访问中。这为每个节点留下两个数字,作为两个属性存储。查询变得便宜:可以通过比较这些数字来测试层次结构成员资格。更新需要重新编号,因此代价昂贵。

应用

当树很少更新时,NSM表现良好。它被调整以快速获取相关节点。它非常适合构建多深度菜单或商店的分类。

要求

  • PHP: 8.2|8.3
  • Laravel: ^11.*

强烈建议使用支持事务的数据库(如MySQL的InnoDB、PostgreSQL)来确保树免受可能损坏的影响。

安装

要安装此包,请在终端中

composer require efureev/laravel-trees

测试

./vendor/bin/phpunit --testdox

composer test

文档

此包与不同的模型主键兼容:intuuid。此包允许创建多根结构:不仅仅是单一根!并允许在树之间移动节点。

迁移

单树结构模型

<?php
namespace App\Models;

use Fureev\Trees\NestedSetTrait;
use Illuminate\Database\Eloquent\Model;

class Category extends Model
{
    use NestedSetTrait;

}

或使用自定义基础配置

<?php
namespace App\Models;

use Fureev\Trees\{NestedSetTrait,Contracts\TreeConfigurable};
use Fureev\Trees\Config\Base;
use Illuminate\Database\Eloquent\Model;

class Category extends Model implements TreeConfigurable
{
    use NestedSetTrait;

    protected static function buildTreeConfig(): Base
    {
        return new Base();
    } 
}

或使用自定义配置

    protected static function buildTreeConfig(): Base
    {
        return Base::make()
            ->setAttribute('parent', ParentAttribute::make()->setName('papa_id'))
            ->setAttribute('left', LeftAttribute::make()->setName('left_offset'))
            ->setAttribute('right', RightAttribute::make()->setName('right_offset'))
            ->setAttribute('level', LevelAttribute::make()->setName('deeeeep'));
    }

多树结构和主键类型为uuid的模型

<?php
namespace App\Models;

// use Fureev\Trees\Config\TreeAttribute;
use Fureev\Trees\Contracts\TreeConfigurable;
use Fureev\Trees\NestedSetTrait;
use Fureev\Trees\Config\Base;
use Illuminate\Database\Eloquent\Model;

class Item extends Model implements TreeConfigurable
{
    use NestedSetTrait;
    
    protected $keyType = 'uuid';

    protected static function buildTreeConfig(): Base
    {
        $config= new Base(true);
        // $config->parent()->setType('uuid'); <-- `parent type` set up automatically from `$model->keyType`

        return $config;
    }
    /*
    or:
     
    protected static function buildTreeConfig(): Base
    {
        return Base(TreeAttribute::make('uuid')->setAutoGenerate(false));
    }
    
    or:
     
    protected static function buildTreeConfig(): Base
    {
       return Base::make()
            ->setAttributeTree(TreeAttribute::make()->setName('big_tree_id'))
            ->setAttribute('parent', ParentAttribute::make()->setName('pid'))
            ->setAttribute('left', LeftAttribute::make()->setName('left_offset'))
            ->setAttribute('right', RightAttribute::make()->setName('right_offset'))
            ->setAttribute('level', LevelAttribute::make()->setName('deeeeep'));
    }
    */
}

在迁移中使用

<?php
use Fureev\Trees\Migrate;
use Illuminate\Database\Migrations\Migration;

class AddTemplates extends Migration
{
    public function up()
    {
        Schema::create('trees', function (Blueprint $table) {
            $table->uuid('id')->primary();
            $table->string('title');
    
            Migrate::columns($table, (new Page)->getTreeConfig());
    
            $table->timestamps();
            $table->softDeletes();
        });
    }
}

关系

节点具有以下关系,这些关系完全功能且可以预加载

  • 节点属于parent
  • 节点有多个children
  • 节点有多个ancestors
  • 节点有多个descendantsNew

创建节点

创建根节点

当你创建根节点时:如果你使用...

  • 单模式:你可能只能创建一个根节点。
  • 多模式:它将作为根节点插入并具有不同的tree_id。默认:递增。你可以自定义此功能。

这些操作是相同的

// For single-root tree
Category::make($attributes)->makeRoot()->save(); 
Category::make($attributes)->saveAsRoot();
Category::create(['setRoot'=>true,...]);

// For multi-root tree. If parent is absent, node set as root.
Page::make($attributes)->save();

创建非根节点

当你创建非根节点时,它将被追加到父节点的末尾。

如果你想让节点成为其他节点的子节点,你可以将其设置为最后一个或第一个子节点。

在以下示例中,$parent是某个现有节点。

追加到指定的父节点

将子节点添加到节点中。在父节点的其他子节点之后插入。

$node->appendTo($parent)->save();
将节点添加到指定的父节点之前

将子节点添加到节点中。在父节点的其他子节点之前插入。

$node->prependTo($parent)->save();
在父节点之前插入

将子节点添加到相同的父节点。在目标节点之前插入。

$node->insertBefore($parent)->save();
在父节点之后插入

将子节点添加到相同的父节点。在目标节点之后插入。

$node->insertAfter($parent)->save();

移动节点

在父节点范围内向上移动节点
$node->up();
在父节点范围内向下移动节点
$node->down();

删除节点

要删除节点

$node->delete();

重要!如果删除的节点有子节点,它们将附加到被删除节点的父节点。这种行为可能会改变。

重要!节点必须以模型的方式删除!不要尝试使用如下查询来删除它们

Category::where('id', '=', $id)->delete();

这将破坏树!

支持SoftDeletes特征,也适用于模型级别。

您也可以删除所有子节点

$node->deleteWithChildren();

检索节点

在某些情况下,我们将使用一个$id变量,它是目标节点的ID。

祖先和后代

祖先形成节点到其父节点的链。有助于显示当前类别的面包屑。

后代是指子树中的所有节点,即节点的子节点,子节点的子节点等。

祖先和后代都可以被预加载。

这是关系

  • ancestors:祖先关系
  • descendantsNew:后代关系
  • children:多对一
  • parent:从属
// Accessing ancestors
$node->ancestors;

// Accessing descendants
$node->descendantsNew;

// Accessing descendants
$node->children;

父节点

获取父节点

$node->parent;

父节点集合

$node->parents($level);

兄弟

兄弟是指有相同父节点的节点。

// Get all siblings of the node
$collection = $node->siblings()->get();

// Get siblings which are before the node
$collection = $node->prevSiblings()->get();

// Get siblings which are after the node
$collection = $node->nextSiblings()->get();

// Get a sibling that is immediately before the node
$prevNode = $node->prevSibling()->first();

// Get a sibling that is immediately after the node
$nextNode = $node->nextSibling()->first();
$prevNode = $node->prev()->first();
$nextNode = $node->next()->first();

节点查询

模型辅助函数

控制台树

table image

Table::fromModel($root->refresh())->draw();
$collection = Structure::all();
Table::fromTree($collection->toTree())
    ->hideLevel()
    ->setExtraColumns(
        [
            'title'                         => 'Label',
            $root->leftAttribute()->name()  => 'Left',
            $root->rightAttribute()->name() => 'Right',
            $root->levelAttribute()->name() => 'Deep',
        ]
    )
    ->draw($output);
Structure::all()->toOutput([],null,'...');

检查一致性

您可以检查树是否损坏(即有某些结构错误)

$bool = Category::isBroken();

可以得到错误统计信息

$data = Category::countErrors();

它将返回包含以下键的数组

  • oddness - 有错误lftrgt值的节点数量
  • duplicates - 有相同lftrgt值的节点数量
  • wrong_parent - 有无效的parent_id值,该值不对应于lftrgt值的节点数量
  • missing_parent - 有parent_id指向不存在的节点的节点数量

修复树

从v3.3.1版本开始,树现在可以被修复。

对于单棵树

Node::fixTree();

对于多棵树

Node::fixMultiTree();