От автора: приветствую Вас дорогой друг. Меню — это неотъемлемая часть любого сайта и даже в том случае, если он состоит всего лишь из одной страницы. И так сложилось, что, как правило, редко встречаются меню одного уровня. Наибольшее распространение получили как раз многоуровневые меню, так как они позволяют расположить большее количество ссылок на значительно меньшем пространстве. Поэтому в данном уроке мы рассмотрим формирование древовидной структуры данных для отображения на экран меню указанного типа.
Уже достаточно давно на нашем сайте был опубликован урок по данной теме. И собственно, решение, которое представлено в уроке, очень неплохое и отлично справляется с поставленной задачей, но в некоторых случаях все же не совершенно. Так как, по сути, структура данных, которая формируется – не древовидная и подходит лишь к той функции, которая показана и используется для вывода ссылок на экран.
Если же потребуется передать данную структуру в определенное место для последующей обработки, то могут возникнуть определенные проблемы. Поэтому в текущем видео я хотел бы предложить несколько иное решение по формированию массива данных и собственно выводу его на экран.
Для начала, хотел бы немного остановиться на заготовке того скрипта с которым мы будем работать. Собственно он состоит всего лишь из двух файлов – index.php и functions.php.
Код файла index.php:
1 2 3 4 |
<?php require "functions.php"; $mysqli = db_connect('localhost', 'root', '', 'tree_menu'); $cats = getCategories($mysqli); |
Как Вы видите все предельно просто – подключаем файл functions.php, в котором определены и будут сегодня определяться функции. Далее вызываем функцию подключения к базе данных и после этого вызываем на исполнение функцию getCategories(), которая вернет в виде массива выборку информации из таблицы базы данных, с которой мы будем сегодня работать, в вот таком виде:
Для данного урока я использую ту же базу данных, что и в указанном выше видео.
Напомню, что в поле title содержится заголовок категории или ссылки меню, в поле parent_id – идентификатор родительской категории, причем элементы самого верхнего уровня в данном поле содержат 0 и наконец, поле id – это идентификатор таблицы.
Далее, код файла functions.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<?php function db_connect($host, $user, $password, $db_name) { $link = mysqli_connect($host, $user, $password, $db_name); if (!$link) { die('Ошибка подключения (' . mysqli_connect_errno() . ') ' . mysqli_connect_error()); } return $link; } function getCategories($link) { if ($result = mysqli_query($link, "SELECT * FROM categories")) { return mysqli_fetch_all($result, MYSQLI_ASSOC); } } |
Код приведенных функцию, думаю, не нуждается в комментариях. Теперь мы можем приступить к работе и по большому счету мы с Вами напишем ровно три функции и парочку шаблонов для отображения данных на экран. Итак, начинаем писать первую функцию, часть которой будет знакома тем, кто смотрел вышеуказанный урок:
1 2 3 4 5 6 7 |
function createTree($arr) { $parents_arr = array(); foreach($arr as $key=>$item) { $parents_arr[$item['parent_id']][$item['id']] = $item; } return $parents_arr; } |
Хотел бы заметить, что код еще не завершен – это только начало. Обратите внимание, что в качестве аргумента мы будем передавать массив из которого будет сформирована древовидная структура. Как Вы видите в коде, используя цикл foreach, мы обходим массив, переданный в виде аргумента, и формируем новый — $parents_arr, ключами которого, являются идентификаторы родительских категорий. И в каждой ячейке данного массива, содержится дополнительный подмассив – всех категорий (дочерних), у которых идентификатор родительской категории равен ключу соответствующего массива.
В итоге при вызове данной функции и распечатки на экран возвращаемого значения, мы получим следующий результат:
Теперь, мы подготовили данные и наша задача, состоит в том, что бы сформировать такой многоуровневый массив, в котором на первом уровне вложенности присутствовали только родительские элементы высшего уровня, далее в каждом из них будет определен элемент children, в котором будет содержаться массив дочерних элементов. Которые в свою очередь, если содержат потомков, так же будут их хранить в указанном элементе. Таким образом, нам нужно получить массив вот такого вида:
А значит, мы можем продолжить писать код функции и несколько изменим возвращаемое значение:
1 2 3 4 5 6 7 8 9 10 |
function createTree($arr) { $parents_arr = array(); foreach($arr as $key=>$item) { $parents_arr[$item['parent_id']][$item['id']] = $item; } $treeElem = $parents_arr[0]; generateElemTree($treeElem,$parents_arr); return $treeElem; } |
Обратите внимание, что мы создаем новый массив $treeElem, который далее возвращается как результат работы функции. И по умолчанию сохраняем в него элементы самого высокого уровня, которые содержатся в ключе с индексом 0, массива $parents_arr. По сути, сейчас мы получили массив элементов верхнего уровня, в который нужно добавить дочерние элементы, непосредственно в ячейку с ключом children. И сделает это дополнительная функция generateElemTree(), которую мы сейчас с Вами напишем.
Итак, код функции generateElemTree():
1 2 3 4 5 6 7 8 9 10 11 |
function generateElemTree(&$treeElem,$parents_arr) { foreach($treeElem as $key=>$item) { if(!isset($item['children'])) { $treeElem[$key]['children'] = array(); } if(array_key_exists($key,$parents_arr)) { $treeElem[$key]['children'] = $parents_arr[$key]; generateElemTree($treeElem[$key]['children'],$parents_arr); } } } |
Важно! Первый аргумент функции – это ссылка на массив $treeElem , так как нам нужно его изменить – доработать и вернуть как результат работы функции createTree(). Второй же аргумент это массив который мы с Вами получили в выше указанной функции. Собственно работа функции хоть и кажется сложной, но на самом деле очень проста.
Для начала, проверяем есть ли в переданном массиве элемент с ключем ‘children’ и если его нет то мы его создаем и инициализируем пустым массивом:
1 2 3 |
if(!isset($item['children'])) { $treeElem[$key]['children'] = array(); } |
А далее мы проверим, есть ли в массиве $parents_arr, элемент с ключом $key. При этом, напомню что в $key – содержится идентификатор текущего элемента, а в массиве $parents_arr, ключи это идентификаторы родительских элементов. Соответственно если условие выполнилось, значит, мы нашли дочерние элементы для текущего элемента. Соответственно массив дочерних элементов записываем я ячейку с ключом children. Затем рекурсивно вызываем эту же функцию, передавая при этом как раз найденный массив дочерних элементов, ведь они так же в свою очередь могут быть родителями для других элементов.
Вот собственно и все, если распечатать на экран содержимое массива $treeElem, мы получим желаемую древовидную структуру данных, которая показана на рисунке выше. Теперь необходимо вывести на экран полученные данные. Для этого напишем функцию шаблонизатор:
1 2 3 4 5 6 7 8 9 10 |
function renderTemplate($path,$arr) { $output = ''; if(file_exists($path)) { extract($arr); ob_start(); include $path; $output = ob_get_clean(); } return $output; } |
Код данной функции я позаимствовал из урока по созданию собственного шаблонизатора, а значит, комментировать ее я не буду, так как в уроке приведено подробнейшее описание.
Теперь, думаю, стоит привести код файла index.php, так как именно в нем мы будем использовать только что написанную функцию:
1 2 3 4 5 |
require "functions.php"; $mysqli = db_connect('localhost', 'root', '', 'tree_menu'); $cats = getCategories($mysqli); $cats = createTree($cats); echo renderTemplate('template.php',['cats'=>$cats]); |
Как Вы видите, с помощью шаблонизатора, подключается файл template.php , код которого приведен ниже:
1 2 3 4 |
<h2>Menu</h2> <?php if(isset($cats)) : ?> <?php echo renderTemplate('menu_part.php',['cats'=>$cats]); ?> <?php endif; ?> |
Внутри шаблоны мы опять же обращаемся к функции шаблонизатора и подгружаем дополнительный шаблон в который передается все та же переменная cats. Хотел бы отметить что так как мы не знаем сколько уровней вложенности в нашей древовидной структуре, соответственно нам нужно рекурсивно обрабатывать ее, а значит рекурсивно подгружать шаблон который будет выводить один уровень вложенности, что собственно и реализовано, используя дополнительный шаблон, код которого приведен ниже:
1 2 3 4 5 6 7 8 9 10 11 |
<ul style="margin-left:10px;"> <?php foreach($cats as $cat) : ?> <li><a href="/category/<?php echo $cat['id']; ?>"><?php echo $cat['title']; ?></a></li> <?php if(count($cat['children']) > 0) : ?> <?php echo renderTemplate('menu_part.php',['cats'=>$cat['children']]); ?> <?php endif; ?> <?php endforeach; ?> </ul> |
Логика работы шаблона сводится к следующему: используя цикл foreach() обходим элементы одного уровня и если у элемента в ячейке ‘children’, содержится массив дочерних элементов – рекурсивно, вызываем функцию renderTemplate(), подгружаем этот же шаблон, но при этом передаем в качестве переменной ‘cats’ ,массив дочерних элементов. При этом на экране, в качестве результата мы увидим следующее:
Вот собственно и все. Всего Вам доброго и удачного кодирования!!!