根据父/子关系排序输出

时间:2015-03-10 18:00:46

标签: php

我目前正在使用一种名为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 );

1 个答案:

答案 0 :(得分:0)

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

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这样的客户端模板。像你在上面尝试的那样在你的循环中构建字符串肯定只会导致你在其他地方引用这个文件夹树时的麻烦,或者修改你显示这些选项的方式。

看一下这个线程,了解有关构建递归迭代器的信息,以完成这类事情:How does RecursiveIteratorIterator work in 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中,您通常会在模型中看到前者,而在您的视图中看到后者。我认为使用自己的迭代器,视图会更简单+更干净,但就像我之前说过的那样,如果我计划使用很多东西,我只会烦恼。