franzose / closure-table
Laravel 的邻接列表闭合表数据库设计模式实现
v6.1.1
2020-10-06 01:22 UTC
Requires
- php: >=7.0
Requires (Dev)
- orchestra/testbench: ^3.4|^4.0|^5.0|^6.0
- phpunit/phpunit: ^6.0|^7.0|^8.0|^9.0
This package is auto-updated.
Last update: 2024-08-29 03:35:40 UTC
README
这是一个用于 Laravel 5.4+ 框架的数据库操作包。当您需要存储和操作数据库中的层次结构数据时,可能需要使用它。该包是闭合表这种著名设计模式的一个实现。但是,为了简化并优化 SQL SELECT
查询,它使用邻接列表来查询直接父子关系。
内容
安装
强烈建议使用 Composer 安装此包
$ composer require franzose/closure-table
如果您使用 Laravel 5.5+,则由于 包自动发现 功能,包的服务提供程序将自动为您注册。否则,您必须手动将其添加到 config/app.php
<?php return [ 'providers' => [ Franzose\ClosureTable\ClosureTableServiceProvider::class ] ];
设置
在基本情况下,您可以简单地运行以下命令
$ php artisan closuretable:make Node
其中 Node
是实体模型名称。运行以上命令后,您将得到以下内容
- 在
app
目录中的两个模型:App\Node
和App\NodeClosure
- 在
database/migrations
目录中创建了一个新的迁移文件
如您所见,该命令需要一个单个参数,即实体模型名称。然而,它接受多个选项以提供某种程度的定制
要求
请注意,根据该包的设计,模型/表有必要的最小属性/列
示例
在示例中,假设我们已经设置了一个扩展 Franzose\ClosureTable\Models\Entity
模型的 Node
模型。
作用域
自 ClosureTable 6 以来,实体模型中可用了很多查询作用域
ancestors() ancestorsOf($id) ancestorsWithSelf() ancestorsWithSelfOf($id) descendants() descendantsOf($id) descendantsWithSelf() descendantsWithSelfOf($id) childNode() childNodeOf($id) childAt(int $position) childOf($id, int $position) firstChild() firstChildOf($id) lastChild() lastChildOf($id) childrenRange(int $from, int $to = null) childrenRangeOf($id, int $from, int $to = null) sibling() siblingOf($id) siblings() siblingsOf($id) neighbors() neighborsOf($id) siblingAt(int $position) siblingOfAt($id, int $position) firstSibling() firstSiblingOf($id) lastSibling() lastSiblingOf($id) prevSibling() prevSiblingOf($id) prevSiblings() prevSiblingsOf($id) nextSibling() nextSiblingOf($id) nextSiblings() nextSiblingsOf($id) siblingsRange(int $from, int $to = null) siblingsRangeOf($id, int $from, int $to = null)
您可以从 Laravel 文档 中了解如何使用查询作用域。
父/根
<?php $nodes = [ new Node(['id' => 1]), new Node(['id' => 2]), new Node(['id' => 3]), new Node(['id' => 4, 'parent_id' => 1]) ]; foreach ($nodes as $node) { $node->save(); } Node::getRoots()->pluck('id')->toArray(); // [1, 2, 3] Node::find(1)->isRoot(); // true Node::find(1)->isParent(); // true Node::find(4)->isRoot(); // false Node::find(4)->isParent(); // false // make node 4 a root at the fourth position (1 => 0, 2 => 1, 3 => 2, 4 => 3) $node = Node::find(4)->makeRoot(3); $node->isRoot(); // true $node->position; // 3 Node::find(4)->moveTo(0, Node::find(2)); // same as Node::find(4)->moveTo(0, 2); Node::find(2)->getChildren()->pluck('id')->toArray(); // [4]
祖先
<?php $nodes = [ new Node(['id' => 1]), new Node(['id' => 2, 'parent_id' => 1]), new Node(['id' => 3, 'parent_id' => 2]), new Node(['id' => 4, 'parent_id' => 3]) ]; foreach ($nodes as $node) { $node->save(); } Node::find(4)->getAncestors()->pluck('id')->toArray(); // [1, 2, 3] Node::find(4)->countAncestors(); // 3 Node::find(4)->hasAncestors(); // true Node::find(4)->ancestors()->where('id', '>', 1)->get()->pluck('id')->toArray(); // [2, 3]; Node::find(4)->ancestorsWithSelf()->where('id', '>', 1)->get()->pluck('id')->toArray(); // [2, 3, 4]; Node::ancestorsOf(4)->where('id', '>', 1)->get()->pluck('id')->toArray(); // [2, 3]; Node::ancestorsWithSelfOf(4)->where('id', '>', 1)->get()->pluck('id')->toArray(); // [2, 3, 4];
自 ClosureTable 6 以来,已弃用了一些方法
-Node::find(4)->getAncestorsTree(); +Node::find(4)->getAncestors()->toTree(); -Node::find(4)->getAncestorsWhere('id', '>', 1); +Node::find(4)->ancestors()->where('id', '>', 1)->get();
后代
<?php $nodes = [ new Node(['id' => 1]), new Node(['id' => 2, 'parent_id' => 1]), new Node(['id' => 3, 'parent_id' => 2]), new Node(['id' => 4, 'parent_id' => 3]) ]; foreach ($nodes as $node) { $node->save(); } Node::find(1)->getDescendants()->pluck('id')->toArray(); // [2, 3, 4] Node::find(1)->countDescendants(); // 3 Node::find(1)->hasDescendants(); // true Node::find(1)->descendants()->where('id', '<', 4)->get()->pluck('id')->toArray(); // [2, 3]; Node::find(1)->descendantsWithSelf()->where('id', '<', 4)->get()->pluck('id')->toArray(); // [1, 2, 3]; Node::descendantsOf(1)->where('id', '<', 4)->get()->pluck('id')->toArray(); // [2, 3]; Node::descendantsWithSelfOf(1)->where('id', '<', 4)->get()->pluck('id')->toArray(); // [1, 2, 3];
自 ClosureTable 6 以来,已弃用了一些方法
-Node::find(4)->getDescendantsTree(); +Node::find(4)->getDescendants()->toTree(); -Node::find(4)->getDescendantsWhere('foo', '=', 'bar'); +Node::find(4)->descendants()->where('foo', '=', 'bar')->get();
子项
<?php $nodes = [ new Node(['id' => 1]), new Node(['id' => 2, 'parent_id' => 1]), new Node(['id' => 3, 'parent_id' => 1]), new Node(['id' => 4, 'parent_id' => 1]), new Node(['id' => 5, 'parent_id' => 1]), new Node(['id' => 6, 'parent_id' => 2]), new Node(['id' => 7, 'parent_id' => 3]) ]; foreach ($nodes as $node) { $node->save(); } Node::find(1)->getChildren()->pluck('id')->toArray(); // [2, 3, 4, 5] Node::find(1)->countChildren(); // 3 Node::find(1)->hasChildren(); // true // get child at the second position (positions start from zero) Node::find(1)->getChildAt(1)->id; // 3 Node::find(1)->getChildrenRange(1)->pluck('id')->toArray(); // [3, 4, 5] Node::find(1)->getChildrenRange(0, 2)->pluck('id')->toArray(); // [2, 3, 4] Node::find(1)->getFirstChild()->id; // 2 Node::find(1)->getLastChild()->id; // 5 Node::find(6)->countChildren(); // 0 Node::find(6)->hasChildren(); // false Node::find(6)->addChild(new Node(['id' => 7])); Node::find(1)->addChildren([new Node(['id' => 8]), new Node(['id' => 9])], 2); Node::find(1)->getChildren()->pluck('position', 'id')->toArray(); // [2 => 0, 3 => 1, 8 => 2, 9 => 3, 4 => 4, 5 => 5] // remove child by its position Node::find(1)->removeChild(2); Node::find(1)->getChildren()->pluck('position', 'id')->toArray(); // [2 => 0, 3 => 1, 9 => 2, 4 => 3, 5 => 4] Node::find(1)->removeChildren(2, 4); Node::find(1)->getChildren()->pluck('position', 'id')->toArray(); // [2 => 0, 3 => 1]
兄弟
<?php $nodes = [ new Node(['id' => 1]), new Node(['id' => 2, 'parent_id' => 1]), new Node(['id' => 3, 'parent_id' => 1]), new Node(['id' => 4, 'parent_id' => 1]), new Node(['id' => 5, 'parent_id' => 1]), new Node(['id' => 6, 'parent_id' => 1]), new Node(['id' => 7, 'parent_id' => 1]) ]; foreach ($nodes as $node) { $node->save(); } Node::find(7)->getFirstSibling()->id; // 2 Node::find(7)->getSiblingAt(0); // 2 Node::find(2)->getLastSibling(); // 7 Node::find(7)->getPrevSibling()->id; // 6 Node::find(7)->getPrevSiblings()->pluck('id')->toArray(); // [2, 3, 4, 5, 6] Node::find(7)->countPrevSiblings(); // 5 Node::find(7)->hasPrevSiblings(); // true Node::find(2)->getNextSibling()->id; // 3 Node::find(2)->getNextSiblings()->pluck('id')->toArray(); // [3, 4, 5, 6, 7] Node::find(2)->countNextSiblings(); // 5 Node::find(2)->hasNextSiblings(); // true Node::find(3)->getSiblings()->pluck('id')->toArray(); // [2, 4, 5, 6, 7] Node::find(3)->getNeighbors()->pluck('id')->toArray(); // [2, 4] Node::find(3)->countSiblings(); // 5 Node::find(3)->hasSiblings(); // true Node::find(2)->getSiblingsRange(2)->pluck('id')->toArray(); // [4, 5, 6, 7] Node::find(2)->getSiblingsRange(2, 4)->pluck('id')->toArray(); // [4, 5, 6] Node::find(4)->addSibling(new Node(['id' => 8])); Node::find(4)->getNextSiblings()->pluck('id')->toArray(); // [5, 6, 7, 8] Node::find(4)->addSibling(new Node(['id' => 9]), 1); Node::find(1)->getChildren()->pluck('position', 'id')->toArray(); // [2 => 0, 9 => 1, 3 => 2, 4 => 3, 5 => 4, 6 => 5, 7 => 6, 8 => 7] Node::find(8)->addSiblings([new Node(['id' => 10]), new Node(['id' => 11])]); Node::find(1)->getChildren()->pluck('position', 'id')->toArray(); // [2 => 0, 9 => 1, 3 => 2, 4 => 3, 5 => 4, 6 => 5, 7 => 6, 8 => 7, 10 => 8, 11 => 9] Node::find(2)->addSiblings([new Node(['id' => 12]), new Node(['id' => 13])], 3); Node::find(1)->getChildren()->pluck('position', 'id')->toArray(); // [2 => 0, 9 => 1, 3 => 2, 12 => 3, 13 => 4, 4 => 5, 5 => 6, 6 => 7, 7 => 8, 8 => 9, 10 => 10, 11 => 11]
树
<?php Node::createFromArray([ 'id' => 1, 'children' => [ [ 'id' => 2, 'children' => [ [ 'id' => 3, 'children' => [ [ 'id' => 4, 'children' => [ [ 'id' => 5, 'children' => [ [ 'id' => 6, ] ] ] ] ] ] ] ] ] ] ]); Node::find(4)->deleteSubtree(); Node::find(1)->getDescendants()->pluck('id')->toArray(); // [2, 3, 4] Node::find(4)->deleteSubtree(true); Node::find(1)->getDescendants()->pluck('id')->toArray(); // [2, 3]
自 ClosureTable 6 以来,已弃用了一些方法
-Node::getTree(); -Node::getTreeByQuery(...); -Node::getTreeWhere('foo', '=', 'bar'); +Node::where('foo', '=', 'bar')->get()->toTree();
集合方法
此库使用扩展的集合类,提供了一些方便的方法
<?php Node::createFromArray([ 'id' => 1, 'children' => [ ['id' => 2], ['id' => 3], ['id' => 4], ['id' => 5], [ 'id' => 6, 'children' => [ ['id' => 7], ['id' => 8], ] ], ] ]); /** @var Franzose\ClosureTable\Extensions\Collection $children */ $children = Node::find(1)->getChildren(); $children->getChildAt(1)->id; // 3 $children->getFirstChild()->id; // 2 $children->getLastChild()->id; // 6 $children->getRange(1)->pluck('id')->toArray(); // [3, 4, 5, 6] $children->getRange(1, 3)->pluck('id')->toArray(); // [3, 4, 5] $children->getNeighbors(2)->pluck('id')->toArray(); // [3, 5] $children->getPrevSiblings(2)->pluck('id')->toArray(); // [2, 3] $children->getNextSiblings(2)->pluck('id')->toArray(); // [5, 6] $children->getChildrenOf(4)->pluck('id')->toArray(); // [7, 8] $children->hasChildren(4); // true $tree = $children->toTree();