Для проекта выбрал модуль https://github.com/evopix/orm-mptt
Описанный в readme функционал – работает без проблем, а вот с остальным пришлось помучиться.
Но обо всем – по порядку.
Многие проекты имеют сложную структуру категорий товаров либо информации.
Часто требуется не фиксированная глубина вложенности.
Для этих целей, как нельзя лучше, подойдет модель хранения данных Nested set и ее реализация посредством модуля для Kohana MPTT.
Более детально о nested set
Есть несколько различных реализаций, но я остановился на модуле от evopix.
Ниже приведу описание основных методов модуля.
Нам потребуется таблица БД, вот ее упрощенная структура:

  • id – уникальный идентификатор-счетчик
  • parent_id – родительский id (для рутов – значение = 0)
  • lvl – уровень (для рутовых категорий он равен 1)
  • lft
  • rgt
  • scope – № ветви
  • name – наименование категории
  • url – url категории

Модель наследуем от ORM_MPTT

 class Model_Category extends ORM_MPTT { }

Создание корневого узла:

$cat = ORM::factory('category');
$cat->name = 'Каталог';
$cat->insert_as_new_root();

Создание последнего дочернего узла:

$parent_cat = ORM::factory('category')->where('name', '=', 'Каталог')->find();
$cat->name = 'Мобильные телефоны';
$cat->insert_as_last_child($parent_cat);
$cat->name = 'Планшеты';
$cat->insert_as_last_child($parent_cat);
$cat->name = 'Аксессуары для планшетов';
$cat->insert_as_last_child($parent_cat);
$cat->name = 'Литература о планшетах';
$cat->insert_as_last_child($parent_cat);

По аналогии создается дочерний узел, но вставляется перед всеми существующими:

$parent_cat = ORM::factory('category')->where('name', '=', 'Каталог')->find();

$cat->name = 'Бытовая техника';

$cat->insert_as_first_child($parent_cat);

Создание братского узла (узла того же уровня вложенности) перед указанным узлом:

$sibling_cat = ORM::factory('category')->where('name', '=', 'Планшеты')->find();

$cat->name = 'Комплектующие';

$cat->insert_as_prev_sibling($sibling_cat);

Создание братского узла (узла того же уровня вложенности) после указанного узла:

$sibling_cat = ORM::factory('category')->where('name', '=', 'Планшеты')->find();
$cat->name = 'Ноутбуки';
$cat->insert_as_next_sibling($sibling_cat);

Перемещение произвольного узла с подузлами в выбранный узел (в нашем случае перемещаем категорию "Аксессуары для планшетов" в категорию "Планшеты" перед всеми имеющимися узлами):

$source_cat = ORM::factory('category')
->where('name', '=', 'Аксессуары для планшетов')->find();

$destination_cat = ORM::factory('category')
->where('name', '=', 'Планшеты')->find();

$cat->move_to_first_child($destination_cat); 
//Перемещаем на место первого дочернего узла

То же самое, но теперь вставляем категорию после всех имеющихся узлов:

$source_cat = ORM::factory('category')
->where('name', '=', 'Литература о планшетах')->find();

$destination_cat = ORM::factory('category')
->where('name', '=', 'Планшеты')->find();

$cat->move_to_last_child($destination_cat); 
//Вставляем последним дочерним узлом

По аналогии возможны перемещения братских узлов:

$sibling_cat = ORM::factory('category')
->where('name', '=', 'Мобильные телефоны')->find();

$cat = ORM::factory('category')
->where('name', '=', 'Планшеты')->find();

$cat->move_to_prev_sibling($sibling_cat); 
//Перемещаем на позицию перед братским узлом
$sibling_cat = ORM::factory('category')
->where('name', '=', 'Мобильные телефоны')->find();

$cat = ORM::factory('category')
->where('name', '=', 'Планшеты')->find();

$cat->move_to_next_sibling($sibling_cat);
//Перемещаем на позицию после братского узла

В этом месте лично меня ожидал неприятный сюрприз.
Никаким образом категории не желали перемещаться.
Для решения данной проблемы необходимо изменить код в модуле modules\orm-mptt\classes\kohana\orm\mptt.php в методе lock(), на следующий:

protected function lock()
{
    $q = 'LOCK TABLE '.$this->_db->quote_table($this->_table_name).' WRITE';

    if ($this->_object_name)
    {
        $q.= ', '.$this->_db->quote_table($this->_table_name);
        $q.= ' AS '.$this->_db->quote_column($this->_object_name).' WRITE';
    }

    $this->_db->query(NULL, $q, TRUE);
}

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

8 thoughts on “Kohana 3.2 – особенности использования модуля ORM MPTT (на основе nested set) Часть 1.

  1. Спасибо wadya за статью! Было полезно. Я уже неделю перебераю разные библиотеки для работы с nested set. Можно вопросы задать. Так как у меня возникла необходимость хранения многокорневой структуры, Вы мульти корневую структуру тестировали, она рабочая? Если да может у Вас и примеры найдутся, или продолжение в следующей части? И второй вопрос я так понимаю доп.поля у Вас это в примере поле url в таблице и так как эта модель расширяет ORM модель, то:
    $cat->name = ‘Планшеты’;
    $cat->url = ‘/site/cat/planshet’;
    $cat->insert_as_last_child($parent_cat);
    все сохранит в базу?
    Заранее благодарен за ответ.

    Reply
  2. 1. По многокорневой структуре – никаких различий с однокорневой не будет, т.к. однокорневая структура это просто частный случай. Единственное, на что следует обратить внимание, так это то, что все перемещения между разными деревьями это добавление нового элемента в дерево-цель и удаление перемещаемого элемента из дерева-источника.

    2. insert_as_last_child() результатом своей работы возвращает созданный объект, т.е. к объекту в этом методе применяется метод ORM save(), т.е. все сохранит в БД.

    Reply
  3. А как перенести целый куст в другое дерево?
    Я например очень хочу использовать данный плагин, но по всей видимости не очень удобно им пользоваться совместо с данным модулем для kohana.

    Reply
    • Тут вариантов – совсем немного. Например путем последовательного создания копий имеющихся с последующим удалением основного узла из старого дерева.

      Reply
  4. интересно а как вы сылку формируете для передачи соседнего id ?
    куда – id
    что -id

    …просто если формировать сылку (стрелочкка рядом типа вверх) как то немного непонятно как соседа id передадите .. не “что” а “куда” нам известно допустим
    это поднимаем…. а куда понимаем? как id соседа получаете?!?!?

    Reply
    • Если ты про методы:
      получить предыдущего/следующего брата, то они в модуле, который я использую есть и вот их код:

      /**
              * Returns the node that is the immediately prior sibling of the given node
              *
              * @return array $resultNode The node returned
              */
              function get_prev_sibling()
              {
      
                  $query = self::factory($this->object_name())
      			->where($this->right_column, '=', $this->left()-1)
      			->and_where($this->scope_column, '=', $this->scope())
      			->and_where($this->level_column, '=', $this->level())
      			;
                  $result = $query->find();
      
                  if(!isset($result->id)) return FALSE;
      
                  return $result;
      
              }
      
              /**
              * Returns the node that is the next sibling of the given node
              *
              * @return array $resultNode The node returned
              */
              function get_next_sibling()
              {
                  $query = self::factory($this->object_name())
      			->where($this->left_column, '=', $this->right()+1)
      			->and_where($this->scope_column, '=', $this->scope())
      			->and_where($this->level_column, '=', $this->level())
      			;
                  $result = $query->find();
      
                  if(!isset($result->id)) return FALSE;
      
                  return $result;
              }
      
      Reply
  5. > получить предыдущего/следующего брата, то они в модуле, который я использую…
    Здесь этих методов нет https://github.com/evopix/orm-mptt
    Не могли бы вы скинуть ссылку на этот модуль (тот который используете и есть эти методы)

    Reply
  6. Этот разработчик исправил глюки связанные с перемещением и убрал метод lock() и unlock().
    И сделал все на транзакциях молодец )
    Вот его класс MPTT.php
    C реализацией evopix у меня возникали проблемы с перемещением. Один код работал с одной таблицей, а с другой отказывался работать, при патчи получалось наоборот.

    Reply

Leave a reply

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> 

required