簡體   English   中英

PHP-從平面數組制作嵌套樹菜單結構

[英]PHP - Making a nested tree menu structure from a flat array

我正在根據從WP數據庫獲得的響應制作一個嵌套菜單數組。 我借助於corcel包從Laravel的控制器中的WP獲取數據,然后使用菜單數據創建一個數組,該數據現在已經深了一層。 因此,當菜單鏈接具有子菜單鏈接時,該數組如下所示:

{
    "Hjem": {
        "ID": 112,
        "title": "Hjem",
        "slug": "hjem",
        "url": "http://hivnorge.app/?p=112",
        "status": "publish",
        "main_category": "Hovedmeny",
        "submenus": [
            {
                "ID": 129,
                "title": "Lorem ipsum",
                "slug": "lorem-ipsum",
                "url": "http://hivnorge.app/?p=129",
                "status": "publish",
                "main_category": "Nyheter"
            }
        ]
    },
    "Nytt test innlegg": {
        "ID": 127,
        "title": "Nytt test innlegg",
        "slug": "nytt-test-innlegg",
        "url": "http://hivnorge.app/?p=127",
        "status": "private",
        "main_category": "Nyheter",
        "submenus": [
            {
                "ID": 125,
                "title": "Test innlegg",
                "slug": "test-innlegg",
                "url": "http://hivnorge.app/?p=125",
                "status": "publish",
                "main_category": "Nyheter"
            },
            {
                "ID": 129,
                "title": "Lorem ipsum",
                "slug": "lorem-ipsum",
                "url": "http://hivnorge.app/?p=129",
                "status": "publish",
                "main_category": "Nyheter"
            }
        ]
    },
    "Prosjektsamarbeidets verdi": {
        "ID": 106,
        "title": "Prosjektsamarbeidets verdi",
        "slug": "prosjektsamarbeidets-verdi",
        "url": "http://hivnorge.no.wordpress.seven.fredrikst/?p=106",
        "status": "publish",
        "main_category": "Prevensjon"
    }
}

這就是我創建此響應的方式:

        $menu = Menu::slug('hovedmeny')->first();
        $res = [];

        foreach ($menu->nav_items as $item) {
            $item->makeHidden($hiddenAttributes)->toArray();
            $parent_id = $item->meta->_menu_item_menu_item_parent;

            if ($parent_id == '0') {
              if ($item->title == '') {
                  $item = $this->findPost($item);
              }
              $parentItem = $item;
              $res[$parentItem->title] = $parentItem->makeHidden($hiddenAttributes)->toArray();
            }
            else {
              $childItem = $this->findPost($item);
              $res[$parentItem->title]['submenus'][] = $childItem->makeHidden($hiddenAttributes)->toArray();
            }
        }

        return $res;

我的問題是,WP的響應僅為每個$item返回parent_id ,而沒有關於某個項目是否有一些子項的數據,因此這是父項的元數據,例如:

         #attributes: array:4 [
            "meta_id" => 209
            "post_id" => 112
            "meta_key" => "_menu_item_menu_item_parent"
            "meta_value" => "0"
          ]

這是子項的元數據:

          #attributes: array:4 [
            "meta_id" => 326
            "post_id" => 135
            "meta_key" => "_menu_item_menu_item_parent"
            "meta_value" => "112"
          ]

如何使它具有靈活性並實現更深層的嵌套,以便子菜單內可以有子菜單?

我試圖在這里尋找解決方案,因為這幾乎與我的問題相同,但無法實現。 在我的數組菜單中,項目還只有parent_id ,並且parent_id0被視為根元素。 另外,如果menu itempost ,則parent_id指向meta id ,而不是我需要的post的id,因此我需要從meta->_menu_item_object_id獲取該對象。

更新

我已成功地使樹狀結構,但我現在的問題是,我不知道如何獲得title的是菜單內容posts 我在上一個示例中通過檢查title是否為空來做到這一點,然后按id搜索該post

          if ($item->title == '') {
              $item = $this->findPost($item);
          }

但是,在新代碼中,我無法確定樹狀結構,因此我不確定該如何做,因為那時我無法將所有結構與idids進行比較。 menu元素與所指向的postid不同,因此我無法制作樹形結構:

    private function menuBuilder($menuItems, $parentId = 0)
    {
        $hiddenAttributes = \Config::get('middleton.wp.menuHiddenAttributes');
        $res = [];

        foreach ($menuItems as $index => $item) {
            $itemParentId = $item->meta->_menu_item_menu_item_parent;

            if ($itemParentId == $parentId) {
                $children = self::menuBuilder($menuItems, $item->ID);

                if ($children) {
                    $item['submenu'] = $children;
                }

                $res[$item->ID] = $item->makeHidden($hiddenAttributes)->toArray();
                unset($menuItems[$index]);
            }
        }

        return $res;
    }

所以,那么我得到的數據是:

   {
    "112": {
        "ID": 112,
        "submenu": {
            "135": {
                "ID": 135,
                "title": "",
                "slug": "135",
                "url": "http://hivnorge.app/?p=135",
                "status": "publish",
                "main_category": "Hovedmeny"
            }
        },
        "title": "Hjem",
        "slug": "hjem",
        "url": "http://hivnorge.app/?p=112",
        "status": "publish",
        "main_category": "Hovedmeny"
    },
    "136": {
        "ID": 136,
        "submenu": {
            "137": {
                "ID": 137,
                "submenu": {
                    "138": {
                        "ID": 138,
                        "title": "",
                        "slug": "138",
                        "url": "http://hivnorge.app/?p=138",
                        "status": "publish",
                        "main_category": "Hovedmeny"
                    }
                },
                "title": "",
                "slug": "137",
                "url": "http://hivnorge.app/?p=137",
                "status": "publish",
                "main_category": "Hovedmeny"
            }
        },
        "title": "",
        "slug": "136",
        "url": "http://hivnorge.app/?p=136",
        "status": "publish",
        "main_category": "Hovedmeny"
    },
    "139": {
        "ID": 139,
        "title": "",
        "slug": "139",
        "url": "http://hivnorge.app/?p=139",
        "status": "publish",
        "main_category": "Hovedmeny"
    }
}

因此,您需要編寫一個遞歸函數,請參閱什么是PHP中的RECURSIVE函數?

所以像

function menuBuilder($menuItems){
    foreach($menuItems as $key => $item)
    {
        if(!empty($item->children)){
            $output[$key] = menuBuilder($item->children);
        }
    }
    return $output;
}

解決此問題的一種方法是使用變量別名。 如果您要管理ID的查找表(數組),則可以將其插入到分層菜單數組的正確位置,因為不同的變量(此處為查找表中的數組條目)可以引用相同的值。

在下面的示例中對此進行了演示。 它還解決了第二個問題(隱含在您的問題中),即未對平面數組進行排序(在數據庫結果表中未定義順序),因此子菜單項可以位於子菜單項所屬的菜單項之前的結果集中。

對於該示例,我創建了一個簡單的平面數組:

# some example rows as the flat array
$rows = [
    ['id' => 3, 'parent_id' => 2, 'name' => 'Subcategory A'],
    ['id' => 1, 'parent_id' => null, 'name' => 'Home'],
    ['id' => 2, 'parent_id' => null, 'name' => 'Categories'],
    ['id' => 4, 'parent_id' => 2, 'name' => 'Subcategory B'],
];

然后要做的工作有兩個主要變量:首先是$menu ,這是要創建的分層數組,其次是$byId ,這是查找表:

# initialize the menu structure
$menu = []; # the menu structure
$byId = []; # menu ID-table (temporary) 

僅在構建菜單后才需要查找表,此后該表將被丟棄。

下一步是通過遍歷平面數組來創建$menu 這是一個更大的foreach循環:

# build the menu (hierarchy) from flat $rows traversable
foreach ($rows as $row) {
    # map row to local ID variables
    $id = $row['id'];
    $parentId = $row['parent_id'];

    # build the entry
    $entry = $row;
    # init submenus for the entry
    $entry['submenus'] = &$byId[$id]['submenus']; # [1]

    # register the entry in the menu structure
    if (null === $parentId) {
        # special case that an entry has no parent
        $menu[] = &$entry;
    } else {
        # second special case that an entry has a parent
        $byId[$parentId]['submenus'][] = &$entry;
    }

    # register the entry as well in the menu ID-table
    $byId[$id] = &$entry;

    # unset foreach (loop) entry alias
    unset($entry);
}

這是條目從平面數組( $rows )映射到分層$menu數組的地方。 由於堆棧和lookup-table $byId ,因此不需要遞歸。

這里的關鍵點是在將新條目添加到$menu結構以及將它們添加到$byId時,使用變量別名(引用)。 這允許使用兩個不同的變量名稱訪問內存中的相同值:

        # special case that an entry has no parent
        $menu[] = &$entry;
         ...

    # register the entry as well in the menu ID-table
    $byId[$id] = &$entry;

這是通過= &賦值完成的,這意味着$byId[$id]可以訪問$menu[<< new key >>]

如果將其添加到子菜單,請執行以下操作:

    # second special case that an entry has a parent
    $byId[$parentId]['submenus'][] = &$entry;
...

# register the entry as well in the menu ID-table
$byId[$id] = &$entry;

這里$byId[$id]指向$menu...[ << parent id entry in the array >>]['submenus'][ << new key >> ]

這解決了始終在向分層結構中插入新條目的正確位置的問題。

為了處理子菜單在其所屬的菜單項之前進入平面數組的情況,需要在查找表中將子菜單初始化為新項時(在[1]處):

# init submenus for the entry
$entry['submenus'] = &$byId[$id]['submenus']; # [1]

這有點特殊情況。 在情況下$byId[$id]['submenus']尚未設置(例如,在第一循環中),它是隱式設置為null ,因為的引用( &前面&$byId[$id]['submenus'] )。 如果已設置,則將使用尚不存在的條目的現有子菜單來初始化條目的子菜單。

這樣做足以不依賴$rows任何特定順序。

這就是循環的作用。

剩下的就是清理工作:

# unset ID aliases
unset($byId); 

由於不再需要外觀ID表,因此它會取消設置。 即,所有別名均未設置。

要完成示例:

# visualize the menu structure
print_r($menu);

然后給出以下輸出:

Array
(
    [0] => Array
        (
            [id] => 1
            [parent_id] => 
            [name] => Home
            [submenus] => 
        )

    [1] => Array
        (
            [id] => 2
            [parent_id] => 
            [name] => Categories
            [submenus] => Array
                (
                    [0] => Array
                        (
                            [id] => 3
                            [parent_id] => 2
                            [name] => Subcategory A
                            [submenus] => 
                        )

                    [1] => Array
                        (
                            [id] => 4
                            [parent_id] => 2
                            [name] => Subcategory B
                            [submenus] => 
                        )

                )

        )

)

我希望這是可以理解的,並且您可以將其應用於您的具體方案。 您可以將其包裝在它自己的函數中(我建議這樣做),我僅在示例中保留它的詳細信息,以更好地演示各部分。

相關問答材料:

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM