PHP - 从平面数组制作嵌套树状菜单结构

PHP - Making a nested tree menu structure from a flat array

我正在根据从 WP 数据库获得的响应制作一个嵌套菜单数组。在 corcel package 的帮助下,我从 Laravel 中控制器中的 WP 获取数据,然后制作一个包含菜单数据的数组,该数组现在是一层深。因此,当菜单 link 有子菜单 links 时,数组如下所示:

{
    "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 的响应只有 returns parent_id 每个 $item 而没有关于某个项目是否有一些 children 的数据,所以这是 parent 项目的元数据,例如:

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

这是 child 项的元数据:

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

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

我试图寻找解决方案 here,因为这与我的问题几乎相同,但无法实施。 在我的数组菜单项中也只有 parent_idparent_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"
    }
}

所以你需要写一个递归函数见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;
}

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

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

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

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

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

lookuptable只在菜单建好后才需要,之后就扔掉了。

下一个重要步骤是通过遍历平面数组来创建 $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 数组的地方。由于堆栈和查找,不需要递归-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 >> ].

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

为了处理子菜单出现在平面数组之前它所属的菜单条目的情况,为新条目初始化时的子菜单需要从查找 table(在 [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 table,因为不再需要它。也就是说,所有的别名都没有设置。

完成示例:

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

                )

        )

)

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

相关问答material:

  • Php: Converting a flat array into a tree like structure
  • Convert a series of parent-child relationships into a hierarchical tree?
  • Build a tree from a flat array in PHP