adsPlace_1

Войти или Зарегистрироваться

Hosting Ukraine

Построение дерева категорий на PHP. Рекурсия

Построение дерева категорий на PHP. Рекурсия

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

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

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

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

  1. id — ключ категории
  2. parent_id — id родительской категории
  3. 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` )
  );

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

Построение дерева категорий на PHP. Рекурсия

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

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 родительской категорий.

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

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

<ul>
	<li>Раздел 1
		<ul>
			<li>Раздел 1.1
				<ul>
					<li>Раздел 1.1.1</li>
				</ul>
			</li>
			<li>Раздел 1.2</li>
		</ul>
	</li>
	<li>Раздел 2
		<ul>
			<li>Раздел 1.1</li>
			<li>Раздел 1.2</li>
		</ul>
	</li>
	<li>Раздел 3
		<ul>
			<li>Раздел 3.1</li>
		</ul>
	</li>
</ul>

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

function build_tree($cats,$parent_id,$only_parent = false){
    if(is_array($cats) and isset($cats[$parent_id])){
        $tree = '<ul>';
        if($only_parent==false){
            foreach($cats[$parent_id] as $cat){
                $tree .= '<li>'.$cat['name'].' #'.$cat['id'];
                $tree .=  build_tree($cats,$cat['id']);
                $tree .= '</li>';
            }
        }elseif(is_numeric($only_parent)){
            $cat = $cats[$parent_id][$only_parent];
            $tree .= '<li>'.$cat['name'].' #'.$cat['id'];
            $tree .=  build_tree($cats,$cat['id']);
            $tree .= '</li>';
        }
        $tree .= '</ul>';
    }
    else return null;
    return $tree;
}

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

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

echo build_tree($cats,0);

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

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

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

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

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

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

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

Последние из рубрики

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

    1. Аватар пользователя Сергей

      Сергей

      17:12 28.10.2012

      А как в данном случае будет выглядеть код для select? Чтобы все загружалось в Select, и желательно чтобы были optgroup

      ответить
    2. Аватар пользователя Виталий

      Виталий

      09:04 23.12.2012

      Отличная статья, спасибо. Пиши еще!

      ответить
    3. Аватар пользователя Vlado

      Vlado

      16:44 06.01.2013

      А как при добавлении в базу узнать, какое значение parent_id нужно под конкретного родителя

      ответить
    4. Аватар пользователя Roman

      Roman

      20:32 22.01.2013

      А как добавить сортировку? К примеру, есть еще одно поле в табл. ‘position’. Сейчас, как я понимаю, сортировка идет по id, т.е. в порядке добавления строк.

      ответить
      • Аватар пользователя Максим Никифоров
        $result=mysql_query("SELECT * FROM  categories ORDER BY position");
        ответить
        • Аватар пользователя Roman

          Roman

          15:31 24.01.2013

          Большое спасибо за ответ.
          Если можно, еще один вопрос. А как в таком случае создавать линки на каталоги? В БД вносить полные пути, например запись для Раздела 1.1.1
          «Раздел_1/Подраздел_1.1/Подраздел_1.1.1/»
          или лучше вносить запись только для данного подраздела, т.е. «Подраздел_1.1.1». Если второе, то как их потом объединить?

          ответить
        • Аватар пользователя roman66

          roman66

          02:47 12.11.2016

          Максим Никифо$LastName = htmlspecialchars($_POST[‘LastName’]);
          $name = htmlspecialchars($_POST[‘name’]);
          $floor = $_POST[‘floor’];
          $email = mysql_real_escape_string($_POST[’email’]);
          $password = md5 ($_POST[‘password’]);
          $phone = $_POST[‘phone’];
          $data = $_POST[‘data’];
          $country = $_POST[‘country’];
          $region = $_POST[‘region’];
          $city = $_POST[‘city’];
          $rdate = date («Y-m-d H:i:s»);

          $regex=»/^[\w\.\-]+@[a-zA-Z0-9]+\.[a-zA-Z]{2,6}$/»;

          if (preg_match($regex, $email))
          {
          $password=md5($password);
          $activation=md5($email.time());
          $count=mysqli_query($link, «SELECT id FROM user WHERE email=’$email'»);
          if (mysqli_num_rows($count)<1)
          {
          mysqli_query($link, "INSERT INTO user (LastName, name, floor, email, password, phone, data, country, region, city, rdate, activation) VALUES('$LastName', '$name', '$floor', '$email', '$password', '$phone', '$data', '$country', '$region', '$city', '$rdate', '$activation')");
          include "send_mail.php";
          $to=$email;
          $subject="Проверка E-mail-а";
          $body="Здравствуйте! Активация! Пожалуйста перейдите по ссылке для активанции вашего аккаунта. «.$base_url.»activation/».$activation.»«;
          send_mail($to, $subject, $body);
          $msg=»Регистрация прошла успешно! Пройдите активацию через email.»;
          }
          else
          {
          $msg=»Данный email уже занят.»;
          }
          }
          elseров,

          ответить
    5. Аватар пользователя Steeply

      Steeply

      08:08 03.02.2013

      это все круто конечно. Но работает только если у вас каталогов не больше 100

      У меня в базе 40 тысяч записей. Такой метод не актуальный, ибо расчет происходит почти 10 минут. Ни один человек не будет ждать при переходе по страницам когда случится «чудо» и сервер просчитает ЭТО.

      Спросите, почему так много? Мне нужно построить дерево регионов — городов. по схеме МИР — СТРАНА — ОБЛАСТЬ(штат/округ) — населенный пункт. Что бы не занимало много место, потомки прячутся во вложенный список родителей, и отображаются только по клику на родителя (список раскрывается).

      Как решить эту задачу, еще не придумал, есть идеи?

      ответить
      • Аватар пользователя Roman

        Roman

        09:40 04.02.2013

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

        ответить
      • Аватар пользователя Максим Никифоров

        Нужно всегда делать выборку именно той инфы, которая нужна пользователю. Технология ajax Вам в помощь.
        Можно выборку разделить на этапы используя ajax. Выбираете сначала континент(по выбору идет ajax-запрос с id континента и возвращается список стран, его вставляете в следуйщий selest), при выборе страны подгружаете уже регионы и т.д. Так будут запросы намного меньше, и не будет выборок не нужной пользователю информации.

        ответить
    6. Аватар пользователя Vladimir

      Vladimir

      11:13 30.04.2013

      А как привязать нужный добавляемый контент к этим категориям ?

      ответить
    7. Аватар пользователя Колянъ

      Колянъ

      21:54 06.06.2013

      где тут рекурсия? function build_tree

      ответить
      • Аватар пользователя Никифоров Максим

        Колянъ,
        Ну вообще то да. Рекурсивная функция — это функция которая вызывает сама себя. Что такое рекурсия Вы можете почитать еще здесь

        ответить
      • Аватар пользователя Denis

        Denis

        11:11 05.02.2019

        Подскажи, как сделать так, что бы при формирование списка выводились только родители. А уж после нажатия на Родителя открывался дочерний пункт.
        Пример:
        Категория 1
        Категория 1.1
        Категория 1.2
        Категория 2
        Категория 2.1
        Категория 2.2
        Нужно, чтобы изначально выводились только:
        Категория 1
        Категория 2

        ответить
    8. Аватар пользователя Колька

      Колька

      04:02 25.11.2013

      Блин, люди такие глупые вопросы задают)))

      ответить
    9. Аватар пользователя Guffi

      Guffi

      23:23 26.11.2013

      Добрый вечер,

      есть идеи как вычислить id корневого элемента?

      Т.е. обращаясь к элементу с id 7 вычислить 2?

      ответить
      • Аватар пользователя Максим Никифоров

        Guffi, не понял Ваш вопрос. Сформулируйте задачу полностью.

        ответить
        • Аватар пользователя Guffi

          Guffi

          10:42 27.11.2013

          Добрый день, спасибо за ответ.

          Идея в том чтобы разворачивать дерево имея id раздела.
          Например:
          Вызывая раздел — Раздел 1.1.1
          мы должны построить дерево —
          Раздел 1

          Раздел 1.1

          Раздел 1.1.1

          Раздел 1.2

          ответить
          • Аватар пользователя Максим Никифоров

            Guffi, смотрите апдейт статьи. Должно работать. Обратите внимание, что обновилась функция build_tree и добавился массив в цикле категорий из базы

            ответить
          • Аватар пользователя Guffi

            Guffi

            13:32 27.11.2013

            Максим Никифоров, Спасибо!

            ответить
    10. Аватар пользователя Андрей

      Андрей

      13:47 28.11.2013

      А как можно развернуть дерево только до раздела:
      Раздел 1
      Раздел 1.1
      Раздел 1.1.1

      ответить
    11. Аватар пользователя Евгений

      Евгений

      18:42 02.12.2013

      Максим, спасибо, отлично написал! Вот только html на выходе выглядит не особо. может быть стоит еще сделать вывод c «/n»
      что бы все выглядело как в структуре в самом начале статьи, а не в одну строку…

      ответить
      • Аватар пользователя Максим Никифоров

        Евгений, а в чем смысл? Для более наглядной структуры html на странице можно использовать отладочные консоли браузеров. Они разложат код как надо. Например, firebug

        ответить
    12. Аватар пользователя Николай

      Николай

      12:34 18.04.2014

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

      ответить
    13. Аватар пользователя prst

      prst

      13:29 18.04.2014

      Так и не понял как построить обратный путь до начала каталога от заданной рубрики. Разве рубрика и категегория не одно и тоже? Можно как то по-подробнее расписать этот момент, очень желательно с примером. Спасибо

      ответить
      • Аватар пользователя Максим Никифоров

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

        echo build_tree($cats,0,find_parent($cats_ID,ВАШ_ID_КАТЕГОРИИ));
        
        ответить
        • Аватар пользователя Марат

          Марат

          00:27 08.09.2015

          Почему-то не работает у меня этот вариант. выводит обычное полное дерево. Мне нужно чтобы сначала отображалась предыдущая категория от указанной изначально и затем уже все дерево ниже указанно. Это для построения дерева партнеров для рефералки. Подскажите, может можно что-то добавить в основную функцию, чтоб не вызывать 2 поочередно. Спасибо.

          ответить
        • Аватар пользователя имя

          имя

          19:11 04.06.2017

          а можно ли вписать результат дерево в массив а не в ul li

          ответить
    14. Аватар пользователя Николай

      Николай

      14:56 18.04.2014

      Спасибо

      ответить
    15. Аватар пользователя Александр

      Александр

      19:57 05.05.2014

      Спасибо за статью =)

      Скажите пожалуйста, а есть ли способ написать рекурсию используя классы CMS e107?

      ответить
      • Аватар пользователя Максим Никифоров

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

        ответить
    16. Аватар пользователя Максим

      Максим

      18:13 20.05.2014

      «А как в данном случае будет выглядеть код для select? Чтобы все загружалось в Select, и желательно чтобы были optgroup»
      Присоединяюсь к вопросу! Помогите!

      ответить
    17. Аватар пользователя Олег

      Олег

      19:02 26.05.2014

      Максим Никифоров Здравствуйте.
      В посте 25 вы привели пример как выбрать одну нужную ветку зная ее ID.
      Вопрос ровно наоборот.
      Скажите пожалуйста как выбрать все ветки кроме одной, зная ее ID.
      Спасибо!

      ответить
      • Аватар пользователя Максим Никифоров

        Олег,
        Попробуйте поменять запрос выборки на

        $result=mysql_query("SELECT * FROM  categories WHERE id<>".$noCatID." OR parent_id<>".$noCatID);
        

        Должно сработать.

        Или, если таких категорий много, тогда лучше исключать из самого массива с категориями:

        $noCatArray = ('1','2');  //Не показывать категории с id=1,2
        //В цикле формируем массив разделов, ключом будет id родительской категории, а также массив разделов, ключом будет id категории
        while($cat =  mysql_fetch_assoc($result)){
        	if(!in_array($cat['id'],$noCatArray)){
        		$cats_ID[$cat['id']][] = $cat;
        		$cats[$cat['parent_id']][$cat['id']] =  $cat;
        	}        
        }
        
        ответить
    18. Аватар пользователя Александр

      Александр

      22:21 15.08.2014

      Доброго времени суток.

      Есть массив вида:

      [0] => auto
      [1] => auto/bmw
      [2] => auto/bmw/x5
      [3] => auto/gmc
      [4] => moto

      Можно ли создать список вложенности по url ?

      ответить
    19. Аватар пользователя Александр

      Александр

      10:38 16.08.2014

      Категория auto — родитель bmw. Вопрос в том, как создать список вложений вида

      auto
      bmw
      x5
      gmc
      moto

      Т.е если урл auto/bmw, значит auto — родитель bmw

      ответить
      • Аватар пользователя Максим Никифоров

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

        ответить
        • Аватар пользователя Александр

          Александр

          16:47 17.08.2014

          У меня есть массив:

          Array
          (
              [url] => Array
                  (
                      [0] => auto/bmw/x5
                      [1] => auto
                      [2] => auto/bmw
                      [3] => moto
                      [4] => auto/gmc
                  )
          
              [sort] => Array
                  (
                      [0] => 1
                      [1] => 2
                      [2] => 1
                      [3] => 1
                      [4] => 2
                  )
          
          )
          

          После обработки, он должен быть:

          moto
          auto
          -auto/bmw
          —auto/bmw/x5
          -auto/gmc

          ответить
          • Аватар пользователя Александр

            Александр

            17:00 17.08.2014

            Александр,
            Т.е это рекурсия с сортировкой, но без id и pid

            ответить
          • Аватар пользователя Максим Никифоров

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

            ответить
          • Аватар пользователя Александр

            Александр

            19:43 17.08.2014

            Максим Никифоров,

            Ищу алгоритм по принципу папок.
            Если написать auto/bmw/x5, то понятно, что есть папка auto в которой лежит bmw и внутри которой x5

            Я могу, конечно, просто вывести их multisort.
            array_multisort($arr[url],$arr[sort]);
            В этом случаи, Будет видно, что под чем.

            Зависимость и вложенность можно проверить, содержит ли в себе auto/ или как то иначе.

            В целом я и спрашиваю, как это можно реализовать?

            ответить
    20. Аватар пользователя Andrey

      Andrey

      23:16 10.09.2014

      Годная функция. Зачет.

      ответить
    21. Аватар пользователя Сергей

      Сергей

      14:44 08.10.2014

      Здравусвуйте, спасибо за статью! У меня вопрос, так и не нашел верного решения, пытаюсь теперь посмотрить ЧПУ, мне нужно зная id подкатегории «Категория1.1.1» чтобы он мне вывел.
      Категория1/Категория1.1/Категория1.1.1/

      function get_url($res_category, $category){
      //$res_category = полная выборка таблицы категорий
      //$id_category = id категории
      for($i=0;$i<count($res_category);$i++){
          if($res_category[$i]['url_name'] == $category){
              $id_category = $i;
              $name_cat = $res_category[$i]['name'];
          }
      }
      
          function url($res_category, $id_category){
              $f='/';
              if($res_category[$id_category]['parent_id']>0){
                  for($i=0;$i<count($res_category);$i++){
                      if($res_category[$i]['id'] == $res_category[$id_category]['parent_id']){
                          $f .= $res_category[$i]['url_name'].'/';
                          $f .= url($res_category, $i);
                      }
                  }
              }
              return $f;
          }
      
      $arr = $name_cat.url($res_category, $id_category);
      return $arr;
      }
      

      Вроде как работает, если ее использовать 1 раз.
      Если функцию положить в цикл get_url
      Fatal error: Cannot redeclare url() (previously declared in)

      Мне кажется что мой код немного с душком:) Как мне все правильно реализовать?

      ответить
      • Аватар пользователя Максим Никифоров

        Сергей, при рекурсии, вызывается get_url(). Вы же объявляете функцию url() внутри функции get_url(). Ошибка «Fatal error: Cannot redeclare url() (previously declared in)» означает, что функция url() уже была объявлена раньше. Вынесете функцию url() из функции get_url(), и ошибки не будет

        ответить
        • Аватар пользователя Сергей

          Сергей

          16:04 08.10.2014

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

          ответить
          • Аватар пользователя Максим Никифоров

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

            ответить
          • Аватар пользователя Сергей

            Сергей

            16:20 08.10.2014

            Максим Никифоров, в голову не приходят другие подходы, спасибо большое! За развернутые ответы!

            ответить
        • Аватар пользователя roman66

          roman66

          02:53 12.11.2016

          Максим Никифоров, а подарок

          ответить
    22. Аватар пользователя Алим

      Алим

      17:25 02.11.2014

      Здравствуйте. А как можно сделать вызов подкатегории нажатием на категорию?

      ответить
    23. Аватар пользователя SvetlanaTata

      SvetlanaTata

      15:49 15.12.2014

      Статья вроде понятная, но не получается перенести на свой пример. Мне нужно также сделать разделы и подразделы. Есть таблицы типы товаров (id, name), виды товаров (id, name) и виды типов (id, tip,vid). по аналогии с вашим примером, я работаю с таблицей виды типов (id, tip,vid), поле тип — родительский id, vid — подкатегория. И в результате ничего не выводится. Где у меня ошибка? или таблицы лучше переделать?

      ответить
      • Аватар пользователя Максим Никифоров

        SvetlanaTata, побробуйте такой вариант выборки:

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

          roman66

          02:55 12.11.2016

          Максим Никифоров, не понел

          ответить
        • Аватар пользователя roman66

          roman66

          02:56 12.11.2016

          Максим Никифоров, а адреса когда выдаютса

          ответить
    24. Аватар пользователя Дмитрий

      Дмитрий

      10:25 02.01.2015

      Добрый день! Скажите, каким образом добавлять/выводить подподкатегории? Здесь я вижу только родительская категория и подкатегория, а дальше углубится как?

      ответить
    25. Аватар пользователя Антон

      Антон

      16:01 11.02.2015

      Подскажите как добавить класс к div, если активна подкатегория первого или второго уровня вложенности?

      // Category Menu
      	$catSelect = db_query("SELECT categoryID, name, parent, sort_order, subcount FROM ".DB_PRFX."categories ORDER BY sort_order ASC");
      	$cats = array();
      	while ($cat = db_fetch_assoc($catSelect)) {
      		$cats_ID[$cat['categoryID']][] = $cat;
      		$cats[$cat['parent']][$cat['categoryID']] =  $cat;
      	}
      	function build_tree($cats,$parent_id,$only_parent = false, $level = 0){
      		
      	    if(is_array($cats) and isset($cats[$parent_id])){
      
      	    	if ($parent_id > 1) $tree = '<div class="sub level'.$level.'">';
      	        if($only_parent==false){
      	            foreach($cats[$parent_id] as $cat){
      					// Проверяем текущий адрес и если он равен категории, то добавляем класс
      	    			if ($_SERVER[REQUEST_URI] == '/category_'.$cat['categoryID'].'.html') { $open = " open"; $active = ' active'; }
      	    			else { $open = ''; $active = ''; }            	
      	    			if ($cat['subcount'] > 0) $tree .= '<div class="dtree'.$open.' has_sub">';
      					else $tree .= '<div class="dtree">';
      	                $tree .= '<a class="cat_'.$cat['categoryID'].$active;
      	                if ($cat['categoryID'] == 254 || $cat['categoryID'] == 219 || $cat['categoryID'] == 278 || $cat['categoryID'] == 280 || $cat['categoryID'] == 372 || $cat['categoryID'] == 427) $tree .= ' cat_bg_but';
      	                $tree .= '" href="/category_'.$cat['categoryID'].'.html">'.$cat['name'].'</a>';
      	                $tree .=  build_tree($cats,$cat['categoryID'], false, $level+1);
      	                $tree .= '</div>';
      	            }
      	        }elseif(is_numeric($only_parent)){
      	            $cat = $cats[$parent_id][$only_parent];
      	            $tree .= '<li><a href="/category_'.$cat['categoryID'].'.html">'.$cat['name'].'</a>';
      	            $tree .=  build_tree($cats,$cat['categoryID']);
      	            $tree .= '</li>';
      	        }
      	        if ($parent_id > 1) $tree .= '</div>';
      
      	    }
      	    else return null;
      	    return $tree;
      	}
      	$result = build_tree($cats, 1);
      	$smarty->assign('allCategory', $result);
      
      
      ответить
    26. Аватар пользователя Вячеслав

      Вячеслав

      11:45 18.02.2015

      Спасибо за решение)))) Очень помогло!!!!

      ответить
    27. Аватар пользователя Fif

      Fif

      17:17 08.04.2015

      Подскажите а как использовать при построении вывода категорий с постраничной навигацией? Уменя выводит категории правильно если без пагинации.

      ответить
    28. Аватар пользователя ezman

      ezman

      01:00 11.04.2015

      Привет! Грамотный пост, грамотное описание! Молодчина. Забрал себе в проект)
      Есть один вопрос. Не могу прикрутить кое-что.
      Например, вывожу я дерево разделов. У Каждого раздела есть свой id ( не путать с parent_id)
      Мне нужно чтобы при построении дерева, выводилось определенное название (name) и выделялось жирным шрифтом.

      function build_tree($cats,$dillermy,$only_parent = false){
      					if(is_array($cats) and isset($cats[$dillermy])){
      						$tree = '<ul class="treeCSS">';
      						if($only_parent==false){
      							foreach($cats[$dillermy] as $cat){
      								$c = $GLOBALS['boldid']; // определяю id категории, которую нужно выделить жирным
      								$catm = $cat['id']; // присваиваю id категории другой переменной
      								if ($catm == $c) { $bold= '<strong>'; $bold1= '</strong>'; } // в случае совпадения присваиваю переменным жирность)))))
      								$tree .= '<li>'.$bold.''.$cat['dilname'].''.$bold1; //.' #'.$cat['id']; // выделяю жирным нужный раздел
      								$tree .= '<li>'.$bold.''.$cat['dilname'];
      								$tree .=  build_tree($cats,$cat['id']);
      								$tree .= '</li>';
      							}
      						}elseif(is_numeric($only_parent)){
      							$cat = $cats[$dillermy][$only_parent];
      							$tree .= '<li>'.$cat['dilname']; //.' #'.$cat['id'];
      							$tree .=  build_tree($cats,$cat['id']);
      							$tree .= '</li>';
      						}
      						$tree .= '</ul>';
      					}
      					else return null;
      					return $tree;
      				}

      Этот фокус работает только для отображения жирным подразделов с уровнем вложенности 2.
      Если больше срабатывает дважды. Вот скриншот http://joxi.ru/V2VeNzJuKb0Omv
      А вот этот же скриншот но с выводом id каждого пункта. http://joxi.ru/xAeenxwug9YDAy
      На этих примерах должен быть жирным только раздел ООО «Рога и Копыта» — это id == 5
      Как можно заставить работать код, как надо? =)
      Заранее благодарен за подсказки!!

      ответить
      • Аватар пользователя ezman

        ezman

        01:03 11.04.2015

        ezman, 10 строчка вписана в моём комментарии ошибочно.

        ответить
    29. Аватар пользователя Евгения

      Евгения

      22:15 19.04.2015

      Здравствуйте, а от куда приходят

      $tmp  

      и

      $cur_id

      ? и что они означают?

      ответить
    30. Аватар пользователя prst

      prst

      13:17 05.05.2015

      Друг! Подскажи, как реализовать хранение и построение меню, если некоторые пункты могут иметь не одного а несколько родителей?

      ответить
      • Аватар пользователя ezman

        ezman

        19:35 05.06.2015

        prst, Покажи пример структуры базы.

        ответить
    31. Аватар пользователя Алекс

      Алекс

      13:11 21.06.2015

      Отличная статья!

      ответить
    32. Аватар пользователя Максим

      Максим

      10:30 14.07.2015

      Уважаемый автор!

      Подскажите, а как быть, если у меня вместо одной колонки parent_id есть несколько колонок parent для каждого уровня вложенности? Дело в том, что таблиц несколько (для каждого элемента иерархии своя). Запросом нужные данные выводятся из всех таблиц и у меня получается грубо говоря раздел, категория, подкатегория и 2 колонки parent для категории и подкатегории. Как изменится код?

      ответить
    33. Аватар пользователя andrey

      andrey

      07:29 21.07.2015

      Самое реальное и понятное построение дерево, из всех которые раньше встречал. Спасибо!

      ответить
    34. Аватар пользователя Владимир

      Владимир

      09:13 08.08.2015

      спасибо!!! очень помогли)

      ответить
    35. Аватар пользователя Ixman

      Ixman

      21:05 08.08.2015

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

      ответить
    36. Аватар пользователя Denis

      Denis

      16:00 26.10.2015

      Спасибо большое за статью, применяю данную функцию для вывода меню. Не могу понять, как доработать, чтобы выводить для хлебной крошки урл текущей страницы. Фактически надо вывести массив, зная id текущей категории (Подраздел 2.1.2), например

      Подраздел 2 -> Подраздел 2.1 -> Подраздел 2.1.2

      ответить
    37. Аватар пользователя Рус

      Рус

      08:54 13.11.2015

      На сколько оптимальным будет это решение для иерархии комментариев?

      ответить
    38. Аватар пользователя Анна

      Анна

      18:26 23.12.2015

      Спасибо, Максим! Очень полезная вещь, ваш код. Подскажите пожалуйста как мне подсчитать количество конкретной родительской категории , например по Вашему примеру допустим в разделе Раздел 2, есть две вложены под категории Раздел 1.1 и Раздел 1.2, должно по идеи получиться 2 шт., но почему то считает что в ней 1 шт. я пробую делаю так:

      // Выбираем данные из БД
      $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;
      		
      		$cat_col = $cat['parent_id'];
      		
      		$sql_col_categories = mysql_query("SELECT COUNT(*) FROM categories WHERE parent_id='".$cat_col."'");
      		$col_parent_id = mysql_fetch_row($sql_col_categories);
      		$col_parent_id_f = $col_parent_id[0];
      	}
      }
      echo $col_parent_id_f;
      

      Но ни фига не выходит!!! Смысл в том что нужно вывести количество всех вложенных под категорий., если указано, что нужно выбрать только 2-ой раздел и не важно сколько у него у этого будет уровней вложенности, нужно пересчитать все кроме его самого, то есть 2-го.
      За ранние благодарю Максим,

      echo build_tree($cats,2);
      

      Да и при том что build_tree($cats,2); то показывает всё правильно, но у меня ни как не получается посчитать, хелпми плиз!!!

      ответить
    39. Аватар пользователя Манук

      Манук

      20:57 07.02.2016

      Спасибо!

      ответить
    40. Аватар пользователя Алладин

      Алладин

      16:48 20.02.2016

      Спасибо, Вам! Очень полезная вещь, но интересует один вопрос.

      Как удалить категорию? т.е удалить то её не проблема, но если в категории есть под категория или целая ветка то как сделать так что бы ветка сместилась на 1 пункт выше. Допустим у нас дерево:

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

      и мы хотим удалить -Раздел 1.1 то в этом случае раздел Раздел 1.1.1 и все его потомки должны будут сместиться на уровень и должно получиться:

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

      дальше мы решили удалить Раздел 2 и у нас должно получиться:

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

      подскажите пожалуйста необходимые запросы к БД или логику на PHP, спасибо!

      ответить
      • Аватар пользователя Максим Никифоров

        Алладин,

        // ID категории для удаления
        $id = 4;
        // Определяем ID родительской категории
        $get_parent_id = mysql_query('SELECT parent_id FROM categories WHERE id='.$id);
        $get_parent_id = mysql_fetch_array($get_parent_id);
        $parent_id = $get_parent_id['parent_id'];
        // Удаляем категорию $id
        mysql_query('DELETE FROM categories WHERE id='.$id);
        // Обновляем данные для вложенных категорий первого уровня категории $id (назначем новое значение ID родительской категории)
        mysql_query('UPDATE categories SET parent_id='.$parent_id.' WHERE parent_id='.$id);
        
        ответить
      • Аватар пользователя Максим Никифоров

        Алладин, код для визуального теста:

        $mysql = mysql_connect("localhost", "root", "");
        mysql_select_db('test');
        
        function build_tree($cats,$parent_id,$only_parent = false){
            if(is_array($cats) and isset($cats[$parent_id])){
                $tree = '<ul>';
                if($only_parent==false){
                    foreach($cats[$parent_id] as $cat){
                        $tree .= '<li>'.$cat['name'].' #'.$cat['id'];
                        $tree .=  build_tree($cats,$cat['id']);
                        $tree .= '</li>';
                    }
                }elseif(is_numeric($only_parent)){
                    $cat = $cats[$parent_id][$only_parent];
                    $tree .= '<li>'.$cat['name'].' #'.$cat['id'];
                    $tree .=  build_tree($cats,$cat['id']);
                    $tree .= '</li>';
                }
                $tree .= '</ul>';
            }
            else return null;
            return $tree;
        }
        
        //Выбираем данные из БД
        $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;
          }
        }
        
        echo build_tree($cats,0);
        
        // ID категории для удаления
        $id = 4;
        // Определяем ID родительской категории
        $get_parent_id = mysql_query('SELECT parent_id FROM categories WHERE id='.$id);
        $get_parent_id = mysql_fetch_array($get_parent_id);
        $parent_id = $get_parent_id['parent_id'];
        // Удаляем категорию $id
        mysql_query('DELETE FROM categories WHERE id='.$id);
        // Обновляем данные для вложенных категорий первого уровня категории $id (назначем новое значение ID родительской категории)
        mysql_query('UPDATE categories SET parent_id='.$parent_id.' WHERE parent_id='.$id);
        
        //Выбираем данные из БД
        $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;
          }
        }
        
        echo build_tree($cats,0);
        
        ответить
    41. Аватар пользователя Bob

      Bob

      14:56 06.06.2016

      Добрый день Максим сорри за тупой вопрос дерево сделал а как определить глубину мне нужно 5 уровней и не более

      спасибо

      ответить
    42. Аватар пользователя Евгений

      Евгений

      14:06 25.09.2016

      Можно ли как то прописать стили для этого и сделать аккордеон?:)

      ответить
    43. Аватар пользователя yura

      yura

      12:03 22.10.2016

      а как развернуть всю ветку?
      К примеру у меня 3 уровня вложенности
      При клике на 1-ый уровень передается id=1, как мне проверить есть ли у этого родителя потомки что бы вывести информацию
      Делаю вывод при помощи ajax

      вот вывод категории с передачей id элемента на который кликнули

      //Выбираем данные из БД
      $result=$db_tel->con->query("SELECT * FROM  divis");
      //Если в базе данных есть записи, формируем массив
      if   (mysqli_num_rows($result) > 0){
          $cats = array();
      //В цикле формируем массив разделов, ключом будет id родительской категории, а также массив разделов, ключом будет id категории
          while($cat =  mysqli_fetch_assoc($result)){
              $cats_ID[$cat['id']][] = $cat;
              $cats[$cat['parent_id']][$cat['id']] =  $cat;
          }
      }
      
      
      function build_tree($cats,$parent_id,$only_parent = false){
          if(is_array($cats) and isset($cats[$parent_id])){
              $tree = '<ul class="ul-treefree ul-dropfree">';
              if($only_parent==false){
                  foreach($cats[$parent_id] as $cat){
                      $tree .= '<li><a href="javascript:void(0)" onClick = "getdetails(id='.$cat['id'].')">'.$cat['name'].' #'.$cat['id'];
                      $tree .=  build_tree($cats,$cat['id']);
                      $tree .= '</a></li>';
                  }
              }elseif(is_numeric($only_parent)){
                  $cat = $cats[$parent_id][$only_parent];
                  $tree .= '<li><a href="javascript:void(0)" onClick = "getdetails(id='.$cat['id'].')">'.$cat['name'].' #'.$cat['id'];
                  $tree .=  build_tree($cats,$cat['id']);
                  $tree .= '</a></li>';
              }
              $tree .= '</ul>';
          }
          else return null;
          return $tree;
      }
      
      echo build_tree($cats,0);
      

      Вот ajax:

      <script>
          function getdetails(id){
              $.ajax({
                  type: "POST",
                  url: "details_tel.php",
                  data: {id:id}
              }).done(function( result )
              {
                  $("#container").html(result);
              });
          }
      </script>
      
      
      <div id="container">
      
      </div>
      

      Ну и выборка для вывода:

      <?php
      include ('class/database.php');
      $db_tel = new DatabaseTel();
      
      $id_dep = $_POST['id_dep'];
      $id_divis = $_POST['id_divis'];
      $id_unit= $_POST['id_unit'];
      
      if (!empty($id_dep)){
          $result=$db_tel->select('main','lname,name,mname,job,phone_work,phone_mobile,phone_inner,date_birthday','department_id='.$id_dep.'','lname');
      }
      if (!empty($id_divis)){
          $result=$db_tel->select('main','lname,name,mname,job,phone_work,phone_mobile,phone_inner,date_birthday','division_id='.$id_divis.'','lname');
      }
      if (!empty($id_unit)){
          $result=$db_tel->select('main','lname,name,mname,job,phone_work,phone_mobile,phone_inner,date_birthday','unit_id='.$id_unit.'','lname');
      }
      
      ?>
      
      ответить
    44. Аватар пользователя Айбол

      Айбол

      20:11 31.10.2016

      как можно добавить отдельный класс (css selector) для подкатегории. Например:

      						<li class = "active dropdown"><a href = "index.php">Басты</a>
      							<ul class = "sub-menu">
      								<li><a href = "#">Тарихи кезеңдер</a></li>
      								<li class = "dropdown"><a href = "#">Тарихи кезеңдер</a>
      									<ul class = "sub-menu">
      										<li><a href = "#">Тарихи кезеңдер</a></li>
      										<li><a href = "#">Тарихи кезеңдер</a></li>
      										<li><a href = "#">Тарихи кезеңдер</a></li>
      										<li><a href = "#">Тарихи кезеңдер</a></li>
      										<li><a href = "#">Тарихи кезеңдер</a></li>
      										<li><a href = "#">Тарихи кезеңдер</a></li>
      									</ul>
      								</li>
      								<li><a href = "#">Тарихи кезеңдер</a></li>
      								<li><a href = "#">Тарихи кезеңдер</a></li>
      								<li><a href = "#">Тарихи кезеңдер</a></li>
      								<li><a href = "#">Тарихи кезеңдер</a></li>
      							</ul>
      						</li>
      
      ответить
      • Аватар пользователя roman66

        roman66

        03:01 12.11.2016

        Айбол,

        $LastName = htmlspecialchars($_POST['LastName']);
        $name     = htmlspecialchars($_POST['name']);
        $floor     = $_POST['floor'];
        $email    = mysql_real_escape_string($_POST['email']);
        $password = md5 ($_POST['password']);
        $phone    = $_POST['phone'];
        $data     = $_POST['data'];
        $country  = $_POST['country'];
        $region   = $_POST['region'];
        $city     = $_POST['city'];
        $rdate    = date ("Y-m-d H:i:s");
        
        $regex="/^[\w\.\-]+@[a-zA-Z0-9]+\.[a-zA-Z]{2,6}$/";
        
        if (preg_match($regex, $email)) 
        {
            $password=md5($password);
            $activation=md5($email.time());
            $count=mysqli_query($link, "SELECT id FROM user WHERE email='$email'");
            if (mysqli_num_rows($count)<1) 
            {
                mysqli_query($link, "INSERT INTO user (LastName, name, floor, email, password, phone, data, country, region, city, rdate, activation) VALUES('$LastName', '$name', '$floor', '$email', '$password', '$phone', '$data', '$country', '$region', '$city', '$rdate', '$activation')");
                include "send_mail.php";
                $to=$email;
                $subject="Проверка E-mail-а";
                $body="Здравствуйте!<br><br> Активация! Пожалуйста перейдите по ссылке для активанции вашего аккаунта. <br><br> <a href='".base_url.'activation/'.$activation."'>".$base_url."activation/".$activation."</a>";
                send_mail($to, $subject, $body);
                $msg="Регистрация прошла успешно! Пройдите активацию через email.";
            }
            else
            {
            $msg="Данный email уже занят.";
            }
        }
        else
        ответить
      • Аватар пользователя roman66

        roman66

        06:02 12.11.2016

        Айбол,
        пустые 03 и 05 а один ты вернул

        ответить
    45. Аватар пользователя Azim

      Azim

      21:43 03.02.2017

      Здравствуйте Максим!
      Спасибо за статью, у меня вопрос,зная id категории мне нужно выводить именно определенную категорию с их подкатегориями, а не все категории, у меня не получается (

      ответить
    46. Аватар пользователя Дмитрий

      Дмитрий

      15:44 09.02.2017

      Помогите определить активную категорию.

      ответить
    47. Аватар пользователя Алексей

      Алексей

      10:45 15.02.2017

      А как добавить класс css, что бы как то отметить те пункты у которых есть подпункты, или хотя бы +/-

      ответить
    48. Аватар пользователя Юрий

      Юрий

      13:23 02.03.2017

      Как сделать так, чтобы при нажатии на ссылку главной категории, подкатегории выводились на этой же странице, но в другом месте???

      ответить
    49. Аватар пользователя PQ

      PQ

      09:19 10.03.2017

      Доброго времени суток!

      Скажите, можно как-то реализовать подсчет товаров в каждой из категорий, при этом в родительских категориях их суммировать? Пример ниже:

      Категория 1 (22)
      Категория 1.1 (15)
      Категория 1.1.1 (10)
      Категория 1.1.2 (5)
      Категория 1.2 (7)
      Категория 1.2.1 (2)
      Категория 1.2.2 (5)

      ответить
    50. Аватар пользователя lol

      lol

      16:34 14.09.2017

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

      ответить
    51. Аватар пользователя Мирослав

      Мирослав

      09:34 29.09.2017

      А как можно создать такой список, только чтоб он раскрывался?

      ответить
    52. Аватар пользователя Андрей

      Андрей

      16:33 30.11.2017

      Здравствуйте, если у записи parent_id больше 0, а родителя на самом деле нет в базе, можно эту запись вывести как родителя?

      ответить
    53. Аватар пользователя Alex

      Alex

      19:49 06.02.2018

      Огромное спасибо! Очень долго не мог понять принцип работы и вывода иерархии. С вашей помощью всё понял за 10 минут

      ответить
    54. Аватар пользователя Илья

      Илья

      14:57 22.03.2018

      Здравствуйте. Я хочу сделать скрипт генеалогического древа. Решил, что надо брать за основу именно деревья. Наткнулся на данный весьма хороший скрипт. Но вот что я не могу понять. В данном скрипте есть только один родитель и от него идут уже дети. Но в реальности родителей двое(мать и отец), и от двух элементов получается вложенный — ребенок. Можно ли это как-то реализовать? По сути, если я правильно понимаю, это просто то же самое дерево, но задом наперед.

      ответить
    55. Аватар пользователя grummimt

      grummimt

      03:04 01.04.2018

      Жесть!


      аффтору зачет.

      ответить
    56. Аватар пользователя Сергей

      Сергей

      20:14 31.12.2019

      Добрый день.

      У меня следующий вопрос: все товары крепятся только к одной категории (конечной) и что бы на SQL выбрать все товары из рутовой категории, надо собрать все ID нижестоящих категорий и в SQL запросе поместить их в IN(…)

      Так вот, а как собрать все ID нижестоящих категорий ?

      ответить
    57. Аватар пользователя Валерий

      Валерий

      16:13 25.02.2020

      Огромное спасибо.
      Всё
      и с первого раза поехало

      ответить

    Оставь свой отзыв

    Для вставки кода используйте кнопки php, html, javascript, css, sql

    * - поля обязательны к заполнению