v0.1 2018-12-27 14:19 UTC

This package is auto-updated.

Last update: 2024-09-28 23:50:53 UTC


README

Build Status codecov

OakTree 是一种支持基于键遍历和自定义(反)序列化的树数据结构实现。有用的实现可能包括产品树、文件/文件夹结构、路由树等。作为一个包,它从头开始编写,大部分灵感来源于 nicmart/Tree

树结构和树遍历方法

节点创建

在实例化过程中,节点可以传递任何类型的值。

use MatthijsBreijer\OakTree\Node;

$node = new Node('data');

获取和设置值

可以使用 Node::getValue()Node::setValue() 来检索和修改节点值。请注意,Node::setValue() 是流畅的,可以链式调用。

var_dump( $node->getValue() ); // string(4) "data"

$node->setValue('new data');
var_dump( $node->getValue() ); // string(8) "new data"

向节点添加子节点

可以使用 Node::addChild() 添加一个或多个子节点。该方法流畅,可以链式调用。

$child1 = new Node('child1');
$child2 = new Node('child2');

$node->addChild($child1)
    ->addChild($child2);

使用特定子键添加子节点到节点

OakTree 跟踪树中节点的键。因此,可以为子节点分配一个键。

$child1 = new Node('child1');
$child2 = new Node('child2');

$node->addChild($child1, 0)
    ->addChild($child2, 'customKey');

删除子节点

$node 的子节点中删除 $child1$child2 实例。Node::removeChild() 是流畅的,可以链式调用。

$node->removeChild($child1)
    ->removeChild($child2);

获取节点的所有直接子节点

$children = $node->getChildren(); // array(2) [0 => $child1, 'customKey' => $child2]

获取直接子节点节点的所有键

$children = $node->getChildrenKeys(); // array(2) [0, 'customKey']

通过其键获取子节点

OakTree 保持树中节点的键完整。因此,可以通过键获取子节点。

// when $child1 is located at array key '1'
$child1 = $node->getChildByKey(1);

// when $child1 is located at array key 'customKey'
$child1 = $node->getChildByKey('customKey');

// requesting a non-existent key throws \OutOfBoundsException
$node->getChildByKey('nonExistentKey');

获取子节点并从其节点树中删除子节点

使用 Node::pop() 可以从树中删除/分离一个节点(及其后代),并独立修改它。

$child1 = new Node('child1');
$child2 = new Node('child2');

$node->addChild($child1)
    ->addChild($child2);

// all examples below produce same result
$child1 = $child1->pop();
$child1 = $node->getChildren()[0]->pop();
$child1 = $node->getChildByKey(0)->pop();

// using one of the above examples $node would look as follows
var_dump($node->getChildren()); // array(1) [1 => $child2]

设置子节点

请注意,此方法会删除之前存在的子节点。 Node::setChildren() 设置一个(新)子节点数组。该方法流畅,可以链式调用。

$node->setChildren([new Node('a'), new Node('b')]);

// or with keys
$node->setChildren([0 => new Node('a'), 'customKey' => new Node('b')]);

获取子节点的父节点

$childNode->getParent(); // returns $parent Node
$root->getParent(); // $root has no parent Node and returns NULL

获取树的根节点

// all return $root Node when part of the same tree
$root->getRoot();
$child1->getRoot();
$grandChild1->getRoot();

树上下文

节点是叶子吗?

叶子是一个没有子节点的节点。

$node->isLeaf(); // bool(true)

节点是子节点吗?

子节点是一个有父节点的节点。

$node->isChild(); // bool(true)

节点是根节点吗?

根节点没有父节点。

$node->isRoot(); // bool(true)

树变更

OakTree 使用 访问者模式 来遍历树。访问者包含应用于树中节点的逻辑。访问者必须实现 MatthijsBreijer\OakTree\Visitor\VisitorInterface 接口。访问者根据其目的返回混合内容。

LeafVisitor

LeafVisitor 返回一个包含树($node 的一个数组,其中 Node::isLeaf() 返回 true)的叶子。

$tree = new Node('root');
$child1 = new Node('child1');
$child2 = new Node('child2');

$tree->addChild($child1)
    ->addChild($child2);

$visitor = new LeafVisitor();

$leafs = $tree->accept($visitor); // array(2) [$child1, $child2]

ClosureVisitor

ClosureVisitor 可以传递一个函数或 \Closure 参数来定义其行为。下面的示例模仿了 LeafVisitor。

$tree = new Node('root');
$child1 = new Node('child1');
$child2 = new Node('child2');

$tree->addChild($child1)
    ->addChild($child2);

$closure = function(NodeInterface $node, VisitorInterface $visitor) {
    $return = $node->isLeaf() ? [$node] : [];

    foreach ($node->getChildren() as $key => $child) {
        $return = array_merge($return, $child->accept($visitor));
    }

    return $return;
};

$visitor = new ClosureVisitor($closure);

$leafs = $tree->accept($visitor); // array(2) [$child1, $child2]

序列化/反序列化

OakTree 节点具有 Node::toArray()Node::fromArray() 方法,允许对树进行自定义(反)序列化。这可以用于缓存数据,快速在树与 API 或反之之间传递信息。节点还实现了 PHP 的 \JsonSerializable 接口。

基本树到数组转换

// Expose product catalog to a view
$product = new Node('Product 1');
$option1 = new Node('Extended package option 1');
$option2 = new Node('Extended package option 2');

$product->addChild($option1)
    ->addChild($option2);

// array(2) [
//     'value' => 'Product 1',
//     'children' => array(2) [
//         array(3) [
//             'value' => 'Extended package option 1',
//             'children' => []
//         ],
//         array(3) [
//             'value' => 'Extended package option 2',
//             'children' => []
//         ]
//     ]
// ]
$array = $product->toArray();

基本数组到树转换

使用上面的示例,可以将结果数组 $array 转换回树,如下所示。

$tree = Node::fromArray($array);

基于闭包的树到数组转换

Node::toArray() 方法接受第二个参数,用于基于闭包转换为数组,这可以进一步进行序列化。

// Build a fictive product catalog tree
$product = new Node( new Product('Product 1') );
$option1 = new Node( new Option('Extended package option 1') );
$option2 = new Node( new Option('Extended package option 2') );

$product->addChild($option1)
    ->addChild($option2);

$closure = function($nodeValue) {
    return [
        'name' => $nodeValue->getName(),
        'type' => get_class($nodeValue)
    ];
};

// array(2) [
//     'value' => array(2) [
//         'name' => 'Product 1',
//         'type' => 'Product'
//     ],
//     'children' => array(2) [
//         array(2) [
//             'value' => array(2) [
//                 'name' => 'Extended package option 1',
//                 'type' => 'Option'
//             ],
//             'children' => []
//         ],
//         array(2) [
//             'value' => array(2) [
//                 'name' => 'Extended package option 2',
//                 'type' => 'Option'
//             ],
//             'children' => []
//         ]
//     ]
// ]
$array = $product->toArray($closure);

基于闭包的数组到树转换

使用前一个示例中创建的 $array 变量,可以将数组结果转换回树,如下所示。

$closure = function($value) {
    $type = $value['type'];
    $name = $value['name'];
    return new $type($name);
};

$tree = Node::fromArray($array, $closure);

安装

Composer

可以使用以下命令使用 PHP Composer 包管理器安装 OakTree:

composer require matthijsbreijer/oaktree

运行测试

cd vendor/matthijsbreijer/oaktree/tests
phpunit