Как построить неограниченный уровень меню через PHP и MySQL
Ну, чтобы построить мое меню, я использую похожую структуру БД, как это
2 Услуги 0 3 Фотогалерея 0 4 Дом 0 5 Отзыв 0 6 часто задаваемых вопросов 0 7 Новости и события 0 8 Отзывы 0 81 FACN 0 83 Организационная структура 81 84 Конституции 81 85 Совет 81 86 IFAWPCA 81 87 Услуги 81 88 публикаций 81
Чтобы назначить другое подменю для существующего подменю, я просто назначаю идентификатор его родителя в качестве значения родительского поля. родитель 0 означает верхнее меню
Теперь нет проблем при создании подменю внутри другого подменю.
теперь это способ получить подменю для верхнего меню
<ul class="topmenu">
<? $list = $obj -> childmenu($parentid);
//this list contains the array of submenu under $parendid
foreach($list as $menu) {
extract($menu);
echo '<li><a href="#">'.$name.'</a></li>';
}
?>
</ul>
Что я хочу сделать, так это
Я хочу проверить, есть ли в новом меню другое дочернее меню
и я хочу продолжать проверять, пока он не ищет все доступные дочерние меню
и я хочу отобразить его дочернее меню внутри своего конкретного элемента списка, как это
<ul>
<li><a href="#">Home</a>
<ul class="submenu">
........ <!-- Its sub menu -->
</ul>
</li>
</ul>
8 ответов
Для этого вам нужно использовать рекурсивные функции. Технически, есть несколько способов сделать это, но рекурсия действительно лучший вариант здесь.
Вот основная суть того, как это будет работать:
function drawMenu ($listOfItems) {
echo "<ul>";
foreach ($listOfItems as $item) {
echo "<li>" . $item->name;
if ($item->hasChildren()) {
drawMenu($item->getChildren()); // here is the recursion
}
echo "</li>";
}
echo "</ul>";
}
Свойства и методы $item
Это всего лишь примеры, и я оставлю это на ваше усмотрение, чтобы реализовать их, как вам нужно, но я думаю, что это донесет смысл.
Вот "дружественная для разработчиков" версия решения "один запрос, без рекурсии" для этой проблемы.
SQL:
SELECT id, parent_id, title, link, position FROM menu_item ORDER BY parent_id, position;
PHP:
$html = '';
$parent = 0;
$parent_stack = array();
// $items contains the results of the SQL query
$children = array();
foreach ( $items as $item )
$children[$item['parent_id']][] = $item;
while ( ( $option = each( $children[$parent] ) ) || ( $parent > 0 ) )
{
if ( !empty( $option ) )
{
// 1) The item contains children:
// store current parent in the stack, and update current parent
if ( !empty( $children[$option['value']['id']] ) )
{
$html .= '<li>' . $option['value']['title'] . '</li>';
$html .= '<ul>';
array_push( $parent_stack, $parent );
$parent = $option['value']['id'];
}
// 2) The item does not contain children
else
$html .= '<li>' . $option['value']['title'] . '</li>';
}
// 3) Current parent has no more children:
// jump back to the previous menu level
else
{
$html .= '</ul>';
$parent = array_pop( $parent_stack );
}
}
// At this point, the HTML is already built
echo $html;
Вам просто нужно понять использование переменной $parent_stack.
Это стек "LIFO" (Last In, First Out) - изображение в статье в Википедии стоит тысячи слов: http://en.wikipedia.org/wiki/LIFO_%28computing%29
Когда пункт меню имеет подопции, мы сохраняем его родительский идентификатор в стеке:
array_push( $parent_stack, $parent );
И затем мы немедленно обновляем $parent, делая его текущим идентификатором опции меню:
$parent = $option['value']['id'];
После того, как мы зациклили все его подопции, мы можем вернуться к предыдущему уровню:
$parent = array_pop( $parent_stack );
Вот почему мы сохранили родительский идентификатор в стеке!
Мое предложение: подумайте над фрагментом кода выше и поймите это.
Вопросы приветствуются!
Одним из преимуществ этого подхода является то, что он исключает риск попадания в бесконечный цикл, который может возникнуть при использовании рекурсии.
С такой структурой базы данных, как ваша, можно создать все меню HTML с помощью одного запроса и без рекурсии.
Да - повторю
- ОДИН ЗАПРОС
- НЕТ РЕКУРСИИ
Этот подход я всегда использую сам.
Вставил код сюда - полностью функциональный:
Перейдите к строке 67, чтобы увидеть интересную часть ("get_menu_html").
Основной цикл начинается в строке 85.
Существует пять "настраиваемых" фрагментов HTML:
- открытие оболочки меню (строка 83)
- закрытие оболочки меню (строка 122)
- пункт меню с открытием ребенка (строка 100)
- пункт меню с закрытием дочерних элементов (строка 92)
- пункт меню без детей (строка 113)
(Код мог бы быть чище, если бы я не беспокоился о табуляции.)
SQL для создания и заполнения образца базы данных доступен в конце скрипта.
Вы можете попробовать сообщить нам свои мысли.
Я бы посоветовал вам взглянуть на предварительно упорядоченный обход деревьев. По этому вопросу есть статья:
Управление иерархическими данными в MySQL
По сути, вы воспринимаете каждую страницу как "узел". Каждый узел имеет ссылку на своего родителя. Когда вы меняете расположение узлов (добавляете дочерний элемент, перемещаете узлы и т. Д.), Вы пересчитываете значение "влево" и "вправо" для каждого узла (статья выше объясняет это очень подробно со ссылками на исходный код в php).). В результате вы получаете возможность очень быстро определить, является ли данный узел прямым или косвенным дочерним узлом какого-либо другого узла, а также получить все дочерние узлы данного узла.
http://i.imagehost.org/0934/product_hier.jpg http://pastie.org/969286
drop table if exists product;
create table product
(
prod_id smallint unsigned not null auto_increment primary key,
name varchar(255) not null,
parent_id smallint unsigned null,
key (parent_id)
)engine = innodb;
insert into product (name, parent_id) values
('Products',null),
('Systems & Bundles',1),
('Components',1),
('Processors',3),
('Motherboards',3),
('AMD',5),
('Intel',5),
('Intel LGA1366',7);
delimiter ;
drop procedure if exists product_hier;
delimiter #
create procedure product_hier
(
in p_prod_id smallint unsigned
)
begin
declare v_done tinyint unsigned default 0;
declare v_depth smallint unsigned default 0;
create temporary table hier(
parent_id smallint unsigned,
prod_id smallint unsigned,
depth smallint unsigned default 0
)engine = memory;
insert into hier select parent_id, prod_id, v_depth from product where prod_id = p_prod_id;
/* http://dev.mysql.com/doc/refman/5.0/en/temporary-table-problems.html */
create temporary table tmp engine=memory select * from hier;
while not v_done do
if exists( select 1 from product p inner join hier on p.parent_id = hier.prod_id and hier.depth = v_depth) then
insert into hier
select p.parent_id, p.prod_id, v_depth + 1 from product p
inner join tmp on p.parent_id = tmp.prod_id and tmp.depth = v_depth;
set v_depth = v_depth + 1;
truncate table tmp;
insert into tmp select * from hier where depth = v_depth;
else
set v_done = 1;
end if;
end while;
select
p.prod_id,
p.name as prod_name,
b.prod_id as parent_prod_id,
b.name as parent_prod_name,
hier.depth
from
hier
inner join product p on hier.prod_id = p.prod_id
inner join product b on hier.parent_id = b.prod_id
order by
hier.depth, hier.prod_id;
drop temporary table if exists hier;
drop temporary table if exists tmp;
end #
delimiter ;
call product_hier(3);
call product_hier(5);
Вам нужно использовать рекурсию, но мой подход другой: я создал класс для индивидуальной обработки каждого меню, затем запросил результаты и сгруппировал каждый элемент в отдельный объект в соответствии с их родителями, упорядочив их по уровням, а затем объединил все объекты. в один... проверить пастин для полного кода
Я бы использовал рекурсивную функцию.
я знаю, что это не совсем похоже на ваш код, но я думаю, что вы можете получить общую концепцию, если понимаете рекурсию. если вы не понимаете рекурсию, проверьте http://en.wikipedia.org/wiki/Recursion_(computer_science)
$list = new List();
function print_menu($list) {
echo '<ul>';
foreach($list as $item) {
echo '<li><a href="#">' . $item->name . '</a>';
if($item->has_child) {
print_menu($item);
}
echo '</li>';
}
echo '</ul>';
}
Я нашел этот способ, работая с Yii Framework.
$children = array();
foreach($model as $k => $item){
if(empty($item->cn_id_menu_padre))
$children[$item->cn_id] = $item->attributes;
else
$children[$item->cn_id_menu_padre]['hijos'][] = $item->attributes;
}
foreach($children as $k=>$child){
if(array_key_exists('hijos',$child))
{
echo 'li y dentro ul<br>';
foreach($child['hijos'] as $hijo){
echo 'li<br>';
}
}
else
echo 'li<br>';
}
В случае, если вам нужен еще один уровень, вы можете сделать еще один уровень в массиве children, например hijos_de_hijos
и сделайте сравнение тогда в операторе if.
О, конечно, чтобы сравнить, если cn_id_menu_padre
пусто, значение в базе данных должно быть null
,