Деревья. PHP Категории (Дерево категорий) Безграничный category php

Лишь в далеком 2009 году. С тех времен все довольно сильно поменялось. Плагин активно развивается. Страница проекта на гитхабе .

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

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

Что мы имеем?
  • Построение дерева на основе html разметки, json и xml формата
  • Drag & drop с тонкой настройкой возможности перемещения нодов по дереву
  • Динамическое добавление/удаление/изменение нодов
  • Возможность построения нескольких деревьев на одной странице и манипуляция нодами между этими деревьями
  • Возможность присвоения нодам произвольных типов (file, folder, drive, да какой угодно), и указать отдельное поведения для каждого
  • Поддержка тем оформления (на скрине в начале статьи пример того, как это реализовал я)
  • Управление с клавиатуры
  • Использование нативных и кастомных чекбоксов
  • AJAX подгрузка нодов, запоминание состояния дерева
  • Плагин красиво и читабельно написан, позволяет писать дополнительные плагины к нему и без проблем кастомизировать.
  • Удобная система байндингов для обработки событий
  • Мультиязычность
  • Управление анимацией раскрытия
  • Поиск по дереву
  • Кастомное контекстное меню
  • Поддержка браузеров: IE 6+, Firefox 2+, Safari 3+, Opera 9+, Chrome
  • Мини-фича, которая меня особенно порадовала - плагин wholerow - выделения нода во всю ширину. А-ля mac style
Как оно работает? Отличные и понятные примеры можно найти на официальном сайте. .

Чтобы начать работу, нам нужно подключить jquery.js, jstree.js(180КБ в несжатом виде) и папку с темами(подключается плагином сама, достаточно положить ее рядом)

Приведу пример использования дерева на основе JSON.

HTML:
JQuery: $("#tree") .bind("before.jstree", function (e, data) { // байндинг на событие перед загрузкой дерева }) .jstree({ // Подключаем плагины "plugins" : [ "themes","json_data" ], "json_data" : { "ajax" : { "url" : "tree.php", // получаем наш JSON "data" : function (n) { // необходиый коллбэк } } }, }) .bind("select_node.jstree", function(e, data){ // байндинг на выделение нода // укажем ему открытие документа на основе якорей window.location.hash = "view_" + data.rslt.obj.attr("id").replace("node_",""); }) });
Получаем простое дерево в духе примеров

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

И так для начала опишу с чем мы будем работать и что нам понадобится.
Система : PHP 5 и выше, mySQL 4 и выше
Вспомогательные классы : dbsql.class.php (класс для работы с базой данных)
Класс вложенных категорий : classTreeCategory.php (непосредственно основной класс, ниже приведен его листинг и пояснения.

Создаем таблицу в БД, следующей структуры:

Просмотр кода MYSQL

В данной таблице присутствует поле ID — порядковый номер категории, podcat — имеет значение ноль у категорий первого порядка или ID родительской категории, name — название категории.

Пример работы класса, вывод категорий списком с подкатегориями:

Просмотр кода PHP

include ("dbsql.class.php" ) ; include ("classTreeCategory.php" ) ; $DB = new DB_Engine("mysql" , $settings [ "dbHost" ] , $settings [ "dbUser" ] , $settings [ "dbPass" ] , $settings [ "dbName" ] ) ; // подключаемся к БД, с указанием данных доступа $category = new TreeCategory ($DB ) ; // передаем в класс категорий, объект работы с БД $category -> table = "category" ; // название таблицы в БД с категорийми $array = $category -> getCategory () ; // получаем все категории из БД в виде многоуровневого массива, отсортированные и вложенные уже в нужном нам порядке $category -> outCategory ($array , "option" ) ; // подготовка вывода категорий (формируем HTML), передаем массив с категориями echo $category -> html ; // вывод категорий в виде HTML name

Как видно из примера выше, все предельно просто, создаем новый объект $category, устанавливаем с какой таблицей БД работаем: ‘category’, далее получаем из таблицы список всех категорий уже оформленный в виде массива и разложенных в иерархичном порядке, с учетом всех подкатегорий. затем передаем массив в метод outCategory() который формирует для нас готовый HTML код, который остается только вывести в браузер.

Метод outCategory(), как мы видим принимает два параметра @array и @string в первом параметре массив со всеми категориями, а во втором строка содержащая значение option или table , это значени указывает какой тип HTML кода требуется сформировать.
Значение option

Просмотр кода HTML

-категория1 --подкатегория 1 ---подподкатегория 1 -категория 2

Для вставки данного HTML кода в поле select какой либо формы.

Значение table — формирует следующий HTML код:

Просмотр кода HTML

Этот HTML код удобен для вставки в таблицу которая отображает все наши категории подкатегории.

Класс имеет также следующие методы:
deleteItem($id); — удаляет одну категорию, не смотря на вложенные
delCategory($array, $id); — удаляет категорию со всеми вложенными подкатегориями, $array — массив со всеми категориями подготовленный методом $category->getCategory(), $id- номер удаляемой категории
addItem(); — данный метод следует вызывать если вы хотите добавить категорию, при этом этот метод считывает значения из данных переданных методом POST, т.е. из массива $_POST.
$name=$this->PHP_slashes(strip_tags($_POST[‘name’])); // имя категории
$podcat=intval($_POST[‘podcat’]); // ID родительской категории, если указан 0 категория будет в корне.
updateItem() ; — аналогично предыдущему методу, кроме того что данный метод обновляет категорию, её название и уровень вложенности.

table="category"; // запрос на выборку списка категорий, название таблицы * $category->outCategory($category->getCategory()); // подготовка вывода категорий(запрос массива категорий) * echo $category->html; // вывод категорий в HTML name * */ /** * Дамп таблицы с которой ведется работа * * DROP TABLE IF EXISTS `category`; * CREATE TABLE `category` (* `id` int(11) NOT NULL auto_increment, * `podcat` int(11) NOT NULL, * `name` varchar(255) NOT NULL, * PRIMARY KEY (`id`), * KEY `id` (`id`) *) ENGINE=MyISAM DEFAULT CHARSET=utf8; * */ class TreeCategory { /** * Строка запроса в БД */ var $table; /** * Интерфейс работы с БД */ var $DB; /** * Массив категорий с вложенными подкатегориями */ var $arrayCat; /** * Авто-подстчет кол-ва прочерков перед названием категории при выводе */ var $countPodcat; /** * HTML код для вывода категорий с подкатегориями */ var $html; /** * Получаем интерфейс для работы с БД и кладем его в локальные переменную */ function __construct($DB) { $this->DB=$DB; $this->component=$_GET["component"]; } /** * Получает список категорий, сортирует и помещает в массив с вложенными массивами и т.д. * @return array category */ function getCategory () { $all = $this->DB->getAll("SELECT * FROM `{$this->table}` ORDER BY `id` ASC"); $path = array(); if(count($all)>0) { foreach($all as $item): if($item["podcat"]==0)$sort[$item["id"]]=$item; if($item["podcat"]>0) { if(isset($path[$item["podcat"]])) { $str="$sort"; foreach($path[$item["podcat"]] as $pitem): $rep=$item["podcat"]; $str.="[$pitem]"; endforeach; $str.="[{$item["podcat"]}]"; $str.="[{$item["id"]}]"; $str.="=$item;"; eval($str); foreach($path[$item["podcat"]] as $pitem): $path[$item["id"]]=$pitem; endforeach; $path[$item["id"]]=$item["podcat"]; } else { $sort[$item["podcat"]]["sub"][$item["id"]]=$item; $path[$item["id"]]=$item["podcat"]; } } endforeach; } $this->arrayCat=$sort; return $this->arrayCat; } /** * Печатает категории, помещает готовый HTML в $this->html * @param array Массив с категориями и вложенными подкатегориями * @param string Тип генерируемого HTML кода для вывода, option или table */ function outCategory(&$arrayCat, $type="option", $idSel=0) { foreach($arrayCat as $sub) { $this->countPodcat++; $this->outItem($sub, $type); if(!empty($sub["sub"]))$this->outCategory($sub["sub"], $type, $idSel); $this->countPodcat--; } } /** * Вспомогательный метод подготовки HTML кода * @param array Массив с категорией * @param string Тип генерируемого HTML кода для вывода, option или table */ function outItem($sub, $type="option", $idSel=0) { for($i=0;$icountPodcat;$i++) { $out.="-"; } if($idSel==$sub["id"])$se="selected"; else $se=""; if($type=="option")$this->html.=" {$out} {$sub["name"]} "; if($type=="table")$this->html.= {$out} {$sub["name"]} HTML; } function delCategory(&$a_tree,&$id=0) { foreach($a_tree as $sub) { if($sub["id"]$id and isset($sub["sub"]))$this->delCategory($sub["sub"],$id); if($sub["id"]==$id) { $sql="DELETE FROM {$this->table} WHERE id = "$id" LIMIT 1"; $this->DB->execute($sql); if (isset($sub["sub"])) $this->delCategory_process($sub["sub"]); } } } function delCategory_process(&$a_tree) { foreach($a_tree as $sub) { $sql="DELETE FROM {$this->table} WHERE id = "{$sub["id"]}" LIMIT 1"; $this->DB->execute($sql); if(isset($sub["sub"]))$this->delCategory_process($sub["sub"]); } } function updateItem() { $name=$this->PHP_slashes(strip_tags($_POST["name"])); $podcat=intval($_POST["podcat"]); $id=intval($_POST["id"]); $sql="UPDATE `{$this->table}` SET `name` = "{$name}",`podcat` = "{$podcat}" WHERE `id`="{$id}" LIMIT 1; "; $this->DB->execute($sql); } function addItem() { $name=$this->PHP_slashes(strip_tags($_POST["name"])); $podcat=intval($_POST["podcat"]); $id=intval($_POST["id"]); $sql="INSERT INTO `{$this->table}` (`id`,`podcat`,`name`) VALUES ("", "$podcat", "$name");"; $this->DB->execute($sql); } function deleteItem($id) { $id=intval($id); $sql="DELETE FROM `{$this->table}` WHERE `id` = "{$id}" LIMIT 1"; $DB->execute($sql); header("Location: ?component={$this->component}"); } function PHP_slashes($string,$type="add") { if ($type == "add") { if (get_magic_quotes_gpc()) { return $string; } else { if (function_exists("addslashes")) { return addslashes($string); } else { return mysql_real_escape_string($string); } } } else if ($type == "strip") { return stripslashes($string); } else { die("error in PHP_slashes (mixed,add | strip)"); } } }

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

Буду признателен если в комментариях вы предложите собственные варианты решения данной задачи — организации категорий бесконечного уровня вложенности.

май 28 , 2016

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

Что мы собираемся делать и что получим в итоге?

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

Создаем таблицу категорий

Для создания сколь угодно разветвленной структуры категорий нам понадобится всего одна таблица. Назовем ее categories и создадим в ней 4 поля: id, category, parent_id и number. id будет первичным ключом и автоинкрементом, category - название категории, parent_id - это id категории-родителя, number - порядковый номер категории в родительской.

Поясню: например, имеем 3 категории товаров, родительская - ноутбуки, в ней лежат еще 2 - Acer и Lenovo. В таблице это будет выглядеть так:
id, category, parent_id, number
1, Ноутбуки, 0, 1
2, Acer, 1, 1
3, Lenovo, 1, 2
Условимся, что корневые категории будут иметь parent_id = 0. Поле number нужно, чтобы организовать вывод категорий в нужном порядке, мы же не гарантируем, что на первом месте всегда будет Acer, поэтому нужно иметь возможность поменять порядок вывода. В каждой подкатегории создается своя нумерация, начиная с 1.

Чтобы было лучше видно, как строится иерархия, создайте таблицу в mysql и забейте в нее тестовые данные. Ниже sql-код для того и другого. Базу данных по привычке назовем webdevkin.

Структура таблицы категорий use webdevkin; create table categories (id int(10) unsigned not null auto_increment, category varchar(255) not null, parent_id int(10) unsigned not null, number int(11) unsigned not null, primary key (id)) engine = innodb auto_increment = 18 avg_row_length = 963 character set utf8 collate utf8_general_ci; Тестовые данные use webdevkin; SET NAMES "utf8"; INSERT INTO categories(`id`, `category`, `parent_id`, `number`) VALUES (1, "Ноутбуки", 0, 1), (2, "Acer", 1, 1), (3, "Lenovo", 1, 2), (4, "Apple", 1, 3), (5, "Macbook Air", 4, 1), (6, "Macbook Pro", 4, 2), (7, "Sony Vaio", 1, 4), (8, "Смартфоны", 0, 2), (9, "iPhone", 8, 1), (10, "Samsung", 8, 2), (11, "LG", 8, 3), (12, "Vertu", 8, 4), (13, "Комплектующие", 0, 3), (14, "Процессоры", 13, 1), (15, "Память", 13, 2), (16, "Видеокарты", 13, 3), (17, "Жесткие диски", 13, 4);

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

Структура проекта

В корне проекта у нас будет лежать index.html и 4 незатейливых папки: img, css, js и php. В img находится одна картинка loading.gif. Она будет показываться посетителям сайта, пока дерево категорий грузится с сервера. В папке css лежит файл main.css со стилями для нашей страницы и папка jstree, в которой находится стили и картинки для библиотеки jstree.

Папку js разделим по старой памяти на vendor и modules. В первой папке будут библиотеки jquery и jstree. Уточню - jquery требуется не только нам, но и как зависимость для jstree. В папке modules единственный файл main.js - главный js-скрипт приложения. В папку php отправим index.php, который выполнит всю серверную работу.

В этот раз удобнее рассказать сначала о серверной стороне дела, а потом перейти на клиентскую часть. Поэтому смотрим, как вытащить данные из таблицы категорий в нужном формате - файл php/index.php

Серверный код - index.php

Что нам нужно сделать?

  • 1. Подлючиться к базе данных
  • 2. Вытащить список категорий
  • 3. Отправить информацию в браузер

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

// Объявляем нужные константы define("DB_HOST", "localhost"); define("DB_USER", "user"); define("DB_PASSWORD", "password"); define("DB_NAME", "webdevkin");

Затем пишем функцию подключения к базе данных, используем mysqli.

// Подключаемся к базе данных function connectDB() { $errorMessage = "Невозможно подключиться к серверу базы данных"; $conn = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME); if (!$conn) throw new Exception($errorMessage); else { $query = $conn->query("set names utf8"); if (!$query) throw new Exception($errorMessage); else return $conn; } }

Дальше нам нужно вытащить список категорий из таблицы. Здесь нужно немного забежать вперед. Библиотека jstree принимает на вход json. Допустимые форматы описаны на сайте библиотеки jstree.com . Мы возьмем самый удобный для нас и будем отдавать с сервера сразу подготовленные данные. Этот формат выглядит так:

[ { "id" : "ajson1", "parent" : "#", "text" : "Simple root node" }, { "id" : "ajson2", "parent" : "#", "text" : "Root node 2" }, { "id" : "ajson3", "parent" : "ajson2", "text" : "Child 1" }, { "id" : "ajson4", "parent" : "ajson2", "text" : "Child 2" }, ]

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

// Вытаскиваем категории из БД function getCategories($db) { $query = " SELECT id AS `id`, IF (parent_id = 0, "#", parent_id) AS `parent`, category as `text` FROM categories ORDER BY `parent`, `number` "; $data = $db->query($query); $categories = array(); while ($row = $data->fetch_assoc()) { array_push($categories, array("id" => $row["id"], "parent" => $row["parent"], "text" => $row["text"])); } return $categories; }

Здесь мы выполняем обычный запрос select к таблице categories, вытаскиваем 3 нужных поля, попутно немного преобразовывая их к требуемому формату. id прокидываем без изменений, parent_id мы возвращаем как parent, причем для корневых категорий возвращаем #. А поле category будет проходить как text. Данные получены, осталось загнать их в массив, который мы будем конвертировать в json и отдавать браузеру. Это видно в основном потоке скрипта

Try { // Подключаемся к базе данных $conn = connectDB(); // Получаем данные из массива GET $action = $_GET["action"]; switch ($action) { case "get_categories": $result = getCategories($conn); break; default: $result = "unknown action"; break; } // Возвращаем клиенту успешный ответ echo json_encode(array("code" => "success", "result" => $result)); } catch (Exception $e) { // Возвращаем клиенту ответ с ошибкой echo json_encode(array("code" => "error", "message" => $e->getMessage())); }

На что нужно обратить внимание. В нашем конкретном случае передача get-параметра action выглядит лишней, но это до тех пор, пока файл index.php служит для одной-единственной задачи - вернуть список категорий. Вскоре будет опубликована статья с развитием функционала дерева, в частности, реализация drag-and-drop на клиенте и обновление соответствующих данных на сервере. В ней мы увидим, что передача get-параметра в качестве указания необходимого действия - это достаточно удобная тема.

И насчет ответа клиенту. Поле code всегда указывает на статус выполнения запроса - success или error. В случае успеха массив категорий возвращается в поле result, при каких-то неполадках в поле message приходит сообщение об ошибке.

С серверной частью нашего приложения все, переходим на клиента.

Разметка для страницы нашего каталога - index.html

Если Вы уже посмотрели демо приложения, то увидели, что разметка предельно проста. Есть 2 главных контейнера: слева - для дерева категорий, справа - заглушка для списка товаров. В секции head будет такой код:

Webdevkin. Дерево категорий на javascript, php и mysql

Список товаров

И добавим немного разметки в main.css

Body { font-family: Ubuntu; font-size: 16px; font-weight: 400; } .container { position: relative; width: 960px; margin: 0 auto; } .column { display: inline-block; vertical-align: top; } .categories { width: 30%; }

С html/css закончили и теперь переходим к самому интересному - javasctipt-коду создания дерева. Здесь-то мы и соберем воедино весь задуманный функционал.

main.js - инициализация приложения

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

Применим ее и здесь: создадим js-модуль, основанный на замыкании, закэшируем нужные элементы dom, напишем несколько приватных методов и один публичный - метод инициализации приложения.

Каркас модуля "use strict"; // Модуль приложения var app = (function($) { // Инициализируем нужные переменные var ajaxUrl = "/php", ui = { $categories: $("#categories"), $goods: $("#goods") }; // Инициализация дерева категорий с помощью jstree function _initTree(data) { // ... } // Загрузка категорий с сервера function _loadData() { // ... } // Инициализация приложения function init() { _loadData(); } // Экспортируем наружу return { init: init } })(jQuery); jQuery(document).ready(app.init);

Как видим в последней строчке, после загрузки документа мы вывываем метод app.init(), который в свою очередь загружает данные с сервера и передает их в метод создания дерева. В ajaxUrl пишем адрес нашего серверного скрипта, в объекте ui будут закешированы два dom-элемента.

Получаем данные с сервера - метод _loadData() // Загрузка категорий с сервера function _loadData() { var params = { action: "get_categories" }; $.ajax({ url: ajaxUrl, method: "GET", data: params, dataType: "json", success: function(resp) { // Инициализируем дерево категорий if (resp.code === "success") { _initTree(resp.result); } else { console.error("Ошибка получения данных с сервера: ", resp.message); } }, error: function(error) { console.error("Ошибка: ", error); } }); }

Здесь пишем самый обычный ajax-запрос к серверному скрипту, получаем данные с категориями и в случае успеха передаем их функции инициализации дерева _initTree(). Мы помним, что данные с сервера нам приходят в формате json, поэтому укажем сразу dataType: "json". А нужная инфа придет в поле result, поэтому в _initTree мы передаем именно resp.result. Обработать ошибки можно как угодно, для примера просто выкинем их в консоль.

Построение дерева в функции _initTree, используем jstree // Инициализация дерева категорий с помощью jstree function _initTree(data) { ui.$categories.jstree({ core: { check_callback: true, multiple: false, data: data }, plugins: ["dnd"] }).bind("changed.jstree", function(e, data) { var category = data.node.text; ui.$goods.html("Товары из категории " + category); console.log("node data: ", data); }); }

И это все, что нужно! Выглядит визуально самая сложная часть приложения до безобразия просто. Нужно к определенному элементу dom всего лишь применить метод jstree с некоторыми параметрами. В нашем случае передаем сами данные в поле data, multiple: false указывает, что нам не нужно множественное выделение, а check_callback: true говорит о том, что хотим после изменения дерева что-то еще и сделать.

В поле plugins перечисляем в массиве желаемые плюшки. Остановимся на dnd - drag-and-drop - прикрутим возможность изменять структуру дерева мышкой. Это очень удобная штука, но пока не функциональная. Можно сколько угодно играться с деревом в браузере, но после обновления страницы увидим старую структуру каталога. Это логично, потому что данные берутся с сервера, и мы не написали кода для обновления mysql при клиентских событиях. Этому будет посвящена одна из следующих статей, а пока будем баловаться, передвигая категории мышкой в браузере.

И напоследок методом bind связываем событие изменения в дереве с каким-то полезным действием. В нашем примере просто выведем надпись с названием категории, но в реальном приложении здесь стоит подтягивать список товаров с сервера. Откуда взялось category = data.node.text? Откройте консоль браузера и увидите, какие еще данные о выбранном узле нам доступны.

Итого

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

UPDATED: Запилил статью, где показывается, как перемещать отдельные элементы дерева мышкой, методом drag-and-drop, и синхронизировать эти данные с сервером. Немного кода на клиенте и сервере - и вуаля! Ссылка чуть ниже под нумером 4.

Сегодня я расскажу, как на и MySQL создавать иерархическое дерево.

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

Вообще они строятся где только возможно. Главное правильно его построить и применить.

Самое главное, когда строишь иерархическое дерево — это правильная структура базы данных! Для примера рассмотрим структуру базы данных, где хранятся категории сайта. Для простого примера, таблица будет иметь 3 поля:

  • id — ключ категории
  • parent_id — id родительской категории
  • name – название раздела
  • Создадим таблицу, выполнив SQL-запрос в PHPMyAdmin:

    CREATE TABLE `categories` (`id` INT NOT NULL AUTO_INCREMENT , `parent_id` INT NOT NULL , `name` VARCHAR(50) NOT NULL , PRIMARY KEY (`id`));

    Теперь нужно заполнить нашу таблицу записями. В результате, должна получится примерно такая таблица:

    Можно заполнить тестовую таблицу запросом:

    INSERT INTO `categories` (`id`, `parent_id`, `name`) VALUES (1, 0, "Раздел 1"), (2, 0, "Раздел 2"), (3, 0, "Раздел 3"), (4, 1, "Раздел 1.1"), (5, 1, "Раздел 1.2"), (6, 4, "Раздел 1.1.1"), (7, 2, "Раздел 2.1"), (8, 2, "Раздел 2.2"), (9, 3, "Раздел 3.1");

    И сейчас внимание! Дальше по логике нужно делать выборки из БД в цикле для выбора каждой категории и её подкатегории. НО! Ладно, если в БД несколько категорий, что тоже в принципе не правильно. А если сайт — интернет-магазин и у него сотня категорий и столько же подкатегорий? Тогда беда! Неведомое количество запросов к базе данных приведет к замедлению работы сайта или же к полному краху mysql-сервера.

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

    Сделаем запрос и сформируем удобный массив для дальнейшей работы.

    //Выбираем данные из БД $result=mysql_query("SELECT * FROM categories"); //Если в базе данных есть записи, формируем массив if (mysql_num_rows($result) > 0){ $cats = array(); //В цикле формируем массив разделов, ключом будет id родительской категории, а также массив разделов, ключом будет id категории while($cat = mysql_fetch_assoc($result)){ $cats_ID[$cat["id"]] = $cat; $cats[$cat["parent_id"]][$cat["id"]] = $cat; } }

    Выбираем все данные из таблицы categories и формируем ассоциативный массив $cats , ключем будет id родительской категорий.

    Сейчас будем строить дерево. Для построения будем использовать рекурсивную функцию .

    Иерархическое дерево будет иметь такую структуру:

    • Раздел 1
      • Раздел 1.1
        • Раздел 1.1.1
      • Раздел 1.2
    • Раздел 2
      • Раздел 1.1
      • Раздел 1.2
    • Раздел 3
      • Раздел 3.1

    Создадим рекурсивную функцию build_tree() . Она будет строить наше иерархическое дерево абсолютно любой вложенности.

    Function build_tree($cats,$parent_id,$only_parent = false){ if(is_array($cats) and isset($cats[$parent_id])){ $tree = "

      "; if($only_parent==false){ foreach($cats[$parent_id] as $cat){ $tree .= ""; } }elseif(is_numeric($only_parent)){ $cat = $cats[$parent_id][$only_parent]; $tree .= "
    • ".$cat["name"]." #".$cat["id"]; $tree .= build_tree($cats,$cat["id"]); $tree .= "
    • "; } $tree .= "
    "; } else return null; return $tree; }

    Функция принимает массив разделов и id раздела. В цикле перебираем подкатегории и если в них есть еще разделы, тогда функция запускается еще раз с новыми параметрами (новый массив разделов и id раздела, который нужно построить). Так формируется дерево любой вложенности!

    Для построения дерева, в коде прописываем:

    Echo build_tree($cats,0);

    Так вот в два шага мы создали иерархическое дерево разделов сайта и не важно сколько там разделов!

    UPD Если нужно дерево категорий в обратном порядке зная id категории, тогда нужно воспользоваться функцией:

    Function find_parent ($tmp, $cur_id){ if($tmp[$cur_id]["parent_id"]!=0){ return find_parent($tmp,$tmp[$cur_id]["parent_id"]); } return (int)$tmp[$cur_id]["id"]; }

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

    Для построения такого дерева запускаем функцию build_tree c такими параметрами:

    Echo build_tree($cats,0,find_parent($cats_ID,ВАШ_ID_КАТЕГОРИИ));

    Есть вопросы? Задавайте в комментариях

    Получает элементы (термины) указанной таксономии по указанным параметрам.

    До версии WP 4.5 первый параметр get_terms() был названием таксономии или списком таких названий:

    $terms = get_terms("post_tag", [ "hide_empty" => false, ]);

    С версии WP 4.5 название таксономии нужно указывать в элементе массива taxonomy в параметре $args , а не во втором параметре, как это было раньше:

    $terms = get_terms([ "taxonomy" => "post_tag", "hide_empty" => false, ]);

    Обратная совместимость работает (функция понимает устарелый вызов).

    C версии 4.6. появился класс мета запросов WP_Term_Query{} . И теперь функция get_terms() является оберткой для этого класса.

    ✈ 1 раз = 0.015166с = тормоз | 50000 раз = 33.29с = очень медленно | PHP 7.1.11, WP 4.9.5

    Возвращает

    Массив/WP_Error/строка.

    • Массив объектов WP_Term - при успешном получении.
    • array() (пустой массив) - если термины не найдены.
    • WP_Error - если любой из указанных таксономий не существует.
    • Количество найденных терминов (в виде строки) - если fields = count .
    Шаблон использования $terms = get_terms(array("taxonomy" => array("post_tag", "my_tax"), // название таксономии с WP 4.5 "orderby" => "id", "order" => "ASC", "hide_empty" => true, "object_ids" => null, "include" => array(), "exclude" => array(), "exclude_tree" => array(), "number" => "", "fields" => "all", "count" => false, "slug" => "", "parent" => "", "hierarchical" => true, "child_of" => 0, "get" => "", // ставим all чтобы получить все термины "name__like" => "", "pad_counts" => false, "offset" => "", "search" => "", "cache_domain" => "core", "name" => "", // str/arr поле name для получения термина по нему. C 4.2. "childless" => false, // true не получит (пропустит) термины у которых есть дочерние термины. C 4.2. "update_term_meta_cache" => true, // подгружать метаданные в кэш "meta_query" => "",)); foreach($terms as $term){ print_r($term); } Использование get_terms($args, $deprecated); $args(строка/массив)

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

    Среди них обязательным является аргумент $args["taxonomy"] в котором указывается название таксономии или несколько названий таксономий в массиве.

    $deprecated(строка/массив) До версии 4.5 в этом аргументе указывались параметры $args, а первом параметре $args указывались называния таксономий. С версии 4.5 названия таксономий указываются в аргументе $args["taxonomy"] .
    По умолчанию: массив аргументов по умолчанию

    Аргументы параметра $args taxonomy(строка/массив) (обязательный) Название таксономии с которой работать. Можно указать несколько названий в виде массива. С версии WP 4.5, до этого названия таксономий указывались в первом параметре самой функции. number(число) Максимальное количество элементов, которые будут получены. Лимит.
    По умолчанию: все . object_ids(число/массив)

    Укажите тут число или массив чисел, чтобы получить термины, у которых поле object_id таблицы wp_term_relationships совпадет с указанными значениями.

    Обычно в поле object_id находятся ID записей к которым прикреплен термин.

    Include(строка/массив) ID терминов, которые нужно включить в выборку. Если указать этот параметр, то многие другие станут бесполезными. Парсится через wp_parse_id_list() .
    По умолчанию: "" exclude(строка/массив) ID терминов, которые нужно исключить. Парсится через wp_parse_id_list() .
    По умолчанию: "" exclude_tree(строка/массив) ID родительских терминов, которые нужно исключить. Исключена будет вся ветка.
    Парсится через wp_parse_id_list() .
    По умолчанию: "" offset(число) Верхний отступ в запросе. Сколько первых элементов пропустить. Указывать нужно число. По умолчанию без отступов. orderby(строка)

    Поле по которому сортировать результат. Может быть:

    • id или term_id - по ID.
    • name - по названию. По умолчанию.
    • count - по полю count в term_taxonomy - по количеству записей.
    • slug - по альтернативному названию.
    • description - по описанию.
    • term_group - по группе.
    • parent - по полю parent.

    • include - по порядку указанному в параметре $include
    • slug__in - с версии 4.9. В порядке указанном в параметре $slug.
    • meta_value - по значению произвольного поля
    • meta_value_num - по значению произвольного поля, значение будет интерпретироваться как число, а не строка.
    • ключ "meta_query" - в параметре $meta_query мы можем указать параметры запроса по метаполям, и там же указать ключ для конкретного запроса. Этот ключ можно использовать как ключ для сортировки по соответствующему метаполю.
    • none - не сортировать

    По умолчанию: "id"

    order(строка)

    Направление сортировки, указанной в параметре "orderby":

    • ASC - по порядку, от меньшего к большему (1, 2, 3; a, b, c);
    • DESC - в обратном порядке, от большего к меньшему (3, 2, 1; c, b, a).

    По умолчанию: "ASC"

    Hide_empty(логический) Скрывать ли термины в которых нет записей. 1(true) - скрывать пустые, 0(false) - показывать пустые.
    По умолчанию: true fields(строка)

    Какие поля возвращать в результирующем массиве. Может быть:

    • all - Вернуть массив объектов (все данные) - по умолчанию;
    • ids - вернуть массив чисел;
    • names - вернуть массив строк.
    • count - (3.2+) возвращает количество найденных терминов.
    • id=>parent - вернуть массив, где ключ = ID термина, а значение = ID родительского термина.
    • id=>slug - вернуть массив, где ключ = ID термина, а значение = слаг (название для УРЛ) термина.
    • id=>name - вернуть массив, где ключ = ID термина, а значение = название (имя) термина.

    По умолчанию: "all"

    count(логический) true - вернет количество терминов. В этом случае, перебивает параметр fields .
    false - вернет массив объектов терминов. name(строка/массив) Укажите тут строку или массив строк, чтобы получить термины с указанными названиями.
    По умолчанию: "" slug(строка/массив) Укажите тут строку или массив строк, чтобы получить термины с указанными ярлыками (слагами).
    По умолчанию: "" hierarchical(логический)

    Включать ли в результат термины, которые имеют не пустые дочерние термины (в которых есть записи). Т.е. в массив будут включены пустые термины, если у их дочерних терминах есть записи, даже если аргумент " hide_empty " равен true .

    • true - да, включить;
    • false - нет, не включать.

    По умолчанию: true

    Search(строка) Поиск по названиям термина и его ярлыку. Получит термины в названиях которых есть вхождение указанной тут строки. Т.е. запрос выглядит так: LIKE "%search_string%" .
    По умолчанию: "" name__like(строка) Показать термины, в названии которых есть указанная строка. Поиск по строке.
    По умолчанию: "" description__like (строка) Показать термины, в описании которых есть указанная строка. Поиск по строке.
    По умолчанию: "" pad_counts(логический)

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

    pad_counts зависит от параметра parent потому что подсчет записей идет на уровне PHP и если например указать parent=0 , то будут получены только верхние термины и pad_counts не сможет правильно посчитать кол-во записей в дочерних терминах. Чтобы обойти это ограничение, нужно получить все термины, не указывая parent , а потом в PHP удалить ненужные... Вот пример такого кода:

    $terms = get_terms(array("hide_empty" => 0, "orderby" => "name", "order" => "ASC", "taxonomy" => "category", "pad_counts" => 1)); // оставим только термины с parent=0 $terms = wp_list_filter($terms, array("parent"=>0));

    По умолчанию: false

    get(строка)

    Если указать "all" то будут жёстко отключены параметры: childless , child_of , hide_empty , hierarchical и pad_counts . "Жёстко" - значит перебьет текущие установки для этих параметров. "Отключены" - значит им будет установлен параметр false или 0.

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

    // фрагмент кода if ("all" == $args["get"]) { $args["childless"] = false; $args["child_of"] = 0; $args["hide_empty"] = 0; $args["hierarchical"] = false; $args["pad_counts"] = false; }

    По умолчанию: ""

    child_of(число)

    ID родительского термина. Вывести элементы таксономии, которые являются дочерними разделами указанного элемента. Будут получены все уровни вложенности, все дерево.

    Если указаны несколько таксономий, параметр будет проигнорирован.
    По умолчанию: 0

    Parent(число)

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

    Будет получен только первый уровень вложенности, а не все дерево как в параметре child_of . Если указать 0, то будут выведены термины верхнего уровня.
    По умолчанию: ""

    Term_taxonomy_id(число/массив) Укажите тут число или массив чисел, чтобы получить термины, у которых поле term_taxonomy_id совпадет с указанными значениями.
    По умолчанию: "" cache_domain(строка) (3.2+) Позволяет установить уникальные ключ кэша, который будет использоваться в get_terms() при объектном кэшировании (object cache). Например, если вы используется один из фильтров get_terms(), чтобы изменить запрос (например "terms_clausses"), установив "cache_domain" в уникальное значение, позволить не перезаписывать сохраненный кэш для одинаковых запросов.
    По умолчанию: "core" update_term_meta_cache(логический) true - загрузить кэш метаданных, чтобы потом их можно было быстро получить. Кэш подгружается для полученных элементов.
    По умолчанию: true meta_query(массив) Запрос для получения элементов на основе метаданных. Смотрите WP_Meta_Query . meta_key(строка) Получит термины у которых есть указанное метаполе. Можно использовать в связке с meta_value .
    По умолчанию: "" meta_value(строка) Получит термины у которых значение метаполя равно указанному значению. Всегда используется в связке с meta_key .
    По умолчанию: "" suppress_filter(логический) Подавлять работу фильтров или нет? Если выставить в true, то фильтр get_terms просто не будет работать для текущего запроса терминов.
    По умолчанию: false (фильтры работают)

    Примеры #1 Получим массив данных о всех категориях на сайте

    Данные в массиве будут отсортированы по количеству записей (orderby=count) в каждой категории. Категории у которых нет записей все равно будут включены в массив (hide_empty=0).

    $myterms = get_terms("category", "orderby=count&hide_empty=0");

    #2 Выведем на экран список названий всех разделов таксономии "my_taxonomy": $terms = get_terms("my_taxonomy"); if($terms && ! is_wp_error($terms)){ echo "
      "; foreach($terms as $term){ echo "
    • ". $term->name ."
    • "; } echo "
    "; }

    В данном примере каждый $term из цикла foreach($terms as $term) , будет содержать такую информацию:

    => 162 => Здоровье => zdorove => 0 => 170 => my_taxonomy => => 0 => 2

    #3 Вывод рубрик через разделитель

    // получаем все термины из таксономии my_term $args = array("hide_empty=0"); $terms = get_terms("my_term", $args); // собираем их и выводим if (!empty($terms) && !is_wp_error($terms)) { $count = count($terms); $i=0; $term_list = "

    "; foreach ($terms as $term) { $i++; $term_list .= "name) . "">" . $term->name . ""; if ($count != $i) { $term_list .= " · "; } else { $term_list .= "

    "; } } echo $term_list; } /* в итоге получим подобный код: */

    Список изменений
    С версии 2.3.0 Введена.
    С версии 4.2.0 Introduced "name" and "childless" parameters.
    С версии 4.4.0 Introduced the ability to pass "term_id" as an alias of "id" for the orderby parameter. Introduced the "meta_query" and "update_term_meta_cache" parameters. Converted to return a list of WP_Term objects.
    С версии 4.5.0 Changed the function signature so that the $args array can be provided as the first parameter. Introduced "meta_key" and "meta_value" parameters. Introduced the ability to order results by metadata.
    С версии 4.8.0 Introduced "suppress_filter" parameter.
    Код get terms : wp-includes/taxonomy.php WP 5.2.3