Это вовсе не полное руководство, а пара замечаний к практическому использованию.
TreeBehavior – весьма полезный компонент CakePHP, реализующий паттерн Nested Sets для указанной модели. Если что-то непонятно, то вот ссылки:
TreeBehavior позволяет хранить несколько корневых элементов, у которых родительский id равен NULL. Не поддавайтесь искушению – для себя сделайте отдельный корневой элемент. Большинство функций, например Model::children не работают с идентификатором записи равной NULL. Поэтому, если возникнет необходимость получить всех потомков корня, придется сначала выбрать самостоятельно все элементы с parent_id=NULL, а потом для каждого из них вызвать Model::children().
То есть, если создается, скажем, структурированный каталог сайтов, не надо поддаваться искушению добавить основные разделы, как корневые – создайте сначала “совсем-совсем” корневой элемент с именем, скажем, ROOT, а к нему уже добавлять реальные разделы, которые будут использоваться. Должна получиться такая структура:
ROOT (id=1, parent_id=0) | +-- Сайты о программировании (id=2, parent_id=1) | +-- Блоги (id=3, parent_id=1) | +-- CakePHP (id=4, parent_id=1) | +----- Форумы о Cake | +----- Компоненты Cake
Зато, при таком подходе можно использовать одну модель для хранения разных деревьев.
И еще. Я либо плохо искал, либо это действительно так. В TreeBehavior я не нашел метода, чтобы получить массив потомков узла в виде дерева. Метод children возвращает “плоский” массив. Пришлось написать небольшой метод для моей модели, который вызывает children, а потом каждому элементу массива добваляет элемент ‘children’ который содежит массив потомков, у которых тоже добвляет элемент ‘children’ и т.д.
Если главные элементы у модели, все-таки все с parent_id=NULL, функция тоже будет работать. Но лучше все-таки использовать схему, описанную мной выше.
function childrenAsTree($options=null) {
// Если TreeBehavior неактивен, то можно и не
// напрягаться
if ($this->Behaviors->enabled('Tree')) {
if (is_array($options)) {
if (array_key_exists('id', $options)) {
$_id = $options['id'];
} else {
$_id = null;
}
} else {
$_id = $options;
}
// все потомки NULL --
// это все записи
if (is_null($_id)) {
$res = $this->find('all', array('recursive'=>-1));
} else {
$res = $this->children($_id);
}
$retArr = $this->_toTree($res, $_id);
return $retArr;
}
}
private function &_toTree(&$arr, $parentId ) {
$parentFieldName = $this->Behaviors->Tree->settings[$this->alias]['parent'];
$retArr = array();
if (!empty($arr)) {
// Set::extract fix :-(
if (is_null($parentId)) {
foreach($arr as $item) {
if (empty($item[$this->alias][$parentFieldName])) {
$retArr[] = $item;
}
}
} else {
$retArr = Set::extract($arr,
"/{$this->alias}[$parentFieldName=$parentId]");
}
for ($i=0; $i<count($retArr); $i++) {
$retArr[$i]['children'] = $this->_toTree(&$arr,
$retArr[$i][$this->alias][$this->primaryKey]);
}
}
return $retArr;
}