Построение дерева из бд на php. Построение дерева категорий на PHP. Рекурсия. Построение дерева в функции _initTree, используем jstree

И так для начала опишу с чем мы будем работать и что нам понадобится.
Система : 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)"); } } }

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

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

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

Сначала давайте посмотрим, что у меня находится в БД

  • id — идентификатор нашей категории
  • category — название категории
  • category_id — идентификатор родительской категории. Можно было назвать parent_id, но так мне привычнее
  • lvl — уровень вложенности категории. Нужен для формирования ...
  • Все, что замазано — вам ни к чему, т.к. там информация нужная для меня лично, и чтобы вас не вводит в заблуждение — я затер эту ненужную инфу.
Вывод категорий в

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

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

Вывод категорий в
  • А вот вам другое решение, когда нужно вывести не в форму, а в список.

    Тут нам lvl уже не нужен, поэтому кода меньше. А ниже результат

    Лишь в далеком 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_",""); }) });
    Получаем простое дерево в духе примеров

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

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

    До версии 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