PHP-создание вложенной структуры меню дерева из плоского массива
я делаю вложенный массив меню из ответа, который я получаю из базы данных WP. Я получаю данные от WP в контроллере в Laravel с помощью пакета corcel, а затем создание массива с данными меню, который теперь находится на одном уровне. Итак, когда ссылка меню имеет ссылки подменю, массив выглядит следующим образом:
{
"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 возвращает parent_id
для каждого $item
и нет данных о том, есть ли у элемента дети, так что это метаданные родительского элемента, например:
#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_id
что это 0
является корневым элементом. Также parent_id
если menu item
это post
на meta id
, а не идентификатор post
это мне нужно, поэтому мне нужно получить это дополнительно от meta->_menu_item_object_id
.
обновление
мне удалось создать дерево, подобное структуре, но проблема в том, что я не знаю, как получить title
для меню элементы posts
. Я сделал это в предыдущем примере, проверив, если title
пусто, тогда я бы искал это post
by id
:
if ($item->title == '') {
$item = $this->findPost($item);
}
но с новым кодом, где я делаю древовидную структуру, я не уверен, как это сделать, с тех пор я не могу сделать древовидную структуру, так как я сравниваю все с id
и ids
элемента меню отличается от id
на post
, что указывает на, тогда Я не могу сделать древовидную структуру:
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"
}
}
2 ответов
один из способов решить эту проблему, чтобы использовать псевдонимы переменных. Если вы позаботитесь об управлении таблицей поиска (массивом) для идентификаторов, вы можете использовать ее для вставки в нужное место иерархического массива меню, поскольку разные переменные (здесь записи массива в таблице поиска) могут ссылаться на одно и то же значение.
в следующем примере это показала. Он также решает вторую проблему (неявную в вашем вопросе), что плоский массив не сортируется (порядок не определен в таблица результатов базы данных), поэтому запись подменю может быть в resultset до пункт меню пункт подменю принадлежит.
для примера я создал простой плоский массив:
# 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']
еще не установлен (например, в первом цикле), он неявно установлен в null
из-за ссылки (&
перед &$byId[$id]['submenus']
). В случае установите, существующее подменю из еще не существующей записи будет использоваться для инициализации подменю записи.
делать это достаточно, чтобы не зависеть от какого-либо конкретного порядка в $rows
.
это то, что делает цикл.
остальное-работа по очистке:
# unset ID aliases
unset($byId);
он сбрасывает таблицу look 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] =>
)
)
)
)
я надеюсь, что это понятно, и вы сможете применить это к вашему конкретному сценарию. Вы можете обернуть это в функцию своего собственного (что я бы предложил), я только сохранил его многословным для примера, чтобы лучше продемонстрировать части.
связанный материал Q&A:
поэтому вам нужно будет написать рекурсивную функцию see что такое рекурсивная функция в PHP?
что-то вроде
function menuBuilder($menuItems){
foreach($menuItems as $key => $item)
{
if(!empty($item->children)){
$output[$key] = menuBuilder($item->children);
}
}
return $output;
}