Как построить неограниченный уровень меню через PHP и MySQL - PullRequest
8 голосов
/ 20 мая 2010

Ну, чтобы построить свое меню, я использую похожую структуру БД, как это

  2  Services                  0
  3  Photo Gallery             0
  4  Home                      0
  5  Feedback                  0
  6  FAQs                      0
  7  News & Events             0
  8  Testimonials              0
 81  FACN                      0
 83  Organisation Structure   81
 84  Constitution             81
 85  Council                  81
 86  IFAWPCA                  81
 87  Services                 81
 88  Publications             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 ]

19 голосов
/ 01 августа 2010

Вот "дружественная для разработчиков" версия решения " one query , no recursion " для этой проблемы.

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

Вот почему мы сохранили родительский идентификатор в стеке!

Мое предложение: подумайте над фрагментом кода выше и поймите его.

Вопросы приветствуются!

Одним из преимуществ этого подхода является то, что он исключает риск попадания в бесконечный цикл, который может произойти при использовании рекурсии.

16 голосов
/ 30 июля 2010

При такой структуре базы данных, как ваша, можно создать целое HTML-меню с помощью одного запроса и без рекурсии .

Да - я повторю:

  • ОДИН ЗАПРОС
  • НЕТ РЕКУРСА

Этот подход я всегда использую сам.

Вставил сюда код - полностью функциональный:

http://pastebin.com/GAFvSew4

Перейти к строке 67, чтобы увидеть интересную часть ("get_menu_html").

Основной цикл начинается со строки 85.

Существует пять «настраиваемых» фрагментов HTML:

  1. открытие оболочки меню (строка 83)
  2. закрытие оболочки меню (строка 122)
  3. пункт меню с открытием дочерних элементов (строка 100)
  4. пункт меню с закрытием дочерних элементов (строка 92)
  5. пункт меню без дочерних элементов (строка 113)

(Код мог бы быть чище, если бы я не беспокоился о табуляции .)

SQL для создания и заполнения образца базы данных доступен в конце скрипта.

Вы можете попробовать сообщить нам свои мысли.

11 голосов
/ 20 мая 2010

Для этого вам нужно использовать рекурсивные функции. Технически, есть несколько способов сделать это, но рекурсия действительно лучший вариант здесь .

Вот основная суть того, как это будет работать:

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 являются лишь примерами, и я оставлю это на ваше усмотрение, чтобы реализовать их так, как вам нужно, но я думаю, что это донесет сообщение.

4 голосов
/ 20 мая 2010

Я бы посоветовал вам посмотреть предварительно упорядоченный обход дерева. По этому вопросу есть статья:

Управление иерархическими данными в MySQL

По сути, вы воспринимаете каждую страницу как «узел». Каждый узел имеет ссылку на своего родителя. Когда вы меняете расположение узлов (добавляете дочерний элемент, перемещаете узлы и т. Д.), Вы пересчитываете значения «влево» и «вправо» для каждого узла (статья выше объясняет это очень подробно со ссылками на исходный код в php ). В итоге вы получаете возможность очень быстро определить, является ли данный узел прямым или косвенным дочерним узлом какого-либо другого узла, а также получить все дочерние узлы данного узла.

2 голосов
/ 20 мая 2010

альтернативный текст 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);
1 голос
/ 20 июня 2013

http://pastebin.com/ariBn3pE

Вам нужно использовать рекурсию, но мой подход другой: я создал класс для обработки каждого меню по отдельности, затем запросил результаты и сгруппировал каждый элемент в свой отдельный объект в соответствии с их родителями, упорядочив их по уровням, а затем объединил все объекты в одном ... проверьте полный код для вставки

0 голосов
/ 10 июня 2016

Я нашел такой способ работы с 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>';
}

В случае, если вам нужен еще один уровень, вы можете создать другой уровень в массиве дочерних элементов, например hijos_de_hijos, и выполнить сравнение в операторе if.

Да, конечно, для сравнения, если cn_id_menu_padre пусто, значение в базе данных должно быть null.

0 голосов
/ 20 мая 2010

Я бы использовал рекурсивную функцию.

я знаю, что это не совсем то же самое, что ваш код, но я думаю, что вы можете получить общую концепцию, если понимаете рекурсию. если вы не понимаете рекурсию, проверьте 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>';
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...