根据父/子关系对输出进行排序


Ordering Ouput Based On Parent/Child Relationship

我目前正在使用一个名为jstree的东西在我的网站上创建项目的"文件夹视图"。组成此文件夹视图的文件夹保存在MySQL数据库中,如下所示:

----------------------------------
| id   | folder_name | parent_id |
|------+-------------+-----------|
| 1    | Folder 1    | 0         |
|------+-------------+-----------|
| 2    | Folder 2    | 0         |
|------+-------------+-----------|
| 3    | Sub 1       | 1         |
|------+-------------+-----------|
| 4    | Sub 2       | 1         |
|------+-------------+-----------|
| 5    | Sub 3       | 2         |
|------+-------------+-----------|
| 6    | SubSub 1    | 3         |
----------------------------------

在不同的页面上,我希望显示文件夹,正确排列在下拉框中。我希望在下拉列表中实现的输出如下所示:

Folder 1
-- Sub 1
---- SubSub 1
-- Sub 2
Folder 2
-- Sub 3

关于如何实现上述目标的任何想法?似乎某种形式的递归函数来对它们进行排序会起作用,但我正在努力解决它。任何帮助将不胜感激。

编辑:

这是我到目前为止所拥有的。它似乎正在工作,但很难想象这是最好的方法。有什么改进想法吗?

$folders = [
    [ "id" => 1, "folder_name" => "Folder 1", "parent_id" => 0 ],
    [ "id" => 2, "folder_name" => "Folder 2", "parent_id" => 0 ],
    [ "id" => 3, "folder_name" => "Sub 1", "parent_id" => 1 ],
    [ "id" => 4, "folder_name" => "Sub 2", "parent_id" => 1 ],
    [ "id" => 5, "folder_name" => "Sub 3", "parent_id" => 2 ],
    [ "id" => 6, "folder_name" => "SubSub 1", "parent_id" => 3 ]
];
function sortedFolders( $parentId, $folders, $depth = 0 )
{
    if( count( $folders ) == 0 )
    {
        return "";
    }
    $str = "";
    for( $i = 0; $i < count( $folders ); $i++ )
    {
        if( $folders[$i]['parent_id'] == $parentId )
        {
            $newFolders = $folders;
            unset( $newFolders[$i] );
            $newFolders = array_values( $newFolders );
            $str .= "<option value='" . $folders[$i]['id'] . "'>";
            for( $j = 0; $j < $depth; $j++ )
            {
                $str .= "-- ";
            }
            $str .= $folders[$i]['folder_name'] . "</option>";
            $str .= sortedFolders( $folders[$i]['id'], $newFolders, ($depth + 1)  );
        }
    }
    return $str;
}
echo sortedFolders( 0, $folders );

我认为您遇到了麻烦,因为您真正想要的数据结构是一棵树,但您拥有的数据是扁平的。我建议做的是运行递归算法,首先浏览查询此表的结果,并构建一个表示文件夹层次结构的树。

function buildFolderTree(array $elements) {
    // Give a root element
    array_unshift($elements, ['id' => 0, 'folder_name' => 'root']);
    // Recursive closure
    $buildTree = function(array &$elements, $parentId = 0) use (&$buildTree) {
        // Build all the nodes in this branch of the tree
        $branch = [];
        foreach ($elements as $k => $element) {
            if (array_key_exists('parent_id', $element) && 
                $element['parent_id'] === $parentId) {
                $children = $buildTree($elements, $element['id']);
                if ($children) {
                    $element['children'] = $children;
                }
                // No need for this, it's in our tree
                unset($element['parent_id']);
                $branch[] = $element;
                // Don't need to keep looking for this one's parent anymore
                unset($elements[$k]);
            }
        }
        return $branch;
    };
    return $buildTree($elements);
}

可能有十几种方法可以设置根元素,但我决定不在循环本身中添加任何特殊大小写,而是在将根元素放在数组的前面后使用递归闭包。 我不确定你会在什么上下文中执行此操作,这种方法似乎最容易在其他地方重用。

对于您的示例,运行 buildFolderTree($folders) 会产生以下关联数组(为了方便阅读,此处json_encoded):

[
    {
        "id": 1,
        "folder_name": "Folder 1",
        "children": [
            {
                "id": 3,
                "folder_name": "Sub 1",
                "children": [
                    {
                        "id": 6,
                        "folder_name": "SubSub 1"
                    }
                ]
            },
            {
                "id": 4,
                "folder_name": "Sub 2"
            }
        ]
    },
    {
        "id": 2,
        "folder_name": "Folder 2",
        "children": [
            {
                "id": 5,
                "folder_name": "Sub 3"
            }
        ]
    }
]

对我来说,我就停在这里 - 吐出一些JSON,并使用它在像React这样的客户端模板中渲染。像上面尝试的那样在循环中构建字符串肯定会在您需要在其他地方引用此文件夹树或修改显示这些选项的方式时导致头痛。

看看这个线程,了解有关构建递归迭代器以运行此类事情的信息:递归迭代器迭代器如何在 PHP 中工作?

或者(工作量要少得多,所以如果这只是在几个地方使用,请这样做)您可以再次使用递归闭包来呈现这些数据:

$renderTreeLevel = function(array $nodes, $depthMarker = '-') use (&$renderTreeLevel) {
    foreach ($nodes as $node) {
        echo '<option value="' . $node['id'] . '">' . $depthMarker . ' ' . $node['folder_name'] . '</option>';
        if (array_key_exists('children', $node)) {
            $renderTreeLevel($node['children'], $depthMarker . '-');
        }
    }
};
?>
<p>some other html...</p>
<select>
<?php $renderTreeLevel($builtTree) ?>
</select>

并输出

<select>
<option value="1">- Folder 1</option><option value="3">-- Sub 1</option><option value="6">--- SubSub 1</option><option value="4">-- Sub 2</option><option value="2">- Folder 2</option><option value="5">-- Sub 3</option></select>

这很好,因为您将如何获取和构建这样一棵树的细节与更直接地通过树并渲染它分开。在 MVC/MVP/MVVM 中,您通常会在模型中看到前者,在视图中看到后者。我认为视图会更简单 + 更干净一些,有自己的迭代器,但就像我之前说的,如果这是我计划大量使用的东西,我只会打扰。