Получить все категории (многоуровневые)

Я использую 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.