类别和子类别的 PHP 树结构,无需循环查询

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4843945/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-25 16:03:24  来源:igfitidea点击:

PHP tree structure for categories and sub categories without looping a query

phparraysrecursiontreemultidimensional-array

提问by John

I'm trying to create a list of categories with any number of sub categories, where sub categories can also has their own sub categories.

我正在尝试创建一个包含任意数量子类别的类别列表,其中子类别也可以有自己的子类别。

I have selected all categories from the Mysql db, the cats are in a standard associate array list, each category has an id, name, parentid where the parentid is 0 if it's top level.

我从 Mysql db 中选择了所有类别,猫在标准关联数组列表中,每个类别都有一个 id、name、parentid,如果它是顶级的,则 parentid 为 0。

I basically want to be able to take the single level array of cats and turn it into a multidimensional array structure where each category can have an element which will contain an array of subcats.

我基本上希望能够将猫的单级数组转换为多维数组结构,其中每个类别都可以有一个包含子猫数组的元素。

Now, I can easily achieve this by looping a query for each category but this is far from ideal, I'm trying to do it without any extra hits on the db.

现在,我可以通过为每个类别循环查询来轻松实现这一点,但这远非理想,我正在尝试在 db 上没有任何额外点击的情况下进行。

I understand I need a recursive function for this. Can anyone point me in the right direction for this tree style structure?

我知道我需要一个递归函数。任何人都可以为这种树式结构指出正确的方向吗?

Cheers

干杯

回答by Arnaud Le Blanc

This does the job:

这可以完成以下工作:

$items = array(
        (object) array('id' => 42, 'parent_id' => 1),
        (object) array('id' => 43, 'parent_id' => 42),
        (object) array('id' => 1,  'parent_id' => 0),
);

$childs = array();

foreach($items as $item)
    $childs[$item->parent_id][] = $item;

foreach($items as $item) if (isset($childs[$item->id]))
    $item->childs = $childs[$item->id];

$tree = $childs[0];

print_r($tree);

This works by first indexing categories by parent_id. Then for each category, we just have to set category->childsto childs[category->id], and the tree is built !

这首先通过 parent_id 对类别进行索引。然后对于每个类别,我们只需要设置category->childschilds[category->id],树就构建好了!

So, now $treeis the categories tree. It contains an array of items with parent_id=0, which themselves contain an array of their childs, which themselves ...

所以,现在$tree是类别树。它包含一个 parent_id=0 的项数组,这些项本身包含一个子项数组,这些子项本身...

Output of print_r($tree):

的输出print_r($tree)

stdClass Object
(
    [id] => 1
    [parent_id] => 0
    [childs] => Array
        (
            [0] => stdClass Object
                (
                    [id] => 42
                    [parent_id] => 1
                    [childs] => Array
                        (
                            [0] => stdClass Object
                                (
                                    [id] => 43
                                    [parent_id] => 42
                                )

                        )

                )

        )

)

So here is the final function:

所以这是最终的功能:

function buildTree($items) {

    $childs = array();

    foreach($items as $item)
        $childs[$item->parent_id][] = $item;

    foreach($items as $item) if (isset($childs[$item->id]))
        $item->childs = $childs[$item->id];

    return $childs[0];
}

$tree = buildTree($items);



这是带有数组的相同版本,这有点棘手,因为我们需要使用引用(但同样有效):

$items = array(
        array('id' => 42, 'parent_id' => 1),
        array('id' => 43, 'parent_id' => 42),
        array('id' => 1,  'parent_id' => 0),
);

$childs = array();
foreach($items as &$item) $childs[$item['parent_id']][] = &$item;
unset($item);

foreach($items as &$item) if (isset($childs[$item['id']]))
        $item['childs'] = $childs[$item['id']];
unset($item);

$tree = $childs[0];

So the array version of the final function:

所以最终函数的数组版本:

function buildTree($items) {

    $childs = array();

    foreach($items as &$item) $childs[$item['parent_id']][] = &$item;
    unset($item);

    foreach($items as &$item) if (isset($childs[$item['id']]))
            $item['childs'] = $childs[$item['id']];

    return $childs[0];
}

$tree = buildTree($items);

回答by Thai

You can fetch all categories at once.

您可以一次获取所有类别。

Suppose you have a flat result from the database, like this:

假设您从数据库中得到一个平坦的结果,如下所示:

$categories = array(
    array('id' => 1,  'parent' => 0, 'name' => 'Category A'),
    array('id' => 2,  'parent' => 0, 'name' => 'Category B'),
    array('id' => 3,  'parent' => 0, 'name' => 'Category C'),
    array('id' => 4,  'parent' => 0, 'name' => 'Category D'),
    array('id' => 5,  'parent' => 0, 'name' => 'Category E'),
    array('id' => 6,  'parent' => 2, 'name' => 'Subcategory F'),
    array('id' => 7,  'parent' => 2, 'name' => 'Subcategory G'),
    array('id' => 8,  'parent' => 3, 'name' => 'Subcategory H'),
    array('id' => 9,  'parent' => 4, 'name' => 'Subcategory I'),
    array('id' => 10, 'parent' => 9, 'name' => 'Subcategory J'),
);

You can create a simple function that turns that flat list into a structure, preferably inside a function. I use pass-by-reference so that there are only one array per category and not multiple copies of the array for one category.

您可以创建一个简单的函数,将平面列表转换为结构,最好是在函数内部。我使用传递引用,以便每个类别只有一个数组,而不是一个类别的数组的多个副本。

function categoriesToTree(&$categories) {

A map is used to lookup categories quickly. Here, I also created a dummy array for the "root" level.

地图用于快速查找类别。在这里,我还为“根”级别创建了一个虚拟数组。

    $map = array(
        0 => array('subcategories' => array())
    );

I added another field, subcategories, to each category array, and add it to the map.

我向每个类别数组添加了另一个字段,即子类别,并将其添加到地图中。

    foreach ($categories as &$category) {
        $category['subcategories'] = array();
        $map[$category['id']] = &$category;
    }

Looping through each categories again, adding itself to its parent's subcategory list. The reference is important here,otherwise the categories already added will not be updated when there are more subcategories.

再次循环遍历每个类别,将自己添加到其父类别的子类别列表中。这里引用很重要,否则已经添加的类别在子类别较多时不会更新。

    foreach ($categories as &$category) {
        $map[$category['parent']]['subcategories'][] = &$category;
    }

Finally, return the subcategories of that dummy category which refer to all top level categories._

最后,返回引用所有顶级类别的虚拟类别的子类别。_

    return $map[0]['subcategories'];

}

Usage:

用法:

$tree = categoriesToTree($categories);

And here is the code in action on Codepad.

这是在Codepad上运行的代码。

回答by user6199980

See the method :

看方法:

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['id']] = $element;
        }
    }
    return $branch;
}

回答by dhavald

I had the same problem and solved it this way: fetch cat rows from DB and for each root categories, build tree, starting with level (depth) 0. May not be the most efficient solution, but works for me.

我遇到了同样的问题并以这种方式解决了它:从数据库中获取 cat 行并为每个根类别构建树,从级别(深度)0 开始。可能不是最有效的解决方案,但对我有用。

$globalTree = array();
$fp = fopen("/tmp/taxonomy.csv", "w");

// I get categories from command line, but if you want all, you can fetch from table
$categories = $db->fetchCol("SELECT id FROM categories WHERE parentid = '0'");

foreach ($categories as $category) {
    buildTree($category, 0);
    printTree($category);
    $globalTree = array();
}

fclose($file);

function buildTree($categoryId, $level)
{
    global $db, $globalTree;
    $rootNode = $db->fetchRow("SELECT id, name FROM categories WHERE id=?", $categoryId);
    $childNodes = $db->fetchAll("SELECT * FROM categories WHERE parentid = ? AND id <> ? ORDER BY id", array($rootNode['id'], $rootNode['id']));
    if(count($childNodes) < 1) {
        return 0;
    } else {
        $childLvl = $level + 1;
        foreach ($childNodes as $childNode) {
            $id = $childNode['id'];
            $childLevel = isset($globalTree[$id])? max($globalTree[$id]['depth'], $level): $level;
            $globalTree[$id] = array_merge($childNode, array('depth' => $childLevel));
            buildTree($id, $childLvl);
        }
    }
}

function printTree($categoryId) {
    global $globalTree, $fp, $db;
    $rootNode = $db->fetchRow("SELECT id, name FROM categories WHERE id=?", $categoryId);
    fwrite($fp, $rootNode['id'] . " : " . $rootNode['name'] . "\n");
    foreach ($globalTree as $node) {
        for ($i=0; $i <= $node['depth']; $i++) {
            fwrite($fp, ",");
        }
        fwrite($fp, $node['id'] " : " . $node['name'] . "\n");
    }
}

ps. I am aware that OP is looking for a solution without DB queries, but this one involves recursion and will help anybody who stumbled across this question searching for recursive solution for this type of question and does not mind DB queries.

附:我知道 OP 正在寻找一种没有 DB 查询的解决方案,但这个解决方案涉及递归,并将帮助任何偶然发现此问题的人为此类问题寻找递归解决方案,并且不介意 DB 查询。