Index SiteMap Portfolio

Назад

июл
31

CakePHP Tree Behavior

Не трудно догадаться из названия, что Tree Behavior (далее TB) позволит модели вести себя как дерево... кхм... :)

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

Кстати, если не думать о Cake, то раньше я уже показывал, что надо освоить для трюков с деревьями.

Оригинал статьи от разработчиков CakePHP: Tree Behavior

Для использования Tree Behavior, в таблицу модели необходимо добавить 3 специальных поля:

  • parent (по умолчанию это parent_id), используется для определения того, кто отец ребенка
  • left (lft) - id левого узла
  • right (rght) - правого

Программа минимум

Пример таблицы для категорий чего-либо:

CREATE TABLE categories (
	id INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT,
	parent_id INTEGER(10) DEFAULT NULL,
	lft INTEGER(10) DEFAULT NULL,
	rght INTEGER(10) DEFAULT NULL,
	name VARCHAR(255) DEFAULT '',
	PRIMARY KEY  (id)
);

INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) 
                                           VALUES(1, 'My Categories', NULL, 1, 30);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) 
                                           VALUES(2, 'Fun', 1, 2, 15);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) 
                                           VALUES(3, 'Sport', 2, 3, 8);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) 
                                           VALUES(4, 'Surfing', 3, 4, 5);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) 
                                           VALUES(5, 'Extreme knitting', 3, 6, 7);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) 
                                           VALUES(6, 'Friends', 2, 9, 14);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) 
                                           VALUES(7, 'Gerald', 6, 10, 11);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) 
                                           VALUES(8, 'Gwendolyn', 6, 12, 13);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) 
                                           VALUES(9, 'Work', 1, 16, 29);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) 
                                           VALUES(10, 'Reports', 9, 17, 22);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) 
                                           VALUES(11, 'Annual', 10, 18, 19);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) 
                                           VALUES(12, 'Status', 10, 20, 21);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) 
                                           VALUES(13, 'Trips', 9, 23, 28);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) 
                                           VALUES(14, 'National', 13, 24, 25);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) 
                                           VALUES(15, 'International', 13, 26, 27);

Стандартный код модели:

  1. // app/models/category.php
  2. class Category extends AppModel {
  3. var $name = 'Category';
  4. var $actsAs = array('Tree');
  5. }
  6. ?>

Для теста создается небольшой контроллер:

  1. class CategoriesController extends AppController {
  2. var $name = 'Categories';
  3. function index() {
  4. $this->data = $this->Category->generatetreelist(null, null, null, ' ');
  5. debug ($this->data); die;
  6. }
  7. }
  8. ?>

По адресу /categories относительно URL нашего проекта будет такой вот список:

  • My Categories
    • Fun
      • Sport
        • Surfing
        • Extreme knitting
      • Friends
        • Gerald
        • Gwendolyn
    • Work
      • Reports
        • Annual
        • Status
      • Trips
        • National
        • International

Добавление элементов

Стандартный способ добавления новой записи для модели выглядит след образом:

  1. // код находится в некотором контроллере
  2. $data['Category']['parent_id'] = 3;
  3. $data['Category']['name'] = 'Skating';
  4. $this->Category->save($data);

При использовании TB не обязательно заполнять значение parent_id. Если оно не указано, то TB добавит элемент в конец дерева:

  1. // код находится в некотором контроллере
  2. $data = array();
  3. $data['Category']['name'] = 'Новая категория';
  4. $this->Category->save($data);

После этого добавления generatetreelist() вернет такой список:

  • My Categories
    • Fun
      • Sport
        • Surfing
        • Extreme knitting
        • Skating New
      • Friends
        • Gerald
        • Gwendolyn
    • Work
      • Reports
        • Annual
        • Status
      • Trips
        • National
        • International
  • Новая категория

Изменение данных

Здесь все аналогично "обычным" моделям. Единтсвенное, что следует помнить: нельзя изменять parent_id, если не хотите изменить структуру ваших данных.

  1. // код находится в некотором контроллере
  2. $this->Category->id = 5; // Id категории "Extreme knitting"
  3. $this->Category->save(array('name' =>'Extreme fishing'));

Обновленная версия данных имеет такой вид (слова «Обновлено» там не будет):

  • My Categories
    • Fun
      • Sport
        • Surfing
        • Extreme fishing Обновлено
        • Skating
      • Friends
        • Gerald
        • Gwendolyn
    • Work
      • Reports
        • Annual
        • Status
      • Trips
        • National
        • International
  • Новая категория

Пример того, как можно передвинуть категорию на новое место:

  1. // код находится в некотором контроллере
  2. $this->Category->id = 5; // Extreme fishing
  3. $newParentId = $this->field('id', array('name' => 'Новая категория'));
  4. $this->Category->save(array('parent_id' => $newParentId));

В строке 3 мы методом field() получили ID категории "Новая категория" по ее имени. Имеем новое дерево:

  • My Categories
    • Fun
      • Sport
        • Surfing
        • Skating
      • Friends
        • Gerald
        • Gwendolyn
    • Work
      • Reports
        • Annual
        • Status
      • Trips
        • National
        • International
  • Новая категория
    • Extreme fishing Перемещено

Удаление данных

TB имеет несколько вариантов для удаления данных. В данном учебном примере рассматривается самый простой: удаление элемента и всех его детей обычным методом delete() вашей модели. Пример:

  1. // код в контроллере
  2. $this->Category->id = 10;
  3. $this->Category->delete();

Удалилась категория "Reports" из категории "Work":

  • My Categories
    • Fun
      • Sport
        • Surfing
        • Skating
      • Friends
        • Gerald
        • Gwendolyn
    • Work
      • Trips
        • National
        • International
  • Новая категория
    • Extreme fishing

Поиск

Большинство TB методов рассчитывает, что выбранные данные отсортированы по полю lft. Если вызвать find() метод модели или один из методов класса TB, то, в последствии, можно будет сильно удивиться полученным результатам.

Поиск детей

Метод children() вернет всех детей категории, ID которой передается в первом параметре. Второй необязательный параметр служит флагом для того, чтобы выбрать только детей первого уровня (если установлен в true). Пример с исходными данным, которые получены после изучения метода delete() в этой статье:

  1. $allChildren = $this->Category->children(1); // массив из 11 категорий
  2. // -- или --
  3. $this->Category->id = 1;
  4. $allChildren = $this->Category->children(); // аналогично, 11 категорий
  5. // Только дети первого уровня
  6. $directChildren = $this->Category->children(1, true); // 2 категории: Fun и Work

Такие вызовы дают обычные списки (массивы с одним уровнем вложенности). Для рекурсивного массива используется find('threaded')

Подсчет детей

Аналогично методу children() работает childCount() для подсчета количества элементов в категории:

  1. $totalChildren = $this->Category->childCount(1); // 11
  2. // -- или --
  3. $this->Category->id = 1;
  4. $directChildren = $this->Category->childCount(); // 11
  5. // 1 уровень
  6. $numChildren = $this->Category->childCount(1, true); // 2

Программа чуть больше, чем минимум

generatetreelist()

Уже известный метод, который показывает структуру наших данных.

getparentnode()

Возвращает родителя элемента.

getpath()

Возвращает путь к элементу по категориям.

moveDown() / moveUp()

Используется для перемещения нода вниз/вверх по дереву. В качестве параметров принимает ID элемента, который нужно сдвинуть и число позиций, на сколько нужно сдвинуть элемент. Все дети передвигаемого элемента будут автоматически перемещены за родителем.

Пример одного из методов контроллера с именем Categories:

  1. function movedown($title = null, $delta = null) {
  2. $cat = $this->Category->findByTitle($title);
  3. if (empty($cat)) {
  4. $this->Session->setFlash('Не найдено категории с именем ' . $title);
  5. $this->redirect(array('action' => 'index'), null, true);
  6. }
  7. $this->Category->id = $cat['Category']['id'];
  8. if ($delta > 0) {
  9. $this->Category->moveDown($this->Category->id, abs($delta));
  10. } else {
  11. $this->Session->setFlash('Укажите на сколько позиций необходимо сдвинуть категорию.');
  12. }
  13. $this->redirect(array('action' => 'show'), null, true);
  14. }

Теперь, если зайти по адресу /categories/movedown/Cookies/1, то категория Cookies сдвинется на одну позицию вниз. Если категории "Cookies" уже нет в базе, то получите ошибку :)

Добавить в закладки на google.com Добавить в закладки на bobrdobr.ru Добавить в закладки на del.icio.us Добавить в закладки на technorati.com Добавить в закладки на linkstore.ru Добавить в закладки на news2.ru Добавить в закладки на rumarkz.ru Добавить в закладки на memori.ru Добавить в закладки на moemesto.ru

Ваше мнение

* Имя:
* E-mail:
*Этот адрес не публикуется. Для общих контаков оставляйте адрес в комментарии.
Сайт/Блог:
Примечание. Комментарии находятся в теге <noindex>, что значит: смысла срать здесь нет.
Добавить в закладки на google.com Добавить в закладки на bobrdobr.ru Добавить в закладки на del.icio.us Добавить в закладки на technorati.com Добавить в закладки на linkstore.ru Добавить в закладки на rumarkz.ru Добавить в закладки на memori.ru Добавить в закладки на moemesto.ru

 Бесплатно

Читать мою ленту
Получать RSS-ленту на почту



Хостинг предоставлен компанией
TutHost.com