简体   繁体   English

PHP - 如何建立树状结构列表?

[英]PHP - How to build tree structure list?

So, my problem is, that I want to build a tree of these 2 tables:所以,我的问题是,我想构建这两个表的树:

Parent table:
+-------+---------------+
| pr_id |  parent_name  |
+-------+---------------+
|   1   |       p       |
|   2   |      p_0      | 
|   3   |     p_0_1     | 
|   4   |       q       | 
+-------+---------------+

Child table:
+-------+---------------+---------------------------+
| ch_id |     pr_id     |        child_name         |
+-------+---------------+---------------------------+
|   1   |       1       |            p_0            |
|   2   |       1       |            p_1            |
|   3   |       2       |           p_0_0           |
|   4   |       2       |           p_0_1           |
|   5   |       3       |          p_0_1_0          |
|   6   |       3       |          p_0_1_1          |
|   7   |       4       |            q_0            |
|   8   |       4       |            q_1            |
+-------+---------------+---------------------------+

And the Tree should look like:树应该看起来像:

  • p p
    • p_0 p_0
      • p_0_0 p_0_0
      • p_0_1 p_0_1
        • p_0_1_0 p_0_1_0
        • p_0_1_1 p_0_1_1
  • q q

Can anybody help me out with a recursive solution??任何人都可以帮助我解决递归问题吗?

You do not need to create 2 tables in the database for this you can maintain it like below from one table only您不需要在数据库中创建 2 个表,您可以仅从一个表中维护它,如下所示

+-------+---------------+---------------------------+
|   id  |   parent_id   |           title           |
+-------+---------------+---------------------------+
|   1   |       0       |   Parent Page             |
|   2   |       1       |   Sub Page                |
|   3   |       2       |   Sub Sub Page            |
|   4   |       0       |   Another Parent Page     |
+-------+---------------+---------------------------+

The array generated will be like生成的数组将类似于

Array
(
    [0] => Array
        (
            [id] => 1
            [parent_id] => 0
            [title] => Parent Page
            [children] => Array
                        (
                            [0] => Array
                                (
                                    [id] => 2
                                    [parent_id] => 1
                                    [title] => Sub Page
                                    [children] => Array
                                                (
                                                    [0] => Array
                                                        (
                                                            [id] => 3
                                                            [parent_id] => 1
                                                            [title] => Sub Sub Page
                                                        )
                                                )
                                )
                        )
        )
    [1] => Array
        (
            [id] => 4
            [parent_id] => 0
            [title] => Another Parent Page
        )
)

You need to use the below recursive function to achieve it您需要使用以下递归函数来实现它

function buildTree(array $elements, $parentId = 0) {
    $branch = array();

    foreach ($elements as $element) {
        if ($element['parent_id'] == $parentId) {
            $children = buildTree($elements, $element['id']);
            if ($children) {
                $element['children'] = $children;
            }
            $branch[] = $element;
        }
    }

    return $branch;
}

$tree = buildTree($rows);

The algorithm is pretty simple:算法非常简单:

  1. Take the array of all elements and the id of the current parent (initially 0/nothing/null/whatever).获取所有元素的数组和当前父级的 id(最初为 0/nothing/null/whatever)。
  2. Loop through all elements.循环遍历所有元素。
  3. If the parent_id of an element matches the current parent id you got in 1., the element is a child of the parent.如果元素的 parent_id 与您在 1. 中获得的当前父 ID 匹配,则该元素是父元素的子元素。 Put it in your list of current children (here: $branch).把它放在你当前孩子的列表中(这里:$branch)。
  4. Call the function recursively with the id of the element you have just identified in 3., ie find all children of that element, and add them as children element.使用您刚刚在 3. 中标识的元素的 id 递归调用该函数,即找到该元素的所有子元素,并将它们添加为子元素。
  5. Return your list of found children.返回您找到的孩子的名单。

Multi Loop and/or Recursion are simple but performance wise not the best solution.多循环和/或递归很简单,但性能方面不是最佳解决方案。 they are totally fine for smaller datasets but these scale not very well.它们对于较小的数据集完全没问题,但这些规模不是很好。

a better solution is when every item is only processed one-time.更好的解决方案是每个项目只处理一次。

The Basic Idea is that you create Objects and use the byReference nature of the objects.基本思想是您创建对象并使用对象的 byReference 特性。 For that I create "ShadowObjects" that are "glue" everything together, these "ShadowObjects" are later filled with data.为此,我创建了将所有内容“粘合”在一起的“ShadowObjects”,这些“ShadowObjects”稍后会填充数据。

Here an Example这是一个例子

you have a generic id , parentId list你有一个通用的idparentId列表

$list = [
    ['id' => 1, 'parent' => 0, 'otherData' => 'a'],
    ['id' => 2, 'parent' => 1, 'otherData' => 'b'],
    ['id' => 3, 'parent' => 1, 'otherData' => 'c'],
    ['id' => 4, 'parent' => 3, 'otherData' => 'd'],
    ['id' => 5, 'parent' => 3, 'otherData' => 'e'],
    ['id' => 6, 'parent' => 4, 'otherData' => 'f'],
    ['id' => 7, 'parent' => 1, 'otherData' => 'g'],
];

The Tree Class树 Class

We need an Element that hold and index our Tree-Elements I like to use an TreeClass for that.我们需要一个元素来保存和索引我们的树元素,我喜欢为此使用 TreeClass。 That class Hold all TreeItems in a list with the unique-ID as identifier, the unique-ID is important to get quick access to every Item. class 将所有 TreeItems 保存在一个列表中,以唯一 ID 作为标识符,唯一 ID 对于快速访问每个项目很重要。

I also like the getRootItems() method to get all Root_Tree_Elements我也喜欢 getRootItems() 方法来获取所有 Root_Tree_Elements

class Tree
{
    private array $items = [];
    
    public function getItem(int $id): ?TreeItem
    {
        return $this->items[$id]??null;
    }
    
    public function setItem(TreeItem $item, int $id): void
    {
        $this->items[$id] = $item;
    }
    
    public function getRootItems(): array
    {
        $rootItems = [];
        foreach ($this->items as $item) {
            if ($item->getParent() === null)
                $rootItems[] = $item;
        }
        
        return $rootItems;
    }
}

The Tree Item树项目

is an object that represent a "tree element" it holds the actual data and the parent / children relations是一个 object 代表一个“树元素”它保存实际数据和父/子关系

class TreeItem 
{
    private ?TreeItem $parent = null;
    private array $children = [];
    private array $data = [];
    
    public function setParent(?TreeItem $parent): void
    {
        $this->parent = $parent;
    }
    
    public function addChild(TreeItem $child): void
    {
        $this->children[] = $child;
    }
    
    public function setData(array $data): void
    {
        $this->data = $data;
    }
}

Processing Loop处理循环

And here is The single Loop that creates the TreeStructure:这是创建 TreeStructure 的单个循环:

from the List we have the information that a parent exists with parent_ID X and that a the item with the ID Y is a child from it.从列表中,我们知道存在具有parent_ID X的父项以及ID Y的项是它的子项的信息。

So the first thing is we ask our Tree if there is an TreeItem with the id X is already existing (only if the ID is not 0 or NULL or something 'invalid').所以第一件事是我们询问我们的树是否已经存在 ID X 的 TreeItem(仅当 ID 不是 0 或 NULL 或“无效”时)。 If not found we create this Tree Item NEW and add this to the Tree, even we don't know all data for the parent, what we know is only the ID of the parent but that is enough as a 'SHADOW_ITEM'.如果没有找到,我们创建这个树项 NEW 并将其添加到树中,即使我们不知道父项的所有数据,我们只知道父项的 ID,但这足以作为“SHADOW_ITEM”。

after that, we look up for the actual TreeItem.之后,我们查找实际的 TreeItem。 We have all Information for that element.我们拥有该元素的所有信息。 We look it up cuz there is the chance that we create it already as a "ShadowItem", So we can fill it with actual DATA.我们查找它,因为我们有可能已经将它创建为“ShadowItem”,所以我们可以用实际的数据填充它。 Keep in mind that TreeItem and the Parent are the same type of Objects.请记住,TreeItem 和 Parent 是同一类型的对象。

Here again, if the Item not exists create it new and add the new Item to the TreeList.同样,如果 Item 不存在,则新建它并将新 Item 添加到 TreeList。 also Add the ParentTreeItem to the current TreeItem.还将 ParentTreeItem 添加到当前 TreeItem。

Then we fill up our Parent Element with the new Children (Bi-Directional Relation parent knows children / child knows Parent).然后我们用新的孩子填充我们的父元素(双向关系父母知道孩子/孩子知道父母)。

We don't need to worry about adding the wrong child to the parent or even duplicate, cuz we only process every item only ONE time.我们不需要担心将错误的孩子添加到父母甚至重复,因为我们只处理每个项目一次。

at the end we fill up the actual data to every item.最后,我们将实际数据填充到每个项目。 So that in total every ShadowItem contains the real data and loose the state of a ShadowItem.因此,总共每个 ShadowItem 都包含真实数据,并且会丢失 ShadowItem 的 state。

$tree = new Tree();
foreach($list as $itemData) {
    
    $id = $itemData['id'];
    $pid = $itemData['parent'];
    
    // if ZERO we have root element no parent exists
    
    $parent = $tree->getItem($pid);
    if ($pid > 0 && $parent === null) {
        $parent = new TreeItem();
        $tree->setItem($parent, $pid);
    }
    
    // create new tree item if not exists / otherwise get existing item
    $item = $tree->getItem($id);
    if ($item === null) {
        $item = new TreeItem();
        $item->setParent($parent);
        $tree->setItem($item, $id);
    }
    
    if ($parent !== null) {
        $parent->addChild($item);
    }
    
    $item->setData($itemData);
}

This is an example and "NOT optimize" to make it more easy to understand.这是一个示例,并且“未优化”以使其更易于理解。 there also many things missing on the Tree and the TreeItem to make actual usable, Tree 和 TreeItem 上还缺少许多东西以使实际可用,

Feel free to add all methods you like.随意添加您喜欢的所有方法。

for example:例如:

  • remove all invalid remaining shadow elements (this can happen if the tree structure data is corrupt, an item points to parent that did not exist anymore)删除所有无效的剩余阴影元素(如果树结构数据损坏,项目指向不再存在的父项,则可能发生这种情况)
  • or a TreeItem method that give you a ordered array structure of this data and all Children (an __toArray() like).或 TreeItem 方法,为您提供此数据和所有子项的有序数组结构(类似 __toArray())。

I use this kind of logic often in Menu generations, it's also easy to "cache"/"store" the finish Tree somewhere.我经常在菜单生成中使用这种逻辑,在某处“缓存”/“存储”完成树也很容易。 a simple serialize / unserialize do the trick:)一个简单的序列化/反序列化就可以了:)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM