0 Пользователей и 1 Гость просматривают эту тему.
  • 15 Ответов
  • 672 Просмотров
*

rsn

  • Захожу иногда
  • 353
  • 23 / 2
Это не расширение Joomla, конечно, но, тем не менее, авторское решение форумчанина )
Часто бывает надо получить из таблицы связей категорий вида «id_категории - id_родительской» - массив
с деревом всех подкатегорий.
Это бывает актуально как для всех категорий (целое дерево), так и для отдельных категорий (одна из веток) - все её подкатегории, всех уровней.
В общем, оформил в виде класса.
После его определения достаточно одной строчки кода, чтобы получить нужное дерево или ветку  ;)

Работает быстро за счёт того, что, в отличие от некоторых других решений, делает всего 1 sql запрос.
Один раз получает таблицу связей, а потом на её основе строит нужное дерево.

В метод getTable заложил реализацию для категорий VirtueMart. Как понимаете, несложной адаптацией запроса можно использовать это с любым расширением, использующим хранение связей категории в виде «id_категории - id_родительской».
Кроме того, при создании объекта класса можно просто передать свою таблицу вторым параметром, тогда он не будет выполнять свой внутренний запрос.

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

Пример использования:
Код
$tree = new Tree(0);
Согласитесь, удобно )

Вообще, я делал этот класс как часть задачи по внедрению Nested Sets для VirtueMart, но это отдельная история, здесь не об этом.

Так вот, код класса:

Код
/**
 * build categories tree for root category or subcategory
 *
 * @author alexey@gnevyshev.ru
 * @return subcategories tree all levels
 *
 * @example of usage:
 * $tree = new Tree(0);
 */
class Tree
{
    public $id = 0;
    public $children = array();
   
    function __construct($cat_id = 0, $table = array())
    {
        $this->id = $cat_id;
        if (empty($table)) $table = $this->getTable();
        $this->children = $this->find_children($table, $cat_id);
    }
   
    private function getTable()
    {
        $db = JFactory::getDbo();
        $query = '
            SELECT cc.category_parent_id, cc.category_child_id, c.ordering
            FROM #__virtuemart_category_categories cc
            LEFT JOIN #__virtuemart_categories c ON c.virtuemart_category_id = cc.category_child_id
            ORDER BY category_parent_id, ordering, category_child_id
        ';
        $db->setQuery($query);
        $relations_table = $db->loadObjectList();
        return $relations_table;
    }
   
    public function find_children($table, $parent_id = 0)
    {
        $children = [];
        foreach ($table as $item) {
            if ($item->category_parent_id == $parent_id) {
                $tree = new Tree($item->category_child_id, $table);
                $children[] = $tree;
            }
        }
        return $children;
    }
}

В моём случае, вместе с SQL запросом, строит полное дерево за 0.0152 сек.
429 категорий.

Принимаю отзывы, предложения и пожелания )
« Последнее редактирование: 31.07.2020, 09:11:31 от rsn »
Iresurs.com. C нами будущее
*

rsn

  • Захожу иногда
  • 353
  • 23 / 2
Пример результата:
Код
Tree Object
(
    [id] => 0
    [children] => Array
        (
            [0] => Tree Object
                (
                    [id] => 265
                    [children] => Array
                        (
                            [0] => Tree Object
                                (
                                    [id] => 566
                                    [children] => Array
                                        (
                                            [0] => Tree Object
                                                (
                                                    [id] => 650
                                                    [children] => Array
                                                        (
                                                            [0] => Tree Object
                                                                (
                                                                    [id] => 282
                                                                    [children] => Array
                                                                        (
                                                                        )

                                                                )

                                                            [1] => Tree Object
                                                                (
                                                                    [id] => 295
                                                                    [children] => Array
                                                                        (
                                                                        )

                                                                )

                                                            [2] => Tree Object
                                                                (
                                                                    [id] => 624
                                                                    [children] => Array
                                                                        (
                                                                        )

                                                                )

                                                            [3] => Tree Object
                                                                (
                                                                    [id] => 641
                                                                    [children] => Array
                                                                        (
                                                                        )

                                                                )

                                                            [4] => Tree Object
                                                                (
                                                                    [id] => 651
                                                                    [children] => Array
                                                                        (
                                                                        )

                                                                )

                                                        )

                                                )

                                            [1] => Tree Object
                                                (
                                                    [id] => 562
                                                    [children] => Array
                                                        (
                                                        )

                                                )

                                            [2] => Tree Object
                                                (
                                                    [id] => 293
                                                    [children] => Array
                                                        (
                                                        )

                                                )

                                            [3] => Tree Object
                                                (
                                                    [id] => 294
                                                    [children] => Array
                                                        (
                                                        )

                                                )

                                            [4] => Tree Object
                                                (
                                                    [id] => 538
                                                    [children] => Array
                                                        (
                                                        )

                                                )

                                        )

                                )

                            [1] => Tree Object
                                (
                                    [id] => 308
                                    [children] => Array
                                        (
                                        )

                                )

                            [2] => Tree Object
                                (
                                    [id] => 305
                                    [children] => Array
                                        (
                                            [0] => Tree Object
                                                (
                                                    [id] => 506
                                                    [children] => Array
                                                        (
                                                        )

                                                )

                                            [1] => Tree Object
                                                (
                                                    [id] => 307
                                                    [children] => Array
                                                        (
                                                        )

                                                )

                                            [2] => Tree Object
                                                (
                                                    [id] => 306
                                                    [children] => Array
                                                        (
                                                        )

                                                )

                                            [3] => Tree Object
                                                (
                                                    [id] => 508
                                                    [children] => Array
                                                        (
                                                        )

                                                )

                                            [4] => Tree Object
                                                (
                                                    [id] => 507
                                                    [children] => Array
                                                        (
                                                        )

                                                )

                                        )

                                )

                            [3] => Tree Object
                                (
                                    [id] => 309
                                    [children] => Array
                                        (
                                        )

                                )

                            [4] => Tree Object
                                (
                                    [id] => 291
                                    [children] => Array
                                        (
                                        )

                                )

                            [5] => Tree Object
                                (
                                    [id] => 502
                                    [children] => Array
                                        (
                                            [0] => Tree Object
                                                (
                                                    [id] => 576
                                                    [children] => Array
                                                        (
                                                        )

                                                )

                                        )

                                )

                            [6] => Tree Object
                                (
                                    [id] => 397
                                    [children] => Array
                                        (
                                        )

                                )

                            [7] => Tree Object
                                (
                                    [id] => 398
                                    [children] => Array
                                        (
                                        )

                                )

                            [8] => Tree Object
                                (
                                    [id] => 446
                                    [children] => Array
                                        (
                                            [0] => Tree Object
                                                (
                                                    [id] => 560
                                                    [children] => Array
                                                        (
                                                        )

                                                )

                                            [1] => Tree Object
                                                (
                                                    [id] => 594
                                                    [children] => Array
                                                        (
                                                            [0] => Tree Object
                                                                (
                                                                    [id] => 599
                                                                    [children] => Array
                                                                        (
                                                                        )

                                                                )

                                                            [1] => Tree Object
                                                                (
                                                                    [id] => 597
                                                                    [children] => Array
                                                                        (
                                                                        )

                                                                )

                                                            [2] => Tree Object
                                                                (
                                                                    [id] => 598
                                                                    [children] => Array
                                                                        (
                                                                        )

                                                                )

                                                        )

                                                )

                                            [2] => Tree Object
                                                (
                                                    [id] => 595
                                                    [children] => Array
                                                        (
                                                        )

                                                )

                                            [3] => Tree Object
                                                (
                                                    [id] => 596
                                                    [children] => Array
                                                        (
                                                        )

                                                )

                                        )

                                )

                            [9] => Tree Object
                                (
                                    [id] => 396
                                    [children] => Array
                                        (
                                            [0] => Tree Object
                                                (
                                                    [id] => 504
                                                    [children] => Array
                                                        (
                                                        )

                                                )

                                            [1] => Tree Object
                                                (
                                                    [id] => 505
                                                    [children] => Array
                                                        (
                                                        )

                                                )

                                            [2] => Tree Object
                                                (
                                                    [id] => 503
                                                    [children] => Array
                                                        (
                                                        )

                                                )

                                            [3] => Tree Object
                                                (
                                                    [id] => 542
                                                    [children] => Array
                                                        (
                                                        )

                                                )

                                            [4] => Tree Object
                                                (
                                                    [id] => 556
                                                    [children] => Array
                                                        (
                                                        )

                                                )

                                        )

                                )

                            [10] => Tree Object
                                (
                                    [id] => 510
                                    [children] => Array
                                        (
                                        )

                                )

                            [11] => Tree Object
                                (
                                    [id] => 509
                                    [children] => Array
                                        (
                                        )

                                )

                            [12] => Tree Object
                                (
                                    [id] => 426
                                    [children] => Array
                                        (
                                        )

                                )

                            [13] => Tree Object
                                (
                                    [id] => 425
                                    [children] => Array
                                        (
                                        )

                                )

                            [14] => Tree Object
                                (
                                    [id] => 563
                                    [children] => Array
                                        (
                                        )

                                )

                            [15] => Tree Object
                                (
                                    [id] => 621
                                    [children] => Array
                                        (
                                        )

                                )

                            [16] => Tree Object
                                (
                                    [id] => 399
                                    [children] => Array
                                        (
                                        )

                                )

                            [17] => Tree Object
                                (
                                    [id] => 467
                                    [children] => Array
                                        (
                                        )

                                )

                            [18] => Tree Object
                                (
                                    [id] => 564
                                    [children] => Array
                                        (
                                        )

                                )

                            [19] => Tree Object
                                (
                                    [id] => 601
                                    [children] => Array
                                        (
                                        )

                                )

                            [20] => Tree Object
                                (
                                    [id] => 555
                                    [children] => Array
                                        (
                                        )

                                )

                            [21] => Tree Object
                                (
                                    [id] => 557
                                    [children] => Array
                                        (
                                        )

                                )

                            [22] => Tree Object
                                (
                                    [id] => 565
                                    [children] => Array
                                        (
                                        )

                                )

                            [23] => Tree Object
                                (
                                    [id] => 561
                                    [children] => Array
                                        (
                                        )

                                )

                        )

                )
...
Iresurs.com. C нами будущее
*

sivers

  • Завсегдатай
  • 1428
  • 195 / 0
Сойдет. Правда, можно было обойтись без связывания таблиц в запросе и без рекурсий. Но это дело вкуса.
Если вдруг ИД вложенной категории окажется меньше, чем ИД ее родителя - отработает правильно или "потеряет" эту вложенную категорию?
На связи в телеге @sivers
*

rsn

  • Захожу иногда
  • 353
  • 23 / 2
можно было обойтись ... без рекурсий
Это как - покажете?
Если число уровней может быть любым
Iresurs.com. C нами будущее
*

rsn

  • Захожу иногда
  • 353
  • 23 / 2
Если вдруг ИД вложенной категории окажется меньше, чем ИД ее родителя - отработает правильно или "потеряет"
Отработает правильно.
Iresurs.com. C нами будущее
*

sivers

  • Завсегдатай
  • 1428
  • 195 / 0
Это как - покажете?
Почему нет. Формат вывода будет немного другой, чем у вас, но можно сделать и точно такой же, если это принципиально. Будет ли это быстрее, чем ваш вариант, не знаю - проверьте.
Код
<?php
/**
 * build categories tree for root category or subcategory
 *
 * @author alexey@gnevyshev.ru
 * @return subcategories tree all levels
 *
 * @example of usage:
 * $tree = new Tree(0);
 */
class Tree
{
    public $tree = array();
   
    function __construct($table = array())
    {
        if(empty($tree)){
            if(empty($table)) $table = $this->getTable();
            foreach($table as $item){
                if(!isset($this->tree[$item->category_parent_id])) $this->tree[$item->category_parent_id] = array();
                if(!isset($this->tree[$item->category_child_id])) $this->tree[$item->category_child_id] = array();
                $this->tree[$item->category_parent_id][$item->category_child_id] = &$this->tree[$item->category_child_id];
            }
        }
    }
   
    private function getTable()
    {
        $db = JFactory::getDbo();
        $query = '
            SELECT cc.category_parent_id, cc.category_child_id
            FROM #__virtuemart_category_categories cc
        ';
        $db->setQuery($query);
        $relations_table = $db->loadObjectList();
        return $relations_table;
    }
   
    public function find_children($parent_id = 0)
    {
        return isset($this->tree[$parent_id])? $this->tree[$parent_id] : false;
    }
}
Работать должно примерно так:
Код
$tree = new Tree();
$rootChildren = $tree->find_children();
$chldren3 = $tree->find_children(3);
$children8 = $tree->find_children(8);
print_r($rootChildren);
print_r($chldren3);
print_r($children8);
На связи в телеге @sivers
*

rsn

  • Захожу иногда
  • 353
  • 23 / 2
Спасибо,
поэкспериментирую на досуге
Iresurs.com. C нами будущее
*

rsn

  • Захожу иногда
  • 353
  • 23 / 2
Будет ли это быстрее, чем ваш вариант, не знаю - проверьте.
Код
<?php
/**
 * build categories tree for root category or subcategory
 *
 * @author alexey@gnevyshev.ru
 * @return subcategories tree all levels
 *
 * @example of usage:
 * $tree = new Tree(0);
 */
class Tree
{
    public $tree = array();
   
    function __construct($table = array())
    {
        if(empty($tree)){
            if(empty($table)) $table = $this->getTable();
            foreach($table as $item){
                if(!isset($this->tree[$item->category_parent_id])) $this->tree[$item->category_parent_id] = array();
                if(!isset($this->tree[$item->category_child_id])) $this->tree[$item->category_child_id] = array();
                $this->tree[$item->category_parent_id][$item->category_child_id] = &$this->tree[$item->category_child_id];
            }
        }
    }
   
    private function getTable()
    {
        $db = JFactory::getDbo();
        $query = '
            SELECT cc.category_parent_id, cc.category_child_id
            FROM #__virtuemart_category_categories cc
        ';
        $db->setQuery($query);
        $relations_table = $db->loadObjectList();
        return $relations_table;
    }
   
    public function find_children($parent_id = 0)
    {
        return isset($this->tree[$parent_id])? $this->tree[$parent_id] : false;
    }
}
Работать должно примерно так:
Код
$tree = new Tree();
$rootChildren = $tree->find_children();
$chldren3 = $tree->find_children(3);
$children8 = $tree->find_children(8);
print_r($rootChildren);
print_r($chldren3);
print_r($children8);

@sivers, а Ваш способ работает побыстрее!
С теми же исходными данными строит дерево за 0,001-0,002 сек. Разница ощутимая.

Единственное - сортировка (порядок подкатегорий на сайте) так не учитывается. Но это и не критично.
Мне, например, такой класс нужен в основном, чтобы получать все подкатегории для каждой нужной категории - просто список, без учёта сортировки.
Зато в скорости огромный выигрыш.

Я дополнял свой класс ещё методами для получения списка подкатегорий для нужной категории
и списка всех категорий. Для удобства.

Сейчас модифицирую на основе предложенного Вами варианта. Выложу итог.
Iresurs.com. C нами будущее
*

sivers

  • Завсегдатай
  • 1428
  • 195 / 0
Единственное - сортировка (порядок подкатегорий на сайте) так не учитывается. Но это и не критично.
Да, обычно нужны только ИДы, чтоб по ним построить запрос товаров. Но если кому важен порядок, то достаточно будет вернуть связывание таблиц в запросе.
Я дополнял свой класс ещё методами для получения списка подкатегорий для нужной категории
Т.е. плоский список ИДов всех дочек произвольной ветки? Его тоже с помощью рекурсии получаете? Или как-то еще? Поделитесь - интересно. Наверное, рекурсия для этого - самый оптимальный вариант. Но есть и более простой (в плане кода, а не оптимизации) для ленивых - через print_r и регулярное выражение.
На связи в телеге @sivers
*

rsn

  • Захожу иногда
  • 353
  • 23 / 2
Т.е. плоский список ИДов всех дочек произвольной ветки? Его тоже с помощью рекурсии получаете? Или как-то еще?
Получал с помощью рекурсии, да.
Но вот тут задумался, может можно как-то без неё )

Хотелось бы иметь именно максимально оптимальный способ в плане ресурсозатрат (быстродействие, нагрузка на сервер).

Цитировать
Или как-то еще? Поделитесь - интересно.

Был такой вариант (методы get_subcats и list_all):
Код
class Tree
{
    public $id = 0;
    public $children = array();
    public $table = array();
   
    function __construct($cat_id = 0, $table = array())
    {
        $this->id = $cat_id;
        if (empty($table)) $this->table = $this->getTable();
        else $this->table = $table;
        $this->children = $this->find_children($cat_id, $this->table);
    }
   
    private function getTable()
    {
        // irprint('Запрос к БД');
        $db = JFactory::getDbo();
        $query = '
            SELECT cc.category_parent_id, cc.category_child_id, c.ordering
            FROM #__virtuemart_category_categories cc
            LEFT JOIN #__virtuemart_categories c ON c.virtuemart_category_id = cc.category_child_id
            ORDER BY category_parent_id, ordering, category_child_id
        ';
        $db->setQuery($query);
        return $db->loadObjectList();
    }
   
    public function find_children($parent_id = 0, $table)
    {
        $children = [];
        foreach ($table as $item) {
            if ($item->category_parent_id == $parent_id) {
                $tree = new Tree($item->category_child_id, $table);
                $children[] = $tree;
            }
        }
        return $children;
    }
   
    private function get_children_recursive($branch)
    {
        $children = [];
        if (!empty($branch->children)) {
            foreach ($branch->children as $child) {
                $children[] = $child->id;
                $sub_children = $this->get_children_recursive($child);
                $children = array_merge($children, $sub_children);
            }
        }
        return $children;
    }
   
    public function get_subcats($cat_id = null)
    {
        $subcats = [];
        if ($cat_id === null) {
            $subcats[] = $this->id;
            $branch = $this;
        } else {
            $subcats[] = $cat_id;
            $branch = new Tree($cat_id, $this->table);
        }
        $children = $this->get_children_recursive($branch);
        foreach ($children as $child) {
            $subcats[] = $child;
        }
        return $subcats;
    }
   
    public function list_all()
    {
        $list = [];
        foreach ($this->table as $row) {
            $list[] = $row->category_child_id;
        }
        sort($list);
        return $list;
    }
}
Сейчас подумываю над тем, как это лучше сделать для класса, предложенного Вами.
Iresurs.com. C нами будущее
*

sivers

  • Завсегдатай
  • 1428
  • 195 / 0
Но вот тут задумался, может можно как-то без неё )

Сравните по расходу памяти/времени с одинаковыми исходными данными метод рекурсии и вот этот, что ниже:
Код
// Произвольный древовидный массив
$tree = array();
$tree[1][5] = 1;
$tree[2][6] = 1;
$tree[2][7] = 1;
$tree[2][8][13][15] = 1;
$tree[2][9][14] = 1;
$tree[3][10] = 1;
$tree[4][11] = 1;
$tree[4][12] = 1;

// Получение плоского списка ветки 2
$list = array();
if(preg_match_all('|\[(\d+)\]|isU', print_r($tree[2], true), $pregs)) $list = $pregs[1];
print_r($list);
На связи в телеге @sivers
*

rsn

  • Захожу иногда
  • 353
  • 23 / 2
Сравните по расходу памяти/времени с одинаковыми исходными данными

Попробую, спасибо!

Пока сделал без рекурсии ) такой вариант:
Код
$start = microtime(true);
// VARIANT 2
class Tree
{
    public $tree = array();
    public $subs = array();
   
    function __construct($table = array())
    {
        if (empty($tree)) {
            if (empty($table)) $table = $this->getTable();
            foreach ($table as $item) {
                if (!isset($this->tree[$item->category_parent_id])) $this->tree[$item->category_parent_id] = array();
                if (!isset($this->tree[$item->category_child_id])) $this->tree[$item->category_child_id] = array();
                $this->tree[$item->category_parent_id][$item->category_child_id] = & $this->tree[$item->category_child_id];
                if (!isset($this->subs[$item->category_parent_id])) $this->subs[$item->category_parent_id] = array();
                if (!isset($this->subs[$item->category_child_id])) $this->subs[$item->category_child_id] = array(); // added later
                $this->subs[$item->category_parent_id][] = $item->category_child_id;
            }
            foreach ($this->subs as $k => $v) {
                foreach ($this->subs as $i => $arr) {
                    if (in_array($k, $arr)) {
                        $this->subs[$i] = array_merge($this->subs[$i], $this->subs[$k]);
                    }
                }
            }
        }
    }
   
    private function getTable()
    {
        $db = JFactory::getDbo();
        $query = 'SELECT category_parent_id, category_child_id FROM #__virtuemart_category_categories';
        $db->setQuery($query);
        return $db->loadObjectList();
    }
   
    public function find_children($parent_id = 0)
    {
        return isset($this->tree[$parent_id])? $this->tree[$parent_id] : false;
    }
   
    public function get_subcats($cat_id = 0, $with_it = true)
    {
        $subcats = $this->subs[$cat_id];
        if ($with_it) array_unshift($subcats, $cat_id);
        return $subcats;
    }
}

$tree = new Tree();
$sucats_for_265 = $tree->get_subcats(265);

$end = microtime(true);
$time = round($end - $start, 4);
irprint('Скрипт выполнен за '.$time.' сек.');

irprint($sucats_for_265);
0.0025 сек.
UPD: Когда добавил строчку, пропущенную изначально
Код
if (!isset($this->subs[$item->category_child_id])) $this->subs[$item->category_child_id] = array(); // added later
время выполнения существенно увеличилось, 0.0215 сек., многовато..
« Последнее редактирование: 05.08.2020, 14:06:28 от rsn »
Iresurs.com. C нами будущее
*

rsn

  • Захожу иногда
  • 353
  • 23 / 2
Рассчитываю на то, что методом получения списков подкатегорий буду пользоваться в переборе.
Поэтому один раз в классе заполняю подкатегории для всех категорий.
Возможно, это было бы лишним, если бы нужно было получить подкатегории только 1 раз.
Но для получения в переборе (когда надо получать все подкатегории для ряда категорий) - это как раз самое то, на мой взгляд.
Iresurs.com. C нами будущее
*

sivers

  • Завсегдатай
  • 1428
  • 195 / 0
Рассчитываю на то, что методом получения списков подкатегорий буду пользоваться в переборе.
Поэтому один раз в классе заполняю подкатегории для всех категорий.
Возможно, это было бы лишним, если бы нужно было получить подкатегории только 1 раз.
Но для получения в переборе (когда надо получать все подкатегории для ряда категорий) - это как раз самое то, на мой взгляд.

В методе со ссылками аналогично. Да и как иначе в один запрос построить ветку без постройки всего дерева? Разве что с использованием lft/rgt (оно как раз для этого в
На связи в телеге @sivers
*

Septdir

  • Живу я здесь
  • 3300
  • 164 / 4
Разве что с использованием lft/rgt
NestedSet или же Вложенное множество называется.
И собственно да именно для этого.

Пример результата:
Насчет этого, каждый волен делать как ему нравиться, однако такой вариант вот первых тяжелый, во вторых не практичный.
Тяжелый потому что это объект с массивом в котором объект с массивом и т.д
Не практичный потому что выводить из этого обеъкета можно только рекурсией.

Для построения любого древа достаточно "однородного" ассоциативного массива.
childer => array (
[1] => array(2,3,4,5)
[4] => array(6,7,8)
)
При такой массиве я могу построить любое древо от любого ключа родителя.
Не можете справиться с задачей сами пишите, решу ее за вас, не бесплатно*.
*Интересная задача, Деньги или Бартер. Натурой не беру!
CodersRank | Контакты | Мой GitHub | Workshop
*

rsn

  • Захожу иногда
  • 353
  • 23 / 2
NestedSet или же Вложенное множество называется.
Думал перейти на эту модель. Чтобы заполнять таблицу в соответствии с ней, как раз и возникла мысль сделать обсуждаемый класс.
Но потом передумал )
Зачем Nested Sets, если и класс сам по себе с задачей получения всех подкатегорий для нужных категорий справляется вполне неплохо? )

Цитировать
При такой массиве я могу построить любое древо от любого ключа родителя.
Предлагаете какое-то решение получше, чем осуждаемые выше?
(когда в исходных данных таблица вида id_родителя-id_дочернего)
Iresurs.com. C нами будущее
Чтобы оставить сообщение,
Вам необходимо Войти или Зарегистрироваться