Deprecated: Assigning the return value of new by reference is deprecated in /home/spwebru/com/wp-settings.php on line 472

Deprecated: Assigning the return value of new by reference is deprecated in /home/spwebru/com/wp-settings.php on line 487

Deprecated: Assigning the return value of new by reference is deprecated in /home/spwebru/com/wp-settings.php on line 494

Deprecated: Assigning the return value of new by reference is deprecated in /home/spwebru/com/wp-settings.php on line 530

Deprecated: Assigning the return value of new by reference is deprecated in /home/spwebru/com/wp-includes/cache.php on line 103

Deprecated: Assigning the return value of new by reference is deprecated in /home/spwebru/com/wp-includes/query.php on line 21

Deprecated: Assigning the return value of new by reference is deprecated in /home/spwebru/com/wp-includes/theme.php on line 623
Использование TreeBehavior в CakePHP - Программируем на CakePHP


Сен 08 2008

Использование TreeBehavior в CakePHP

Раздел: Веб-разработкаМета @ 23:21

Сегодня у этого блога первый маленький юбилей — 10-й пост. Скорость набрана не плохая, буду очень стараться не уронить планку.

Очень часто в своих проектах нам приходится реализовывать хранение и вывод иерархических данных — всевозможные древовидные структуры разделов и подразделов статей, категории товаров интернет-магазинов, папки с фотографиями и тому подобное. Многие организуют такие списки через простейшие id, name, parent_id, но у этого способа масса недостатков. Один из них — необходимость рекурсии, если неизвестен уровень вложенности искомого раздела. К счастью, в CakePHP есть встроенное средство для работы с иерархическими данными TreeBehavior, работающий по алгоритму MPTT (Multi Path Tree Traversal). Подробнее об этом алгоритме я напишу в следующий раз. Все примеры кода взяты из официальной документации на CakePHP.

Для того, чтобы использовать TreeBehavior, в таблице с иерархическими данными надо добавить три целочисленных поля:

  1. Родитель — в нем будет храниться id родительского объекта. По-умолчанию должно называться parent_id.
  2. Слева — хранит номер левой границы текущего объекта (читай подробнее про алгоритм MPTT). По-умолчанию должно называться lft.
  3. Справа — хранит номер правой границы текущего объекта. По-умолчанию — rght.
    В соответствии с документацией по CakePHP, поле «родитель» используется для упрощения поиска прямых потомков выбранного объекта. В классической реализации алгоритма MPTT это поле отсутствует.

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

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);

Создадим модель:

<?php
// app/models/category.php
class Category extends AppModel {
var $name = ‘Category’;
var $actsAs = array(’Tree’);
}
?>

И простой контроллер для проверки что тестовое дерево заполнено правильно:

<?php
class CategoriesController extends AppController {
var $name = ‘Categories’;

function index() {
$this->data = $this->Category->generatetreelist(null, null, null, ‘&nbsp;&nbsp;&nbsp;’);
debug ($this->data); die;
}
}
?>

При заходе на страничку /categories/ должен быть выдан список, соответствующий следующему:

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

1. Добавляем новую категорию. Это делается точно также как в любой другой модели:

$data = array();
$data['Category']['parent_id'] = 3;
$data['Category']['name'] = ‘Skating’;
$this->Category->save($data);

Если нужно создать корневую категорию, то просто не указываем parent_id:

$data = array();
$data['Category']['name'] = ‘Other Category’;
$this->Category->save($data);

2. Изменяем категорию. Опять все как обычно:

$this->Category->id = 5; // id of Extreme knitting
$this->Category->save(array(’name’ =>’Extreme fishing’));

Если не изменять parent_id, то при сохранении просто заменятся поля из переданного массива. Если же наоборот указать parent_id — то вся соответствующая ветка дерева будет перенесена в новое место.

$this->Category->id = 5; // id of Extreme fishing
$newParentId = $this->field(’id’, array(’name’ => ‘Other Category’));
$this->Category->save(array(’parent_id’ => $newParentId));

3. Удаляем категорию и всех ее потомков:

$this->Category->id = 10;
$this->Category->delete();

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

Функция children() возвращает всех потомков выбранного элемента. Если второй (необязательный) параметр установить в true, то функция вернет только прямых потомков.

$allChildren = $this->Category->children(1); // одномерный массив из 11 элементов
// или
$this->Category->id = 1;
$allChildren = $this->Category->children(); // одномерный массив из 11 элементов
// Пробуем получить только прямых потомков
$directChildren = $this->Category->children(1, true); // одномерный массив из 2-х элементов

Для получения рекурсивного массива можно использовать функцию find(’threaded’).

Функция childCount() возвращает количество потомков у выбранного элемента. Точно также — второй параметр указывает считать только прямых потомков или всех.

$totalChildren = $this->Category->childCount(1); // вернет число 11
$numChildren = $this->Category->childCount(1, true); // вернет число 2

Функция generatetreelist() возвращает массив с элементами дерева в порядке вложенности. Вложенность может быть обозначена префиксами, как в примере в моем предыдущем посте на эту тему.

Дальше пока еще сам не потестировал, когда внедрим в несколько своих проектов — напишу продолжение.

Теги: ,

Напиши комментарий!