girover/tree

1.0.1 2022-06-22 18:21 UTC

This package is auto-updated.

Last update: 2024-09-15 11:48:01 UTC


README

tree-template-3

构建家谱

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads

内容

介绍

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();

附加和分离

使用方法createsave创建可创建的模型时,创建的模型将不会与任何树中的任何节点关联。

    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 模型的对象上使用以下方法之一 toHtmltoTreedraw,例如 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);
        }
    }

您还可以通过在节点模型上使用这些方法之一 toHtmltoTreedraw 来实现相同的功能,例如 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 的属性,其值为 truefalse,那么您想为具有该值 true 的节点添加一个名为 is-died 的 CSS 类,以给予它们更多的样式。

    // AppServiceProvider
    public function boot()
    {
        $this->app->singleton('nodeCssClasses',function($app){
            return function($nodeable){
                return ($nodeable->is_died) ? 'is-died' :'';
            };
        });
    }

现在所有 is_diedtrue 的可节点化元素将如下所示

    <a class="node is-died">...</a>

测试

./vendor/bin/PHPUnit

变更日志

有关最近更改的更多信息,请参阅 变更日志

贡献

有关详细信息,请参阅 贡献指南

安全漏洞

有关如何报告安全漏洞,请参阅我们的安全策略 安全策略

致谢

许可

MIT 许可证 (MIT)。有关更多信息,请参阅 许可文件