Блог

Хранение деревьев в базе данных. Часть вторая, практическая

31 августа 2013 Рубрики: Программирование. Метки: closure table, laravel, php, базы данных и древовидные структуры.

Полгода назад написал бандл ClosureTable для фреймворка Laravel 3. Поводом для написания стала вот эта замечательная презентация Билла Карвина о способах хранения и обработки иерархических данных в MySQL с использованием PHP.

Первая часть статьи «Хранение деревьев в базе данных» была теоретической. В этой части статьи я кратко расскажу вам о ключевых моментах использования бандла ClosureTable для фреймворка Laravel 3.

Бандл включает в себя пять файлов:

Моя реализация шаблона проектирования Closure Table для фреймворка Laravel 3 с одной стороны довольно простая и гибкая, с другой — имеет ряд ограничений.

Простота

Все базовые операции с моделью ClosureTable выполняются, как правило, «ненапряжно» и в пару строчек. А то и в одну. Например, чтобы получить всё дерево целиком, нам понадобится вызов всего лишь одного метода:

$pages_tree = Page::fulltree(); // получим многомерный массив

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

// в результате будет перенесен не только пункт меню с идентификатором #15,
// но и все его дочерние элементы, если таковые существуют
$item = MenuItem::find(15);
$item->move_to(MenuItem::find(10));

Другие примеры вы найдёте на странице бандла на Гитхабе.

Простота требует жертв

Без человеческих, конечно, обошлось. Однако существует ряд ограничений, которые я попытаюсь смягчить в следующей версии библиотеки. (Бандлом её уже не назовешь, поскольку Laravel 4, следующая версия хорошего фреймворка, теперь использует Composer, а библиотеки к нему теперь пишутся в виде пакетов.)

«Полуограничение»

Придётся явно указывать название таблицы со связями сущностей в ClosureTable-модели в поле public static $treepath. Однако это позволит не создавать всякий раз новый объект модели ClosureTable\TreePath, а работать непосредственно с ClosureTable-моделью.

Внешний ключ на непосредственного предка

Необходимо явно указывать название этого поля, поскольку иначе к нему невозможно будет обратиться. Там, где требуется это поле, используется запись static::$parent_key или $this->{static::$parent_key}.

Название внешнего ключа хранится в свойстве public static $parent_key модели ClosureTable\ClosureTable и по умолчанию имеет значение parent_id. Я взял этот способ из шаблона проектирования Adjacency List для упрощения выборки непосредственного предка.

Поля таблицы связей

С одной стороны вроде бы незначительный, но с другой — не очень приятный момент. Таблица связей должа иметь ровно четыре поля:

Как видите, изменить название поля без нехороших последствий можно только у уникального идентификатора. Названия остальных полей используются в коде напрямую, безо всяких подстановок. Конечно, если это очень принципиально, можно воспользоваться автозаменой, благо файла, где придётся заменять, всего два. Но лучше использовать «стандартные» названия.

В бой!

Нам этом мой краткий обзор бандла заканчивается. Другие примеры использования всегда будут на главной странице репозитория, так что самое время затестить!