Иерархия категорий (PHP / MySQL)

Я пытаюсь получить все свои категории и подкатегории из базы данных MySQL в иерархии:

Мой результат должен быть таким (просто пример):

  1. Cat A
    • Sub-Cat 1
      • Sub_Sub_Cat 1
      • Sub_Sub_Cat 2
    • Sub_Cat 2
  2. Кошка B
  3. Кошка C
  4. ...

Код MySQL:

CREATE TABLE IF NOT EXISTS `categories` (
   `category_id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
   `parent_id` mediumint(8) unsigned NOT NULL DEFAULT '0' COMMENT 'for sub-categories'
  PRIMARY KEY (`category_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 ;

Просто, как получить его в хирархи с кодами PHP?


person beytarovski    schedule 15.12.2010    source источник


Ответы (5)


При использовании модели списка смежности вы можете сгенерировать структуру за один проход.

Взято из One Pass Parent-Child Структура массива (сентябрь 2007 г., Нейт Вайнер):

$refs = array();
$list = array();

$sql = "SELECT item_id, parent_id, name FROM items ORDER BY name";

/** @var $pdo \PDO */
$result = $pdo->query($sql);

foreach ($result as $row)
{
    $ref = & $refs[$row['item_id']];

    $ref['parent_id'] = $row['parent_id'];
    $ref['name']      = $row['name'];

    if ($row['parent_id'] == 0)
    {
        $list[$row['item_id']] = & $ref;
    }
    else
    {
        $refs[$row['parent_id']]['children'][$row['item_id']] = & $ref;
    }
}

Вот фрагмент из связанной статьи для создания списка для вывода. Это рекурсивно, если для узла есть дочерние элементы, он снова вызывает себя, чтобы создать поддерево.

function toUL(array $array)
{
    $html = '<ul>' . PHP_EOL;

    foreach ($array as $value)
    {
        $html .= '<li>' . $value['name'];
        if (!empty($value['children']))
        {
            $html .= toUL($value['children']);
        }
        $html .= '</li>' . PHP_EOL;
    }

    $html .= '</ul>' . PHP_EOL;

    return $html;
}

Связанный вопрос:

person simshaun    schedule 15.12.2010
comment
+1 Удивительно, что первая часть сделала именно то, что мне нужно, и была настолько менее сложной, что я думал, что это должно быть. - person brenjt; 15.12.2011
comment
Я пришел к выводу, что это очень умная идея. Мне интересно преобразовать этот массив в массив JSON, но без идентификаторов в качестве индексов. Это тоже возможно? - person Umair A.; 21.03.2012
comment
У меня проблемы с использованием этого в уже созданном динамическом меню. Мне нужно избавиться от первого сгенерированного ul. - person HenryW; 01.07.2013
comment
Я исправил это, указав начало и конец UL и закрыв в них toUL. Например: $ html. = '‹Ul›' .toUL ($ v ['children']). '‹/Ul›'; - person HenryW; 01.07.2013
comment
Что нужно передать в виде массива методу toUL () ?? $ refs или $ list или $ row ?? - person Dev; 26.06.2014
comment
@Dev, ты должен пройти $list - person Rob Schmuecker; 30.06.2014
comment
это очень помогло мне - person hetal gohel; 16.11.2018

У меня есть новая идея, думаю, будет неплохо. Идея такова: в столбец category_parent мы вставим ссылку на всех родителей этого узла.

+----+----------------------+-----------------+
| id | category_name        |    hierarchy    |
+----+----------------------+-----------------+
| 1  | cat1                 |        1        |
+----+----------------------+-----------------+
| 2  | cat2                 |        2        |
+----+----------------------+-----------------+
| 3  | cat3                 |        3        |
+----+----------------------+-----------------+
| 4  | subcat1_1            |       1-4       |
+----+----------------------+-----------------+
| 5  | subcat1_2            |       1-5       |
+----+----------------------+-----------------+
| 6  | subsubcat1_1         |      1-4-6      |
+----+----------------------+-----------------+
| 7  | subsubcat1_2         |      1-4-7      |
+----+----------------------+-----------------+
| 8  | subsubcat1_3         |      1-4-8      |
+----+----------------------+-----------------+
| 9  | subsubcat1
select * from table_name where hierarchy = '1-4-8-9' or hierarchy = '1-4-8' or hierarchy = '1-4' or hierarchy = '1'
1 | 1-4-8-9 | +----+----------------------+-----------------+ | 10 | subsubcat1
select * from table_name where hierarchy = '1-4-8-9' or hierarchy = '1-4-8' or hierarchy = '1-4' or hierarchy = '1'
2 | 1-4-8-10 | +----+----------------------+-----------------+ | 11 | subsubcat1_3
Insert into table_name (category_name, hierarchy) values ('new_name', (concat(parent_hierarch, '-', (SELECT Auto_increment FROM information_schema.tables WHERE table_name='table_name'))))
1 | 1-4-8-9-11 | +----+----------------------+-----------------+ | 12 | subsubsubcat1_3
Insert into table_name (category_name, hierarchy) values ('new_name', (concat(parent_hierarch, '-', (SELECT Auto_increment FROM information_schema.tables WHERE table_name='table_name'))))
1 | 1-4-8-9-12 | +----+----------------------+-----------------+ | 13 | subsubsubcat1_3
Insert into table_name (category_name, hierarchy) values ('new_name', (concat(parent_hierarch, '-', (SELECT Auto_increment FROM information_schema.tables WHERE table_name='table_name'))))
2 | 1-4-8-9-11-13 | +----+----------------------+-----------------+ | 14 | subsubsubcat1_2
Insert into table_name (category_name, hierarchy) values ('new_name', (concat(parent_hierarch, '-', (SELECT Auto_increment FROM information_schema.tables WHERE table_name='table_name'))))
3 | 1-4-8-9-11-14 | +----+----------------------+-----------------+

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

Insert into table_name (category_name, hierarchy) values ('new_name', (concat(parent_hierarch, '-', (SELECT Auto_increment FROM information_schema.tables WHERE table_name='table_name'))))

Теперь давайте сделаем желаемые запросы:

1- все подкатегории автомобилей:

select * from table_name where hierarchy like '1-%'

2- если вам нужны все родительские элементы BLACK, вы просто набираете:

select * from table_name where hierarchy = '1-4-8-9' or hierarchy = '1-4-8' or hierarchy = '1-4' or hierarchy = '1'

(вы можете создать этот запрос из php, разделив поле иерархии на '-' char)

3- Чтобы увидеть все категории с уровнем и прямым родителем:

select *, SUBSTR(hierarchy, 1, (LENGTH(hierarchy) - LENGTH(id) - 1)) as parent, LENGTH(hierarchy) - LENGTH(REPLACE(hierarchy, '-', '')) as level From table_name
+----+----------------------+-----------------+-----------+--------+
| id | category name        |    hierarchy    |   parent  |  level |
+----+----------------------+-----------------+-----------+--------+
| 1  | cat1                 |        1        |           |    0   |
+----+----------------------+-----------------+-----------+--------+
| 2  | cat2                 |        2        |           |    0   |
+----+----------------------+-----------------+-----------+--------+
| 3  | cat3                 |        3        |           |    0   |
+----+----------------------+-----------------+-----------+--------+
| 4  | subcat1_1            |       1-4       |     1     |    1   |
+----+----------------------+-----------------+-----------+--------+
| 5  | subcat1_2            |       1-5       |     1     |    1   |
+----+----------------------+-----------------+-----------+--------+
| 6  | subsubcat1_1         |      1-4-6      |    1-4    |    2   |
+----+----------------------+-----------------+-----------+--------+
| 7  | subsubcat1_2         |      1-4-7      |    1-4    |    2   |
+----+----------------------+-----------------+-----------+--------+
| 8  | subsubcat1_3         |      1-4-8      |    1-4    |    2   |
+----+----------------------+-----------------+-----------+--------+
| 9  | subsubcat1
select * from table_name where hierarchy = '1-4-8-9' or hierarchy = '1-4-8' or hierarchy = '1-4' or hierarchy = '1'
1 | 1-4-8-9 | 1-4-8 | 3 | +----+----------------------+-----------------+-----------+--------+ | 10 | subsubcat1
select * from table_name where hierarchy = '1-4-8-9' or hierarchy = '1-4-8' or hierarchy = '1-4' or hierarchy = '1'
2 | 1-4-8-10 | 1-4-8 | 3 | +----+----------------------+-----------------+-----------+--------+ | 11 | subsubcat1_3
Insert into table_name (category_name, hierarchy) values ('new_name', (concat(parent_hierarch, '-', (SELECT Auto_increment FROM information_schema.tables WHERE table_name='table_name'))))
1 | 1-4-8-9-11 | 1-4-8-9 | 4 | +----+----------------------+-----------------+-----------+--------+ | 12 | subsubsubcat1_3
Insert into table_name (category_name, hierarchy) values ('new_name', (concat(parent_hierarch, '-', (SELECT Auto_increment FROM information_schema.tables WHERE table_name='table_name'))))
1 | 1-4-8-9-12 | 1-4-8-9 | 4 | +----+----------------------+-----------------+-----------+--------+ | 13 | subsubsubcat1_3
Insert into table_name (category_name, hierarchy) values ('new_name', (concat(parent_hierarch, '-', (SELECT Auto_increment FROM information_schema.tables WHERE table_name='table_name'))))
2 | 1-4-8-9-11-13 |1-4-8-9-11 | 5 | +----+----------------------+-----------------+-----------+--------+ | 14 | subsubsubcat1_2
Insert into table_name (category_name, hierarchy) values ('new_name', (concat(parent_hierarch, '-', (SELECT Auto_increment FROM information_schema.tables WHERE table_name='table_name'))))
3 | 1-4-8-9-11-14 |1-4-8-9-11 | 5 | +----+----------------------+-----------------+-----------+--------+

Это новая идея, и ее нужно улучшить.

person Wajih    schedule 13.11.2015
comment
Однако что, если вам когда-нибудь придется внести изменения в иерархию, например, сдвинуть subcat1_2 на subcat3_2 или удалить категорию? - person Akhil Gupta; 19.05.2016

@Amnon Ваш код отлично работает. Просто протестировал его с помощью CodeIgniter, и он отлично сработал. Вот рабочий код, если он кому-то нужен:

<?php

function disTree($all_cats) {
$tree = array();
foreach ($all_cats as $cat)
{
    $pid  = $cat->parent_id;
    $id   = $cat->cat_id;
    $name = $cat->cat_name;

    // Create or add child information to the parent node
    if (isset($tree[$pid]))
        // a node for the parent exists
        // add another child id to this parent
        $tree[$pid]["children"][] = $id;
    else
        // create the first child to this parent
        $tree[$pid] = array("children"=>array($id));

    // Create or add name information for current node
    if (isset($tree[$id]))
        // a node for the id exists:
        // set the name of current node
        $tree[$id]["name"] = $name;
    else
        // create the current node and give it a name
        $tree[$id] = array( "name"=>$name );
}
return $tree;
}


function toUL($tree, $id, $html){
  $html .= '<ul>'.PHP_EOL;

  if (isset($tree[$id]['name']))
    $html .= '<li>' . $tree[$id]['name'];

  if (isset($tree[$id]['children']))
  {
    $arChildren = &$tree[$id]['children'];
    $len = count($arChildren);
    for ($i=0; $i<$len; $i++) {
        $html .= toUL($tree, $arChildren[$i], "");
    }
    $html .= '</li>'.PHP_EOL;
  }

  $html .= '</ul>'.PHP_EOL;
  return $html;
}

$tree = disTree($all_cats);
// Display the tree
echo toUL($tree, 0, "");

?>

Единственное, что я изменил, - это добавление собственного массива ($ all_cats).

person Farhan    schedule 29.06.2015
comment
Спасибо. вы сделали его намного чище, проще и понятнее. Спасибо! Я часами сидел с подобным кодом и наконец заставил его работать. - person ヨハンソン; 27.06.2016

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

$tree = array();
foreach ($query->result() as $row)
{
    $pid  = $row->parent_id;
    $id   = $row->id;
    $name = $row->name;

    // Create or add child information to the parent node
    if (isset($tree[$pid]))
        // a node for the parent exists
        // add another child id to this parent
        $tree[$pid]["children"][] = $id;
    else
        // create the first child to this parent
        $tree[$pid] = array("children"=>array($id));

    // Create or add name information for current node
    if (isset($tree[$id]))
        // a node for the id exists:
        // set the name of current node
        $tree[$id]["name"] = $name;
    else
        // create the current node and give it a name
        $tree[$id] = array( "name"=>$name );
}
return $tree;

и отобразить дерево:

function toUL($tree, $id, $html){
  $html .= '<ul>'.PHP_EOL;

  if (isset($tree[$id]['name']))
    $html .= '<li>' . $tree[$id]['name'];

  if (isset($tree[$id]['children']))
  {
    $arChildren = &$tree[$id]['children'];
    $len = count($arChildren);
    for ($i=0; $i<$len; $i++) {
        $html .= toUL($tree, $arChildren[$i], "");
    }
    $html .= '</li>'.PHP_EOL;
  }

  $html .= '</ul>'.PHP_EOL;
  return $html;
}

// Display the tree
echo toUL($tree, 0, "");
person Amnon    schedule 25.02.2013

person    schedule
comment
#Waruna Manjula Plus 1 для вашего помощника по поиску решения! Я превратил его в раскрывающееся меню, но у меня возникла проблема с установкой выбранной родительской категории. Как я могу это сделать? Я знаю, как установить выбранную текущую категорию, но мне нужно показать выбранную родительскую категорию. - person Europeuser; 11.11.2020