Получить все категории (многоуровневые)
Я использую codeigniter и имею таблицу с 3 столбцами (id, name, parent_id). Категория может иметь много подкатегорий, а подкатегория может иметь много подкатегорий.
Я пытаюсь получить все категории и их подкатегории, используя этот код:
public function getCategory($id)
{
$categories = array();
while($id != 0)
{
$this->db->from('categories'); //$this->table is a field with the table of categoris
$this->db->where('id', $id);
$this->db->limit(1);
$result = $this->db->get()->row(); //fetching the result
$categories[] = $result;
$id = $result->parent_id;
}
return $categories;
}
public function getAllCategories()
{
$this->db->select('id');
$this->db->from('categories'); //$this->table is a field with the table of categoris
$this->db->where('parent_id', 0);
$mainCategories = $this->db->get()->result(); //fetching the result
$result = array();
foreach($mainCategories as $id)
{
$result[] = $this->getCategory($id->id);
}
return $result;
}
но он возвращает мне только категории 1 уровня.
мой вопрос в том, как решить мою задачу: получить все категории и подкатегории для каждого уровня.
4 ответов
самым простым решением вашей проблемы было бы добавить рекурсию.
public function getCategoryTreeForParentId($parent_id = 0) {
$categories = array();
$this->db->from('categories');
$this->db->where('parent_id', $parent_id);
$result = $this->db->get()->result();
foreach ($result as $mainCategory) {
$category = array();
$category['id'] = $mainCategory->id;
$category['name'] = $mainCategory->name;
$category['parent_id'] = $mainCategory->parent_id;
$category['sub_categories'] = $this->getCategoryTreeForParentId($category['id']);
$categories[$mainCategory->id] = $category;
}
return $categories;
}
этот подход может быть значительно увеличен в скорости путем предварительной загрузки всех категорий и работы над массивом, таким образом пропуская запрос для каждого нового parent_id.
также я не использовал объекты codeigniter, так как я их не знаю. Если они поддерживают magic setters / getters или могут быть убеждены взять массив дочерних объектов, их следует использовать вместо массива, который я создаю здесь.
что делает алгоритм: загрузите все категории с заданным parent_id, выполните цикл через все эти категории, предположите идентификатор итерации-категории как parent_id и загрузите все для него. Это эффективно загружает все категории, если они ссылаются на существующие категории.
существует небольшая опасность: когда у вас есть конструкция категорий A и B, где A имеет B в качестве родителя и B имеет a в качестве родителя, вы столкнетесь с бесконечным циклом, поскольку они загружают друг друга снова и снова. Это заставляет вас иметь чистую древовидную структуру в ваших данных.
обновление
Как это все еще получает голоса: Существует еще один совет,касающийся этого осуществления. Если дерево категорий больше или имеет несколько уровней, могут возникнуть проблемы с производительностью, так как эта реализация загружает категории снова и снова с новыми параметрами запроса. Это может довольно легко привести к десяткам, сотням или тысячам запросов, в зависимости от вашего дерево категорий.
более эффективным способом является загрузка все категории (с одним запросом) из вашей таблицы категорий и сортировать их с рекурсией в вашем приложении. Это один из редких случаев, когда ранняя оценка действительно улучшает производительность.
Если дерево требуется более одного раза в одном запросе, можно даже добавить кэширование через статические переменные (со всеми обычными опасностями кэширования).
это библиотека codeigniter, я нашел ее в ответе здесь, в stackoverflow, но я не помню ее владельца. Я внес в него некоторые изменения, чтобы вернуть вложенные категории в виде списка. это очень хорошо работает со мной.
<?php
if (!defined('BASEPATH'))
exit('No direct script access allowed');
class Tree {
var $table = '';
var $CI = '';
var $ul_class = '';
var $iteration_number = 0;
function Tree($config = array()) {
$this -> table = $config['table'];
$this -> CI = &get_instance();
$this -> CI -> load -> database();
$this -> ul_class = $config['ul_class'];
}
function getTree($parent_id = 1) {
$this -> iteration_number++;
$this -> CI -> db -> where('parent_id', $parent_id);
$first_level = $this -> CI -> db -> get($this -> table) -> result();
if($this->iteration_number == 1)
$tree = '<ul id="red" class="' . $this -> ul_class . '">';
else
$tree = '<ul>';
foreach ($first_level as $fl) {
$this -> CI -> db -> where('parent_id', $fl -> category_id);
$count = $this -> CI -> db -> count_all_results($this -> table);
if ($count != 0) {
$tree .= '<li><span>' . $fl -> category_name . '</span>';
$tree .= $this -> getTree($fl -> category_id);
} else {
$tree .= '<li><a href="'.base_url().'categories/sub/'.str_replace(' ', '-', $fl -> category_name).'" cat_id="'.$fl -> category_id.'" class="leaf">' . $fl -> category_name . '</a>';
}
$tree .= '</li>';
}
$tree .= '</ul>';
return $tree;
}
}
?>
вы можете использовать его с вашего контроллера следующим образом:
$config['ul_class']='treeview';
$config['table']='categories';
$this->load->library('tree',$config);
$tree = $this->tree->getTree();
и структура таблицы DB следующим образом:
CREATE TABLE IF NOT EXISTS `categories` (
`category_id` int(4) NOT NULL AUTO_INCREMENT,
`category_name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`category_description` varchar(500) COLLATE utf8_unicode_ci NOT NULL,
`parent_id` int(4) NOT NULL,
PRIMARY KEY (`category_id`),
KEY `categories_categories_fk_idx` (`parent_id`)
) ENGINE=InnoDB
вы можете изменить результат, возвращаемый функцией getTree в библиотеке, чтобы вернуть то, что тебе нужно.
Я собираюсь предложить другое решение, хотя вам нужно будет сделать небольшую реструктуризацию:
используя иерархию стиля линии, вы можете избежать всех проблем рекурсии и иметь гораздо более быстрые данные. Пометьте каждую категорию полным списком предков, например: 0001-0005-0015 ... и т. д., а затем можно запускать простые запросы с подобными, чтобы получить ваши подзапросы.
У меня есть библиотека Codeigniter на Git & blog post, объясняющая, как все это работает: http://codebyjeff.com/blog/2012/10/nested-data-with-mahana-hierarchy-library
предполагая, что ваш топ-уровня категории ид_родительского_объекта = 0. Вы можете получить свои данные из БД со следующим кодом и создать 2-мерный массив
$this->db->from('categories');
$query = $this->db->get();
$rows = $query->result()) {
// ok, we have an array ($rows). We need it
// transformed into a tree
$categories = array();
foreach ($rows as $category) {
// add the category into parent's array
$categories[$category->parent_id][] = $category;
}
return $categories;
затем вы можете пересечь массив $categories, учитывая, что $categories[0] является массивом, содержащим категории верхнего уровня, а $categories[$i] содержит подкатегории категории (или подкатегории) с id = $i.