PHP - 从平面数组制作嵌套的树形菜单结构 [英] PHP - Making a nested tree menu structure from a flat array

查看:33
本文介绍了PHP - 从平面数组制作嵌套的树形菜单结构的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在根据从 WP 数据库获得的响应制作一个嵌套的菜单数组.我在 corcel 包的帮助下从 Laravel 控制器中的 WP 获取数据,然后制作一个带有菜单数据的数组,现在是一层深.所以,当一个菜单链接有一个子菜单链接时,数组看起来像这样:

I am making a nested menu array from the response that I get from WP database. I am getting the data from WP in the controller in Laravel with the help of corcel package, and then making an array with menu data, which is now one level deep. So, when a menu link has a submenu links, the array looks like this:

{
    "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"
    }
}

这就是我创建此回复的方式:

This is how I am creating this response:

        $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 并且没有关于一个项目是否有一些孩子的数据,所以这是元父项的数据例如:

The problem I have is that the response from WP only returns parent_id for each $item and no data about if an item has some children, so this is the meta data of the parent item for example:

         #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"
          ]

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

How can I make this flexible and enable deeper nesting, so that I can have submenus inside submenus?

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

I have tried to look for the solution here, because that is pretty much the same problem as mine, but wasn't able to implement it. In my array menu items also have only parent_id, and the parent_id that is 0 is considered as a root element. Also the parent_id if the menu item is a post, points to the meta id, and not the id of the post that I need, so I need to get that additionaly from meta->_menu_item_object_id.

更新

我已经设法制作了一个树状结构,但我现在遇到的问题是我不知道如何为 poststitle代码>.在前面的示例中,我通过检查 title 是否为空来做到这一点,然后我将通过 id 搜索该 post:

I have managed to make a tree like structure, but the problem I have now is that I don't know how to get the title for the menu elements that are posts. I did that in the previous example by checking if the title is empty then I would search for that post by id:

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

但是,使用新代码,我正在制作一个树状结构,我不知道该怎么做,从那以后我无法制作树结构,因为我正在将所有内容与 id 进行比较,并且菜单元素的 ids 与指向的 postid 不同,所以我是无法制作树结构:

But, with the new code, where I am making a tree like structure I am not sure how to do that, since then I am not able to make the tree structure, since I am comparing everything with the id, and the ids of the menu element is different from the id of the post that is pointing to, so then I am not able to make the tree structure:

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

那么,我得到的数据是:

So, then the data I get is:

   {
    "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"
    }
}

推荐答案

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

One way to solve this to make use of variable aliases. If you take care to manage a lookup-table (array) for the IDs you can make use of it to insert into the right place of the hierarchical menu array as different variables (here array entries in the lookup table) can reference the same value.

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

In the following example this is demonstrated. It also solves the second problem (implicit in your question) that the flat array is not sorted (the order is undefined in a database result table), therefore a submenu entry can be in the resultset before the menu entry the submenu entry belongs to.

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

For the example I created a simple flat array:

# 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 是查找表:

Then for the work to do there are tow main variables: First the $menu which is the hierarchical array to create and second $byId which is the lookup table:

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

只要建好菜单就需要查找表,后面会扔掉.

The lookup table is only necessary as long as the menu is built, it will be thrown away afterwards.

下一步是通过遍历平面数组来创建$menu.这是一个更大的 foreach 循环:

The next big step is to create the $menu by traversing over the flat array. This is a bigger foreach loop:

# 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,不需要递归.

This is where the entries are mapped from the flat array ($rows) into the hierarchical $menu array. No recursion is required thanks to the stack and lookup-table $byId.

这里的关键是在将新条目添加到 $menu 结构以及将它们添加到 $byId 时使用变量别名(引用).这允许使用两个不同的变量名访问内存中的相同值:

The key point here is to use variable aliases (references) when adding new entries to the $menu structure as well as when adding them to $byId. This allows to access the same value in memory with two different variable names:

        # 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[<<新键 >>].

It is done with the = & assignment and it means that $byId[$id] gives access to $menu[<< new key >>].

如果将其添加到子菜单中,则执行相同的操作:

The same is done in case it is added to a submenu:

    # 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...[ <<数组中的父 id 条目 >>]['submenus'][ <<新键 >>].

这解决了始终找到将新条目插入到层次结构中的正确位置的问题.

This is solves the problem to always find the right place where to insert a new entry into the hierarchical structure.

为了处理子菜单在它所属的菜单条目之前进入平面数组的情况,需要在为新条目初始化时将子菜单从查找表中取出(在 [1]):

To deal with the cases that a submenu comes in the flat array before the menu entry it belongs to, the submenu when initialized for new entries needs to be taken out of the lookup table (at [1]):

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

这有点特殊.如果 $byId[$id]['submenus'] 尚未设置(例如在第一个循环中),由于引用的原因,它被隐式设置为 null(&$byId[$id]['submenus'] 前面的 &).如果已设置,则将使用尚未存在的条目中的现有子菜单来初始化条目的子菜单.

This is a bit of a special case. In case that $byId[$id]['submenus'] is not yet set (e.g. in the first loop), it is implicitly set to null because of the reference (the & in front of &$byId[$id]['submenus']). In case it is set, the existing submenu from a not yet existing entry will be used to initialize the submenu of the entry.

这样做足以不依赖于 $rows 中的任何特定顺序.

Doing so is enough to not depend on any specific order in $rows.

这就是循环的作用.

剩下的就是清理工作:

# unset ID aliases
unset($byId); 

它会取消设置外观 ID 表,因为不再需要它.也就是说,所有别名都未设置.

It unsets the look ID table as it is not needed any longer. That is, all aliases are unset.

完成示例:

# 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] => 
                        )

                )

        )

)

我希望这是可以理解的,并且您能够将其应用到您的具体场景中.您可以将其包装在它自己的函数中(我建议这样做),我只是在示例中保持冗长以更好地演示各个部分.

I hope this is understandable and you're able to apply this on your concrete scenario. You can wrap this in a function of it's own (which I would suggest), I only kept it verbose for the example to better demonstrate the parts.

相关问答资料:

这篇关于PHP - 从平面数组制作嵌套的树形菜单结构的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆