php рекурсивная функция категории меню
Построение дерева на php (вывод меню с неограниченной вложенностью)
Современные сайты и интернет магазины требуют меню с неограниченной вложенностью. Обычно заказчики сами не знают, какая вложенность может понадобиться их сайту в будущем.
При создании сайтов и интернет магазинов многие программисты сталкиваются с проблемой вывода меню с неограниченной вложенностью. Ведь если сайт будет с меню без подкатегорий, то стоимость сайта будет очень низкая. Чтобы увеличить качество и стоимость создания сайта, в этой статье мы рассмотрим, как нужно делать меню неограниченной вложенностью, с 1-им циклом и 1-м запросом к базе, и какое меню делать не нужно. Начнем с того, как делать не нужно.
Как делать не нужно
Многие начинающие разработчики при создании меню, которое состоит, например, из 3 уровней вложенности, делают 3 вложенных цикла, в каждом цикле делают запросы к базе данных на выборку и вывод подкатегорий. В этом методе есть следующие недостатки:
У нас был заказчик, который очень удивлялся высокой стоимости поддержки сайта, т.к. у него и вывод меню был сделан не правильно, и все остальное. И для веб мастера нужно было потратить много сил и времени, чтобы что-то исправить на сайте. В итоге он решил сделать себе новый сайт. Хотя стоимость сайта была достаточно высока, но она окупилась через какое-то время, за счет низкой стоимости поддержки сайта.
Как делать нужно
Нужно делать так, чтобы вложенность меню генерировалась автоматически, и было всего 1-о обращение к базе данных.
Построение и вывод дерева на php
Сейчас мы сделаем вывод дерева на примере структуры сайта нашей компании (ox2.ru). Мы постоим дерево следующего вида (3 уровня вложенности):
Подготовка базы данных
Для построения дерева на php нам нужна база данных следующего вида:
Первое поле id категории, оно уникально для каждой категории. Второе поле name – имя категории, третье полеparent_id – оно ссылается на id категории родителя.
Например, для раздела Тариф «Оптимальный» parent_id = 4, поскольку она является подкатегорией раздела с id равным 4-ем (Создание интернет магазина, а она имеет parent_id равным 2-ум, т.к. является 2-ым уровнем вложенности раздела Услуги). Если parent_id = 0, то это главная категория.
Вот дамп нашей базы данных:
Вывод дерева на PHP
Поскольку много читателей не знакомы с PHP5, мы сделали 2 версии вывода меню (на PHP4 и на PHP5)
Для тех кто программирует на PHP5, пропустите следующий раздел
Вывод меню на PHP4
Вывод меню на PHP5
Комментарии (Написать комментарий)
Комментарий:
Большое спасибо!
Комментарий:
Спасибо, ваша статья помогла мне решить поставленную задачу. Скрипт написал правда свой, но идею с логикой частично позаимствовал у вас )
Комментарий:
Спасибо за образец, ваш сайт помогает в освоении практики программирования.
Вывод многоуровневого меню с неограниченным уровнем вложенности
Дата публикации: 2013-03-08
От автора: при создании сайтов практически всегда необходимо выводить меню, то есть определенный блок с навигацией либо по страницам, либо по категориям веб-приложения. И очень часто данное меню необходимо отобразить в виде многоуровневого дерева. И хорошо, если предусматривается только второй уровень вложенности. А если третий? Или, вообще, неограниченный уровень вложенности? Как быть в этом случае? Поэтому в данном уроке мы с Вами научимся выводить на экран многоуровневое меню с неограниченным уровнем вложенности.
1. Создание базы данных.
Первым делом, как всегда, необходимо определиться со структурой базы данных. Так как чтобы выводить меню, необходимо, где то хранить его данные. Итак, давайте создадим базу данных, с названием data_car. Мы с Вами будем выводить категории для автомобильного сайта, поэтому и имя базы данных я выбрал близкое к этой тематике.
Итак, вот такая структура таблицы (таблицу назовем categories) нам понадобится для вывода, и хранения данных многоуровневого меню:
Бесплатный курс по PHP программированию
Освойте курс и узнайте, как создать динамичный сайт на PHP и MySQL с полного нуля, используя модель MVC
В курсе 39 уроков | 15 часов видео | исходники для каждого урока
Теперь описание полей таблицы:
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:
Как Вы видите, нам потребуются только лишь настройки для подключения к базе данных. Вы, конечно же, можете добавить в данный файл, дополнительные данные, которые необходимы для работы Вашего веб-приложения.
Далее давайте создадим новый файл functions.php (сохраним его в папку functions) и создадим в нем первую функцию, которая будет выполнять подключение к базе данных:
Как Вы видите, очень простая функция, которая принимает четыре параметра: адрес сервера, базы данных, имя пользователя, пароль и название базы данных. Вначале выполняем соединение с сервером базы данных, используя функцию mysql_connect($host,$user,$pass), затем выбираем базу данных для работы — mysql_select_db($database,$db) и в конце определяем кодировку для работы с базой данных.
Теперь давайте создадим файл index.php и добавим в него первые строки кода:
Итак, первым делом, отправляем заголовок с кодировкой – функция header(«Content-Type:text/html;charset=utf8″). Затем подключаем два ранее созданных файла: файл конфигураций config.php и файл functions.php. И наконец, вызываем функцию db(), для подключения к серверу базы данных. Теперь давайте проверим в браузере, что у нас получилось. Если на экране, на данном этапе ничего не вывелось, то есть, нет сообщений об ошибках, значит мы на верном пути.
Бесплатный курс по PHP программированию
Освойте курс и узнайте, как создать динамичный сайт на PHP и MySQL с полного нуля, используя модель MVC
В курсе 39 уроков | 15 часов видео | исходники для каждого урока
3. Получение массива категорий.
Для того чтобы вывести многоуровневое меню, без ограничения по уровню вложенности необходимо использовать рекурсию.
Рекурсия — это обращение функции к самой себе, другими словами вызов функции внутри кода функции самой себя.
Теперь поговорим о том, по какому принципу мы будем выводить многоуровневое меню. Данная задача (как и любая в программировании) имеет несколько решений. К примеру, одно из таких решений состоит в том, чтобы создать функцию (принимающая параметром идентификатор родительской категории), которая в своем коде формировала SQL запрос по выборке данных меню, по полю parent_id. То есть, при первом вызове функции, выбираются все записи, у которых поле parent_id = 0, затем parent_id = 1, далее parent_id = 2 и т.д. Далее в цикле вытягиваем данные из результата отработки SQL запроса и в этом же цикле вызываем данную функцию (рекурсивно саму на себя) и передаем ей идентификатор родительской категории, полученный у записи, вытащенной на данной итерации цикла. То есть на первой итерации мы с Вами вытаскиваем в переменную ассоциативный массив, в этом массиве есть ячейка с ключом parent_id, вот ее значение и передаем при вызове функции.
Данный метод, казалось бы, очень неплохой, во-первых, он отлично работает, а во-вторых, имеет очень красивую и краткую реализацию. Но есть существенный недостаток – происходит многократное обращение к базе данных и выполнение запросов по выборке данных. А это не очень хорошо.
Поэтому, мы пойдем по другому пути и решим поставленную задачу иначе. Итак, первым делом мы обратимся к базе данных и вытащим все категории, которые в ней содержатся. Далее из этих категорий сформируем массив, определенного вида. И только потом создадим функцию, которая будет обрабатывать данный массив и выводить категории на экран.
Итак, давайте в файле functions.php создадим функцию get_cat():
Теперь давайте в файле index.php вызовем данную функцию:
Как Вы видите, функция действительно правильно работает и полученный массив категорий отсортирован в нужном порядке. Теперь, осталось создать функцию, которая и будет выводить данные категории в виде многоуровневого меню.
4. Вывод категорий в виде многоуровневого дерева.
Итак, давайте откроем файл functions.php и создадим функцию view_cat(), которая и будет выводить многоуровневое меню:
Итак, данная функция принимает два параметра: первый – это собственно массив, который необходимо обработать, и второй идентификатор родительской категории, то есть той категории, данные которой, мы будем отображать. Замете, что если не передать второй параметр, то по умолчанию он равен нулю, то есть вначале отображаем родительскую категорию.
Так как данная функция будет рекурсивно вызываться, то первым делом необходимо описать условие выхода из рекурсии, что мы с Вами и делаем. А именно, проверяем, существует ли ячейка массива с ключом ($arr[$parent_id]) — идентификатором родительской категории – который передается параметром при вызове функции.
PHPDesigner
Вы здесь: Главная » PHP » Построение дерева категорий на PHP. Рекурсия
Построение дерева категорий на PHP. Рекурсия
Сегодня я расскажу, как на PHP и MySQL создавать иерархическое дерево.
Такие деревья используются при построении категорий динамического сайта, например в интернет-магазине или при выводе комментариев к посту.
Вообще они строятся где только возможно. Главное правильно его построить и применить.
Самое главное, когда строишь иерархическое дерево — это правильная структура базы данных! Для примера рассмотрим структуру базы данных, где хранятся категории сайта. Для простого примера, таблица будет иметь 3 поля:
Создадим таблицу, выполнив SQL-запрос в PHPMyAdmin:
Теперь нужно заполнить нашу таблицу записями. В результате, должна получится примерно такая таблица:
Можно заполнить тестовую таблицу запросом:
С базой данных всё! Дальше идем к формированию самого дерева разделов.
И сейчас внимание! Дальше по логике нужно делать выборки из БД в цикле для выбора каждой категории и её подкатегории. НО! Ладно, если в БД несколько категорий, что тоже в принципе не правильно. А если сайт — интернет-магазин и у него сотня категорий и столько же подкатегорий? Тогда беда! Неведомое количество запросов к базе данных приведет к замедлению работы сайта или же к полному краху mysql-сервера.
Можно используя только один запрос к БД выбрать все категории и ихние подкатегории.
Сделаем запрос и сформируем удобный массив для дальнейшей работы.
Выбираем все данные из таблицы categories и формируем ассоциативный массив $cats, ключем будет id родительской категорий.
Сейчас будем строить дерево. Для построения будем использовать рекурсивную функцию.
Иерархическое дерево будет иметь такую структуру:
Создадим рекурсивную функцию build_tree(). Она будет строить наше иерархическое дерево абсолютно любой вложенности.
Функция принимает массив разделов и id раздела. В цикле перебираем подкатегории и если в них есть еще разделы, тогда функция запускается еще раз с новыми параметрами (новый массив разделов и id раздела, который нужно построить). Так формируется дерево любой вложенности!
Для построения дерева, в коде прописываем:
Так вот в два шага мы создали иерархическое дерево разделов сайта и не важно сколько там разделов!
UPD Если нужно дерево категорий в обратном порядке зная id категории, тогда нужно воспользоваться функцией:
Данная функция принимает массив категорий, ключом которой есть id рубрики, и id категории от которой нужно идти вверх.
Для построения такого дерева запускаем функцию build_tree c такими параметрами:
Рекурсия на PHP — алгоритм, применение
К написанию статьи сподвигли часы раздумий и экспериментов в области построения иерархических списков. Изначально логика обкатывалась на SQL запросах, но в последствии решил реализовать на PHP, дабы снять зависимость от СУБД. На простом примере я покажу как можно пройти от корня иерархии до каждого конечного элемента и обратно, информация скорее для новичков.
Итак, тестовая иерархия, с которой нам предстоит работать:
В базе данных имеется самая простая таблица на самом простом MSSQL сервере, тонкости подключения опустим, наша цель — разобраться с иерархией и рекурсией.
Описание полей есть в комментариях, чуть подробнее о поле access:
По умолчанию в моей системе для каждого нового документа проставляется inherit, то есть наследование от родителя. Для нашего эксперимента для некоторых эелементов пропишем доменные группы. В группе Domain Users моя учётная запись имеется, а вот в AD Group Secret меня нет.
Теперь предлагаю получить необходимые данные и перейти непосредственно к делу:
Задача №1
Необходимо научиться работать с иерархией как с деревом а не списком. Уровень вложенности заранее не известен и может быть любым, следовательно должно быть универсальное средство, позволяющее выполнять проход по дереву как сверху вниз, так и в обратном направлении.
Задача №2
Необходимо гибко управлять доступами, то есть, давать права на группы, отдельные документы и т.д., по аналогии с файловой системой NTFS, можно закрыть права на всю папку, но для одного документа в этой папке доступ нарезать — тоже самое должно получиться и у нас.
Задача №3
Необходимо скрыть от пользователей ресурсы, к которым у них нет доступа, но самое главное, при наличии прав хотя бы на один документ где то в глубине закрытой для него ветки, делать видимыми элементы ведущие к этому документу (иначе как пользователь до него доберётся?)
Вот собственно базовая функция:
Описание по большей части привёл в комментариях, но если говорить просто — после того как цикл foreach проходит строку и делает что то с данными(в нашем случае просто копирует данные в другой массив, добавляя поле level и точки к имени), он запускает эту же функцию, передав ей uid строки, и поскольку в условии if мы сравниваем его с pid, то следующий запуск однозначно захватит дочерние элементы. Цикл foreach перебирает все строки у которых uid родителя совпадает с переданным значением, поэтому перезапуская саму себя, функция отработает на каждом элементе каждого уровня. Для наглядности, мы так же передаём level увеличивая его на единицу. В итоге мы увидим какой документ какой уровень вложенности имеет.
Выводим массив $array в браузер:
Уже не плохо, не так ли?
А теперь немного усложним нашу функцию:
Разбираем по порядку:
1. Добавлено поле path — для формирования пути, добавляем к значению «/» и имя строки, затем полученное значение передаём в функцию, где история повторяется и на выходе получается путь от корня до элемента.
3. Добавлен индекс $array_idx_lvl = array();. Этот индекс нам так же потребуется позже, смысл таков — результирующий набор складывается не в одну кучу, а с разбивкой на массивы индексируемые по level.
4. Поле Access. Когда функция запускает саму себя, вместе с остальными параметрами она передаёт свою настройку прав $_row[‘access’] дочерям, а далее происходит следующее, проверяются права — если выставлено наследование (inherit), то применяются права родителя, если нет — через in_array проверяем, есть ли указанная в access доменная группа среди групп зашедшего пользователя. Если есть — добавляем в строку allow (разрешить), иначе deny (запрет).
Итоговый результат:
Ну что же, со спуском разобрались, теперь осталось разобраться с подъёмом и заполнением последнего поля view, определяющего видимость элементов. В начале статьи, я говорил для чего это нужно, но можно предположить иную ситуацию. Допустим вы решили привязать древовидный список к навигационному меню сайта, сделанному в виде многоуровневого выпадающего списка с кучей пунктов, и вы просто не хотите, чтобы пользователь, имеющий доступ всего лишь к одному документу ворочал весь этот массив и в объёмном меню искал свой пункт, ведь по сути ему нужно показать всего лишь одну ветку ведущую к нужной кнопке.
Почему здесь нужен проход в обратную сторону? Предположим у пользователя закрыт доступ для всего контента за исключением одного, самого дальнего(на последнем уровне) документа, если подумать, логично было бы брать начало от доступного, и вести его к корню дерева, показывая только нужные элементы.
Что делает эта функция — принимает в качестве параметра uid строки, с которой нужно начать действовать, обращается к этой строке и проверяет видимость. Если в поле view не show(т.е. показывать), а что то другое, проверяет что находится в безопасности, и если там стоит allow(доступ открыт), делает элемент видимым, в противном случае скрытым(hide), затем запускает себя же, передавая свой pid и настройку видимости, а так же переменную $ident увеличенную на 1, тем самым блокируя последующие самозапуски. При втором проходе, по переданному pid находится родительский элемент, выполняется та же проверка, за исключением одного, если от дочернего в переменной $view передано ‘show‘, то не смотря ни на что, текущему элементу так же присвоится show, то есть видимый.
На мой взгляд, работа с ограничителем — самый оптимальный вариант, ибо представьте ситуацию, на 10 уровне у нас 100 документов, для полного обхода всего дерева, нам нужно запускать эту функцию на каждом элементе, т.к. если на последнем уровне мы запустим функцию 100 раз, то выполняя самозапуски, перебор 100 раз дойдёт до корня. Если умножить на 10 уровней — уже получится 1000 циклов, что не есть хорошо, поэтому подъём нужно осуществлять равномерно, уровень за уровнем.
Запускает эту функцию следующий код:
Вот тут как раз и потребовался индекс по уровню. Здесь мы движемся от самого дальнего уровня, заходя в каждый, обрабатывая в нём каждый элемент.
Перед запуском, я намеренно прописал разрешающую группу для пункта «Отчет для налоговой», чтобы наглядно показать что код отрабатывает корректно. Несмотря на то, что доступ к разделу «Бухгалтерская отчетность» закрыт, он видимый.
Вот и собственно всё, думаю с задачей мы справились, основа получена, алгоритм работает, можно применить в реальной системе.
Применение простых алгоритмов в PHP: рекурсивный метод
Однажды я обнаружил очень интересную особенность развития современных web-программистов. Мы смело оперируем фабриками, синглтонами и декораторами, но забываем о такой фундаментальной части программирования, как классические алгоритмы. Ведь если присмотреться к их реализации, то это тоже своего рода паттерны. С институтской скамьи можно вспомнить, к примеру, nested sets, b-tree, сортировку «пузырьком». Реализация многих алгоритмов давно устоялась. А потому я хотел бы посвятить свою статью алгоритмам и их применении в PHP.
Начну я с самого простого — построения древовидной иерархии.
Казалось бы, что тут сложного? В базе данных есть таблица примерно следующего содержания:
Необходимо представить этот массив в виде древовидного меню. Я не буду говорить о том, какими неправильными способами можно решить эту задачу. Единственно верный подход в данном случае — рекурсивный метод.
Алгоритм (паттерн, если так хотите) будет примерно следующим: 0. Создаём объект дерева и выбираем все элементы в таблице. 1. Вызываем метод построения. Он инициализирует сборку массива родительских категорий. Именно этот момент является ноу-хау данного алгоритма. Он позволяет нам организовать изящную рекурсию. 2. Итеративно обходим массив, начиная с нулевого элемента. Выводим информацию о текущем элементе. 3. Увеличиваем уровень погружения. Рекурсивно вызываем метод для дочернего элемента. Если он есть в массиве родительских категорий, то идем к шагу 2, иначе — выходим в шаг-инициализатор. 4. Уменьшаем уровень погружения. Выходим из итерации.
Итак, метод сборки массива категорий будет выглядеть примерно вот так:
Далее напишем наш рекурсивный метод в соответствии с приведенным выше алгоритмом:
Теперь можем вызвать построение дерева, начиная с 0 элемента и 0 уровня. Замечу, что приведённый метод может вызывать построение с любой вложенной ноды и не ограничен по глубине.
А вот как будет выглядеть наше дерево в итоге:
Данный метод применим при построении меню на сайте, каталогов продукции и т. п. У него, разумеется, есть недостатки. При построении достаточно большого каталога метод будет работать довольно долго. Но выигрыш тут в том, что метод можно модифицировать и ограничить не только уровнем входа, но и уровнем погружения. Таким образом, можно достраивать дерево постепенно при запросе пользователей, что решит данную проблему.
Я не рассматриваю здесь проблемы хранения такой структуры, т. к. нас сейчас интересует только обход массива.