Многоуровневое меню на PHP + MySQL

Многоуровневое меню на PHP + MySQL

Многоуровневое меню на PHP + MySQL

Ни один сайт не обходится без навигации или как еще называют "меню сайта". Так вот меню сайта бывает одноуровневым и многоуровневым в виде дерева. Если с одноуровневым меню особых сложностей в плане реализации не возникает, то при создании многоуровневого меню нужно хорошо подумать.

Самое главное в этой задаче это спроектировать базу данных для нашего многоуровневого меню. Создадим таблицу Categories с тремя полями id, title, parent где:

  • ID – идентификатор
  • Title – Название меню
  • Parent – Родитель категории по умолчанию 0

За ветвление меню отвечает поле Parent если Parent = 0, то эта категория является родительской. Для того чтобы добавить потомков к родительской категории нужно в поле parent указать ID нужного родителя. Например:

Таблицы с категориями

ID TITLE PARENT
1 Автомобили 0
2 Мотоциклы 0
3 Мазда 1
4 Хонда 1
5 Кавасаки 2
6 Харлей 2
7 Лодки 0

Как видно из таблицы, у родительской категории Автомобили есть два потомка – это Мазда и Хонда связанных по полю Parent. А у категории Мотоциклы два потомка – это Кавасаки и Харлей. При этом у категории Лодки нет потомков. Надеюсь, что Вы поняли,как связать категории.

Далее переходим от слов к практике. Создадим таблицу Categories.


CREATE TABLE IF NOT EXISTS `categories` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `title` varchar(255) NOT NULL,
  `parent` int(10) unsigned NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=20 ;

--
-- Дамп данных таблицы `categories`
--
INSERT INTO `categories` (`id`, `title`, `parent`) VALUES
(1, 'Автомобили', 0),
(2, 'Мотоциклы', 0),
(3, 'Мазда', 1),
(4, 'Хонда', 1),
(5, 'Кавасаки', 2),
(6, 'Харлей', 2),
(7, 'Мазда 3', 3),
(8, 'Мазда 6', 3),
(9, 'Седан', 7),
(10, 'Хечбэк', 7),
(11, 'Лодки', 0),
(12, 'Лифтбэк', 8),
(13, 'Кроссовер', 8),
(14, 'Белый', 13),
(15, 'Красный', 13),
(16, 'Черный', 13),
(17, 'Зеленый', 13),
(18, 'Мазда CX', 3),
(19, 'Мазда MX', 3);

Алгоритм работы состоит из следующего:

  • Создаем соединение с базой данных
  • Получаем все данные из таблицы Categories
  • Обрабатываем полученные данные изменив ключ массива на номер ID
  • Из обработанного массива строим дерево зависимостей неограниченной вложенности используя метод рекурсии для построения
  • Выводим наше многоуровневое меню на экран

Создаем соединение с базой данных


<?php

//Устанавливаем кодировку и вывод всех ошибок
header('Content-Type: text/html; charset=UTF-8');
error_reporting(E_ALL);

//Объектно-ориентированный стиль
$mysqli = new mysqli('localhost', 'user', 'password', 'database');

//Устанавливаем кодировку utf8
$mysqli->query("SET NAMES 'utf8'");

/*
 * Это "официальный" объектно-ориентированный способ сделать это
 * однако $connect_error не работал вплоть до версий PHP 5.2.9 и 5.3.0.
 */
if ($mysqli->connect_error) {
    die('Ошибка подключения (' . $mysqli->connect_errno . ') '
            . $mysqli->connect_error);
}

/*
 * Если нужно быть уверенным в совместимости с версиями до 5.2.9,
 * лучше использовать такой код
 */
if (mysqli_connect_error()) {
    die('Ошибка подключения (' . mysqli_connect_errno() . ') '
            . mysqli_connect_error());
}

Пишем функцию получения данных из таблицы Categories


//Получаем массив нашего меню из БД в виде массива
function getCat($mysqli){
    $sql = 'SELECT * FROM `categories`';
    $res = $mysqli->query($sql);

    //Создаем масив где ключ массива является ID меню
    $cat = array();
    while($row = $res->fetch_assoc()){
        $cat[$row['id']] = $row;
    }
    return $cat;
}

Получаем массив вот такого вида, где ключ массива это ID категории.

Многоуровневое меню на PHP + MySQL

Далее нам нужно из этого массива построить дерево зависимости дочерних элементов от родительских проходя рекурсивно по всему массиву.

Функция построения дерева из массива от Tommy Lacroix


//Функция построения дерева из массива от Tommy Lacroix
function getTree($dataset) {
    $tree = array();

    foreach ($dataset as $id => &$node) {    
        //Если нет вложений
        if (!$node['parent']){
            $tree[$id] = &$node;
        }else{ 
            //Если есть потомки то перебераем массив
            $dataset[$node['parent']]['childs'][$id] = &$node;
        }
    }
    return $tree;
}

Получаем массив в виде дерева

Многоуровневое меню на PHP + MySQL

Скрипт целиком

>
<?php

//Устанавливаем кодировку и вывод всех ошибок
header('Content-Type: text/html; charset=UTF-8');
error_reporting(E_ALL);

//Объектно-ориентированный стиль
$mysqli = new mysqli('localhost', 'user', 'pass', 'db');

//Устанавливаем кодировку utf8
$mysqli->query("SET NAMES 'utf8'");

/*
 * Это "официальный" объектно-ориентированный способ сделать это
 * однако $connect_error не работал вплоть до версий PHP 5.2.9 и 5.3.0.
 */
if ($mysqli->connect_error) {
    die('Ошибка подключения (' . $mysqli->connect_errno . ') '
            . $mysqli->connect_error);
}

/*
 * Если нужно быть уверенным в совместимости с версиями до 5.2.9,
 * лучше использовать такой код
 */
if (mysqli_connect_error()) {
    die('Ошибка подключения (' . mysqli_connect_errno() . ') '
            . mysqli_connect_error());
}

//Получаем массив нашего меню из БД в виде массива
function getCat($mysqli){
    $sql = 'SELECT * FROM `categories`';
    $res = $mysqli->query($sql);

    //Создаем масив где ключ массива является ID меню
    $cat = array();
    while($row = $res->fetch_assoc()){
        $cat[$row['id']] = $row;
    }
    return $cat;
}

//Функция построения дерева из массива от Tommy Lacroix
function getTree($dataset) {
    $tree = array();

    foreach ($dataset as $id => &$node) {    
        //Если нет вложений
        if (!$node['parent']){
            $tree[$id] = &$node;
        }else{ 
            //Если есть потомки то перебераем массив
            $dataset[$node['parent']]['childs'][$id] = &$node;
        }
    }
    return $tree;
}

//Получаем подготовленный массив с данными
$cat  = getCat($mysqli); 

//Создаем древовидное меню
$tree = getTree($cat);

//Шаблон для вывода меню в виде дерева
function tplMenu($category){
    $menu = '<li>
        <a href="#" title="'. $category['title'] .'">'. 
        $category['title'].'</a>';
        
        if(isset($category['childs'])){
            $menu .= '<ul>'. showCat($category['childs']) .'</ul>';
        }
    $menu .= '</li>';
    
    return $menu;
}

/**
* Рекурсивно считываем наш шаблон
**/
function showCat($data){
    $string = '';
    foreach($data as $item){
        $string .= tplMenu($item);
    }
    return $string;
}

//Получаем HTML разметку
$cat_menu = showCat($tree);

//Выводим на экран
echo '<ul>'. $cat_menu .'</ul>';

?>

Результат работы

Многоуровневое меню на PHP + MySQL для админки

Если Вы хотите использовать данное меню в админке своего сайта, то нужно переписать пару функций tplMenu(), showCat().


<?php

function tplMenu($category,$str)
 {   
    if($category['parent'] == 0){
       $menu = '<option value="'.$category['id'].'">'.$category['title'].'</option>';
    }else{   
       $menu = '<option value="'.$category['id'].'">'.$str.' '.$category['title'].'</option>';
    }
    
    if(isset($category['childs'])){
        $i = 1;
        for($j = 0; $j < $i; $j++){
            $str .= '→';
        }         
        $i++;
        
        $menu .= showCat($category['childs'], $str);
    }
    return $menu;
 }

/**
* Рекурсивно считываем наш шаблон
**/
function showCat($data, $str){
    $string = '';
    $str = $str;
    foreach($data as $item){
        $string .= tplMenu($item, $str);
    }
    return $string;
}

//Получаем HTML разметку
$cat_menu = showCat($tree, '');

//Выводим на экран
echo '<select><option value="0">Выбери '. $cat_menu .'</select>';
?>

Результат работы

 

В следующей статье мы научимся сортировать данное меню, до новых встреч.

Скачать: Многоуровневое меню на PHP + MySQL


Поддержи проект:


...
Vasilich
Здравствуйте! В функции getTree() создаётся дерево массива $dataset с условием if. Как я понял в массив $tree, попадают сначала значения с parent == 0, а затем под каждым таким значением добавляется массив $dataset[$node['parent']]['childs'][$id] - второй уровень вложенности. После этого работает только else, и благодаря '&amp;' перед $node, в foreach вместо $dataset подставляется $dataset[$node['parent']]['childs'][$id] - третий уровень. Подскажите пожалуйста, это так или я ошибаюсь? Если не так, то не смогли бы Вы подробно описать логику метода? И при чём тут '&amp;' перед $node? В сети, описание ссылок php скудноватое. Искал подробное описание этого метода, но так ничего не нашёл.

12/05/2019 19:51:44

...
Дмитрий
Добрый день.<br /> Шикарный скрипт. Спасибо за подробные объяснения.<br /> Помогите пожалуйста с видоизменением шаблона: нужно, чтоб выводилось не списком, а строками таблицы (сложность в том, что дочерние элементы у Вас - вложенный список, а если делать в виде таблицы - это полноценные строки, поэтому начинается путаница с закрывающими тегами...) 

18/03/2019 11:04:11

Администрация
Администрация

Попробуйте так поправить шаблон в скрипте.

<?php

	//Шаблон для вывода меню в виде дерева
	function tplMenu($category){
	$menu = '<tr>
	<td><a href="#" title="'. $category['title'] .'">'. 
	$category['title'].'</a></td>';

	if(isset($category['childs'])){
	$menu .= '<table>'. showCat($category['childs']) .'</table>';
	}
	$menu .= '</tr>';

	return $menu;
}

//Выводим на экран
echo '<table>'. $cat_menu .'</table>';

?>

При этом нужно понимать, что записи будут выводиться в одной ячейки, по этому используется вложенный список.

18/03/2019 12:09:11

...
Евгений
Комментарии в функции построения дерева из массива от Tommy Lacroix совсем не правильные. Только запутают. //Если нет вложений ...//Если есть потомки то перебераем массив

21/09/2018 02:02:11

Администрация
Администрация

Здравствуйте Евгений.

Как смог так и объяснил, если кому-то пока за разъяснением никто не обращался. Если будут такие запросы я с радостью отвечу! 

21/09/2018 08:52:35

...
Хомер
Как то не вывела 3 уровня ваша функция, а только 2((( думаю моя будет попроще На ней можно сделать сколько угодно уровней. Принимает $items - это все пункты меню. В $nodeParent находиться меню родителя, и в него записываем детей. А раз все это по ссылке идет, то и основной массив форматируется. В итоге отсортированные элементы удаляем. Структура данных, такая же как и у вас))

$items = [
   0 => [
      'id' => 1,
       'parent' => 0,
       'title' => '1'
      ],
   1 => [
      ...
   ],
   ....
];

function recurseMenu(&$items = [], $parent = 0, &$nodeParent = []){       
      foreach ($items as $id => &$node) {
           if($parent == 0){
               if($node['parent'] == 0){
                   $this->recurseMenu($items, $id, $node);
               }
           }
           else{
               if($node['parent'] == $parent){
                   $nodeParent['child'][ $id ] = $node;
                  recurseMenu($items, $node['id'], $nodeParent['child'][ $id ]);
                   unset($items[$id]);
               }
           }
       }

   }

20/11/2017 01:11:09

Администрация
Администрация

Во-первых функция построения дерева не моя а Tommy Lacroix. И данная функция отлично работает. Если бы она выводила только два уровня, то я думаю читатели моего блога уже давно написали об этом.  А так как жалоб нет и скрипт у меня прекрасно работает, то Ваше замечание тут не уместно. Скорей всего что-то Вы неправильно сделали...

20/11/2017 19:01:01

...
Anton
Спасибо, кране помогли данной статьей

18/11/2017 22:08:09

Администрация
Администрация

И Вам спасибо, что посетили наш ресурс.

19/11/2017 22:52:39

...
Иван
верстка у меня уже готова. вид меню почти как на вашем первом скриншоте сверху. и при нажатии кроссовер или лифтбек уже выводится список машин со всеми цветами. вот еще хотел добавить , чтобы выводились категории предыдущих пунктов, но незнаю как ) спасибо за ответ!

29/03/2017 06:40:31

Администрация
Администрация

Если я правильно понял, то вас интересуют "хлебные крошки" прочитайте эту статью Хлебные крошки многомерного меню PHP + MySQL

17/04/2017 16:57:51

...
Иван
здравствуйте.. спасибо за статью!  скажите как сделать чтобы при вызове категории мне выводились ее вложенные категории?  в вашем случае при выборе мазда 6 мне бы выводились лифтбек и кроссовер... а при выборе кроссовер выдавало список вложенных цветов

28/03/2017 20:38:05

Администрация
Администрация

Иван, вам нужно использовать javascript + соответствующая верстка. Посмотрите в сторону фреймворка Bootstrap, там уже практически все готово.

28/03/2017 21:55:17

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

03/12/2016 17:04:53

Администрация
Администрация

Вынесу данный функционал ниже статьи для тех кому понадобится.

05/12/2016 08:49:08

...
Роман
Отличная статья вышла у Вас. Здорово все разжевано. Есть предложение по следующей статье, относящейся к многоуровневому меню, но на этот раз для админки. Пример: мне нужно сделать в админке выпадающий список для выбора категории для своей статьи, но выводиться должно все с отступами, например: html & css bootsrap faundation php ci yii js jquery ну и т.д. Заранее спасибо)

03/12/2016 16:47:00

Администрация
Администрация

Спасибо за отзыв.

05/12/2016 08:48:09

...
ильдар
> нужно как-то в коде добавить доп. массив, в котором будут храниться все предыдущие url (в вашем случае title) до родителя > для того, чтобы можно было, к примеру, на пятом уровне вложенности дерева вывести сразу нужную цепочку url (в вашем случае title) в результате у меня получилось сделать задуманное только через доп. запросы

<?php
$parent_key = 'id_parent';
$key1       = 'id';
$key2       = 'url';
$db_name    = 'db_name';
$id_        = $row[$key1]; // init
$urls       = array();
while ($id_ != 0) {
  $sql    = 'select * from ' . $db_name . ' where ' . $key1 . ' = ' . $id_;
  $result = mysql_query($sql);
  if (mysql_num_rows($result) &gt; 0) {
    while ($row = mysql_fetch_array($result)) {
      $id_    = $row[$parent_key];
      $urls[] = $row[$key2];
    }
  }
}
$urls = array_reverse($urls);
?>

03/11/2016 15:43:58

Администрация
Администрация

Если все таки Вам нужны такие ссылки, то взгляниете на эту статью Хлебные крошки многомерного меню PHP + MySQL там как раз есть механизм  получения родителей, немного поправьте код и у вас все получится!

07/11/2016 09:28:38


Copyright © 2014 - 2021 All rights reserved.