recursiva - proyecto arboles php
PHP-Hacer una estructura de menú de árbol anidado desde una matriz plana (2)
Estoy haciendo una matriz de menú anidado de la respuesta que recibo de la base de datos de WP. Estoy obteniendo los datos de WP en el controlador en Laravel con la ayuda del paquete corcel , y luego haciendo una matriz con datos de menú, que ahora tiene un nivel de profundidad. Entonces, cuando un enlace de menú tiene un submenú de enlaces, la matriz se ve así:
{
"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"
}
}
Así es como estoy creando esta respuesta:
$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;
El problema que tengo es que la respuesta de WP solo devuelve parent_id
para cada $item
y no hay datos sobre si un elemento tiene algunos hijos, por lo que estos son los metadatos del elemento principal, por ejemplo:
#attributes: array:4 [
"meta_id" => 209
"post_id" => 112
"meta_key" => "_menu_item_menu_item_parent"
"meta_value" => "0"
]
Y estos son los metadatos del elemento hijo:
#attributes: array:4 [
"meta_id" => 326
"post_id" => 135
"meta_key" => "_menu_item_menu_item_parent"
"meta_value" => "112"
]
¿Cómo puedo hacer esto flexible y permitir un anidamiento más profundo, de modo que pueda tener submenús dentro de los submenús?
Intenté buscar la solución aquí , porque ese es más o menos el mismo problema que el mío, pero no pude implementarlo. En mi menú de elementos, los elementos también tienen solo parent_id
, y parent_id
que es 0
se considera como un elemento raíz. También el parent_id
si el menu item
del menu item
es una post
, apunta al meta id
, y no al id de la post
que necesito, así que necesito obtener esa información adicional de meta->_menu_item_object_id
.
ACTUALIZAR
He logrado hacer una estructura similar a un árbol, pero el problema que tengo ahora es que no sé cómo obtener el title
para los elementos del menú que son posts
. Lo hice en el ejemplo anterior comprobando si el title
está vacío, entonces buscaría esa post
por id
:
if ($item->title == '''') {
$item = $this->findPost($item);
}
Pero, con el nuevo código, donde estoy haciendo una estructura de árbol no estoy seguro de cómo hacerlo, ya que no puedo hacer la estructura de árbol, ya que estoy comparando todo con el id
, y los ids
del elemento de menú es diferente de la id
de la post
que apunta, por lo que no puedo hacer la estructura de árbol:
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;
}
Entonces, entonces la información que obtengo es:
{
"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"
}
}
Entonces, necesitaría escribir una función recursiva. ¿Qué es una función RECURSIVA en PHP?
Así que algo así como
function menuBuilder($menuItems){
foreach($menuItems as $key => $item)
{
if(!empty($item->children)){
$output[$key] = menuBuilder($item->children);
}
}
return $output;
}
Una forma de resolver esto es utilizar alias variables. Si se ocupa de administrar una tabla de búsqueda (matriz) para los ID, puede usarla para insertarla en el lugar correcto de la matriz de menú jerárquica, ya que las diferentes variables (aquí las entradas de matriz en la tabla de búsqueda) pueden hacer referencia al mismo valor .
En el siguiente ejemplo, esto se demuestra. También resuelve el segundo problema (implícito en su pregunta) de que la matriz plana no está ordenada (el orden no está definido en una tabla de resultados de la base de datos), por lo tanto, una entrada de submenú puede estar en el conjunto de resultados antes de la entrada de menú.
Por ejemplo, creé una matriz plana simple:
# 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''],
];
Luego, para el trabajo que hay que hacer hay dos variables principales: Primero, el $menu
que es la matriz jerárquica para crear y el segundo $byId
que es la tabla de búsqueda:
# initialize the menu structure
$menu = []; # the menu structure
$byId = []; # menu ID-table (temporary)
La tabla de búsqueda solo es necesaria, siempre y cuando se construya el menú, luego se desechará.
El siguiente gran paso es crear el $menu
atravesando la matriz plana. Este es un ciclo foreach más grande:
# 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);
}
Aquí es donde se mapean las entradas de la matriz plana ( $rows
) en la matriz jerárquica $menu
. No se requiere recursividad gracias a stack y lookup-table $byId
.
El punto clave aquí es usar alias variables (referencias) al agregar nuevas entradas a la estructura $menu
así como al agregarlas a $byId
. Esto permite acceder al mismo valor en la memoria con dos nombres de variable diferentes:
# special case that an entry has no parent
$menu[] = &$entry;
...
# register the entry as well in the menu ID-table
$byId[$id] = &$entry;
Se hace con la asignación = &
y significa que $byId[$id]
da acceso a $menu[<< new key >>]
.
Lo mismo se hace en caso de que se agregue a un submenú:
# 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;
Aquí $byId[$id]
apunta a $menu...[ << parent id entry in the array >>][''submenus''][ << new key >> ]
.
Esto resuelve el problema de encontrar siempre el lugar correcto donde insertar una nueva entrada en la estructura jerárquica.
Para tratar los casos en que un submenú viene en la matriz plana antes de la entrada del menú al que pertenece, el submenú cuando se inicializa para nuevas entradas debe eliminarse de la tabla de búsqueda (en [1]):
# init submenus for the entry
$entry[''submenus''] = &$byId[$id][''submenus'']; # [1]
Este es un poco un caso especial. En caso de que $byId[$id][''submenus'']
aún no esté configurado (por ejemplo, en el primer ciclo), se establece implícitamente en null
causa de la referencia (el &
delante de &$byId[$id][''submenus'']
). En caso de que esté configurado, el submenú existente de una entrada que aún no existe se utilizará para inicializar el submenú de la entrada.
Hacerlo es suficiente para no depender de ningún orden específico en $rows
.
Esto es lo que hace el ciclo.
El resto es trabajo de limpieza:
# unset ID aliases
unset($byId);
Desinstala la tabla de ID de aspecto ya que ya no se necesita. Es decir, todos los alias están desarmados.
Para completar el ejemplo:
# visualize the menu structure
print_r($menu);
Que luego da el siguiente resultado:
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] =>
)
)
)
)
Espero que esto sea comprensible y que pueda aplicar esto en su escenario concreto. Puede envolver esto en una función propia (que sugeriría), solo lo guardé detallado para el ejemplo para demostrar mejor las partes.
Material de preguntas y respuestas relacionadas:
- Php: Convertir una matriz plana en una estructura similar a un árbol
- Convierta una serie de relaciones padre-hijo en un árbol jerárquico?
- Construye un árbol desde una matriz plana en PHP