girover / tree
构建家谱
Requires
- php: ^8.0
- illuminate/contracts: ^8.0|^9.0
- spatie/laravel-package-tools: ^1.4.3
Requires (Dev)
- brianium/paratest: ^6.2
- nunomaduro/collision: ^5.3
- orchestra/testbench: ^6.15
- phpunit/phpunit: ^9.3
- spatie/laravel-ray: ^1.23
- vimeo/psalm: ^4.8
README
构建家谱
内容
介绍
girover/laravel-family-tree 是一个允许您构建家谱的包。使用此包,创建树和向树中添加节点将变得非常简单。
每个树允许有 85 代。假设每代有 20 年,这意味着一个树中可以存储 1700 年的数据。
树中的每个节点都允许有 676 个直接子节点。
先决条件
- Laravel 8+
- PHP 8+
- Mysql 5.7+
安装
您可以通过 composer 添加此包
composer require girover/laravel-family-tree
在安装包之前,您应该配置您的数据库。
然后,您可以通过运行 Artisan 命令来安装包
php artisan tree:install
此命令将处理以下任务
- 将配置文件
config\tree.php
发布到 Laravel 应用程序的配置文件夹。 - 将迁移文件发布到应用程序的
Database\migrations
文件夹。 - 迁移已发布的迁移。
- 将资产 (css, js, images) 发布到应用程序的公共文件夹,并将它们放置在
public\vendor\tree
。 - 将 JSON 翻译文件发布到
resources\lang\vendor\tree
资产
在发布资产(css, js, images)之后,它们将被放置在项目 public
文件夹中名为 vendor/tree
的文件夹中。您可以自由地将这些资产移动到其他目录。
您应该在 blade 文件中添加 CSS 文件 vendor/tree/css/tree.css
以获得树样式。
图片
树中的每个节点都有一张头像照片。默认情况下,男性和女性图标将存储在公共文件夹下的
vendor/tree/images
.
然而,您可以选择另一个文件夹来存储节点的图片,但您必须在 config\tree.php
文件中提供它。
'photos_folder' => 'path/to/your/images',
因此,您的图片文件夹应在 public
文件夹中。例如:如果图片存储在名为 images/avatars
的文件夹中,配置必须是
'photos_folder' => 'images/avatars',
注意:在存储文件夹中保存照片时,创建到照片文件夹的符号链接很重要。
用法
要开始构建家谱,您必须有两个模型。第一个模型代表数据库中的树,它必须使用特性
\Girover\Tree\Traits\Treeable
第二个模型代表这些树中的节点,它必须使用特性
\Girover\Tree\Traits\nodeable
注意:表示数据库中树和节点的模型的名称必须在 config/tree.php
中提供。
// config/tree.php return [ /* |---------------------------------------------------------- | Model That uses trait \Girover\Tree\Traits\Treeable |---------------------------------------------------------- | | example: App\Models\Family::class */ 'treeable_model' => App\Models\Family::class, /* |---------------------------------------------------------- | Model That uses trait \Girover\Tree\Traits\Nodeable |---------------------------------------------------------- | | example: App\Models\Person::class */ 'nodeable_model' => App\Models\Person::class, . . .
Treeable 模型
要开始构建树或创建新的树,非常简单,多亏了 Eloquent 模型从 Laravel。
代表数据库中树的模型应使用 特性:\Girover\Tree\Traits\Treeable
例如,如果您的模型名称为 Tree
,则它应该看起来像这样
namespace App\Models; use Girover\Tree\Traits\Treeable; class Tree extends Model { use Treeable; }
现在您可以使用您的模型来处理树。
use App\Models\Tree; $tree = Tree::create( [ 'info' => 'info', 'another_info' => 'another info', ] );
在创建树之后,您可以开始添加尽可能多的节点。
让我们开始添加第一个节点,即树中的根
。
$data = ['name'=>'root', 'birth_date'=>'2000-01-01']; $tree->createRoot($data);
如果你想创建一个新的根节点呢?
在这种情况下,你可以使用newRoot
方法,之前创建的根节点将成为新创建的根节点的子节点。
$new_root_data = ['name'=>'new_root', 'birth_date'=>'2001-01-01']; $tree->newRoot($new_root_data);
在树中创建根节点后,让我们为根节点添加第一个子节点。
use Girover\Tree\Models\Tree; $tree = Tree::find(1); $first_child_data = ['name'=>'first child', 'birth_date'=>'2001-01-01']; $tree->pointerToRoot()->newChild($first_child_data ,'m'); // m = male // Or you can do this instead $tree->pointerToRoot()->newSon($first_child_data);
现在我们的树由两个节点组成,根节点和根节点的第一个子节点。
你可以在Tree对象上调用以下方法
指针
树中有一个内部指针
,这个指针
指向一个节点。
指针可以在树中的所有节点之间移动。
因为指针指向树内的一个节点
,所以它可以调用使用Nodeable Trait的模型的所有方法。
要获取指针,你可以这样做
use App\Models\Tree; $tree = Tree::find(1); $pointer = $tree->pointer();
现在你可以使用指针在树内执行许多操作,例如移动节点、删除节点以及检索有关节点的更多信息。
例如,要将指针移动到特定节点
use App\Models\Tree; use App\Models\Node; $tree = Tree::find(1); $node = Node::find(10); $tree->pointer()->to($node);
现在你可以通过调用node
方法来获取节点数据
$node = $pointer->node(); echo $node->attribute_1; echo $node->attribute_2; echo $node->attribute_3;
请注意,我们在调用to($node)
方法之后调用了node
方法。
这是因为当创建树对象时,它的指针
指向null
。
Nodeable 模型
什么是节点
节点是树中的一个人,树中的每个节点都通过使用位置机制
与其他节点相连。
要检索节点,你可以使用使用trait Girover\Tree\Traits\Nodeable
的Eloquent模型。
use App\Models\Node; $node = Node::find(1);
检索节点
要获取节点所属的树。
$node->getTree();
要获取节点的父节点。
return $node->father();
要获取节点的祖父节点。
return $node->grandfather();
要获取与给定参数匹配的祖先节点。
$node->ancestor(); // returns father $node->ancestor(2); // returns grandfather $node->ancestor(3); // returns the father of grandfather
要获取节点的所有祖先节点。
return $node->ancestors();
要获取节点的所有叔叔节点。
return $node->uncles();
要获取节点的所有阿姨节点。
return $node->aunts();
要获取节点的所有子节点。
return $node->children();
要获取节点的所有儿子节点。
return $node->sons();
要获取节点的所有女儿节点。
return $node->daughters();
要获取节点的所有后代节点。
return $node->descendants();
要获取节点的所有男性后代节点。
return $node->maleDescendants();
要获取节点的所有女性后代节点。
return $node->femaleDescendants();
要获取节点的第一个子节点。
return $node->firstChild();
要获取节点的最后一个子节点。
return $node->lastChild();
要获取节点的所有兄弟姐妹节点。
return $node->siblings();
要获取节点的所有兄弟节点。
return $node->brothers();
要获取节点的所有姐妹节点。
return $node->sisters();
要获取节点的下一个兄弟姐妹节点。只获取一个兄弟姐妹。
return $node->nextSibling();
要获取节点的所有下一个兄弟姐妹节点。比当前节点年轻的兄弟姐妹。
return $node->nextSiblings();
要获取节点的下一个兄弟节点。只获取一个兄弟。
return $node->nextBrother();
要获取节点的所有下一个兄弟节点。比当前节点年轻的兄弟。
return $node->nextBrothers();
要获取节点的下一个姐妹节点。只获取一个姐妹。
return $node->nextSister();
要获取节点的所有下一个姐妹节点。比当前节点年轻的姐妹。
return $node->nextSisters();
要获取节点的上一个兄弟姐妹节点。只有一个兄弟姐妹。
return $node->prevSibling();
要获取节点的所有上一个兄弟姐妹节点。比当前节点年长的兄弟姐妹。
return $node->prevSiblings();
要获取节点的上一个兄弟节点。只有一个兄弟。
return $node->prevBrother();
要获取节点的所有上一个兄弟节点。比当前节点年长的兄弟。
return $node->prevBrothers();
要获取节点的上一个姐妹节点。只有一个姐妹。
return $node->prevSister();
要获取节点的所有上一个姐妹节点。比当前节点年长的姐妹。
return $node->prevSisters();
要获取节点的第一个兄弟姐妹节点。
return $node->firstSibling();
要获取节点的最后一个兄弟姐妹节点。
return $node->lastSibling();
要获取节点的第一个兄弟节点。
return $node->firstBrother();
要获取节点的最后一个兄弟节点。
return $node->lastBrother();
要获取节点的第一个姐妹节点。
return $node->firstSister();
要获取节点的最后一个姐妹节点。
return $node->lastSister();
添加节点
以下代码将创建一个节点的父节点,仅当该节点是树中的根节点时。
如果节点不是根节点,将抛出Girover\Tree\Exceptions\TreeException
异常。
$data = ['name' => $name, 'birth_date' => $birth_date]; $node->createFather($data); // OR $tree = Tree::find(1); $tree->pointerToRoot()->createFather($data);
为节点创建新的兄弟节点
$data = ['name'=>$name, 'birth_date'=>$birth_date]; return $node->newSibling($data, 'm'); // m = male
为节点创建新的兄弟节点
$data = ['name'=>$name, 'birth_date'=>$birth_date]; $node->newBrother($data); // or you can use the newSibling method $node->newSibling($data, 'm');
为节点创建新的姐妹节点
$data = ['name'=>$name, 'birth_date'=>$birth_date]; $node->newSister($data); // or you can use the newSibling method $node->newSibling($data, 'f');
为节点创建新的儿子节点
$data = ['name'=>$name, 'birth_date'=>$birth_date]; $node->newSon($data); // or you can use the newChild method $node->newChild($data, 'm');
为节点创建新的女儿节点
$data = ['name'=>$name, 'birth_date'=>$birth_date]; $node->newDaughter($data); // or you can use the newChild method $node->newChild($data, 'f');
将节点设置为该树的主节点。
$node->makeAsMainNode();
删除节点
删除一个节点。
use App\Models\Person; $node = Person::find(10)->delete();
注意: 将删除节点及其所有后代。
但如果只想删除节点而不删除其后代,
则可以在删除节点之前移动其后代。
use App\Models\Person; $node = Person::find(10); $another_node = Person::find(30); $node->moveChildrenTo($another_node); $node->delete();
或者可以直接使用onDeleteMoveChildren
方法将子节点直接移动到已删除节点的父节点,而不传递任何节点作为参数。
use App\Models\Person; $node = Person::find(10); $node->onDeleteMoveChildren()->delete();
删除节点的所有子节点
use App\Models\Person; $node = Person::find(10); return $node->deleteChildren(); // number of deleted nodes
检查节点
确定节点是否是该树的根节点
return $node->isRoot(); // returns true or false
确定节点是否是另一个节点的祖先
use App\Models\Person; $node = Person::find(1); $another_node = Person::find(2); return $node->isAncestorOf($another_node); // returns true OR false
确定节点是否是另一个节点的父亲
use App\Models\Person; $node = Person::find(1); $another_node = Person::find(2); return $node->isFatherOf($another_node); // returns true OR false
确定节点是否有子节点
return $node->hasChildren(); // returns true or false
确定节点是否是另一个节点的子节点
use App\Models\Person; $node = Person::find(1); $another_node = Person::find(2); return $node->isChildOf($another_node); // returns true OR false
确定节点是否有兄弟节点
return $node->hasSiblings(); // returns true or false
确定节点是否是另一个节点的兄弟节点
use App\Models\Person; $node = Person::find(1); $another_node = Person::find(2); return $node->isSiblingOf($another_node); // returns true OR false
计算节点的子节点数量
return $node->countChildren();
计算节点的儿子数量
return $node->countSons();
计算节点的女儿数量
return $node->countDaughters();
计算节点的所有兄弟节点数量
return $node->countSiblings();
计算节点的所有兄弟数量
return $node->countBrothers();
计算节点的所有姐妹数量
return $node->countSisters();
计算节点的所有后代数量
return $node->countDescendants();
计算节点的所有男性后代数量
return $node->countMaleDescendants();
计算节点的所有女性后代数量
return $node->countFemaleDescendants();
关系
获取节点的所有妻子
$node->wives; // to add constraints $node->wives()->where('name', $name)->get();
当尝试获取女性节点的妻子时,将抛出Girover\Tree\Exceptions\TreeException
异常。
注意: 这将包括离婚的妻子。
要获取未离婚的妻子,可以这样做
$node->wives()->ignoreDivorced()->get();
获取节点的丈夫
$node->husband; // to add constraints $node->husband()->where('name', $name)->get();
当尝试获取男性节点的丈夫时,将抛出Girover\Tree\Exceptions\TreeException
异常。因此,如果$husband
是女性,将抛出异常。
注意: 这将包括离婚的丈夫。
要获取未离婚的丈夫,可以这样做
$node->husband()->ignoreDivorced()->get();
为节点分配妻子
$wife = Node::find($female_node_id) $data = ['date_of_marriage'=>'2000/01/01', 'marriage_desc'=>'description']; return $node->getMarriedWith($wife, $data);
当尝试用女性节点(女人)做这件事时,将抛出Girover\Tree\Exceptions\TreeException
异常。因此,如果$node
是女性,将抛出异常。
与妻子离婚
$husband = Node::find($male_node_id) $wife = Node::find($female_node_id) return $husband->divorce($wife);
当尝试用女性节点做这件事时,将抛出Girover\Tree\Exceptions\TreeException
异常。因此,如果$husband
是女性,将抛出异常。
将上传的图片设置为节点
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Models\Node; class PersonController extends Controller { public function addPhoto(Request $request) { $person = Node::find($request->person_id); $photo = $request->file('photo'); $person->setPhoto($photo, 'new name'); return view('persons.index')->with('success', 'photo was added'); } }
移动节点
将现有节点移动到另一个节点的子节点
注意: 这将移动节点及其所有子节点。
use App\Models\Node; $node = Node::find(10); $another_node = Node::find(30); $node->moveTo($another_node);
将节点的子节点移动到另一个节点的子节点
可以这样做
use App\Models\Node; $node = Node::find(10); $another_node = Node::find(30); $node->moveChildrenTo($another_node);
注意: 当尝试将节点1移动到节点2时,如果节点2是节点1的后代,将抛出TreeException
异常。
如果节点2是女性节点,也将抛出相同的异常。
要将节点移动到另一个节点之后,可以这样做
use App\Models\Node; $node = Node::find(10); $another_node = Node::find(30); $node->moveAfter($another_node);
要将节点移动到另一个节点之前,可以这样做
use App\Models\Node; $node = Node::find(10); $another_node = Node::find(30); $node->moveBefore($another_node);
注意: 当尝试移动根节点或尝试将节点移动到其后代之前或之后时,将抛出TreeException
异常。
获取节点在树中的代数
return $node->generation(); // int or null
注意: 您可以使用树的指针访问Node类的所有方法。
例如
use Girover\Tree\Models\Tree; $tree = Tree::find(1); $tree->pointer()->to('aa.aa')->father(); // move Pointer to location 'aa.aa' and then get its father. $tree->pointer()->grandfather(); // get grandfather of node that Pointer indicates to $tree->pointer()->ancestor(3); // get father of node that Pointer indicates to $tree->pointer()->children(); // get children of node that Pointer indicates to $tree->pointer()->sons(); // get sons of node that Pointer indicates to $tree->pointer()->newDaughter($data); // create daughter for the node that Pointer indicates to $tree->pointer()->newSon($data); // create son of node that Pointer indicates to $tree->pointer()->newSister($data); // create sister for the node that Pointer indicates to $tree->pointer()->newChild($data, 'm'); // create son for the node that Pointer indicates to $tree->pointer()->firstChild(); // get the first child of node that Pointer indicates to . . . . . $tree->pointer()->toHtml();
附加和分离
使用方法create
或save
创建可创建的模型时,创建的模型将不会与任何树中的任何节点关联。
use App\Models\Person; $person = Person::create(['name'=>'Person', 'birth_date'=>'2000-01-01']); $another_person = new Person; $another_person->name = 'Another'; $another_person->birth_date = '2000-01-01'; $another_person->save();
Both $person 和 $another_person 都将在数据库表中创建,但它们在任意树中还不是节点。
要将 Adam 作为树中的一个节点,您可以使用所有可节点化方法,例如
use App\Models\Person; $new_person = Person::where('name', 'new person')->first(); $person = Person::find(1); $person->newSon($new_person); //or $person->createFather($new_person); // only if $person is a root in the tree //or $person->newBrother($new_person); // only if $person is not a root in the tree //or . . .
断开连接
在删除可节点化模型时,它们将从数据库表中删除,但如果您需要将它们从树中删除(断开连接)而保留在数据库中呢?
让我们看看下面的代码
use App\Models\Person; $person = Person::find(100); $person->delete();
在上面的代码中,该人物及其所有后代将从数据库中删除。
但我们需要从树中而不是从数据库中删除它。看看这段代码
use App\Models\Person; $person = Person::find(100); $person->detachFromTree();
现在这个人在数据库中仍然存在,但不再是任何树中的节点了。
辅助工具
要获取所有断开的可节点化模型,可以使用辅助函数 detachedNodeables
。
这将返回包含所有断开可节点化模型的 \Illuminate\Database\Eloquent\collection
实例。
<?php $detached = detachedNodeables();
要获取断开可节点化模型的数量,请使用辅助函数 countDetachedNodeables
。
<?php $count_detached = countDetachedNodeables();
渲染树
要在浏览器中显示树,您可以在 Treeable 模型的对象上使用以下方法之一 toHtml
、toTree
或 draw
,例如 App\Models\Tree
。
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Models\Tree; class TreeController extends Controller { public function index() { $tree = Tree::find(1); $treeHTML = $tree->toHtml() // OR $treeHTML = $tree->toTree() // OR $treeHTML = $tree->Draw() return view('tree.index')->with('treeHTML', $treeHTML); } }
现在在您的 blade 文件中,您可以使用自定义 blade 指令 @tree($treeHtml)
或使用此语法 {!! toHtml !!}
来渲染树。
<!-- views/tree/index.blade.php --> <div> @tree($treeHtml) </div> <!-- OR --> <div> {!! $treeHTML !!} </div>
注意 上面的示例将渲染整个树,但如果您想从特定节点开始渲染子树,则可以使用 Pinter 来实现。
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Models\Tree; use App\Models\Node; class TreeController extends Controller { public function index() { $tree = Tree::find(1); $person = Node::find(11); $treeHTML = $tree->pointer()->to($person)->toHtml(); // OR $treeHTML = $tree->pointer()->to($person)->toTree(); // OR $treeHTML = $tree->pointer()->to($person)->draw(); return view('tree.index')->with('treeHTML', $treeHTML); } }
您还可以通过在节点模型上使用这些方法之一 toHtml
、toTree
或 draw
来实现相同的功能,例如 App\Models\Node
。
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Models\Node; class TreeController extends Controller { public function index() { $person = Node::find(11); $treeHTML = $person->toHtml(); // OR $treeHTML = $person->toTree(); // OR $treeHTML = $person->draw(); return view('tree.index')->with('treeHTML', $treeHTML); } }
自定义节点样式
如果您想为节点添加一些 HTML 属性,则可以将自定义函数 nodeHtmlAttributes
绑定到 服务容器。此函数必须返回一个可调用的函数,该函数接受一个参数表示可节点化模型。并且这个可调用的函数必须返回包含其值的 HTML 属性字符串。
让我们举个例子。
如果您的可节点化模型有字段 id, name, gender, is_died
,那么您想将它们作为 HTML 属性添加到节点中,以进行样式化或控制节点。
// AppServiceProvider public function boot() { $this->app->singleton('nodeHtmlAttributes',function($app){ return function($nodeable){ return " data-id='{$nodeable->id}' data-name='{$nodeable->name}' data-gender='{$nodeable->gender}' data-is-died='{$nodeable->is_died}' "; }; }); }
现在所有节点元素都将具有以下属性
<a class="node" data-id='1' data-name='name' data-gender='1' data-is-died='0'>...</a>
如果您想为具有特定角色的节点添加一些 CSS 类,则可以将自定义函数 nodeCssClass
绑定到 服务容器。此函数必须返回一个可调用的函数,该函数接受一个参数表示可节点化模型。并且这个可调用的函数必须返回 CSS 类名称字符串。
让我们举个例子。
如果您的可节点化模型有一个名为 is_died
的属性,其值为 true
或 false
,那么您想为具有该值 true
的节点添加一个名为 is-died
的 CSS 类,以给予它们更多的样式。
// AppServiceProvider public function boot() { $this->app->singleton('nodeCssClasses',function($app){ return function($nodeable){ return ($nodeable->is_died) ? 'is-died' :''; }; }); }
现在所有 is_died
为 true
的可节点化元素将如下所示
<a class="node is-died">...</a>
测试
./vendor/bin/PHPUnit
变更日志
有关最近更改的更多信息,请参阅 变更日志。
贡献
有关详细信息,请参阅 贡献指南。
安全漏洞
有关如何报告安全漏洞,请参阅我们的安全策略 安全策略。
致谢
许可
MIT 许可证 (MIT)。有关更多信息,请参阅 许可文件。