junstyle/tree

基于父ID处理树结构的库

3.11 2020-11-09 03:22 UTC

This package is auto-updated.

Last update: 2024-09-06 12:57:07 UTC


README

Build Status SensioLabsInsight

概述

此库提供了使用父ID引用对结构化数据进行处理的处理。一个典型的例子是关系型数据库中的一个表,其中每个记录的“父”字段引用另一个记录的主键。当然,使用并不限于数据库来源的数据,而是任何东西:你提供数据,库就会使用它,无论数据来自哪里以及如何处理。

重要的是要知道,此包创建的树结构是只读的:你不能用它来修改树节点。

另一方面,一个很好的事情是它相当快。这不仅意味着代码本身,而且意味着构造函数接受简单创建的输入数据格式。例如,要从数据库内容创建一个树,只需要一个SELECT即可,无论树的深度如何,即使是数千个节点。

安装

安装Tree的首选方式是通过Composer。为此,只需执行composer require bluem/tree(根据您的Composer安装,它可能是“composer.phar”而不是“composer”),然后一切应该都能正常工作。或者您可以将“"bluem/tree": "^3.0"”手动添加到您的composer.json文件中的依赖项,然后安装/更新依赖项。

或者,您可以使用git克隆仓库或下载标记的版本。

更新

由于此库使用语义版本控制,当您运行composer update时,您将获得修复和功能添加,但不会破坏API的更改。

使用方法

创建树

// Create the tree with an array of arrays (or use an array of Iterators,
// Traversable of arrays or Traversable of Iterators):
$data = [
    ['id' => 1, 'parent' => 0, 'title' => 'Node 1'],
    ['id' => 2, 'parent' => 1, 'title' => 'Node 1.1'],
    ['id' => 3, 'parent' => 0, 'title' => 'Node 3'],
    ['id' => 4, 'parent' => 1, 'title' => 'Node 1.2'],
];
$tree = new BlueM\Tree($data);

// When using a data source that uses different keys for "id" and "parent",
// or if the root node ID is not 0 (in this example: -1), use the options
// array you can pass to the constructor:
$data = [
    ['nodeId' => 1, 'parentId' => -1, 'title' => 'Node 1'],
    ['nodeId' => 2, 'parentId' => 1, 'title' => 'Node 1.1'],
    ['nodeId' => 3, 'parentId' => -1, 'title' => 'Node 3'],
    ['nodeId' => 4, 'parentId' => 1, 'title' => 'Node 1.2'],
];
$tree = new BlueM\Tree(
    $data,
    ['rootId' => -1, 'id' => 'nodeId', 'parent' => 'parentId']
);

使用新数据更新树

// Rebuild the tree from new data
$tree->rebuildWithData($newData);

检索节点

// Get the top-level nodes (returns array)
$rootNodes = $tree->getRootNodes();

// Get all nodes (returns array)
$allNodes = $tree->getNodes();

// Get a single node by its unique identifier
$node = $tree->getNodeById(12345);

获取节点的前辈、兄弟、子节点、祖先和后代

// Get a node's parent node (will be null for the root node)
$parentNode = $node->getParent();

// Get a node's siblings as an array
$siblings = $node->getSiblings();

// Ditto, but include the node itself (identical to $node->getParent()->getChildren())
$siblings = $node->getSiblingsAndSelf();

// Get a node's preceding sibling (null, if there is no preceding sibling)
$precedingSibling = $node->getPrecedingSibling();

// Get a node's following sibling (null, if there is no following sibling)
$followingSibling = $node->getFollowingSibling();

// Does the node have children?
$bool = $node->hasChildren();

// Get the number of Children
$bool = $node->countChildren();

// Get a node's child nodes
$children = $node->getChildren();

// Get a node's ancestors (parent, grandparent, ...)
$ancestors = $node->getAncestors();

// Ditto, but include the node itself
$ancestorsPlusSelf = $node->getAncestorsAndSelf();

// Get a node's descendants (children, grandchildren, ...)
$descendants = $node->getDescendants();

// Ditto, but include the node itself
$descendantsPlusSelf = $node->getDescendantsAndSelf();

访问节点的属性

// Get a node's ID
$id = $node->getId();

// Get the node's hierarchical level (1-based)
$level = $node->getLevel();

// Access node properties using get() overloaded getters or __get():
$value = $node->get('myproperty');
$value = $node->myproperty;
$value = $node->getMyProperty();

// Get the node's properties as an associative array
$array = $node->toArray();

// Get a string representation (which will be the node ID)
echo "$node";

示例:使用字面数据

<?php

require 'vendor/autoload.php';

// Create the Tree instance
$tree = new BlueM\Tree([
    ['id' => 1, 'name' => 'Africa'],
    ['id' => 2, 'name' => 'America'],
    ['id' => 3, 'name' => 'Asia'],
    ['id' => 4, 'name' => 'Australia'],
    ['id' => 5, 'name' => 'Europe'],
    ['id' => 6, 'name' => 'Santa Barbara', 'parent' => 8],
    ['id' => 7, 'name' => 'USA', 'parent' => 2],
    ['id' => 8, 'name' => 'California', 'parent' => 6],
    ['id' => 9, 'name' => 'Germany', 'parent' => 5],
    ['id' => 10, 'name' => 'Hamburg', 'parent' => 9],
]);
...
...

示例:使用自连接的数据库表

<?php

require 'vendor/autoload.php';

// Database setup (or use Doctrine or whatever ...)
$db = new PDO(...);

// SELECT the records in the sort order you need
$stm = $db->query('SELECT id, parent, title FROM tablename ORDER BY title');
$records = $stm->fetchAll(PDO::FETCH_ASSOC);

// Create the Tree instance
$tree = new BlueM\Tree($records);
...
...

JSON序列化

由于Tree实现了JsonSerializable,树可以被序列化为JSON。默认情况下,生成的JSON表示树数据的平面(非层次)表示,一旦从JSON解码,就可以重新输入到新的Tree实例中。在3.0之前的版本中,您必须从子类化TreeNode类来定制JSON输出。现在,序列化被提取到一个外部的辅助类中,可以通过设置构造函数参数或在序列化前运行时更改它。然而,默认的序列化结果与之前相同,所以除非您调整了JSON序列化,否则您不会注意到任何行为上的变化。

要控制JSON,您可以通过构造函数传递一个选项jsonSerializer(即传递类似于['jsonSerializer' => $mySerializer]作为参数2的参数),它必须是一个实现了\BlueM\Tree\Serializer\TreeJsonSerializerInterface的对象。或者您可以在树上调用setJsonSerializer()方法。后者方法也可以用来重新设置序列化行为,通过不带参数调用它。

该库包含两个不同的序列化器:默认的序列化器是\BlueM\Tree\Serializer\FlatTreeJsonSerializer,如果没有设置序列化器,它将生成“旧版”的扁平JSON输出。此外,还有\BlueM\Tree\Serializer\HierarchicalTreeJsonSerializer,它创建一个层次结构、深度优先排序的树节点表示。如果您需要其他内容,请随意编写自己的序列化器。

处理不一致的数据

在构建树的过程中如果检测到问题(如节点指向自身或无效的父ID引用),则会抛出InvalidParentException异常。通常这很有意义,但并不总是如此。对于这些情况,您可以在选项参数中传递一个可调用的值作为键buildWarningCallback,该键可以作为Tree构造函数的第二个参数传递,并在发现问题时调用它。可调用的签名应与方法Tree::buildWarningHandler()相同,这是默认实现(并抛出InvalidParentException)。例如,如果您只想忽略具有无效父ID的节点,则可以传递一个空的可调用对象。

请注意,具有无效父ID的节点将不会添加到树中。如果您需要修复节点(例如,使用根节点作为父节点),则可以创建Tree的子类,重写buildWarningHandler()方法并在重写的方法中执行此操作。

运行测试

PHPUnit已配置为开发依赖项,因此运行测试只需执行以下操作:

  • composer install
  • ./vendor/bin/phpunit

如果您想查看TestDox输出或覆盖率数据,可以在phpunit.xml.dist<log>部分中取消注释注释的行。

版本历史

3.1 (2019-09-15)

  • 构建树现在更加灵活和可扩展
    • null可以用作根ID
    • 之前是privateTree::build()方法现在改为protected,以便挂钩到构建过程中
    • 直到版本3.0,在数据不一致的情况下,会抛出InvalidParentException异常。现在,可以通过新的构造函数选项“buildWarningCallback”或通过子类化和重写方法buildWarningHandler()来切换行为

3.0 (2019-03-28)

  • JSON序列化可以通过设置自定义序列化器轻松自定义。(请参阅本Readme中的“JSON序列化”部分。)可能的BC中断:如果您的代码中已经子类化了TreeTree\Node并添加了自定义的jsonSerialize()实现,则您的当前代码可能会中断。这是版本号增加的主要原因,因为其他一切都没有改变。您很可能不需要进行任何更改即可与v3兼容。
  • 许可证变更:从BSD-2更改为BSD-3

2.0 (2018-02-04)

  • BC中断:getAncestors()getAncestorsAndSelf()不再将根节点作为返回数组中的最后一个元素。 解决方案:如果您需要它,请自己添加。
  • BC中断:移除了getAncestors()的参数。 解决方案:如果之前传递了true作为参数,将其更改为getAncestorsAndSelf()
  • BC中断:移除了getDescendants()的参数。 解决方案:如果之前传递了true作为参数,将其更改为getDescendantsAndSelf()
  • BC中断:移除了getSiblings()的参数。 解决方案:如果之前传递了true作为参数,将其更改为getSiblingsAndSelf()
  • BC中断:将BlueM\Tree\InvalidParentException移动到BlueM\Tree\Exception\InvalidParentException解决方案:更新命名空间导入。
  • 新增:添加了方法Tree::rebuildWithData(),用于使用新数据重新构建树。
  • 新增:TreeTree\Node实现了JsonSerializable并提供了默认实现,这意味着您可以轻松地将整个树或节点序列化为JSON。
  • 新增:树数据不再必须是 数组,而是必须是 可迭代对象,这意味着您可以传入一个 数组 或者实现 Traversable 接口的对象。此外,节点的数据也不再必须是数组,也可以是实现 Iterator 接口的对象。这些更改应使使用库更加灵活。
  • 内部更改:将自动加载从 PSR-0 更改为 PSR-4,将源目录从 lib/ 重命名为 src/,将测试目录从 test/ 重命名为 tests/
  • 内部更改:代码现代化,现在需要 PHP >= 7.0

1.5.3 (2016-05-20)

  • 处理混合类型(字符串和整数)的ID

1.5.2 (2016-05-10)

  • 在Readme中添加了关于JSON序列化的信息。没有代码更改。

1.5.1 (2016-01-16)

  • 从构造函数中的 build() 移除多余的第二个参数

1.5 (2015-01-14)

  • Tree 类中添加了 createNode() 方法,这使得可以使用节点子类的实例作为节点

1.4 (2015-01-07)

  • Node 类上添加了 getSiblingsAndSelf() 方法。
  • getSiblings() 的参数已弃用,将在版本 2 中删除

1.3 (2014-11-07)

  • Tree 类上添加了 getNodeByValuePath() 方法,可以用于根据任意属性的前辈值和节点值在树中查找深度嵌套的节点。 (请参阅方法文档注释中的示例。)

1.2 (2014-10-14)

  • Node 类上实现了 __isset()__get()。这使得可以将节点传递给 Twig(或其他以类似方式处理对象属性的库),并且可以直观地访问节点的属性。
  • 改进了节点属性的不区分大小写的处理

1.1 (2014-09-24)

  • 添加了 getDescendantsAndSelf()
  • 添加了 getAncestorsAndSelf()
  • getDescendants()getAncestors() 的参数已弃用,将在版本 2 中删除
  • 添加了一个检查,以确保节点不会使用自己的ID作为父ID。如果这种情况发生,将抛出一个异常,而之前不会抛出此类异常。因此,这可能会破坏向后兼容性,但只有在数据不一致的情况下才会。

1.0 (2014-06-26)

  • 首次公开发布

作者 & 许可证

此代码由 Carsten Blüm (www.bluem.net) 编写,并许可在 BSD 3-Clause 许可证下。