bluem / tree
基于父ID处理树结构的库
Requires
- php: >=7.3 || >=8.0
- ext-json: *
Requires (Dev)
- phpunit/phpunit: >=8.0 || >=9.0
README
此库提供使用父ID引用对结构化数据进行处理的操作。一个典型的例子是关系型数据库中的表,其中每条记录的“父”字段引用另一条记录的主键。当然,使用并不限于数据库中的数据,任何数据:你提供数据,库就会使用它,不管数据来自哪里以及如何处理。
重要的是要知道,此包创建的树结构是 只读的:你不能用它来执行树节点的修改。
另一方面,一个很好的特点是它相当快。这不仅意味着代码本身,还意味着构造函数以简单易创建的格式接收输入数据。例如,要从数据库内容创建树,只需要一个 SELECT
语句就足够了,无论树的深度如何,甚至对于成千上万的节点。
安装
安装 Tree 的首选方法是使用 Composer。为此,只需执行 composer require bluem/tree
(根据您的 Composer 安装情况,它可能是“composer.phar”而不是“composer”),然后一切应该运行正常。或者您可以在您的 composer.json
文件中手动添加 "bluem/tree": "^3.0"
到依赖项中,然后安装/更新依赖项。
或者,您可以使用 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 $integer = $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' => 7], ['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 表示树数据的平面(非分层)表示,解码后可以重新输入到新的 Tree
实例中。在版本 3.0 之前,您必须子类化 Tree
和 Node
类来定制 JSON 输出。现在,序列化已提取到外部辅助类中,可以通过设置构造函数参数或运行时在序列化之前来更改。然而,默认序列化结果与之前相同,所以除非您调整了 JSON 序列化,否则您不会注意到任何行为上的变化。
要控制 JSON,您可以将一个选项 jsonSerializer
传递给构造函数(即作为参数 2 传递类似于 ['jsonSerializer' => $mySerializer]
的内容),它必须是一个实现 \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.2 (2021-12-30)
- 有关类型提示的轻微现代化,但除此之外API或行为没有变化
- 在PHP 8.1上抑制警告
- 最低PHP版本现在是7.3(以前是7.0)
3.1 (2019-09-15)
- 构建树现在更灵活、更可扩展
null
可以用作根ID- 方法
Tree::build()
,之前是private
的,现在是protected
的,以便在构建过程中挂钩 - 直到版本3.0,在数据不一致的情况下,会抛出
InvalidParentException
。现在,通过新的构造函数选项“buildWarningCallback”或通过子类化和重写方法buildWarningHandler()
来切换行为
3.0 (2019-03-28)
- 通过设置自定义序列化器,可以轻松自定义JSON序列化。有关详细信息,请参阅此Readme中的“JSON序列化”部分。潜在的BC不兼容性:如果在您的代码中子类化了
Tree
或Tree\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()
,用于使用新数据重建树。 - 新增:
Tree
和Tree\Node
实现了JsonSerializable
并提供默认实现,这意味着您可以将整个树或节点轻松序列化为JSON。 - 新增:树数据不再必须是
array
,而是必须是iterable
,这意味着您可以传递array
或实现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()
方法,这使得可以使用Node子类的实例作为节点
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许可证下。