lmh/tree
Requires
- php: >=7.0
- ext-json: *
Requires (Dev)
- doctrine/instantiator: 1.0.*
- phpunit/phpunit: 6.*
README
概述
此库提供了基于父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之前的版本中,你必须继承 Tree 和 Node 类来定制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- 之前为
private的Tree::build()方法现在是protected,以便在构建过程中进行挂钩 - 在版本3.0之前,在数据不一致的情况下,会抛出
InvalidParentException异常。现在,可以通过新的构造函数选项“buildWarningCallback”或通过子类化和重写方法buildWarningHandler()来切换行为
3.0 (2019-03-28)
- 可以通过设置自定义序列化器轻松自定义JSON序列化。(请参阅本说明中的“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。 - 新增功能:树形数据不再必须是
数组,而必须是可迭代对象,这意味着您可以传递一个数组或实现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 许可证下使用。