【问题标题】:PHP - Making a nested tree menu structure from a flat arrayPHP - 从平面数组制作嵌套的树形菜单结构
【发布时间】:2017-06-27 11:58:28
【问题描述】:

我正在根据从 WP 数据库获得的响应创建一个嵌套菜单数组。在corcel package 的帮助下,我在 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"
          ]

我怎样才能使它灵活并启用更深的嵌套,以便我可以在子菜单中拥有子菜单?

我试图寻找解决方案here,因为这与我的问题几乎相同,但无法实施。 在我的数组菜单项中也只有parent_id,而parent_id0 被视为根元素。还有parent_id 如果menu itempost,则指向meta id,而不是我需要的post 的id,所以我需要从meta->_menu_item_object_id 获得额外的。

更新

我已经设法制作了一个树状结构,但我现在遇到的问题是我不知道如何为posts 的菜单元素获取title。在前面的示例中,我通过检查title 是否为空来做到这一点,然后我将通过id 搜索post

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

但是,使用新代码,我正在制作树状结构的地方,我不知道该怎么做,从那以后我无法制作树状结构,因为我正在将所有内容与 id 进行比较,并且菜单元素的ids 与指向的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 arrays recursion tree submenu


    【解决方案1】:

    解决此问题的一种方法是使用变量别名。如果您注意管理 ID 的查找表(数组),您可以使用它来插入分层菜单数组的正确位置,因为不同的变量(这里是查找表中的数组条目)可以引用相同的值.

    在以下示例中对此进行了演示。它还解决了第二个问题(隐含在您的问题中),即平面数组未排序(在数据库结果表中未定义顺序),因此子菜单条目可以在结果集中 before 菜单子菜单条目所属的条目。

    例如,我创建了一个简单的平面数组:

    # 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 数组的位置。由于堆栈和查找表$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'](例如在第一个循环中),则由于引用(&$byId[$id]['submenus'] 前面的&)而将其隐式设置为null。如果已设置,则将使用尚未存在的条目中的现有子菜单来初始化该条目的子菜单。

    这样做足以不依赖于$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] => 
                            )
    
                    )
    
            )
    
    )
    

    我希望这是可以理解的,并且您能够将其应用到您的具体场景中。您可以将它包装在它自己的函数中(我建议这样做),我只是为了更好地演示这些部分而保持它的详细程度。

    相关问答材料:

    【讨论】:

    • 如果您想阅读更多关于 PHP 中的参考资料,手册中有一个部分供您参考 php.net/manual/en/language.references.php
    • 非常感谢您的详细解答!
    • 感谢您的赏金!如果有用,请考虑对答案进行投票。
    【解决方案2】:

    所以你需要编写一个递归函数,参见What is a RECURSIVE Function in PHP?

    类似

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

    【讨论】:

    • 我不知道该怎么做,因为我无法从 WP 得到的响应中检查或查看,如果 menuItem 有子项,我只能查看该项是否有父项,然后我不确定如何将它放在数组中的正确位置。
    猜你喜欢
    • 2013-01-22
    • 1970-01-01
    • 1970-01-01
    • 2014-10-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多