Вывод многоуровневого меню с неограниченным уровнем вложенности

Многоуровневое меню

От автора: при создании сайтов практически всегда необходимо выводить меню, то есть определенный блок с навигацией либо по страницам, либо по категориям веб-приложения. И очень часто данное меню необходимо отобразить в виде многоуровневого дерева. И хорошо, если предусматривается только второй уровень вложенности. А если третий? Или, вообще, неограниченный уровень вложенности? Как быть в этом случае? Поэтому в данном уроке мы с Вами научимся выводить на экран многоуровневое меню с неограниченным уровнем вложенности.

План урока

    1. Создание базы данных.

    2. Основные настройки и подключение к базу данных.

    3. Получение массива категорий.

    4. Вывод категорий в виде многоуровневого дерева.

Детали учебника

Тема: PHP

Сложность: Средняя

Урок: Видео (.mp4)

Время: 00:52:37

Размер архива: 100 Mb

скачать исходникискачать урок

1. Создание базы данных.

Первым делом, как всегда, необходимо определиться со структурой базы данных. Так как что бы выводить меню, необходимо, где то хранить его данные. Итак, давайте создадим базу данных, с названием data_car. Мы с Вами будем выводить категории для автомобильного сайта, поэтому и имя базы данных я выбрал близкое к этой тематике.

Итак, вот такая структура таблицы (таблицу назовем categories) нам понадобится для вывода, и хранения данных многоуровневого меню:

Теперь описание полей таблицы:

id – идентификатор таблицы (как обычно идентификаторы AUTO_INCREMENT и PRIMARY KEY)

title – заголовок категорий

parent_id – идентификатор родительской категории. По умолчанию и если категория родительская, значит значение данного поля 0, для определенной записи. Если же категория дочерняя, то в данном поле указываем идентификатор категории родителя (то есть поля id).

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

Теперь, когда база данных создана, начнем создавать скрипт.

2. Основные настройки и подключение к базу данных.

Итак, давайте определимся с файловой структурой будущего скрипта. В данном уроке мы с Вами только лишь выведем блок с многоуровневым меню, поэтому нам понадобится, вот такой набор файлов и папок:

functions
— functions.php
config.php
index.php

Смотрите папка functions, содержит в себе один файл – functions.php, в котором будут описаны все функции необходимые для нормальной работы скрипта. Далее в файле config.php мы опишем все основные настройки и конечно index.php – основная точка входа.

Хочу сразу сказать, что все файлы, которые мы будем создавать, необходимо сохранять в кодировке UTF-8.

Итак, давайте посмотрим, какие основные настройки нам потребуются, вот код файла config.php:

<?php
/**
* Основные настройки
* подключения к базе данных
*/

//Хост
define("HOST","localhost");
//Имя пользователя
define("USER","Viktor");
//Пароль
define("PASS","1234");
//имя базы данных
define("DB","data_car");
?>

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

Далее давайте создадим новый файл functions.php (сохраним его в папку functions) и создадим в нем первую функцию, которая будет выполнять подключение к базе данных:

<?php
/**
* Основные функции
*/

//соединени с базой данных
function db($host,$user,$pass,$database) {
	$db = mysql_connect($host,$user,$pass);
	if (!$db) {
		exit('Ошибка при подключении к базе данных');
	}
	if(!mysql_select_db($database,$db)) {
		exit('База данных не существует');
	}
	mysql_query('SET NAMES utf8');
}
?>

Как Вы видите, очень простая функция, которая принимает четыре параметра: адрес сервера, базы данных, имя пользователя, пароль и название базы данных. Вначале выполняем соединение с сервером базы данных, используя функцию mysql_connect($host,$user,$pass), затем выбираем базу данных для работы — mysql_select_db($database,$db) и в конце определяем кодировку для работы с базой данных.

Теперь давайте создадим файл index.php и добавим в него первые строки кода:

<?php
/**
* Основная точка входа
*/

//Отправляем заголовок с кодировкой
header("Content-Type:text/html;charset=utf8");

//Подключаем файл с функциями и файл конфигурации
include 'functions/functions.php';
include 'config.php';

//соединение с базой данных
db(HOST,USER,PASS,DB);
?>

Итак, первым делом, отправляем заголовок с кодировкой – функция header(«Content-Type:text/html;charset=utf8″). Затем подключаем два ранее созданных файла: файл конфигураций config.php и файл functions.php. И наконец, вызываем функцию db(), для подключения к серверу базы данных. Теперь давайте проверим в браузере, что у нас получилось. Если на экране, на данном этапе ничего не вывелось, то есть, нет сообщений об ошибках, значит мы на верном пути.

3. Получение массива категорий.

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

Рекурсия — это обращение функции к самой себе, другими словами вызов функции внутри кода функции самой себя.

Теперь поговорим о том, по какому принципу мы будем выводить многоуровневое меню. Данная задача (как и любая в программировании) имеет несколько решений. К примеру, одно из таких решений состоит в том, что бы создать функцию (принимающая параметром идентификатор родительской категории), которая в своем коде формировала SQL запрос по выборке данных меню, по полю parent_id. То есть, при первом вызове функции, выбираются все записи, у которых поле parent_id = 0, затем parent_id = 1, далее parent_id = 2 и т.д. Далее в цикле вытягиваем данные из результата отработки SQL запроса и в этом же цикле вызываем данную функцию (рекурсивно саму на себя) и передаем ей идентификатор родительской категории, полученный у записи, вытащенной на данной итерации цикла. То есть на первой итерации мы с Вами вытаскиваем в переменную ассоциативный массив, в этом массиве есть ячейка с ключом parent_id, вот ее значение и передаем при вызове функции.

Данный метод, казалось бы, очень неплохой, во-первых, он отлично работает, а во-вторых, имеет очень красивую и краткую реализацию. Но есть существенный недостаток – происходит много кратное обращение к базе данных и выполнение запросов по выборке данных. А это не очень хорошо.

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

Итак, давайте в файле functions.php создадим функцию get_cat():

<?php
//Функция получения массива каталога
function get_cat() {
	//запрос к базе данных
	$sql = "SELECT * FROM categories";
	$result = mysql_query($sql);
	if(!$result) {
		return NULL;
	}
	$arr_cat = array();
	if(mysql_num_rows($result) != 0) {

		//В цикле формируем массив
		for($i = 0; $i < mysql_num_rows($result);$i++) {
			$row = mysql_fetch_array($result,MYSQL_ASSOC);

			//Формируем массив, где ключами являются адишники на родительские категории
			if(empty($arr_cat[$row['parent_id']])) {
				$arr_cat[$row['parent_id']] = array();
			}
			$arr_cat[$row['parent_id']][] = $row;
		}
		//возвращаем массив
		return $arr_cat;
	}
}
?>

Итак, смотрите, вначале формируем и выполняем SQL запрос по выборке всех данных из таблицы categories, далее проверяем, успешно ли выполнился запрос. Теперь конечно можно было бы просто при помощи цикла пройтись по результату, полученному из базы данных и создать ассоциативный массив данных по категориям. Но такой массив очень неудобно обрабатывать, поэтому его необходимо отсортировать в удобном порядке. Нужно сделать так, что бы на выходе данной функции, получился массив ключами которого, были бы идентификаторы родительских категорий. И в каждой ячейке данного массива, содержался бы еще массив – всех категорий (дочерних), у которых идентификатор родительской категории равен ключу соответствующего массива. Другими словами отсортируем массив по дочерним категориям. Что мы с Вами и делаем. Описываем цикл for, в котором будем поочередно проходиться по каждой строке результирующей таблицы, полученной из базы данных. Затем в переменную $row, сохраняем массив данных – одной строки результирующей таблицы. А дальше формируем массив $arr_cat, у которого ключи соответствуют идентификаторам родительских категорий. И наконец, возвращаем массив $arr_cat, как результат работы функции в целом.

Теперь давайте в файле index.php вызовем данную функцию:

<?php
//получаем массив каталога
$result = get_cat();
print_r($result);
?>

Вызываем функцию get_cat() и сразу же распечатаем полученный массив $result, что бы убедиться, что все корректно работает:

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

4. Вывод категорий в виде многоуровневого дерева.

Итак, давайте откроем файл functions.php и создадим функцию view_cat(), которая и будет выводить многоуровневое меню:

<?php
//вывод каталога с помощью рекурсии
function view_cat($arr,$parent_id = 0) {

	//Условия выхода из рекурсии
	if(empty($arr[$parent_id])) {
		return;
	}
	echo '<ul>';
	//перебираем в цикле массив и выводим на экран
	for($i = 0; $i < count($arr[$parent_id]);$i++) {
		echo '<li><a href="?category_id='.$arr[$parent_id][$i]['id'].
					'&parent_id='.$parent_id.'">'
					.$arr[$parent_id][$i]['title'].'</a>';
		//рекурсия - проверяем нет ли дочерних категорий
		view_cat($arr,$arr[$parent_id][$i]['id']);
		echo '</li>';
	}
	echo '</ul>';

}?>

Итак, данная функция принимает два параметра: первый – это собственно массив, который необходимо обработать, и второй идентификатор родительской категории, то есть той категории, данные которой, мы будем отображать. Замете, что если не передать второй параметр, то по умолчанию он равен нулю, то есть вначале отображаем родительскую категорию.

Так как данная функция будет рекурсивно вызываться, то первым делом необходимо описать условие выхода из рекурсии, что мы с Вами и делаем. А именно, проверяем, существует ли ячейка массива с ключом ($arr[$parent_id]) — идентификатором родительской категории – который передается параметром при вызове функции.

Далее выводим, открывающий тег ul — наше меню будем выводить в виде не нумерованного списка. И описываем цикл for, при помощи которого, будем проходить по элементам массива $arr. Затем между тегами li выводим ссылку на категорию.

Путь ссылки сформируем таким образом – при помощи GET параметров передадим две переменные: первая это идентификатор категории и вторая – идентификатор ее родительской категории. При этом, что бы получить идентификатор категории — вначале обращаемся к ячейке массива $arr, по идентификатору родительской категории, который был передан как параметр к функции — $parent_id, затем – в этой ячейке у нас содержится так же массив, поэтому доступ к его ячейкам, получаем, используя счетчик цикла $i, и указываем непосредственно ключ, ассоциативного массива то есть ['id'] – получаем идентификатор категории.
То есть, смотрите, когда мы будем вызывать данную функцию, конечно же, второй параметр ей передавать не будем, значит, по умолчанию он будет равен нулю, поэтому подразумевается что будем выводить только лишь родительские категории. Далее на первой итерации (шаге) цикла выводится первая ссылка на родительскую категорию, и затем рекурсивно вызываем опять же функцию view_cat(), передаем ей массив, который необходимо обработать и идентификатор данной категории (той которую мы только, что вывели на экран) — $arr[$parent_id][$i]['id'], теперь он считается идентификатором родительской категории. Другими словами – проверяем, есть ли у данной категории – дочерние категории.

И наконец, после цикла выводим, закрывающий тег

. На этом функция закончена. Теперь давайте перейдем в файл index.php и вызовем данную функцию:

<?php
//Выводим каталог на экран с помощью рекурсивной функции
echo '<div style="width:300px;float:left; padding:10px; border:1px solid #074776">';
view_cat($result);
echo '</div>';
?>

Теперь давайте перейдем в браузер и посмотрим, что у нас получилось:

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

Надеюсь, данный урок будет Вам полезен. Удачного кодирования. И до новых встреч в следующих видео уроках.

Верстка сайта для начинающих

Прямо сейчас научитесь верстать сайты с нуля.

Смотреть курс

Метки:

Комментарии Вконтакте:

Комментарии Facebook:

Комментарии (25)

  1. Шамшур Иван

    На самом деле можно обойтись и без рекурсии. Как-то писал об этом у себя на блоге. Если интересно, вот ссылка biznesguide.ru/coding/179.html

    • Андрей Кудлай

      Автор и не говорил, что без рекурсии нельзя решить задачу :) Это всего лишь один из вариантов.
      Кстати, насколько я понимаю, в Вашем варианте автор функции формирования иерархического дерева — Tommy Lacroix (tommylacroix.com/2008/09/10/php-design-pattern-building-a-tree/).

      • Шамшур Иван

        Да, верно. Автор этого метода Tommy Lacroix.

        • Виктор Гавриленко

          Очень интересная реализация. Но я конечно особо не вникал в суть предложенного решения, но заметил, что рекурсия все таки используется в Вашем решении. Функция getCommentsTemplate() рекурсивно вызывается в шаблоне comments_template.php(вот кстати цитата из статьи: «мы заново вызываем функцию getCommentsTemplate(). Получается вот такой вот рекурсивный вызов этой функции.»)

  2. Виталий

    Добрый вечер!Скажи на счет меню.Вот как мне сделать меню,чтобы при нажатие на родительское меню выводились все статьи или товары данной категории,а при нажатие дочерного меню родительского меню выводились только выбранные статьи и при этом всё это сделать в виде аккордиона?

    • Виктор Гавриленко

      Здравствуйте, Виталий!
      Вот урок по реализации меню в виде аккордеона:http://webformyself.com/ispolzovanie-vidzheta-akkordeon-dlya-menyu/

      А по поводу вывода статтей соответствующим меню, то используйте идентификаторы пунктов меню. То есть если У Вас в меню выводятся категории, то у каждой из них есть свой идентификатор — этот же идентификатор прописан у каждой статьи(так как каждая статья привязана к соответствующей категории). Значит, когда происходит переход по ссылке категории через адресную строку передается идентификатор этой категории. А зная его, Вы можете вытащить из базы данных те статьи, у которых есть данный идентификатор категории

      • Евгений

        Тот аккордион немного перестал срабатывать в таком многоуровневом меню почему-то…(((

      • Алексей

        А если у меня в адресной строке есть id категории, как я могу получить все id дочерних категорий?

  3. Александр

    Приветствую а меня вот какой вопрос заинтересовал по данному меню как можно прикрепить определённый текст на parent_id тоесть например нажимаешь на Ford категорию посылает на страницу с описанием этой категории я так понимаю в БД надо сделать нечто поля content_id c типом текст естественно или как у менмного возникло недопонимание и следующая задача как так же вывести определённый текст по суб категории ford тоесть 1>3>text, 1>text вот так схемотично очень интересует этот вопрос тоесть как это всё генерировать потому что далее я буду делать систему как у меня сейчас есть вывод категорий и по ним выводятся новости тоесть я нажал на категорию Ford и у меня выводятся новости этой категории а вот как с под категориями быть я и хотел узнать можите мне немного объяснить как это всё по скайпу или по ICQ возможно я давно уже искал похожее решение несложное для понимания возможно это из за того что видео урок был предложен а не просто текст……Поможите я просто CMS пишу велосипед и хотелось бы внедрить??

  4. MaGiC

    Спасибо за этот метод, вопрос:
    Как добавить класс к текущему пункту меню? Чтобы можно было оформить соответственно…

    • Виктор Гавриленко

      Здравствуйте!
      Ответил на Ваш вопрос в следующем комменте. Если будут вопросы, обращайтесь к нам на форум, он как раз и создан для этих целей.

  5. Людмила

    Чудесный урок! Но все же как добавить стили, особенно к вложенным спискам пунктов меню

    • Андрей Кудлай

      Вариантов несколько в зависимости от желаемого конечного результат:
      1) просто добавить в функцию рекурсии классы для списка
      2) в зависимости от значения счетчика цикла присваивать нужный класс
      3) писать правила вида: ul ul {/* здесь стили для вложенного меню */} ul ul ul {/* здесь стили для второго вложенного меню */}

    • Виктор Гавриленко

      Здравствуйте!
      Если нужно добавить различные стили ко всем вложенным пунктам меню, можно использовать счетчик итераций цикла. К примеру создаете переменную которая хранит количество итераций цикла и передаете ее в функцию view_cat(), а при выводе на экран добавляете к тегу класс к примеру class=’parent’ и добавляете номер текущей итерации цикла, что бы получилось вот так: class=»parent2″, где 2 — это номер вложенности, или номер текущей итерации цикла. ТОгда в стилях соответственно нужно будет прописать стили для каждого класса. Но тогда Вам нужно будет предусмотреть эти же правила для всех уровней вложенности.
      А вообще лучше просто всем внутренним категориям, добавить просто определенный класс, к примеру parent и задать стили для него.

      Если что то будет не понятно, то обращайтесь к нам на форум, там такие вопросы решать намного легче.

      • Mamay

        Очень интересно… Но мне,как новичку не все понятно. Может Вы сделаете урок на тему добавления стилей к вложенным пунктам меню?
        Просто наглядно быстрее доходит ))

  6. Руслан

    Тема действительно очень хорошая, Спасибо за предложений вариан.

  7. Юрий

    Безумно понравилась идея….одно только-такое меню можно сделать выпадающим и для каждого пункта задать стили?Если можно с примером кода или отдельным видеоуроком…думаю все Ваши читатели будут безумно рады…просто не могу понять саму реализацию кода…идея ясна…а вот реализация нет…

  8. Максим

    Классный урок. Все понятно, хот и не сразу. Единственный момент — как вывести товары из такого меню. Если уровней определенное (заранее известное) количество то ни чего сложного, но когда уровней может быть сколько угодно, то как выводить товары всех подуровней. В таблице товаров есть поле в котором храниться id последней под категории а вывести нужно товары первой категории и всех подкатегорий (которых может быть сколько угодно).

  9. Андрей

    Здравствуйте! Подскажите пожалуйста как присвоить css класс списку второго уровня? Спасибо!

    • Виктор Гавриленко

      Здравствуйте, Андрей!
      Можно воспользоваться параметром — иденитификатор родительской категории, то есть если данный параметр не равен 0, значит это следующий уровень. Как то так.

  10. Илья

    За урок большое спасибо. Пытаться писать программы начал недавно. В связи с этим вопрос. Как сделать, что бы при клике по вложенной категории на экране появлялась определенная страница. Или по другому сформулирую, как связать полученный href в результате обхода массива со страницей и вывести ее на экран. Извините, если вопрос глупый.

  11. Сергей

    Спасибо за урок) Я бы без вас с этой задачей долго бы еще сидел думал.

  12. Батыржан

    здравствуйте!Спасибо за меню!А как сделать так:

    <select>
    <option>Родительская категория</option>
    <option>— Подкатегории (1 уровень)</option>
    <option>—- Подкатегории (2 уровень)</option>
    <option>—— Подкатегории (3 уровень)</option>
    </select>

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Я не робот.

Spam Protection by WP-SpamFree