lmh/tree

基于父ID处理树结构的库

3.2 2020-11-06 02:10 UTC

This package is auto-updated.

Last update: 2024-09-06 11:20:55 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序列化。(请参阅本说明中的“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 许可证下使用。