Класс базируется на библиотеке Максима Полторака phpDBTree 1.4
GitHub: https://github.com/kvf77/DbTree
Список изменений:
v4.4 - Метод MakeUlList изменен - расширена функциональность
v4.3 - добавлен новый метод MakeUlList
v4.2 - добавлены примеры кода, демонстрирующие все возможности библиотеки
v4.1 - добавлен новый метод SortChildren, правки документации
Основной особенностью библиотеки является, то, что все запросы в методах переписаны согласно стандартам ANSI и работают без изменений на подавляющем большинстве баз данных.
При работе с базой данных библиотека использует класс safemysql.class.php Романа Шевченко.
<?php
$tree_params = array(
'table' => 'sections',
'id' => 'sections_id',
'left' => 'sections_left',
'right' => 'sections_right',
'level' => 'sections_level'
);
$db = new SafeMySQL($dsn);
$tree = new DbTreeExt($tree_params, $db);
?>
Где $db - это объект класса для работы с базой данных, в нашем случае safemysql.class.php. А $tree_params - это перечень основных служебных полей таблицы, в которой мы будем хранить наше дерево.
Все, теперь у нас есть полностью настроенное дерево, с которым мы можем работать.
В качестве практического примера я рассмотрю возможность хранения в рамках одной таблицы нескольких деревьев. Также мы предположим, что структура нашего сайта хранится в виде дерева Nested Sets. Мы посмотрим, какие возможности предоставляет нам библиотека для работы с сайтом и для его обслуживания.
И так, приступим.
Исходные данные: мы будем использовать дерево для хранения структуры сайта (то есть перечень его разделов). Чтобы усложнить задачу, предположим, что сайт у нас поддерживает 2 языка. Это значит, что структур сайта у нас будет 2. Чтобы не множить таблицы, мы оба дерева (русское и английское) будем хранить в одной таблице.
В этом конкретном примере мы с вами посмотрим, какие возможности библиотека предоставляет создателю сайта, и каким образом вы можете облегчить свою жизнь.
CREATE TABLE `sections` (
`section_id` int AUTO_INCREMENT NOT NULL,
`section_left` int NOT NULL default '0',
`section_right` int NOT NULL default '0',
`section_level` int default NULL,
`section_lang` varchar(2) NOT NULL default '',
`section_name` varchar(255) NOT NULL default '',
`section_path` varchar(255) NOT NULL default '',
`section_full_path` varchar(255) default NULL,
PRIMARY KEY (`section_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8
Для начала, создадим таблицу, для хранения деревье в базе данных.
Первые 4 поля имеют отношения непосредственно к структуре дерева и напрямую в них вмешиваться не следует.
<?php
$tree_params = array(
'table' => 'sections',
'id' => 'sections_id',
'left' => 'sections_left',
'right' => 'sections_right',
'level' => 'sections_level'
);
$db = new SafeMySQL($dsn);
$dbtree = new DbTreeExt($tree_params, $db);
// Русское дерево
$data = array(
'section_left' => 1,
'section_right' => 2,
'section_level' => 0,
'section_lang' => 'ru',
'section_name' => 'Root',
'section_path' => '',
'section_full_path' => ''
);
$sql = 'INSERT INTO sections SET ?u';
$db->query($sql, $data);
// Английское дерево
$data = array(
'section_left' => 1,
'section_right' => 2,
'section_level' => 0,
'section_lang' => 'en',
'section_name' => 'Root',
'section_path' => '',
'section_full_path' => ''
);
$sql = 'INSERT INTO sections SET ?u';
$db->query($sql, $data);
?>
Теперь наша таблица содержит все первоначальные данные и мы можем работать.
Какие этапы нам нужно пройти?
Разумеется, есть еще подэтапы проверки введенных пользователем данных, но мы их опустим, как опустим 3 этап, потому что он не имеет отношения к решаемой проблеме. Мы могли бы опустить и 2 этап, но так как нам он часто будет нужен, я приведу его 1 раз и далее буду ссылаться на этот пример.
<?php
// Первый и второй этапы
// Инициализируем класс
$tree_params = array(
'table' => 'sections',
'id' => 'sections_id',
'left' => 'sections_left',
'right' => 'sections_right',
'level' => 'sections_level'
);
$db = new SafeMySQL($dsn);
$dbtree = new DbTreeExt($tree_params, $db);
// Получаем все дерево для русской версии сайта
$sections = $dbtree->Full(
array(
'section_id',
'section_level',
'section_name',
'section_lang'
),
array(
'and' => array(
'section_lang = "ru"'
)
)
);
// Обрабатываем полученное дерево
foreach ($sections as $item) {
// Делаем отступы (лесенка) согласно уровню вложенности
$item['spacer'] = str_repeat(' ', 6 * $item['section_level']);
// Печатаем на экран дерево с правильными отступами
echo $item['spacer'] . $item['section_name'] . '<br />';
}
// Выводим массив $sections на экран,
// чтобы дать возможность пользователю выбрать,
// у какого раздела создать подраздел.
?>
Третий этап мы пропускаем, потому что принятие данных от пользователя или из другого источника не является предметом материала, это вы и сами прекрасно реализуете.
А мы переходим к 4 этапу. Данные получены, проверены и нам надо на их основе добавить новый раздел. Давайте перечислим, какие данные нам нужны, чтобы добавить новый раздел:
При добавлении нового раздела нам необходимо рассчитать section_full_path. Для этого нам необходимо получить всех родителей добавляемого раздела и сложить их section_path в единый путь.
Разместим все эти данные для удобства в массиве $section: $section['section_id'], и так далее.
<?php
$tree_params = array(
'table' => 'sections',
'id' => 'sections_id',
'left' => 'sections_left',
'right' => 'sections_right',
'level' => 'sections_level'
);
$db = new SafeMySQL($dsn);
$dbtree = new DbTreeExt($tree_params, $db);
// На входе данные для создания новой ветки:
$section = array(
'secton_id' => xxx, // ID РОДИТЕЛЯ, к которому добавляем ветку
'section_lang' => 'ru', // языковая версия дерева
'section_name' => 'new child', // Название нового раздела
'section_path' => 'newchild', // Кусок пути для этого раздела
'section_full_path' => '' // Здесь пока пусто - нужно рассчитать
);
// $section_id - id родителя, к которому добавляем новый раздел
// $section_lang - языковая версия дерева
// $section
// Получаем всех родителей раздела для построения полного пути
$data = $dbtree->Parents($section_id, array(
'section_level',
'section_path'
), array(
'and' => array(
'section_lang = "ru"'
)
)
);
// Используя полный список родителей -
// строим полный путь до нового раздела
foreach ($data as $item) {
if (0 <> $item['section_level']) {
$section['section_full_path'] .= $item['section_path'] . '/';
}
}
$section['section_full_path'] .= $section['section_path'] . '/';
// Добавляем новый раздел
$id = $section['section_id'];
unset($section['section_id']);
// Вставляем новую ветку в дерево
$id = $dbtree->Insert($id, array(
'and' => array(
'section_lang = "' . $section['section_lang'] . '"'
)
), $section
);
?>
К этому моменту, если не произошло ошибок, у нас в русском языковом дереве появился новый раздел.
<?php
$tree_params = array(
'table' => 'sections',
'id' => 'sections_id',
'left' => 'sections_left',
'right' => 'sections_right',
'level' => 'sections_level'
);
$db = new SafeMySQL($dsn);
$dbtree = new DbTreeExt($tree_params, $db);
$dbtree->Delete($section_id, array(
'and' => array(
'section_lang = "' . $section_lang . '"'
)
)
);
?>
Перемещение раздела к другому родителю с данным классом также не представляет особой проблемы.
Какие этапы нам необходимо пройти?
Давайте посмотрим как это делается на практике. Первые два этапа мы опустим, поскольку они сводятся к получению всего дерева и выводу его пользователю для определения нужных section_id разделов. Замечу, что перемещать узлы можно только в пределах одного и того же дерева, то есть, если вы выбрали русскую версию дерева, то и новый родитель должен принадлежать этому же дереву.
Когда section_id перемещаемого узла и section_id нового родителя получены, действуем следующим образом:
<?php
$tree_params = array(
'table' => 'sections',
'id' => 'sections_id',
'left' => 'sections_left',
'right' => 'sections_right',
'level' => 'sections_level'
);
$db = new SafeMySQL($dsn);
$dbtree = new DbTreeExt($tree_params, $db);
$dbtree->MoveAll($old_id, $new_id, array(
'and' => array(
'section_lang = "' . $section_lang . '"'
)
)
);
?>
Все, ваш раздел успешно перенесен. Не забывайте обрабатывать section_full_path, задавая им новые значения с учетом изменившегося пути.
Таким же образом легко поменять позицию у двух узлов (при этом дети не перемещаются).
<?php
$tree_params = array(
'table' => 'sections',
'id' => 'sections_id',
'left' => 'sections_left',
'right' => 'sections_right',
'level' => 'sections_level'
);
$db = new SafeMySQL($dsn);
$dbtree = new DbTreeExt($tree_params, $db);
$dbtree->ChangePosition($id1, $id2, array(
'and' => array(
'section_lang = "' . $section_lang . '"'
)
)
);
?>
Таким образом мы поменяем местами узлы с номерами $id1 и $id2 соответственно. В данном случае section_full_path пересчитывать НЕ НУЖНО, поскольку поменялся лишь порядок отображения дерева, но не пути.
Современный сайт не мыслим без таких удобств, как "хлебные крошки" (когда пользователь видит дорожку следования по сайту) или подробного меню, предлагающего ему все разнообразие возможных действий. В этой части нашего курса мы с вами посмотрим, какие возможности для реализации подобного предлагает моя библиотека.
Давайте начнем с простого – построим навигатор по сайту. Что нам для этого понадобится? Да ничего особенного, все данные к этому моменту у нас уже должны быть. Нам нужен только номер раздела, в котором сейчас находится пользователь. По идее, вы должны были его получить, когда определяли, какую страницу показывать пользователю на основании section_full_path.
Раз все данные у нас уже есть, остается только построить навигатор и вывести его на эран в нужном месте.
<?php
$tree_params = array(
'table' => 'sections',
'id' => 'sections_id',
'left' => 'sections_left',
'right' => 'sections_right',
'level' => 'sections_level'
);
$db = new SafeMySQL($dsn);
$dbtree = new DbTreeExt($tree_params, $db);
// Получаем всех родителей раздела в котором находится пользователь
$data = $dbtree->Parents($section_id, array(
'section_level',
'section_path'
), array(
'and' => array(
'section_lang = "ru"'
)
)
);
// Печатаем навигатор
// если добавить к названию ссылку, с использованием section_full_path,
// то пользователь сможет перемещаться по своим шагам в любом направлении
foreach ($data as $item) {
echo $section['section_name'] . ' > ';
}
?>
Перейдем к построению меню, для подсказки пользователю возможных действий. Библиотека предлагает вам минимум 3 варианта построения меню.
Для иллюстрации приведу пример.
Вот так выглядит все наше дерево:
MainSection 1
Section
Section
Section
Section
MainSection 2
Section
MainSection 3
Section
Section <<<<<
Section
Section
MainSection 4
Section
Section
Приоткрытое дерево для выделенного элемента будет выглядеть так:
MainSection 1
MainSection 2
MainSection 3
Section
Section
Section
Section
MainSection 4
Надеюсь, что эти примеры достаточно наглядны.
И так, давайте построим приоткрытое дерево. В исходных данных нам требуется все тот же section_id текущего раздела, в котором находится в данный момент пользователь.
<?php
$tree_params = array(
'table' => 'sections',
'id' => 'sections_id',
'left' => 'sections_left',
'right' => 'sections_right',
'level' => 'sections_level'
);
$db = new SafeMySQL($dsn);
$dbtree = new DbTreeExt($tree_params, $db);
$data = $dbtree->Ajar($section_id, array(
'section_name',
'section_level',
'section_full_path'
), array(
'and' => array(
'section_lang = "' . $section['section_lang'] . '"'
)
)
);
?>
На основе section_level вы легко можете отобразить ветку в виде лесенки (смотрите пример построения всего дерева).
Section
Section
Section
Это противоположный вариант метода Parents - там мы получали всех родителей, а здесь нам необходимо получить всех детей:
<?php
$tree_params = array(
'table' => 'sections',
'id' => 'sections_id',
'left' => 'sections_left',
'right' => 'sections_right',
'level' => 'sections_level'
);
$db = new SafeMySQL($dsn);
$dbtree = new DbTreeExt($tree_params, $db);
$data = $dbtree->Branch($section_id, array(
'section_name',
'section_level',
'section_full_path'
), array(
'and' => array(
'section_lang = "' . $section['section_lang'] . '"'
)
)
);
?>
На данный момент библиотека состоит из двух классов:
Соответственно, класс DbTree может прекрасно обходиться без DbTreeExt. Чтобы иметь полную мощь бибилиотеки, вам необходимо инициализировать объект класса DbTreeExt.
__construct($fields, $db, $lang = 'en')
Конструктор класса. Принимает параметрами массив со списком служебных полей дерева: id, level, left, right и название таблицы table.
$db - объект safemysql.class.php для работы с базой.
$lang - двухбуквенный код языка, на котром будут выдаваться сообщения об ошибках.
GetNode($nodeId, $fields = '*')
Возвращает все данные узла с id $nodeId. Чтобы получить список определенных полей, вам необходимо перечислить из через запятую в параметре $fields.
GetParent($nodeId, $fields = '*', $condition = '')
Возвращает данные ближайшего родителя узла с id $nodeId. Чтобы получить список определенных полей, вам необходимо перечислить из через запятую в параметре $fields.
Дополнительные ограничительные параметры можно передать в $condition (формат параметра смотрите в описании метода PrepareCondition().
Insert($parentId, $data = array(), $condition = '')
Вставляет узел в дерево. $paretnId - id родительского узла, к которому добавляется ребенок. $data - массив данных для узла. Дополнительные ограничительные параметры можно передать в $condition (формат параметра смотрите в описании метода PrepareCondition().
InsertNear($nodeId, $data = array(), $condition = '')
Если вам необходимо определить порядок следования детей после вставки, вы можете воспользоваться этим методом. $nodeId определяет элемент после которого будет вставлен новый, с тем же уровнем вложенности. $data - массив данных для узла. Дополнительные ограничительные параметры можно передать в $condition (формат параметра смотрите в описании метода PrepareCondition().
MoveAll($nodeId, $parentId, $condition = '')
Перемещает ноду вместе в детьми к новому родителю. $nodeId - id перемещаемого узла, $parentId - id нового родителя. Дополнительные ограничительные параметры можно передать в $condition (формат параметра смотрите в описании метода PrepareCondition().
Обратите внимание: новый родитель должен быть ВНЕ переносимой ветки.
ChangePosition($nodeId1, $nodeId2)
Меняет ноды местами. То есть позволяет изменить порядок следования нод в рамках одного родителя и одного уровня. $nodeId1 и $nodeId2 - id узлов, которые меняем местами.
ChangePositionAll($nodeId1, $nodeId2, $position = 'after', $condition = '')
Меняет порядок детей у одного родителя в рамках одного уровня. Все дети переносимого элемента также перемещаются вместе с ним, сохраняя иерархию. $nodeId1 - уникальный номер перемещаемого узла (все его дети будут перемещены вместе с ним), $nodeId2 уникальный номер элемента, относительно которого будет происходить перемещение. $position - позиция, в которую будет помещен переносимый элемент ($nodeId1) относительно другого элемента ($nodeId2): 'after' - переносимый элемент ($nodeId1) будет поставлен после указанного элемента ($nodeId2), 'before' - переносимый элемент ($nodeId1) будет поставлен перед указанным ($nodeId2). Дополнительные ограничительные параметры можно передать в $condition (формат параметра смотрите в описании метода PrepareCondition().
Delete($nodeId, $condition = '')
Удаляет ноду $nodeId. Все дети ноды останутся, переместясь на один уровень вверх.
Дополнительные ограничительные параметры можно передать в $condition (формат параметра смотрите в описании метода PrepareCondition().
DeleteAll($nodeId, $condition = '')
Удаляет ноду $nodeId и всех ее детей.
Дополнительные ограничительные параметры можно передать в $condition (формат параметра смотрите в описании метода PrepareCondition().
PrepareCondition($condition, $where = false, $prefix = '')
Эта функция не предназначена для прямого вызова и вызывается другими методами класса. В результате ее работы будет возвращена строка условия для запроса.
$condition – собственно требующиеся дополнительные условия. Формат следующий: array('and' => array('id = 0', 'id2 >= 3'), 'or' => array('sec = 'www'', 'sec2 <> 'erere'')), и т.д., где ключ массива – это условие (AND, OR, etc), значение ключа – непосредственно условие (user_id = 1).
$where – указывает – требуется ли добавление в начало условия ключевого слова WHERE.
$prefix – для некоторых сложных запросов есть необходимость в псевдонимах для таблицы. Префикс задает имя псевдонима (A.user_id).
Full($fields = '*', $condition = '')
Получаем дерево полностью.
Чтобы получить список определенных полей, вам необходимо перечислить из через запятую в параметре $fields.
Дополнительные ограничительные параметры можно передать в $condition (формат параметра смотрите в описании метода PrepareCondition().
Branch($nodeId, $fields = '*', $condition = '')
Получаем всех детей $nodeId.
Чтобы получить список определенных полей, вам необходимо перечислить из через запятую в параметре $fields.
Дополнительные ограничительные параметры можно передать в $condition (формат параметра смотрите в описании метода PrepareCondition().
Parents($nodeId, $fields = '*', $condition = '')
Получаем всех родителей $nodeId.
Чтобы получить список определенных полей, вам необходимо перечислить из через запятую в параметре $fields.
Дополнительные ограничительные параметры можно передать в $condition (формат параметра смотрите в описании метода PrepareCondition().
Ajar($nodeId, $fields = '*', $condition = '')
Возвращает приоткрытое дерево для $nodeId.
Чтобы получить список определенных полей, вам необходимо перечислить из через запятую в параметре $fields.
Дополнительные ограничительные параметры можно передать в $condition (формат параметра смотрите в описании метода PrepareCondition().
SortChildren($parentId, $orderField)
Сортирует детей $parentId по алфавиту по полю, указанному в $orderField.
MakeUlList($tree, $nameField, $linkField = array(), $linkPrefix = null, $delimiter = '')
Строит HTML дерево UL/LI. Добавляет ссылку <a href> (если нужно).
$tree - массив дерева формата nested sets (получается в результате работы любого метода библиотеки, возвращающего дерево, например, метода Full).
$nameField - массив полей, на основе которого сформируется ссылка
$linkField - название поля, в котором содержится URL ссылки.
$linkPrefix - дополнительная часть URL (добавляется в начале каждой ссылки).
$delimiter - разделяющий символ, этим значением будут разделены данные из массива $linkField
Дереву присваивается ID, состоящее из названи таблицы + _tree (<ul id="tablename_tree">).
Последние два параметра являются необязательными. Если они опущены, ссылка не формируется.
DbTree v4.4. Класс для работы с деревьями Nested Sets (вложенные множества). Это удобное высокоскоростное решение для хранения разного рода деревьев в базе данных. Кузьма Феськов 2015-07-21 http://www.sesmikcms.ru/resources/img/000/000/001/img_165_original-640-480.jpg http://www.sesmikcms.ru/pages/read/biblioteka-dlja-raboty-s-derevjami-nested-sets/
У тебя в описании MakeUlList:
Ответить$nameField - массив полей, на основе которого сформируется ссылка
$linkField - название поля, в котором содержится URL ссылки.
У них не надо поменять местами описания?
Как перенести ветвь с детьми, в корень сайта?
ОтветитьКакие требования к версии PHP?
Ответить