web-dev-qa-db-ja.com

PHPクエリをループしないカテゴリとサブカテゴリのツリー構造

任意の数のサブカテゴリを持つカテゴリのリストを作成しようとしていますが、サブカテゴリには独自のサブカテゴリを含めることもできます。

Mysql dbからすべてのカテゴリを選択しました。猫は標準のアソシエート配列リストにあり、各カテゴリにはid、name、parentidがあり、parentidが最上位の場合は0です。

私は基本的に、単一レベルの猫の配列を取得し、それを各カテゴリにサブ猫の配列を含む要素を持つことができる多次元配列構造に変換できるようにしたいと考えています。

今、私は各カテゴリのクエリをループすることでこれを簡単に達成できますが、これは理想とはほど遠いです、私はdbに余分なヒットなしでそれをしようとしています。

これには再帰関数が必要だと理解しています。誰もがこのツリースタイルの構造の正しい方向に私を向けることができますか?

乾杯

34
John

これは仕事をします:

_$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);
_

これは、最初にparent_idでカテゴリにインデックスを付けることで機能します。次に、各カテゴリに対して、_category->childs_を_childs[category->id]_に設定するだけで、ツリーが構築されます!

したがって、_$tree_がカテゴリツリーになりました。これには、parent_id = 0の項目の配列が含まれています。この配列自体には、子の配列が含まれています。

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
                                )

                        )

                )

        )

)
_

これが最終関数です。

_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];
_

最終関数の配列バージョン:

_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);
_
79
Arnaud Le Blanc

すべてのカテゴリを一度に取得できます。

次のように、データベースからフラットな結果が得られたとします:

$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'),
);

そのフラットリストを構造体、できれば関数内に変換する単純な関数を作成できます。参照渡しを使用して、カテゴリごとに1つの配列のみが存在し、1つのカテゴリに対して配列の複数のコピーが存在しないようにします。

function categoriesToTree(&$categories) {

マップを使用して、カテゴリをすばやく検索します。ここでは、「ルート」レベルのダミー配列も作成しました。

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

別のフィールド、サブカテゴリを各カテゴリ配列に追加し、マップに追加しました。

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

各カテゴリを再度ループし、親のサブカテゴリリストに自分自身を追加します。 参照はここで重要です、それ以外の場合、サブカテゴリがさらにある場合、すでに追加されているカテゴリは更新されません。

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

最後に、すべてのトップレベルカテゴリを参照するそのダミーカテゴリのサブカテゴリを返します。_

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

}

使用法:

$tree = categoriesToTree($categories);

そして、これが Codepad で動作するコードです。

18
Thai

メソッドを参照してください:

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;
}
1
user6199980

私は同じ問題を抱えて、この方法で解決しました。DBから猫の行を取得し、各ルートカテゴリについて、レベル(深さ)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");
    }
}

追伸私はOPがDBクエリなしのソリューションを探していることを知っていますが、これは再帰を含み、この質問を見つけた人がこのタイプの質問の再帰的なソリューションを探してDBクエリを気にしない人を助けるでしょう.

0
dhavald