Сделал новым способом, какие есть убожества? PHP: $ret = [ 1=>["id"=>1, "parent_id"=>0, 'title' => 'Фильмы'], 2=>["id"=>2, "parent_id"=>1, 'title' => 'Джеки Чан'], 3=>["id"=>3, "parent_id"=>1, 'title' => 'Форсаж'], 4=>["id"=>4, "parent_id"=>0, 'title' => 'Антивирусы'], 5=>["id"=>5, "parent_id"=>4, 'title' => 'Касперский'], 6=>["id"=>6, "parent_id"=>1, 'title' => 'Шурик'], 7=>["id"=>7, "parent_id"=>5, 'title' => 'Версия 2020'], ]; PHP: foreach ($ret as $id => $node) { if (isset($ret[$node['parent_id']])) { $ret[$node['parent_id']]['sub'][$id] =& $ret[$id]; } } $return = []; foreach ($ret as $k=>$v){ if($v["parent_id"] == 0){ $return[$k]= $v; } } Получается вот такое Код (Text): Array ( [1] => Array ( [id] => 1 [parent_id] => 0 [title] => Фильмы [sub] => Array ( [2] => Array ( [id] => 2 [parent_id] => 1 [title] => Джеки Чан ) [3] => Array ( [id] => 3 [parent_id] => 1 [title] => Форсаж ) [6] => Array ( [id] => 6 [parent_id] => 1 [title] => Шурик ) ) ) [4] => Array ( [id] => 4 [parent_id] => 0 [title] => Антивирусы [sub] => Array ( [5] => Array ( [id] => 5 [parent_id] => 4 [title] => Касперский [sub] => Array ( [7] => Array ( [id] => 7 [parent_id] => 5 [title] => Версия 2020 ) ) ) ) ) ) И потом формирую разметку PHP: function derevo($el) { foreach($el as $k => $v) { $markup .= '<li>'; if(is_array($v)) { $markup .= '<a href="'.$v['parent_id'].'">' . $v['title'] . '</a>'; if(isset($v['sub']) && !empty($v['sub'])) { $markup .= derevo($v['sub']); continue; } else { continue; } } $markup .= '<li>'; } return '<ul>' . $markup . '</ul>'; } echo derevo($return);
вот тебе маленький кусок кода, который трахнет твой компутер в ЦП и в ОЗУ PHP: for ($x = 1; $x < 256; $x++){ for ($y = 1; $y < 256; $y++){ for ($z = 1; $z < 256; $z++){ $array[$x][$y][$z] = $x * $y * $z; } } } циферки безобидные на первый взгляд, но это обманчивое впечатление.
первым способом можно делать и не париться пусть себе перебирается. категорий не бывает по 2к обычно если прям так перфекционизм кушает, то можно удалять "использованную" часть если так заморачиваться, то ни один проект никогда не закончишь.
PHP: // Инициализация категорий, после которой категории будем выбирать из локальной переменной public function init_categories($reinit = false): void { dtimer::log(__METHOD__ . " start reinit flag: " . var_export($reinit, true)); if ($reinit === false ) { //если категории уже инициализированы if($this->all_categories !== null){ return; } if (function_exists('apcu_fetch')) { dtimer::log(__METHOD__ . " ACPU CACHE CATEGORIES READ "); $this->all_categories = apcu_exists($this->config->host . 'all_categories') ? apcu_fetch($this->config->host . 'all_categories') : null; $this->categories_tree = &$this->all_categories[0]['subcategories']; $this->categories_uri = &$this->all_categories[0]['uri']; unset($this->all_categories[0]); //remove root element if ($this->all_categories) { return; } } } // Выбираем все категории $cats = $this->db3->getInd("id", "SELECT * FROM s_categories ORDER BY parent_id, pos ASC"); $ids = array_keys($cats); // Дерево категорий $tree = []; $tree['subcategories'] = []; $tree['level'] = 0; $tree['children'] = [0]; $tree['path'] = []; $tree['uri'] = []; $tree['total'] = 0; //указатели на узлы дерева $ptr[0] = &$tree; //корневой элемент // Не кончаем, пока не кончатся категории, или пока ни одну из оставшихся некуда приткнуть $finish = false; while(!empty($ids) && !$finish) { $flag = false; foreach ($ids as $i=>$cid) { if(!isset($ptr[$cats[$cid]['parent_id']])){ continue; } $cat = &$cats[$cid]; $cat['id'] = (int)$cid; $cat['parent_id'] = (int)$cat['parent_id']; $cat['vparent_id'] = (int)$cat['vparent_id']; $cat['vcat1'] = explode(',', $cat['vcat1']); $cat['vcat2'] = explode(',', $cat['vcat2']); $cat['enabled'] = (bool)$cat['enabled']; $cat['visible'] = (bool)$cat['visible']; $cat['path'] = []; $cat['vchildren'] = []; $cat['children'] = []; $cat['subcategories'] = []; $cat['total'] = 0; //сначала часть родительского пути $cat['path'] = $ptr[$cat['parent_id']]['path']; //саму себя в конце $cat['path'][] = &$cat; //запишемся в массив указателей $ptr[$cid] = &$cat; //добавимся в дочерние к родительской категории $ptr[$cat['parent_id']]['subcategories'][] = &$ptr[$cid]; if($cat['visible']) $ptr[$cat['parent_id']]['total'] += 1; // Уровень вложенности категории $cat['level'] = 1 + $ptr[$cat['parent_id']]['level']; //добавим виртуальные разделы к его родителю $cats[$cat['vparent_id']]['vchildren'][] = $cid; unset($ids[$i]); $flag = true; } if(!$flag){ $finish = true; } } $ids = array_keys($ptr); unset($ids[0]); //уберем корневой раздел $ids = array_reverse($ids); //обратный порядок важен чтобы матрешка собралась правильно foreach ($ids as $cid) { $cat = &$ptr[$cid]; //отмечаем первый и последний элемент (используется в меню) $i = 0; while(!empty($cat['subcategories'][$i])){ if($cat['subcategories'][$i]['visible']){ $cat['subcategories'][$i]['first'] = true; break(1); } ++$i; } $j = count($cat['subcategories']) - 1; while(!empty($cat['subcategories'][$i])){ if($cat['subcategories'][$j]['visible']){ $cat['subcategories'][$j]['last'] = true; break(1); } --$j; } //сначала добавим саму себя $cat['children'][] = $cid; //теперь прибавим к родительскому разделу свои $ptr[$cat['parent_id']]['children'] = array_merge($ptr[$cat['parent_id']]['children'], $cat['children']); //транслит имена добавим в корневой массив $tree['uri'][$cat['trans']] = $cid; if($cat['trans2'] !== '') { $tree['uri'][$cat['trans2']] = $cid; } } if (function_exists('apcu_store')) { dtimer::log(__METHOD__ . " update categories APCU"); apcu_store($this->config->host . 'all_categories', $ptr, 14400); } unset($ptr[0]); //unset root element $this->all_categories = &$ptr; $this->categories_uri = &$tree['uri']; $this->categories_tree = &$tree['subcategories']; } Вывод категорий 1 запросом с последующим сохранением в apcu кеш. Преимущества: 1. Есть мало памяти 2. Относительно быстро инициализируется (1 запрос в БД, перечисление чуть больше, чем кол-во категорий). Недостатки: 1. Невозможно компактно сериализовать по причине использования указателей.
Карочи. Вот тебе без рекурсий, за один проход по массиву, без лишнего расхода памяти, без лишнего расхода процессура. Функция берет на вход твой массив, на выходе отдает древовидную структуру. По ней уже строй HTML хоть застройся. Можно сразу HTML строить и в функции, но я не люблю мух с котлетами смешивать, сорян. Дальше сам. PHP: <?php $tree = [ ['name' => 'Уровень 1', 'id' => 1, 'pid' => 0], ['name' => 'Уровень 1.1', 'id' => 2, 'pid' => 1], ['name' => 'Уровень 1.2', 'id' => 3, 'pid' => 1], ['name' => 'Уровень 1.3', 'id' => 4, 'pid' => 1], ['name' => 'Уровень 2', 'id' => 5, 'pid' => 0], ['name' => 'Уровень 2.1', 'id' => 6, 'pid' => 5], ['name' => 'Уровень 2.2', 'id' => 7, 'pid' => 5], ['name' => 'Уровень 3', 'id' => 8, 'pid' => 0], ['name' => 'Уровень 3.1', 'id' => 9, 'pid' => 8], ['name' => 'Уровень 3.1.1', 'id' => 10, 'pid' => 9], ['name' => 'Уровень 3.1.2', 'id' => 11, 'pid' => 9] ]; function rebuildTree($tree){ $branches = []; $resut = []; foreach ($tree as $key=>$node){ $ref = &$tree[$key]; $branches[$node['id']] = &$ref; $branches[$node['pid']]['chld'][] = &$ref; if ($node['pid'] === 0){ $result[] = &$ref; } } return $result; } print_r(rebuildTree($tree)); Выхлоп: PHP: Array ( [0] => Array ( [name] => Уровень 1 [id] => 1 [pid] => 0 [chld] => Array ( [0] => Array ( [name] => Уровень 1.1 [id] => 2 [pid] => 1 ) [1] => Array ( [name] => Уровень 1.2 [id] => 3 [pid] => 1 ) [2] => Array ( [name] => Уровень 1.3 [id] => 4 [pid] => 1 ) ) ) [1] => Array ( [name] => Уровень 2 [id] => 5 [pid] => 0 [chld] => Array ( [0] => Array ( [name] => Уровень 2.1 [id] => 6 [pid] => 5 ) [1] => Array ( [name] => Уровень 2.2 [id] => 7 [pid] => 5 ) ) ) [2] => Array ( [name] => Уровень 3 [id] => 8 [pid] => 0 [chld] => Array ( [0] => Array ( [name] => Уровень 3.1 [id] => 9 [pid] => 8 [chld] => Array ( [0] => Array ( [name] => Уровень 3.1.1 [id] => 10 [pid] => 9 ) [1] => Array ( [name] => Уровень 3.1.2 [id] => 11 [pid] => 9 ) ) ) ) ) )
При объединении таблиц, айдишники бьются Код (Text): SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; SET AUTOCOMMIT = 0; START TRANSACTION; SET time_zone = "+00:00"; /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8mb4 */; -- -- База данных: `derevo` -- -- -------------------------------------------------------- -- -- Структура таблицы `category` -- CREATE TABLE `category` ( `id` int(11) NOT NULL, `name` varchar(256) NOT NULL, `pid` int(11) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- -- Дамп данных таблицы `category` -- INSERT INTO `category` (`id`, `name`, `pid`) VALUES (1, 'Фильмы', 0), (2, 'Антивирусы', 0); -- -------------------------------------------------------- -- -- Структура таблицы `subcategory` -- CREATE TABLE `subcategory` ( `id` int(11) NOT NULL, `name` varchar(256) NOT NULL, `pid` int(11) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- -- Дамп данных таблицы `subcategory` -- INSERT INTO `subcategory` (`id`, `name`, `pid`) VALUES (1, 'Джеки Чан', 1), (2, 'Форсаж', 1), (3, 'Касперский', 2), (4, 'Шурик', 1); -- -- Индексы сохранённых таблиц -- -- -- Индексы таблицы `category` -- ALTER TABLE `category` ADD PRIMARY KEY (`id`); -- -- Индексы таблицы `subcategory` -- ALTER TABLE `subcategory` ADD PRIMARY KEY (`id`); -- -- AUTO_INCREMENT для сохранённых таблиц -- -- -- AUTO_INCREMENT для таблицы `category` -- ALTER TABLE `category` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3; -- -- AUTO_INCREMENT для таблицы `subcategory` -- ALTER TABLE `subcategory` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=5; COMMIT; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; Код (Text): SELECT * FROM category UNION SELECT * FROM subcategory ORDER BY pid DESC; Получается такое PHP: Array ( [0] => Array ( [id] => 3 [name] => Касперский [pid] => 2 ) [1] => Array ( [id] => 1 [name] => Джеки Чан [pid] => 1 ) [2] => Array ( [id] => 2 [name] => Форсаж [pid] => 1 ) [3] => Array ( [id] => 4 [name] => Шурик [pid] => 1 ) [4] => Array ( [id] => 1 [name] => Фильмы [pid] => 0 ) [5] => Array ( [id] => 2 [name] => Антивирусы [pid] => 0 ) )
А я тут причем? У тебя есть плоское представление дерева? Есть. Вот оно: PHP: $tree = [ ['name' => 'Уровень 1', 'id' => 1, 'pid' => 0], ['name' => 'Уровень 1.1', 'id' => 2, 'pid' => 1], ['name' => 'Уровень 1.2', 'id' => 3, 'pid' => 1], ['name' => 'Уровень 1.3', 'id' => 4, 'pid' => 1], ['name' => 'Уровень 2', 'id' => 5, 'pid' => 0], ['name' => 'Уровень 2.1', 'id' => 6, 'pid' => 5], ['name' => 'Уровень 2.2', 'id' => 7, 'pid' => 5], ['name' => 'Уровень 3', 'id' => 8, 'pid' => 0], ['name' => 'Уровень 3.1', 'id' => 9, 'pid' => 8], ['name' => 'Уровень 3.1.1', 'id' => 10, 'pid' => 9], ['name' => 'Уровень 3.1.2', 'id' => 11, 'pid' => 9] ]; У всех элементов одинаковая структура? Одинаковая. Смысл у элементов одинаковый? Одинаковый. Ну так если у них одинаковая структура и смысл, то и храни их в одной таблице. Нахрена городить города? id - уникальный индекс pid - кольцевая ссылка на эту же таблицу на столбец id И понеслась.
Возьми этот код и запусти с пошаговым выполнением через phpStorm. Сможешь понять в деталях как это происходит. PHP: <?php function rebuildTree($tree){ $branches = []; $result = []; foreach ($tree as $key=>$node){ $ref = &$tree[$key]; $branches[$node['id']] = &$ref; $branches[$node['pid']]['child'][] = &$ref; if ($node['pid'] === 0){ $result[] = &$ref; } } return $result; } $tree = [ ["id" => 1, "name" => "cat 1", "pid" => 0], ["id" => 2, "name" => "cat 2", "pid" => 1], ["id" => 3, "name" => "cat 3", "pid" => 2], ["id" => 4, "name" => "cat 4", "pid" => 1], ]; print_r(rebuildTree($tree));
Ды не, у него категории и подкатегории - это две разные таблицы. А подподкатегории, видимо, третья таблица. Стало быть, айдишники в них пересекаются и, при вытаскивании финальной сборки, все ломается к чертям. Еще на стадии подготовки данных. Такое бывает, когда данные, которые должны лежать в одной таблице, лежат в разных, а потом сваливаются в одну кучу.
Еще 1 недостаток нашел в твоей функции. Если в исходном массиве $tree сначала попадется элемент с родителем, который еще не добавлен в $branches, то дальше, когда цикл дойдет до этого родителя - child потрет. Вот пример дерева, которое вызовет проблемы. PHP: $tree = [ ["id" => 1, "name" => "cat 1", "pid" => 2], ["id" => 2, "name" => "cat 2", "pid" => 0], ["id" => 3, "name" => "cat 3", "pid" => 2], ["id" => 4, "name" => "cat 4", "pid" => 1], ]; Доработка: PHP: <?php function rebuildTree($tree){ $branches = []; $result = []; foreach ($tree as $key=>$node){ $ref = &$tree[$key]; if(isset($branches[$node['id']])){ $ref["child"] = $branches[$node['id']]["child"]; } $branches[$node['id']] = &$ref; $branches[$node['pid']]['child'][] = &$ref; if ($node['pid'] === 0){ $result[] = &$ref; } } return $result; } $tree = [ ["id" => 1, "name" => "cat 1", "pid" => 2], ["id" => 2, "name" => "cat 2", "pid" => 0], ["id" => 3, "name" => "cat 3", "pid" => 2], ["id" => 4, "name" => "cat 4", "pid" => 1], ]; print_r(rebuildTree($tree));
А я не тебе ответил в предыдущем сообщении, Дим. Там все в тему сказано. Что там бьется - это уже проблема твоей архитектуры бд. Ты выложил код, сказал, что он норм и тебе нравится, я использовал тестовые данные из твоего поста, написал свой код. А ты ко мне с бьющимися айдишниками. Это не моя проблема, решение описано было и выше на форуме и в тележке, причем не раз.
Одной сортировки недостаточно. Пруф. PHP: $tree = [ ["id" => 3, "name" => "cat 3", "pid" => 0], ["id" => 1, "name" => "cat 1", "pid" => 2], ["id" => 4, "name" => "cat 4", "pid" => 3], ["id" => 2, "name" => "cat 2", "pid" => 4], ];
@Dimon2x, да можно и рекурсией, только зачем? Я просто тебя не понимаю. Я тебе ещё в начале темы где-то назвал Nested Sets и Materialized Path, умные люди придумали, двести пятьдесят раз код написан. Под Ларку прекрасные библиотеки. Без ларки тоже есть библиотеки. В виде готовых SQL-запросов, если хочется самому - тоже есть. А ты всё хочешь рекурсивно, или в разных таблицах хранить категории и подкатегории. Ну может я такой глупый и не вижу в этом смысла, не знаю. У меня, если вложенные категории в проекте, я сразу пишу nested sets, если древовидные комменты - nested sets, если что угодно древовидное - nested sets.
Что значит не бьется? В последний раз слышал это слово от теток из планово-экономического отдела. У них обычно что-то не бьется. Означать может все что угодно. Сделай для себя полезную вещь - посмотри как работает функция пошагово через дебагер.
Тоже не выйдет. У тебя же на каждой итерации добавляется 2 элемента в $branches: первый - сама категория, второй - элемент от родительской категории. Тут без условия можно с двойным перебором, когда на первом цикле у тебя все категории встанут в branches, а на втором цикле разложить деток.